diff --git a/application/shoppinglists/__pycache__/shoplist_api.cpython-313.pyc b/application/shoppinglists/__pycache__/shoplist_api.cpython-313.pyc index ab4f420..3a6bdad 100644 Binary files a/application/shoppinglists/__pycache__/shoplist_api.cpython-313.pyc and b/application/shoppinglists/__pycache__/shoplist_api.cpython-313.pyc differ diff --git a/application/shoppinglists/__pycache__/shoplist_database.cpython-313.pyc b/application/shoppinglists/__pycache__/shoplist_database.cpython-313.pyc index c457824..a6bb1b3 100644 Binary files a/application/shoppinglists/__pycache__/shoplist_database.cpython-313.pyc and b/application/shoppinglists/__pycache__/shoplist_database.cpython-313.pyc differ diff --git a/application/shoppinglists/__pycache__/shoplist_processess.cpython-313.pyc b/application/shoppinglists/__pycache__/shoplist_processess.cpython-313.pyc index 1795e96..99f14f3 100644 Binary files a/application/shoppinglists/__pycache__/shoplist_processess.cpython-313.pyc and b/application/shoppinglists/__pycache__/shoplist_processess.cpython-313.pyc differ diff --git a/application/shoppinglists/shoplist_api.py b/application/shoppinglists/shoplist_api.py index 8dac703..f3cb556 100644 --- a/application/shoppinglists/shoplist_api.py +++ b/application/shoppinglists/shoplist_api.py @@ -238,6 +238,18 @@ def deleteListItem(): return jsonify({"error":False, "message":"item deleted succesfully!"}) return jsonify({"error":True, "message":"There was an error with this POST statement"}) +@shopping_list_api.route('/api/deleteList', methods=["POST"]) +@access_api.login_required +def deleteList(): + if request.method == "POST": + shopping_list_uuid = request.get_json()['shopping_list_uuid'] + site_name = session['selected_site'] + user_id = session['user_id'] + shoplist_processess.deleteShoppingList(site_name, {'shopping_list_uuid': shopping_list_uuid}, user_id) + return jsonify({"error":False, "message":"List Deleted succesfully!"}) + return jsonify({"error":True, "message":"There was an error with this POST statement"}) + + # Added to Database @shopping_list_api.route('/api/saveListItem', methods=["POST"]) @access_api.login_required @@ -293,3 +305,15 @@ def setListItemState(): return jsonify({"list_items":items, "error":False, "message":"items fetched succesfully!"}) return jsonify({"list_items":items, "error":True, "message":"There was an error with this GET statement"}) + + +@shopping_list_api.route('/api/postGeneratedList', methods=["POST"]) +@access_api.login_required +def postGeneratedList(): + if request.method == "POST": + payload: dict = request.get_json() + site_name: str = session['selected_site'] + user_id: int = session['user_id'] + shoplist_processess.postNewGeneratedList(site_name, payload, user_id) + return jsonify(status=201, message=f"List Generated successfully!") + return jsonify(status=405, message=f"{request.method} is not an accepted method on this endpoint!") \ No newline at end of file diff --git a/application/shoppinglists/shoplist_database.py b/application/shoppinglists/shoplist_database.py index 6379783..6665739 100644 --- a/application/shoppinglists/shoplist_database.py +++ b/application/shoppinglists/shoplist_database.py @@ -1,7 +1,6 @@ # 3rd Party imports import psycopg2 - # applications imports import config from application import postsqldb @@ -150,7 +149,6 @@ def getRecipeItemsByUUID(site, payload, convert=True, conn=None): except Exception as error: raise postsqldb.DatabaseError(error, payload, sql) - def getItemsWithQOH(site, payload, convert=True, conn=None): recordset = [] count = 0 @@ -263,7 +261,6 @@ def getListsModal(site, payload, convert=True, conn=None): except Exception as error: raise postsqldb.DatabaseError(error, payload, sql) - def getItemsModal(site, payload, convert=True, conn=None): recordsets = [] count = 0 @@ -298,6 +295,63 @@ def getItemsModal(site, payload, convert=True, conn=None): except Exception as error: raise postsqldb.DatabaseError(error, payload, sql) +def getItemByUUID(site, payload:dict, convert=True, conn=None): + """ payload: dict = {'item_uuid'}""" + record = () + self_conn = False + with open('application/shoppinglists/sql/getItemByUUID.sql', 'r') as file: + sql = file.read().replace("%%site_name%%", site) + try: + + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = True + self_conn = True + + with conn.cursor() as cur: + cur.execute(sql, payload) + rows = cur.fetchone() + if rows and convert: + record = postsqldb.tupleDictionaryFactory(cur.description, rows) + elif rows and not convert: + record = rows + + if self_conn: + conn.close() + + return record + except Exception as error: + raise postsqldb.DatabaseError(error, payload, sql) + +def deleteShoppingListsTuple(site_name, payload, convert=True, conn=None): + deleted = () + self_conn = False + sql = f"WITH deleted_rows AS (DELETE FROM {site_name}_shopping_lists WHERE {site_name}_shopping_lists.list_uuid IN ({','.join(['%s'] * len(payload))}) RETURNING *) SELECT * FROM deleted_rows;" + try: + + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = True + self_conn = True + + with conn.cursor() as cur: + cur.execute(sql, payload) + rows = cur.fetchall() + if rows and convert: + deleted = [postsqldb.tupleDictionaryFactory(cur.description, r) for r in rows] + elif rows and not convert: + deleted = rows + + if self_conn: + conn.commit() + conn.close() + + return deleted + except Exception as error: + raise postsqldb.DatabaseError(error, payload, sql) + def deleteShoppingListItemsTuple(site_name, payload, convert=True, conn=None): deleted = () self_conn = False diff --git a/application/shoppinglists/shoplist_processess.py b/application/shoppinglists/shoplist_processess.py index b8dd9fe..dd8cc4d 100644 --- a/application/shoppinglists/shoplist_processess.py +++ b/application/shoppinglists/shoplist_processess.py @@ -36,4 +36,152 @@ def addRecipeItemsToList(site:str, data:dict, user_id: int, conn=None): if self_conn: conn.commit() conn.close() - \ No newline at end of file + +def postNewGeneratedList(site: str, data: dict, user_id: int, conn=None): + """data={'list_type', 'list_name', 'list_description', 'custom_items', 'uncalculated_items', 'calculated_items', 'recipes', 'full_system_calculated', 'shopping_lists'}""" + list_type: str = data['list_type'] + list_name: str = data['list_name'] + list_description: str = data['list_description'] + custom_items: list = data['custom_items'] + uncalculated_items: list = data['uncalculated_items'] + calculated_items: list = data['calculated_items'] + recipes: list = data['recipes'] + full_system_calculated: list = data['full_system_calculated'] + shopping_lists: list = data['shopping_lists'] + + + self_conn=False + + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = False + self_conn = True + + shopping_list = database_payloads.ShoppingListPayload( + name=list_name, + description=list_description, + author=int(user_id), + sub_type="plain", + list_type=list_type + ) + shopping_list = shoplist_database.insertShoppingListsTuple(site, shopping_list.payload(), conn=conn) + + items_to_add_to_system = [] + # start by checcking if i should iterate full sku calc + if full_system_calculated: + safety_stock_items = shoplist_database.getItemsSafetyStock(site, conn=conn) + for item in safety_stock_items: + qty = float(item['item_info']['safety_stock']-float(item['total_sum'])) + temp_item = database_payloads.ShoppingListItemPayload( + list_uuid=shopping_list['list_uuid'], + item_type='calculated sku', + item_name=item['item_name'], + uom=item['item_info']['uom'], + qty=qty, + item_uuid=item['item_uuid'], + links=item['links'] + ) + items_to_add_to_system.append(temp_item) + + if calculated_items and not full_system_calculated: + for item_uuid in calculated_items: + item = shoplist_database.getItemByUUID(site, {'item_uuid': item_uuid}, conn=conn) + qty = float(item['item_info']['safety_stock']-float(item['total_sum'])) + temp_item = database_payloads.ShoppingListItemPayload( + list_uuid=shopping_list['list_uuid'], + item_type='calculated sku', + item_name=item['item_name'], + uom=item['item_info']['uom'], + qty=qty, + item_uuid=item['item_uuid'], + links=item['links'] + ) + items_to_add_to_system.append(temp_item) + + + if custom_items: + for item in custom_items: + temp_item = database_payloads.ShoppingListItemPayload( + list_uuid=shopping_list['list_uuid'], + item_type='custom', + item_name=item['item_name'], + uom=item['uom'], + qty=float(item['qty']), + item_uuid=None, + links={'main': item['link']} + ) + items_to_add_to_system.append(temp_item) + + if uncalculated_items: + for item in uncalculated_items: + temp_item = database_payloads.ShoppingListItemPayload( + list_uuid=shopping_list['list_uuid'], + item_type='uncalculated sku', + item_name=item['item_name'], + uom=item['uom'], + qty=float(item['qty']), + item_uuid=None, + links={'main': item['link']} + ) + items_to_add_to_system.append(temp_item) + + + if recipes: + for recipe_uuid in recipes: + recipe_items = shoplist_database.getRecipeItemsByUUID(site, (recipe_uuid,), conn=conn) + for item in recipe_items: + temp_item = database_payloads.ShoppingListItemPayload( + list_uuid=shopping_list['list_uuid'], + item_type='recipe', + item_name=item['item_name'], + uom=item['uom'], + qty=float(item['qty']), + item_uuid=item['item_uuid'], + links=item['links'] + ) + items_to_add_to_system.append(temp_item) + + if shopping_lists: + for shopping_list_uuid in shopping_lists: + shopping_list_items = shoplist_database.getShoppingList(site, (shopping_list_uuid,), conn=conn)['sl_items'] + for item in shopping_list_items: + temp_item = database_payloads.ShoppingListItemPayload( + list_uuid=shopping_list['list_uuid'], + item_type=item['item_type'], + item_name=item['item_name'], + uom=item['uom']['id'], + qty=float(item['qty']), + item_uuid=item['item_uuid'], + links=item['links'] + ) + items_to_add_to_system.append(temp_item) + + + if items_to_add_to_system: + for item in items_to_add_to_system: + shoplist_database.insertShoppingListItemsTuple(site, item.payload(), conn=conn) + + if self_conn: + conn.commit() + conn.close() + +def deleteShoppingList(site: str, data: dict, user_id: int, conn=None): + shopping_list_uuid = data['shopping_list_uuid'] + self_conn=False + + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = False + self_conn = True + + shopping_list_items = shoplist_database.getShoppingList(site, (shopping_list_uuid, ), conn=conn)['sl_items'] + shopping_list_items = [item['list_item_uuid'] for item in shopping_list_items] + + shoplist_database.deleteShoppingListsTuple(site, (shopping_list_uuid,), conn=conn) + shoplist_database.deleteShoppingListItemsTuple(site, shopping_list_items, conn=conn) + + if self_conn: + conn.commit() + conn.close() \ No newline at end of file diff --git a/application/shoppinglists/sql/getItemByUUID.sql b/application/shoppinglists/sql/getItemByUUID.sql new file mode 100644 index 0000000..cb5274a --- /dev/null +++ b/application/shoppinglists/sql/getItemByUUID.sql @@ -0,0 +1,15 @@ +WITH sum_cte AS ( + SELECT mi.id, SUM(mil.quantity_on_hand) AS total_sum + FROM %%site_name%%_item_locations mil + JOIN %%site_name%%_items mi ON mil.part_id = mi.id + GROUP BY mi.id +) + +SELECT items.*, + COALESCE(row_to_json(item_info.*), '{}') AS item_info, + COALESCE(sum_cte.total_sum, 0) AS total_sum +FROM %%site_name%%_items items +LEFT JOIN %%site_name%%_item_info item_info ON items.item_info_id = item_info.id +LEFT JOIN units ON units.id = item_info.uom +LEFT JOIN sum_cte ON items.id = sum_cte.id +WHERE items.item_uuid = %(item_uuid)s \ No newline at end of file diff --git a/application/shoppinglists/static/js/shoppingListGeneratorHandler.js b/application/shoppinglists/static/js/shoppingListGeneratorHandler.js index 95452c9..e1258e7 100644 --- a/application/shoppinglists/static/js/shoppingListGeneratorHandler.js +++ b/application/shoppinglists/static/js/shoppingListGeneratorHandler.js @@ -1074,4 +1074,28 @@ async function generateListsTable() { } } +} + + +// Generate Functions +async function postGenerateList() { + let data = { + list_type: String(document.getElementById('generated_list_type').value), + list_name: String(document.getElementById('generated_list_name').value), + list_description: String(document.getElementById('generated_list_description').value), + custom_items: Object.values(custom_items), + uncalculated_items: Object.values(uncalculated_items), + calculated_items: Object.keys(calculated_items), + recipes: Object.keys(recipes), + full_system_calculated: full_sku_enabled, + shopping_lists: Object.keys(shopping_lists) + } + + const response = await fetch(`/shopping-lists/api/postGeneratedList`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }); } \ No newline at end of file diff --git a/application/shoppinglists/static/js/shoppingListsHandler.js b/application/shoppinglists/static/js/shoppingListsHandler.js index 3346489..d135b34 100644 --- a/application/shoppinglists/static/js/shoppingListsHandler.js +++ b/application/shoppinglists/static/js/shoppingListsHandler.js @@ -54,11 +54,11 @@ async function replenishShoppingListCards(lists) { footer_div.setAttribute('class', 'uk-card-footer') footer_div.style = 'height: 40px; border: none;' - let editOp = document.createElement('a') - editOp.setAttribute('class', 'uk-button uk-button-small uk-button-default') - editOp.innerHTML = ' Edit' - editOp.style = "margin-right: 10px;" - editOp.href = `/shopping-lists/edit/${lists[i].list_uuid}` + //let editOp = document.createElement('a') + //editOp.setAttribute('class', 'uk-button uk-button-small uk-button-default') + //editOp.innerHTML = ' Edit' + //editOp.style = "margin-right: 10px;" + //editOp.href = `/shopping-lists/edit/${lists[i].list_uuid}` let viewOp = document.createElement('a') viewOp.setAttribute('class', 'uk-button uk-button-small uk-button-default') @@ -66,8 +66,14 @@ async function replenishShoppingListCards(lists) { viewOp.href = `/shopping-lists/view/${lists[i].list_uuid}` //viewOp.style = "margin-right: 20px;" + let deleteOp = document.createElement('a') + deleteOp.setAttribute('class', 'uk-button uk-button-small uk-button-default') + deleteOp.innerHTML = ' Delete' + deleteOp.onclick = async function(params) { await deleteList(lists[i].list_uuid)} + //viewOp.style = "margin-right: 20px;" + - footer_div.append(editOp, viewOp) + footer_div.append(viewOp, deleteOp) main_div.append(card_header_div, body_div, footer_div) @@ -123,6 +129,35 @@ async function addList() { }); } +async function deleteList(shopping_list_uuid) { + const response = await fetch(`/shopping-lists/api/deleteList`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + shopping_list_uuid: shopping_list_uuid + }), + }); + data = await response.json(); + transaction_status = "success" + if (data.error){ + transaction_status = "danger" + } + + UIkit.notification({ + message: data.message, + status: transaction_status, + pos: 'top-right', + timeout: 5000 + }); + + let lists = await getShoppingLists() + await replenishShoppingListCards(lists) + await updatePaginationElement() + +} + async function changeSite(site){ const response = await fetch(`/changeSite`, { method: 'POST', diff --git a/application/shoppinglists/templates/generate.html b/application/shoppinglists/templates/generate.html index b8bbd8f..3d8e855 100644 --- a/application/shoppinglists/templates/generate.html +++ b/application/shoppinglists/templates/generate.html @@ -138,7 +138,7 @@
Fill out the basic info asked for here, the description could be helpful to remind yourself and others what the list was generated for. @@ -152,11 +152,14 @@