diff --git a/.gitignore b/.gitignore index e5ad1d5..2714bdf 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ static/css/uikit.min.css instance/application.cfg.py test.py .VScodeCounter -celerybeat-schedule \ No newline at end of file +celerybeat-schedule +instance/application.cfg.py diff --git a/application/access_module/templates/login.html b/application/access_module/templates/login.html index 1a5d763..f37aec3 100644 --- a/application/access_module/templates/login.html +++ b/application/access_module/templates/login.html @@ -3,6 +3,7 @@ Login + @@ -15,19 +16,20 @@ - + - +
- + +

PantryTrack

  • Login
  • {% if instance_settings['signup_enabled'] %} diff --git a/application/administration/templates/admin_index.html b/application/administration/templates/admin_index.html index 246a4c7..3ae4b5c 100644 --- a/application/administration/templates/admin_index.html +++ b/application/administration/templates/admin_index.html @@ -3,6 +3,7 @@ + diff --git a/application/administration/templates/role.html b/application/administration/templates/role.html index a666d7e..57a05cd 100644 --- a/application/administration/templates/role.html +++ b/application/administration/templates/role.html @@ -3,6 +3,7 @@ + diff --git a/application/administration/templates/setup.html b/application/administration/templates/setup.html index 92220c3..a730920 100644 --- a/application/administration/templates/setup.html +++ b/application/administration/templates/setup.html @@ -3,6 +3,7 @@ My Pantry - Setup + diff --git a/application/administration/templates/site.html b/application/administration/templates/site.html index 3d5b200..b4dbf59 100644 --- a/application/administration/templates/site.html +++ b/application/administration/templates/site.html @@ -3,6 +3,7 @@ + diff --git a/application/administration/templates/user.html b/application/administration/templates/user.html index 408af39..1f35aa2 100644 --- a/application/administration/templates/user.html +++ b/application/administration/templates/user.html @@ -3,6 +3,7 @@ User + diff --git a/application/items/templates/index.html b/application/items/templates/index.html index b422f77..6ab2d81 100644 --- a/application/items/templates/index.html +++ b/application/items/templates/index.html @@ -3,7 +3,7 @@ - + diff --git a/application/items/templates/item_new.html b/application/items/templates/item_new.html index 6d52797..baf9d1e 100644 --- a/application/items/templates/item_new.html +++ b/application/items/templates/item_new.html @@ -3,7 +3,7 @@ - + diff --git a/application/items/templates/itemlink.html b/application/items/templates/itemlink.html index b3724bd..ac9d024 100644 --- a/application/items/templates/itemlink.html +++ b/application/items/templates/itemlink.html @@ -3,6 +3,7 @@ + diff --git a/application/items/templates/transaction.html b/application/items/templates/transaction.html index 1c3c6ea..6db72d4 100644 --- a/application/items/templates/transaction.html +++ b/application/items/templates/transaction.html @@ -3,6 +3,7 @@ + diff --git a/application/items/templates/transactions.html b/application/items/templates/transactions.html index 1f5a0c5..12c4e39 100644 --- a/application/items/templates/transactions.html +++ b/application/items/templates/transactions.html @@ -3,6 +3,7 @@ + diff --git a/application/meal_planner/templates/meal_planner.html b/application/meal_planner/templates/meal_planner.html index 07324aa..d6a5f8f 100644 --- a/application/meal_planner/templates/meal_planner.html +++ b/application/meal_planner/templates/meal_planner.html @@ -3,6 +3,7 @@ + diff --git a/application/poe/templates/receipts.html b/application/poe/templates/receipts.html index 0b15262..7ba7533 100644 --- a/application/poe/templates/receipts.html +++ b/application/poe/templates/receipts.html @@ -3,6 +3,7 @@ + diff --git a/application/poe/templates/scanner.html b/application/poe/templates/scanner.html index 970eb2d..0d5f51f 100644 --- a/application/poe/templates/scanner.html +++ b/application/poe/templates/scanner.html @@ -3,6 +3,7 @@ + diff --git a/application/receipts/__pycache__/receipts_api.cpython-313.pyc b/application/receipts/__pycache__/receipts_api.cpython-313.pyc index d989544..8479051 100644 Binary files a/application/receipts/__pycache__/receipts_api.cpython-313.pyc and b/application/receipts/__pycache__/receipts_api.cpython-313.pyc differ diff --git a/application/receipts/receipts_api.py b/application/receipts/receipts_api.py index c7336c0..f5dd547 100644 --- a/application/receipts/receipts_api.py +++ b/application/receipts/receipts_api.py @@ -123,7 +123,7 @@ def addSKULine(): if request.method == "POST": item_id = int(request.get_json()['item_id']) receipt_id = int(request.get_json()['receipt_id']) - + print(item_id, receipt_id) site_name = session['selected_site'] item = receipts_database.getItemAllByID(site_name, (item_id, )) data = { diff --git a/application/receipts/sql/getItemAllByID.sql b/application/receipts/sql/getItemAllByID.sql index 8b5ca81..bee8937 100644 --- a/application/receipts/sql/getItemAllByID.sql +++ b/application/receipts/sql/getItemAllByID.sql @@ -1,4 +1,4 @@ -WITH passed_id AS (SELECT %s AS passed_id), +WITH passed_id AS (SELECT id AS passed_id, item_uuid as passed_uuid FROM %%site_name%%_items WHERE id=%s), logistics_id AS (SELECT logistics_info_id FROM %%site_name%%_items WHERE id=(SELECT passed_id FROM passed_id)), info_id AS (SELECT item_info_id FROM %%site_name%%_items WHERE id=(SELECT passed_id FROM passed_id)), cte_conversions AS ( @@ -33,12 +33,11 @@ WITH passed_id AS (SELECT %s AS passed_id), cte_shopping_lists AS ( SELECT %%site_name%%_shopping_lists.*, - %%site_name%%_shopping_list_items.uuid, %%site_name%%_shopping_list_items.item_type, %%site_name%%_shopping_list_items.qty FROM %%site_name%%_shopping_lists - JOIN %%site_name%%_shopping_list_items ON %%site_name%%_shopping_lists.id = %%site_name%%_shopping_list_items.sl_id - WHERE %%site_name%%_shopping_list_items.item_id = (SELECT passed_id FROM passed_id) + JOIN %%site_name%%_shopping_list_items ON %%site_name%%_shopping_lists.list_uuid = %%site_name%%_shopping_list_items.list_uuid + WHERE %%site_name%%_shopping_list_items.item_uuid = (SELECT passed_uuid FROM passed_id) ), cte_itemlinks AS ( SELECT * FROM %%site_name%%_itemlinks WHERE link=(SELECT passed_id FROM passed_id) diff --git a/application/receipts/templates/receipt.html b/application/receipts/templates/receipt.html index 518f990..b4fb82c 100644 --- a/application/receipts/templates/receipt.html +++ b/application/receipts/templates/receipt.html @@ -3,6 +3,7 @@ + diff --git a/application/receipts/templates/receipts_index.html b/application/receipts/templates/receipts_index.html index 9674041..5728cd9 100644 --- a/application/receipts/templates/receipts_index.html +++ b/application/receipts/templates/receipts_index.html @@ -3,6 +3,7 @@ + diff --git a/application/recipes/__pycache__/database_recipes.cpython-313.pyc b/application/recipes/__pycache__/database_recipes.cpython-313.pyc index a2753f9..53ef95c 100644 Binary files a/application/recipes/__pycache__/database_recipes.cpython-313.pyc and b/application/recipes/__pycache__/database_recipes.cpython-313.pyc differ diff --git a/application/recipes/__pycache__/recipe_processes.cpython-313.pyc b/application/recipes/__pycache__/recipe_processes.cpython-313.pyc index e49bf37..2bc4861 100644 Binary files a/application/recipes/__pycache__/recipe_processes.cpython-313.pyc and b/application/recipes/__pycache__/recipe_processes.cpython-313.pyc differ diff --git a/application/recipes/database_recipes.py b/application/recipes/database_recipes.py index 39bfe18..4befb8a 100644 --- a/application/recipes/database_recipes.py +++ b/application/recipes/database_recipes.py @@ -309,6 +309,8 @@ def selectItemTupleByUUID(site, payload, convert=True, conn=None): def selectConversionTuple(site, payload, convert=True, conn=None): """payload=(item_id, uom_id)""" selected = () + if convert: + selected = {} self_conn = False sql = f"SELECT conversions.conv_factor FROM {site}_conversions conversions WHERE item_id = %s AND uom_id = %s;" try: diff --git a/application/recipes/recipe_processes.py b/application/recipes/recipe_processes.py index 3aebafb..45fa488 100644 --- a/application/recipes/recipe_processes.py +++ b/application/recipes/recipe_processes.py @@ -112,7 +112,9 @@ def process_recipe_receipt(site_name, user_id, data:dict, conn=None): rp_item_uom = item['uom']['id'] item_stuff = database_recipes.selectItemTupleByUUID(site_name, (item['item_uuid'],), conn=conn) conv_factor = database_recipes.selectConversionTuple(site_name, (item_stuff['item_id'], rp_item_uom)) - qty = float(item['qty']) / float(conv_factor['conv_factor']) + print(conv_factor) + conversion = conv_factor.get('conv_factor', 1) + qty = float(item['qty']) / float(conversion) payload = { 'item_id': item_stuff['item_id'], 'logistics_info_id': item_stuff['logistics_info_id'], @@ -164,7 +166,7 @@ def postNewSkuFromRecipe(site_name: str, user_id: int, data: dict, conn=None): ) # create item info - item_info = database_payloads.ItemInfoPayload(barcode=None) + item_info = database_payloads.ItemInfoPayload(barcode=None, uom=data['uom_id'], cost=data['cost']) # create Food Info food_info = database_payloads.FoodInfoPayload() @@ -184,6 +186,7 @@ def postNewSkuFromRecipe(site_name: str, user_id: int, data: dict, conn=None): links = {'main': data['main_link']} search_string = f"&&{name}&&" + print(item_info) item = database_payloads.ItemsPayload( barcode=None, diff --git a/application/recipes/static/js/recipeEditHandler.js b/application/recipes/static/js/recipeEditHandler.js index 03d500c..85f1398 100644 --- a/application/recipes/static/js/recipeEditHandler.js +++ b/application/recipes/static/js/recipeEditHandler.js @@ -369,14 +369,14 @@ async function deleteInstruction(index){ async function openNewSKUModal() { - let itemsModal = document.getElementById('addNewSKUItem') + let addNewSKUItem = document.getElementById('addNewSKUItem') document.getElementById('newSKUName').value = "" document.getElementById('newSKUSubtype').value = "FOOD" document.getElementById('newSKUQty').value = 1 document.getElementById('newSKUUOM').value = "1" document.getElementById('newWeblink').value = "" document.getElementById('newSKUCost').value = 0.00 - UIkit.modal(itemsModal).show(); + UIkit.modal(addNewSKUItem).show(); } diff --git a/application/recipes/templates/recipe_edit.html b/application/recipes/templates/recipe_edit.html index 42a7342..6e6d36a 100644 --- a/application/recipes/templates/recipe_edit.html +++ b/application/recipes/templates/recipe_edit.html @@ -3,6 +3,7 @@ Recipes + diff --git a/application/recipes/templates/recipe_view.html b/application/recipes/templates/recipe_view.html index c2dd946..4d8f60a 100644 --- a/application/recipes/templates/recipe_view.html +++ b/application/recipes/templates/recipe_view.html @@ -3,6 +3,7 @@ Recipes + diff --git a/application/recipes/templates/recipes_index.html b/application/recipes/templates/recipes_index.html index c43e38d..da1475b 100644 --- a/application/recipes/templates/recipes_index.html +++ b/application/recipes/templates/recipes_index.html @@ -3,6 +3,7 @@ Recipes + diff --git a/application/shoppinglists/__pycache__/shoplist_api.cpython-313.pyc b/application/shoppinglists/__pycache__/shoplist_api.cpython-313.pyc index 61161c5..ab4f420 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 25279d8..c457824 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/shoplist_api.py b/application/shoppinglists/shoplist_api.py index 4c7f673..8dac703 100644 --- a/application/shoppinglists/shoplist_api.py +++ b/application/shoppinglists/shoplist_api.py @@ -29,6 +29,13 @@ def shopping_list(mode, list_uuid): return render_template("edit.html", list_uuid=list_uuid, current_site=session['selected_site'], sites=sites) return redirect("/") +@shopping_list_api.route("/generate") +@access_api.login_required +def generateList(): + sites = [site[1] for site in postsqldb.get_sites(session['user']['sites'])] + units = postsqldb.get_units_of_measure() + return render_template("generate.html", current_site=session['selected_site'], sites=sites, units=units) + # API CALLS # Added to Database @shopping_list_api.route('/api/addList', methods=["POST"]) @@ -107,7 +114,7 @@ def getShoppingListItem(): return jsonify({'list_item': list_item, 'error': True, 'message': 'List Items queried unsuccessfully!'}) # Added to database -@shopping_list_api.route('/api/getItems', methods=["GET"]) +@shopping_list_api.route('/api/getItemstwo', methods=["GET"]) @access_api.login_required def getItems(): recordset = [] @@ -141,6 +148,40 @@ def getRecipesModal(): return jsonify(status=201, recipes=recordsets, end=math.ceil(count/limit), message=f"Recipes fetched successfully!") return jsonify(status=405, recipes=recordsets, end=math.ceil(count/limit), message=f"{request.method} is not an accepted method on this endpoint!") +@shopping_list_api.route('/api/getListsModal', methods=["GET"]) +@access_api.login_required +def getListsModal(): + recordsets = [] + count = 0 + if request.method == "GET": + page = int(request.args.get('page', 1)) + limit = int(request.args.get('limit', 10)) + search_string = request.args.get('search_string', 10) + site_name = session['selected_site'] + offset = (page - 1) * limit + + payload = (search_string, limit, offset) + recordsets, count = shoplist_database.getListsModal(site_name, payload) + return jsonify(status=201, lists=recordsets, end=math.ceil(count/limit), message=f"Recipes fetched successfully!") + return jsonify(status=405, lists=recordsets, end=math.ceil(count/limit), message=f"{request.method} is not an accepted method on this endpoint!") + + +@shopping_list_api.route("/api/getItems", methods=["GET"]) +@access_api.login_required +def getItemsModal(): + items = [] + count = 0 + if request.method == "GET": + page = int(request.args.get('page', 1)) + limit = int(request.args.get('limit', 10)) + search_string = request.args.get('search_string', 10) + site_name = session['selected_site'] + offset = (page - 1) * limit + payload = (search_string, limit, offset) + items, count = shoplist_database.getItemsModal(site_name, payload) + return jsonify(status=201, items=items, end=math.ceil(count/limit), message=f"Items fetched successfully!") + return jsonify(status=405, items=items, end=math.ceil(count/limit), message=f"{request.method} is not an accepted method on this endpoint!") + # Added to database @shopping_list_api.route('/api/postListItem', methods=["POST"]) @@ -231,8 +272,24 @@ def getSKUItemsFull(): 'qty': float(float(item['item_info']['safety_stock']) - float(item['total_sum'])), 'item_id': item['id'], 'links': item['links'], - 'uom_fullname': item['uom_fullname'] + 'uom_fullname': item['uom_fullname'], + 'list_item_state': False } items.append(new_item) 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"}) + +# Added to Database +@shopping_list_api.route('/api/setListItemState', methods=["POST"]) +@access_api.login_required +def setListItemState(): + items = [] + count = {'count': 0} + if request.method == "POST": + site_name = session['selected_site'] + print(request.get_json()) + + shoplist_database.updateShoppingListItemsTuple(site_name, {'uuid': request.get_json()['list_item_uuid'], 'update': {'list_item_state': request.get_json()['list_item_state']}}) + + 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"}) diff --git a/application/shoppinglists/shoplist_database.py b/application/shoppinglists/shoplist_database.py index 8e596b8..6379783 100644 --- a/application/shoppinglists/shoplist_database.py +++ b/application/shoppinglists/shoplist_database.py @@ -227,6 +227,77 @@ def getRecipesModal(site, payload, convert=True, conn=None): except Exception as error: raise postsqldb.DatabaseError(error, payload, sql) +def getListsModal(site, payload, convert=True, conn=None): + recordsets = [] + count = 0 + self_conn = False + + + sql = f"SELECT lists.list_uuid, lists.name FROM {site}_shopping_lists lists WHERE lists.name LIKE '%%' || %s || '%%' LIMIT %s OFFSET %s;" + sql_count = f"SELECT COUNT(*) FROM {site}_shopping_lists lists WHERE lists.name LIKE '%%' || %s || '%%';" + 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: + recordsets = [postsqldb.tupleDictionaryFactory(cur.description, row) for row in rows] + if rows and not convert: + recordsets = rows + + + cur.execute(sql_count, (payload[0], )) + count = cur.fetchone()[0] + + if self_conn: + conn.close() + + return recordsets, count + + except Exception as error: + raise postsqldb.DatabaseError(error, payload, sql) + + +def getItemsModal(site, payload, convert=True, conn=None): + recordsets = [] + count = 0 + self_conn = False + with open(f"application/shoppinglists/sql/getItemsForModal.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.fetchall() + if rows and convert: + recordsets = [postsqldb.tupleDictionaryFactory(cur.description, row) for row in rows] + if rows and not convert: + recordsets = rows + + cur.execute(f"SELECT COUNT(*) FROM {site}_items WHERE search_string LIKE '%%' || %s || '%%';", (payload[0], )) + count = cur.fetchone()[0] + + if self_conn: + conn.close() + + return recordsets, count + + 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/sql/getItemsForModal.sql b/application/shoppinglists/sql/getItemsForModal.sql new file mode 100644 index 0000000..6ee93a0 --- /dev/null +++ b/application/shoppinglists/sql/getItemsForModal.sql @@ -0,0 +1,11 @@ +SELECT items.item_uuid as item_uuid, + items.item_name as item_name, + units.fullname AS fullname, + units.id AS unit_id, + items.links AS links +FROM %%site_name%%_items items +LEFT JOIN %%site_name%%_item_info item_info ON item_info.id = items.item_info_id +LEFT JOIN units ON item_info.uom = units.id +WHERE items.search_string LIKE '%%' || %s || '%%' +ORDER BY items.item_name +LIMIT %s OFFSET %s; \ No newline at end of file diff --git a/application/shoppinglists/static/js/shoppingListGeneratorHandler.js b/application/shoppinglists/static/js/shoppingListGeneratorHandler.js new file mode 100644 index 0000000..95452c9 --- /dev/null +++ b/application/shoppinglists/static/js/shoppingListGeneratorHandler.js @@ -0,0 +1,1077 @@ +function uuidv4() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); +} + +document.addEventListener('DOMContentLoaded', async function() { + await generateCustomItemsTable() + await generateUncalculatedItemsTable() + await generateCalculatedItemsTable() +}); + +var items_limit = 25 +var item_current_page = 1 +var item_end_page = 1 +var item_search_string = "" + +async function fetchItems(){ + const url = new URL('/shopping-lists/api/getItems', window.location.origin); + url.searchParams.append('page', item_current_page); + url.searchParams.append('limit', items_limit); + url.searchParams.append('search_string', item_search_string); + const response = await fetch(url); + data = await response.json(); + item_end_page = data.end + return data.items; +} + +var recipes_limit = 25 +var recipes_current_page = 1 +var recipes_end_page = 1 +var recipes_search_string = "" + +async function fetchRecipes(){ + const url = new URL('/shopping-lists/api/getRecipesModal', window.location.origin); + url.searchParams.append('page', recipes_current_page); + url.searchParams.append('limit', recipes_limit); + url.searchParams.append('search_string', recipes_search_string); + const response = await fetch(url); + data = await response.json(); + recipes_end_page = data.end + return data.recipes; +} + +var lists_limit = 25 +var lists_current_page = 1 +var lists_end_page = 1 +var lists_search_string = "" + +async function fetchLists(){ + const url = new URL('/shopping-lists/api/getListsModal', window.location.origin); + url.searchParams.append('page', lists_current_page); + url.searchParams.append('limit', lists_limit); + url.searchParams.append('search_string', lists_search_string); + const response = await fetch(url); + data = await response.json(); + lists_end_page = data.end + return data.lists; +} + + +// Custom Item Card Functions +var custom_items_card_active = false; +var custom_items = {}; +async function addCustomItemsCard(){ + if(!custom_items_card_active){ + document.getElementById('customItemsCard').hidden = false + custom_items_card_active = true; + + } +} + +async function removeCustomItemsCard(){ + document.getElementById('customItemsCard').hidden = true + custom_items_card_active = false; + custom_items = {} +} + +var customZoneState = true +async function changeCustomZoneState() { + customZoneState = !customZoneState + document.getElementById('customCardZone').hidden = !customZoneState +} + +async function openCustomItemsModal(){ + document.getElementById('customName').value = "" + document.getElementById('customQty').value = "" + document.getElementById('customUnit').value = 1 + document.getElementById('customLink').value = "" + document.getElementById('customItemModalButton').innerHTML = "Add" + document.getElementById('customItemModalButton').onclick = async function () {await addCustomItem()} + UIkit.modal(document.getElementById('CustomItemModal')).show() +} + +async function addCustomItem() { + let uuid = uuidv4(); + let customUnit = document.getElementById('customUnit') + let selected_fullname = customUnit.options[customUnit.selectedIndex] + custom_items[uuid] = { + item_type: 'custom', + item_name: document.getElementById('customName').value, + uom: parseInt(document.getElementById('customUnit').value), + qty: parseFloat(document.getElementById('customQty').value), + link: document.getElementById('customLink').value, + fullname: selected_fullname.dataset.fullname + } + UIkit.modal(document.getElementById('CustomItemModal')).hide() + console.log(custom_items) + await generateCustomItemsTable() +} + +async function editCustomItem(customItemUUID) { + let data = custom_items[customItemUUID] + document.getElementById('customName').value = data['item_name'] + document.getElementById('customQty').value = data['qty'] + document.getElementById('customUnit').value = data['uom'] + document.getElementById('customLink').value = data['link'] + document.getElementById('customItemModalButton').innerHTML = "Save" + document.getElementById('customItemModalButton').onclick = async function () { + custom_items[customItemUUID] = { + item_type: 'custom', + item_name: document.getElementById('customName').value, + uom: document.getElementById('customUnit').value, + qty: parseFloat(document.getElementById('customQty').value), + link: document.getElementById('customLink').value, + fullname: data.fullname + } + await generateCustomItemsTable() + UIkit.modal(document.getElementById('CustomItemModal')).hide() + + } + UIkit.modal(document.getElementById('CustomItemModal')).show() +} + +async function deleteCustomItem(customItemUUID) { + delete custom_items[customItemUUID] + await generateCustomItemsTable() +} + +async function generateCustomItemsTable() { + let customItemsTableBody = document.getElementById('customItemsTableBody') + customItemsTableBody.innerHTML = "" + + for(const key in custom_items){ + if(custom_items.hasOwnProperty(key)){ + let tableRow = document.createElement('tr') + + + let nameCell = document.createElement('td') + nameCell.innerHTML = `${custom_items[key].item_name}` + + let qty_uomCell = document.createElement('td') + qty_uomCell.innerHTML = `${custom_items[key].qty} ${custom_items[key].fullname}` + + let opCell = document.createElement('td') + + let editButton = document.createElement('button') + editButton.setAttribute('class', 'uk-button uk-button-default uk-button-small') + editButton.innerHTML = "Edit" + editButton.setAttribute('uk-tooltip', 'Edit item information for this row.') + + editButton.onclick = async function() {await editCustomItem(key)} + + let removeButton = document.createElement('button') + removeButton.setAttribute('class', 'uk-button uk-button-default uk-button-small') + removeButton.setAttribute('uk-tooltip', 'Removes item from the saved custom items list.') + removeButton.innerHTML = "Remove" + removeButton.onclick = async function() {await deleteCustomItem(key)} + + + opCell.append(editButton, removeButton) + + tableRow.append(nameCell, qty_uomCell, opCell) + customItemsTableBody.append(tableRow) + + } + } + +} + +// Uncalculated System Items Card +var uncalculated_items_card_active = false; +var uncalculated_items = {}; +async function addUncalculatedItemsCard(){ + if(!uncalculated_items_card_active){ + document.getElementById('uncalcedItemsCard').hidden = false + uncalculated_items_card_active = true; + } +} + +async function removeUncalcedItemsCard(){ + document.getElementById('uncalcedItemsCard').hidden = true + uncalculated_items_card_active = false; + uncalculated_items = {} +} + +var uncalculatedZoneState = true +async function changeUncalculatedZoneState() { + uncalculatedZoneState = !uncalculatedZoneState + document.getElementById('uncalculatedCardZone').hidden = !uncalculatedZoneState +} + +async function openUncalculatedItemsModal(){ + let items = await fetchItems() + console.log(items) + await generateUncalculatedItemsModalTable(items) + await updateUncalculatedItemsModalPagination() + UIkit.modal(document.getElementById('uncalculatedItemModal')).show() +} + +async function addUncalculatedItem(item_data, qtySRCid) { + console.log(qtySRCid) + let qty = parseFloat(document.getElementById(qtySRCid).value) + + let link = "" + if(item_data.links.hasOwnProperty('main')){ + link = item_data.links.main + } + + uncalculated_items[item_data.item_uuid] = { + item_type: 'uncalculated sku', + item_name: item_data.item_name, + uom: item_data.unit_id, + qty: qty, + link: link, + fullname: item_data.fullname + } + UIkit.modal(document.getElementById('uncalculatedItemModal')).hide() + console.log(uncalculated_items) + + await generateUncalculatedItemsTable() +} + +async function editUncalculatedItem(itemUUID) { + let data = uncalculated_items[itemUUID] + document.getElementById('uncalculatedItemName').value = data['item_name'] + document.getElementById('uncalculatedItemQty').value = data['qty'] + document.getElementById('uncalculatedItemUnit').value = data['uom'] + document.getElementById('uncalculatedItemLink').value = data['link'] + document.getElementById('uncalculatedItemModalEditButton').innerHTML = "Save" + document.getElementById('uncalculatedItemModalEditButton').onclick = async function () { + uncalculated_items[itemUUID] = { + item_type: data.item_type, + item_name: data.item_name, + uom: data.uom, + qty: parseFloat(document.getElementById('uncalculatedItemQty').value), + link: document.getElementById('uncalculatedItemLink').value, + fullname: data.fullname + } + await generateUncalculatedItemsTable() + UIkit.modal(document.getElementById('uncalculatedItemModalEdit')).hide() + + } + UIkit.modal(document.getElementById('uncalculatedItemModalEdit')).show() + +} + +async function deleteUncalculatedItem(itemUUID) { + delete uncalculated_items[itemUUID] + await generateUncalculatedItemsTable() +} + +async function searchItemTable(event) { + console.log(event) + if(event.key==='Enter'){ + item_search_string = event.srcElement.value + let items = await fetchItems() + await generateUncalculatedItemsModalTable(items) + await updateUncalculatedItemsModalPagination() + } +} + +async function setItemsPage(pageNumber){ + item_current_page = pageNumber; + let items = await fetchItems() + await generateUncalculatedItemsModalTable(items) + await updateUncalculatedItemsModalPagination() +} + +async function updateUncalculatedItemsModalPagination() { + let paginationElement = document.getElementById('itemsPage'); + paginationElement.innerHTML = ""; + // previous + let previousElement = document.createElement('li') + if(item_current_page<=1){ + previousElement.innerHTML = ``; + previousElement.classList.add('uk-disabled'); + }else { + previousElement.innerHTML = ``; + } + paginationElement.append(previousElement) + + //first + let firstElement = document.createElement('li') + if(item_current_page<=1){ + firstElement.innerHTML = `1`; + firstElement.classList.add('uk-disabled'); + }else { + firstElement.innerHTML = `1`; + } + paginationElement.append(firstElement) + + // ... + if(item_current_page-2>1){ + let firstDotElement = document.createElement('li') + firstDotElement.classList.add('uk-disabled') + firstDotElement.innerHTML = ``; + paginationElement.append(firstDotElement) + } + // last + if(item_current_page-2>0){ + let lastElement = document.createElement('li') + lastElement.innerHTML = `${item_current_page-1}` + paginationElement.append(lastElement) + } + // current + if(item_current_page!=1 && item_current_page != item_end_page){ + let currentElement = document.createElement('li') + currentElement.innerHTML = `
  • ${item_current_page}
  • ` + paginationElement.append(currentElement) + } + // next + if(item_current_page+2${item_current_page+1}` + paginationElement.append(nextElement) + } + // ... + if(item_current_page+2<=item_end_page){ + let secondDotElement = document.createElement('li') + secondDotElement.classList.add('uk-disabled') + secondDotElement.innerHTML = ``; + paginationElement.append(secondDotElement) + } + //end + let endElement = document.createElement('li') + if(item_current_page>=item_end_page){ + endElement.innerHTML = `${item_end_page}`; + endElement.classList.add('uk-disabled'); + }else { + endElement.innerHTML = `${item_end_page}`; + } + paginationElement.append(endElement) + //next button + let nextElement = document.createElement('li') + if(item_current_page>=item_end_page){ + nextElement.innerHTML = ``; + nextElement.classList.add('uk-disabled'); + }else { + nextElement.innerHTML = ``; + console.log(nextElement.innerHTML) + } + paginationElement.append(nextElement) +} + +async function generateUncalculatedItemsModalTable(items) { + // used to fill out items modal... bad name bad + let uncalculatedItemsModalTableBody = document.getElementById('uncalculatedItemsModalTableBody') + uncalculatedItemsModalTableBody.innerHTML = "" + + for(let i = 0; i < items.length; i++){ + + let tableRow = document.createElement('tr') + + let nameCell = document.createElement('td') + nameCell.innerHTML = `${items[i].item_name}` + + let qtyCell = document.createElement('td') + let qty_input_id = `${items[i].item_uuid}_qty` + qtyCell.innerHTML = `` + + let uomCell = document.createElement('td') + uomCell.innerHTML = `${items[i].fullname}` + + let opCell = document.createElement('td') + + let selectButton = document.createElement('button') + selectButton.setAttribute('class', 'uk-button uk-button-default uk-button-small') + selectButton.innerHTML = "Select" + selectButton.setAttribute('uk-tooltip', 'Selects item to add to list, must have Qty set or assumes 1.') + + selectButton.onclick = async function() { + await addUncalculatedItem(items[i], qty_input_id) + } + + opCell.append(selectButton) + + tableRow.append(nameCell, qtyCell, uomCell, opCell) + uncalculatedItemsModalTableBody.append(tableRow) + } + +} + +async function generateUncalculatedItemsTable() { + let uncalculatedItemsTableBody = document.getElementById('uncalculatedItemsTableBody') + uncalculatedItemsTableBody.innerHTML = "" + + for(const key in uncalculated_items){ + if(uncalculated_items.hasOwnProperty(key)){ + let tableRow = document.createElement('tr') + + + let nameCell = document.createElement('td') + nameCell.innerHTML = `${uncalculated_items[key].item_name}` + + let qty_uomCell = document.createElement('td') + qty_uomCell.innerHTML = `${uncalculated_items[key].qty} ${uncalculated_items[key].fullname}` + + let opCell = document.createElement('td') + + let editButton = document.createElement('button') + editButton.setAttribute('class', 'uk-button uk-button-default uk-button-small') + editButton.innerHTML = "Edit" + editButton.setAttribute('uk-tooltip', 'Edit item information for this row.') + + editButton.onclick = async function() {await editUncalculatedItem(key)} + + let removeButton = document.createElement('button') + removeButton.setAttribute('class', 'uk-button uk-button-default uk-button-small') + removeButton.setAttribute('uk-tooltip', 'Removes item from the saved custom items list.') + removeButton.innerHTML = "Remove" + removeButton.onclick = async function() {await deleteUncalculatedItem(key)} + + + opCell.append(editButton, removeButton) + + tableRow.append(nameCell, qty_uomCell, opCell) + uncalculatedItemsTableBody.append(tableRow) + + } + } + +} + +// Calculated System Item Functions +var calculated_items_card_active = false; +var calculated_items = {}; +async function addCalculatedItemsCard(){ + if(!calculated_items_card_active && !full_sku_card_active){ + document.getElementById('calculatedItemsCard').hidden = false + calculated_items_card_active = true; + } else if (!calculated_items_card_active && full_sku_card_active){ + let prompt_text = `This will remove the Full Calculation Operator as both these + operators can not exist together. You will lose all configured items, are you sure you wish to proceed?` + UIkit.modal.confirm(prompt_text).then(async function() { + await removeFullSKUCard() + document.getElementById('calculatedItemsCard').hidden = false + calculated_items_card_active = true; + }, async function() { + document.getElementById('calculatedItemsCard').hidden = false + calculated_items_card_active = true; + }); + } +} + +async function removeCalculatedItemsCard(){ + document.getElementById('calculatedItemsCard').hidden = true + calculated_items_card_active = false; + calculated_items = {} +} + +var calculatedZoneState = true +async function changeCalculatedZoneState() { + calculatedZoneState = !calculatedZoneState + document.getElementById('calculatedCardZone').hidden = !calculatedZoneState +} + +async function openCalculatedItemsModal(){ + let items = await fetchItems() + console.log(items) + await generateCalculatedItemsModalTable(items) + await updateCalculatedItemsModalPagination() + UIkit.modal(document.getElementById('calculatedItemModal')).show() +} + +async function addCalculatedItem(item_data) { + let link = "" + if(item_data.links.hasOwnProperty('main')){ + link = item_data.links.main + } + + calculated_items[item_data.item_uuid] = { + item_type: 'calculated sku', + item_name: item_data.item_name, + uom: item_data.unit_id, + qty: 0, + link: link, + fullname: item_data.fullname + } + UIkit.modal(document.getElementById('calculatedItemModal')).hide() + console.log(calculated_items) + + await generateCalculatedItemsTable() +} + +async function deleteCalculatedItem(itemUUID) { + delete calculated_items[itemUUID] + await generateCalculatedItemsTable() +} + +async function searchCalculatedItemTable(event) { + console.log(event) + if(event.key==='Enter'){ + item_search_string = event.srcElement.value + let items = await fetchItems() + await generateCalculatedItemsModalTable(items) + await updateCalculatedItemsModalPagination() + } +} + +async function setCalculatedItemsPage(pageNumber){ + item_current_page = pageNumber; + let items = await fetchItems() + await generateCalculatedItemsModalTable(items) + await updateCalculatedItemsModalPagination() +} + +async function updateCalculatedItemsModalPagination() { + let paginationElement = document.getElementById('itemsCalculatedPage'); + paginationElement.innerHTML = ""; + // previous + let previousElement = document.createElement('li') + if(item_current_page<=1){ + previousElement.innerHTML = ``; + previousElement.classList.add('uk-disabled'); + }else { + previousElement.innerHTML = ``; + } + paginationElement.append(previousElement) + + //first + let firstElement = document.createElement('li') + if(item_current_page<=1){ + firstElement.innerHTML = `1`; + firstElement.classList.add('uk-disabled'); + }else { + firstElement.innerHTML = `1`; + } + paginationElement.append(firstElement) + + // ... + if(item_current_page-2>1){ + let firstDotElement = document.createElement('li') + firstDotElement.classList.add('uk-disabled') + firstDotElement.innerHTML = ``; + paginationElement.append(firstDotElement) + } + // last + if(item_current_page-2>0){ + let lastElement = document.createElement('li') + lastElement.innerHTML = `${item_current_page-1}` + paginationElement.append(lastElement) + } + // current + if(item_current_page!=1 && item_current_page != item_end_page){ + let currentElement = document.createElement('li') + currentElement.innerHTML = `
  • ${item_current_page}
  • ` + paginationElement.append(currentElement) + } + // next + if(item_current_page+2${item_current_page+1}` + paginationElement.append(nextElement) + } + // ... + if(item_current_page+2<=item_end_page){ + let secondDotElement = document.createElement('li') + secondDotElement.classList.add('uk-disabled') + secondDotElement.innerHTML = ``; + paginationElement.append(secondDotElement) + } + //end + let endElement = document.createElement('li') + if(item_current_page>=item_end_page){ + endElement.innerHTML = `${item_end_page}`; + endElement.classList.add('uk-disabled'); + }else { + endElement.innerHTML = `${item_end_page}`; + } + paginationElement.append(endElement) + //next button + let nextElement = document.createElement('li') + if(item_current_page>=item_end_page){ + nextElement.innerHTML = ``; + nextElement.classList.add('uk-disabled'); + }else { + nextElement.innerHTML = ``; + console.log(nextElement.innerHTML) + } + paginationElement.append(nextElement) +} + +async function generateCalculatedItemsModalTable(items) { + let calculatedItemsModalTableBody = document.getElementById('calculatedItemsModalTableBody') + calculatedItemsModalTableBody.innerHTML = "" + + for(let i = 0; i < items.length; i++){ + + let tableRow = document.createElement('tr') + + let nameCell = document.createElement('td') + nameCell.innerHTML = `${items[i].item_name}` + + let opCell = document.createElement('td') + + let selectButton = document.createElement('button') + selectButton.setAttribute('class', 'uk-button uk-button-default uk-button-small') + selectButton.innerHTML = "Select" + selectButton.setAttribute('uk-tooltip', 'Selects item to add to list, must have Qty set or assumes 1.') + + selectButton.onclick = async function() { + await addCalculatedItem(items[i]) + } + + opCell.append(selectButton) + + tableRow.append(nameCell, opCell) + calculatedItemsModalTableBody.append(tableRow) + } + +} + +async function generateCalculatedItemsTable() { + let calculatedItemsTableBody = document.getElementById('calculatedItemsTableBody') + calculatedItemsTableBody.innerHTML = "" + + for(const key in calculated_items){ + if(calculated_items.hasOwnProperty(key)){ + let tableRow = document.createElement('tr') + + + let nameCell = document.createElement('td') + nameCell.innerHTML = `${calculated_items[key].item_name}` + + let opCell = document.createElement('td') + + let removeButton = document.createElement('button') + removeButton.setAttribute('class', 'uk-button uk-button-default uk-button-small') + removeButton.setAttribute('uk-tooltip', 'Removes item from the saved calculated systems items list.') + removeButton.innerHTML = "Remove" + removeButton.onclick = async function() {await deleteCalculatedItem(key)} + + opCell.append(removeButton) + + tableRow.append(nameCell, opCell) + calculatedItemsTableBody.append(tableRow) + + } + } + +} + +// Recipes Operator functions +var recipes_items_card_active = false; +var recipes = {}; +async function addRecipesCard(){ + if(!recipes_items_card_active){ + document.getElementById('recipesCard').hidden = false + recipes_items_card_active = true; + } +} + +async function removeRecipesItemsCard(){ + document.getElementById('recipesCard').hidden = true + recipes_items_card_active = false; + recipes = {} +} + +var recipesZoneState = true +async function changeRecipesZoneState() { + recipesZoneState = !recipesZoneState + document.getElementById('recipesCardZone').hidden = !recipesZoneState +} + +async function openRecipesModal(){ + let recipes = await fetchRecipes() + await generateRecipesModalTable(recipes) + await updateRecipesModalPagination() + UIkit.modal(document.getElementById('recipesModal')).show() +} + +async function addRecipe(recipe_data) { + recipes[recipe_data.recipe_uuid] = recipe_data + UIkit.modal(document.getElementById('recipesModal')).hide() + console.log(recipes) + await generateRecipesTable() +} + +async function deleteRecipe(recipeUUID) { + delete recipes[recipeUUID] + await generateRecipesTable() +} + +async function searchRecipesTable(event) { + console.log(event) + if(event.key==='Enter'){ + recipes_search_string = event.srcElement.value + let recipes = await fetchRecipes() + await generateRecipesModalTable(recipes) + await updateRecipesModalPagination() + } +} + +async function setRecipesPage(pageNumber){ + recipes_current_page = pageNumber; + let recipes = await fetchRecipes() + await generateRecipesModalTable(recipes) + await updateRecipesModalPagination() +} + +async function updateRecipesModalPagination() { + let paginationElement = document.getElementById('recipesPage'); + paginationElement.innerHTML = ""; + // previous + let previousElement = document.createElement('li') + if(recipes_current_page<=1){ + previousElement.innerHTML = ``; + previousElement.classList.add('uk-disabled'); + }else { + previousElement.innerHTML = ``; + } + paginationElement.append(previousElement) + + //first + let firstElement = document.createElement('li') + if(recipes_current_page<=1){ + firstElement.innerHTML = `1`; + firstElement.classList.add('uk-disabled'); + }else { + firstElement.innerHTML = `1`; + } + paginationElement.append(firstElement) + + // ... + if(recipes_current_page-2>1){ + let firstDotElement = document.createElement('li') + firstDotElement.classList.add('uk-disabled') + firstDotElement.innerHTML = ``; + paginationElement.append(firstDotElement) + } + // last + if(recipes_current_page-2>0){ + let lastElement = document.createElement('li') + lastElement.innerHTML = `${recipes_current_page-1}` + paginationElement.append(lastElement) + } + // current + if(recipes_current_page!=1 && recipes_current_page != recipes_end_page){ + let currentElement = document.createElement('li') + currentElement.innerHTML = `
  • ${recipes_current_page}
  • ` + paginationElement.append(currentElement) + } + // next + if(recipes_current_page+2${recipes_current_page+1}` + paginationElement.append(nextElement) + } + // ... + if(recipes_current_page+2<=recipes_end_page){ + let secondDotElement = document.createElement('li') + secondDotElement.classList.add('uk-disabled') + secondDotElement.innerHTML = ``; + paginationElement.append(secondDotElement) + } + //end + let endElement = document.createElement('li') + if(recipes_current_page>=recipes_end_page){ + endElement.innerHTML = `${recipes_end_page}`; + endElement.classList.add('uk-disabled'); + }else { + endElement.innerHTML = `${recipes_end_page}`; + } + paginationElement.append(endElement) + //next button + let nextElement = document.createElement('li') + if(recipes_current_page>=recipes_end_page){ + nextElement.innerHTML = ``; + nextElement.classList.add('uk-disabled'); + }else { + nextElement.innerHTML = ``; + } + paginationElement.append(nextElement) +} + +async function generateRecipesModalTable(recipes) { + let recipesModalTableBody = document.getElementById('recipesModalTableBody') + recipesModalTableBody.innerHTML = "" + + for(let i = 0; i < recipes.length; i++){ + + let tableRow = document.createElement('tr') + + let nameCell = document.createElement('td') + nameCell.innerHTML = `${recipes[i].name}` + + let opCell = document.createElement('td') + + let selectButton = document.createElement('button') + selectButton.setAttribute('class', 'uk-button uk-button-default uk-button-small') + selectButton.innerHTML = "Select" + selectButton.setAttribute('uk-tooltip', 'Selects Recipe to add to list.') + + selectButton.onclick = async function() { + await addRecipe(recipes[i]) + } + + opCell.append(selectButton) + + tableRow.append(nameCell, opCell) + recipesModalTableBody.append(tableRow) + } + +} + +async function generateRecipesTable() { + let recipesTableBody = document.getElementById('recipesTableBody') + recipesTableBody.innerHTML = "" + + for(const key in recipes){ + if(recipes.hasOwnProperty(key)){ + let tableRow = document.createElement('tr') + + + let nameCell = document.createElement('td') + nameCell.innerHTML = `${recipes[key].name}` + + let opCell = document.createElement('td') + + let removeButton = document.createElement('button') + removeButton.setAttribute('class', 'uk-button uk-button-default uk-button-small') + removeButton.setAttribute('uk-tooltip', 'Removes Recipe from the saved receipes') + removeButton.innerHTML = "Remove" + removeButton.onclick = async function() {await deleteRecipe(recipes[key].recipe_uuid)} + + opCell.append(removeButton) + + tableRow.append(nameCell, opCell) + recipesTableBody.append(tableRow) + + } + } + +} + +// Full SKU Calcuation Functions +var full_sku_card_active = false; +var full_sku_enabled = false; +async function addFullSKUCard(){ + if(!full_sku_card_active){ + document.getElementById('fullSKUCard').hidden = false + full_sku_card_active = true; + full_sku_enabled = false; + document.getElementById('fullSKUCheckbox').checked = false + } +} + +async function removeFullSKUCard(){ + document.getElementById('fullSKUCard').hidden = true + full_sku_card_active = false; + full_sku_enabled = false; + document.getElementById('fullSKUCheckbox').checked = false +} + + +async function fullSKUEnabledChange(event){ + if(calculated_items_card_active){ + let prompt_text = `This will remove the calculated system item operator as both these + operators can not exist together. You will lose all configured itesm, are you sure you wish to proceed?` + UIkit.modal.confirm(prompt_text).then(async function() { + await removeCalculatedItemsCard() + full_sku_enabled = true; + event.target.checked = full_sku_enabled; + console.log(calculated_items) + }, async function() { + full_sku_enabled = false; + event.target.checked = full_sku_enabled; + }); + } else { + full_sku_enabled = event.target.checked + } +} + +// Shopping Lists functions +var lists_card_active = false; +var shopping_lists = {}; +async function addListsCard(){ + if(!lists_card_active){ + document.getElementById('listsCard').hidden = false + lists_card_active = true; + } +} + +async function removeListsItemsCard(){ + document.getElementById('listsCard').hidden = true + lists_card_active = false; + shopping_lists = {} +} + +var ListsZoneState = true +async function changeListsZoneState() { + ListsZoneState = !ListsZoneState + document.getElementById('listsCardZone').hidden = !ListsZoneState +} + +async function openListsModal(){ + let lists = await fetchLists() + await generateListsModalTable(lists) + await updateListsModalPagination() + UIkit.modal(document.getElementById('listsModal')).show() +} + +async function addList(list_data) { + shopping_lists[list_data.list_uuid] = list_data + UIkit.modal(document.getElementById('listsModal')).hide() + console.log(shopping_lists) + await generateListsTable() +} + +async function deleteList(listUUID) { + delete shopping_lists[listUUID] + await generateListsTable() +} + +async function searchListsTable(event) { + console.log(event) + if(event.key==='Enter'){ + lists_search_string = event.srcElement.value + let lists = await fetchLists() + await generateListsModalTable(lists) + await updateListsModalPagination() + } +} + +async function setListsPage(pageNumber){ + lists_current_page = pageNumber; + let lists = await fetchLists() + await generateListsModalTable(lists) + await updateListsModalPagination() +} + +async function updateListsModalPagination() { + let paginationElement = document.getElementById('listsPage'); + paginationElement.innerHTML = ""; + // previous + let previousElement = document.createElement('li') + if(lists_current_page<=1){ + previousElement.innerHTML = ``; + previousElement.classList.add('uk-disabled'); + }else { + previousElement.innerHTML = ``; + } + paginationElement.append(previousElement) + + //first + let firstElement = document.createElement('li') + if(lists_current_page<=1){ + firstElement.innerHTML = `1`; + firstElement.classList.add('uk-disabled'); + }else { + firstElement.innerHTML = `1`; + } + paginationElement.append(firstElement) + + // ... + if(lists_current_page-2>1){ + let firstDotElement = document.createElement('li') + firstDotElement.classList.add('uk-disabled') + firstDotElement.innerHTML = ``; + paginationElement.append(firstDotElement) + } + // last + if(lists_current_page-2>0){ + let lastElement = document.createElement('li') + lastElement.innerHTML = `${lists_current_page-1}` + paginationElement.append(lastElement) + } + // current + if(lists_current_page!=1 && lists_current_page != lists_end_page){ + let currentElement = document.createElement('li') + currentElement.innerHTML = `
  • ${lists_current_page}
  • ` + paginationElement.append(currentElement) + } + // next + if(lists_current_page+2${lists_current_page+1}` + paginationElement.append(nextElement) + } + // ... + if(lists_current_page+2<=lists_end_page){ + let secondDotElement = document.createElement('li') + secondDotElement.classList.add('uk-disabled') + secondDotElement.innerHTML = ``; + paginationElement.append(secondDotElement) + } + //end + let endElement = document.createElement('li') + if(lists_current_page>=lists_end_page){ + endElement.innerHTML = `${lists_end_page}`; + endElement.classList.add('uk-disabled'); + }else { + endElement.innerHTML = `${lists_end_page}`; + } + paginationElement.append(endElement) + //next button + let nextElement = document.createElement('li') + if(lists_current_page>=lists_end_page){ + nextElement.innerHTML = ``; + nextElement.classList.add('uk-disabled'); + }else { + nextElement.innerHTML = ``; + } + paginationElement.append(nextElement) +} + +async function generateListsModalTable(lists) { + let listsModalTableBody = document.getElementById('listsModalTableBody') + listsModalTableBody.innerHTML = "" + + for(let i = 0; i < lists.length; i++){ + + let tableRow = document.createElement('tr') + + let nameCell = document.createElement('td') + nameCell.innerHTML = `${lists[i].name}` + + let opCell = document.createElement('td') + + let selectButton = document.createElement('button') + selectButton.setAttribute('class', 'uk-button uk-button-default uk-button-small') + selectButton.innerHTML = "Select" + selectButton.setAttribute('uk-tooltip', 'Selects Shopping List to add to list.') + + selectButton.onclick = async function() { + await addList(lists[i]) + } + + opCell.append(selectButton) + + tableRow.append(nameCell, opCell) + listsModalTableBody.append(tableRow) + } + +} + +async function generateListsTable() { + let listsTableBody = document.getElementById('listsTableBody') + listsTableBody.innerHTML = "" + + for(const key in shopping_lists){ + if(shopping_lists.hasOwnProperty(key)){ + let tableRow = document.createElement('tr') + + + let nameCell = document.createElement('td') + nameCell.innerHTML = `${shopping_lists[key].name}` + + let opCell = document.createElement('td') + + let removeButton = document.createElement('button') + removeButton.setAttribute('class', 'uk-button uk-button-default uk-button-small') + removeButton.setAttribute('uk-tooltip', 'Removes Shopping List from the saved shopping lists') + removeButton.innerHTML = "Remove" + removeButton.onclick = async function() {await deleteList(shopping_lists[key].list_uuid)} + + opCell.append(removeButton) + + tableRow.append(nameCell, opCell) + listsTableBody.append(tableRow) + + } + } + +} \ No newline at end of file diff --git a/application/shoppinglists/static/js/shoppingListViewHandler.js b/application/shoppinglists/static/js/shoppingListViewHandler.js index fe4ec5d..bfae935 100644 --- a/application/shoppinglists/static/js/shoppingListViewHandler.js +++ b/application/shoppinglists/static/js/shoppingListViewHandler.js @@ -21,13 +21,17 @@ async function replenishForm(shopping_list){ async function replenishLineTable(sl_items){ let listItemsTableBody = document.getElementById('listItemsTableBody') listItemsTableBody.innerHTML = "" + console.log(sl_items) for(let i = 0; i < sl_items.length; i++){ let tableRow = document.createElement('tr') let checkboxCell = document.createElement('td') - checkboxCell.innerHTML = `` - + checkboxCell.innerHTML = `` + checkboxCell.onclick = async function (event) { + await updateListItemState(sl_items[i].list_item_uuid, event.target.checked) + } + namefield = sl_items[i].item_name if(sl_items[i].links.hasOwnProperty('main')){ namefield = `${sl_items[i].item_name}` @@ -37,8 +41,9 @@ async function replenishLineTable(sl_items){ nameCell.innerHTML = namefield let qtyuomCell = document.createElement('td') - qtyuomCell.innerHTML = `${sl_items[i].qty} ${sl_items[i].uom_fullname}` + qtyuomCell.innerHTML = `${sl_items[i].qty} ${sl_items[i].uom.fullname}` + checkboxCell.checked = sl_items[i].list_item_state tableRow.append(checkboxCell, nameCell, qtyuomCell) listItemsTableBody.append(tableRow) } @@ -65,4 +70,18 @@ async function fetchItemsFullCalculated() { const response = await fetch(url); data = await response.json(); return data.list_items; +} + +async function updateListItemState(list_item_uuid, state){ + console.log(list_item_uuid, state) + const response = await fetch(`/shopping-lists/api/setListItemState`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + list_item_uuid: list_item_uuid, + list_item_state: state + }), + }); } \ No newline at end of file diff --git a/application/shoppinglists/static/quick-lists/MRP.json b/application/shoppinglists/static/quick-lists/MRP.json new file mode 100644 index 0000000..809f1bd --- /dev/null +++ b/application/shoppinglists/static/quick-lists/MRP.json @@ -0,0 +1,10 @@ +{ + "name": "MRP - %DATE%", + "description": "This is a shopping list generated using Full Calculated SKUs Operator Only! Generated on %DATE%", + "custom_items": [], + "uncalculated_items": [], + "calculated_items": [], + "recipes": [], + "planner": [], + "full_sku_enabled": true +} \ No newline at end of file diff --git a/application/shoppinglists/templates/edit.html b/application/shoppinglists/templates/edit.html index 76ae487..ee9d5b2 100644 --- a/application/shoppinglists/templates/edit.html +++ b/application/shoppinglists/templates/edit.html @@ -3,7 +3,8 @@ - + + diff --git a/application/shoppinglists/templates/generate.html b/application/shoppinglists/templates/generate.html new file mode 100644 index 0000000..b8bbd8f --- /dev/null +++ b/application/shoppinglists/templates/generate.html @@ -0,0 +1,660 @@ + + + + + + + + + + + + + + + + + + {% if session['user']['flags']['darkmode'] %} + + {% endif %} + + + + + + + + {% if session['user']['flags']['darkmode'] %} + + {% else %} + + {% endif %} + +
    +
    +
    +
    +

    Generate Shopping List

    +
    +
    +

    Generating a shopping list is as powerful as you wish to make it. In this form you will make the choices of what and when you want to + generate the list for. You will be adding specific items in the system, even can add all the safety stock items or specific ones, recipes, even other shopping lists + can be added to this one list. This is a snapshot of a moment though and any calculated safety stocks become static and can change if there is a long wait. +

    +
    + +
    +

    Shopping List Type

    +
    +
    +

    Choosing the shopping list type informs the system of your intent when actually using the list when viewed. It will inform the system if it should + completely delete the list (known as a "Temporary" list) or if it should just change the states of all items to new (known as a "Permanent" list). Permanent lists would be used + for reoccuring lists that never change and can be reused. Temporary lists will generally be used when generating a huge one time list, or something small that you just want to + track quickly. +

    +
    +
    + + +
    + +
    +

    Shopping List Type

    +
    +
    +

    Fill out the basic info asked for here, the description could be helpful to remind yourself and others what the list was generated for. + The shopping list name must be unique! +

    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    + +
    +

    Shopping List Operators

    +
    +
    +

    Operators are what drives the engine that is a shopping list. You can configure each on specifically for the list you + want to generate and each will operate in their own manner behind the scenes. +

    +
    + +
    +
    + + + + + + + + + + + + +
    +
    +
    +
    + +
    + +
    +
    +

    Add Custom Line...

    +

    A custom Item is an item that does not exist in your system but you would like to show up on your shopping list. This is useful for items + you dont often have to buy or do not want to keep track of but give the users the ability to see it. +

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Name
    QTY
    UOM + +
    Main Link
    +

    + + +

    +
    +
    + +
    +
    + +
    +

    System Item Search

    +
    +
    +
    +
    +

    An Uncalulated System Item is an item choosen from the configured SKUs in your + system. The uncalculated part means the quantity asked for in the list will always remain to be what + you set it to during configuration of the list or while editing. +

    +
    +
    + +
    +
    + +
    +
    + Select an Item from the system... + + + + + + + + + + + +
    NameQtyUOMOperations
    +
    +
    +
    +
    +
    +
    +
    +

    Edit Uncalulated Item...

    +

    An Uncalulated System Item is an item choosen from the configured SKUs in your + system. The uncalculated part means the quantity asked for in the list will always remain to be what + you set it to during configuration of the list or while editing. +

    +
    While editing System Items the Name and UOM inputs will be Disabled!
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Name
    QTY
    UOM + +
    Main Link
    +

    + + +

    +
    +
    + +
    +
    + +
    +

    System Item Search

    +
    +
    +
    +
    +

    A Calculated System Item is an item choosen from the configured SKUs in your + system. The calculated part means the quantity asked for in the list will be calculated at the time + that the list is generated based on set safety stock and quantity on hand. +

    +
    +
    + +
    +
    + +
    +
    + Select an Item from the system... + + + + + + + + + +
    NameOperations
    +
    +
    +
    +
    +
    + +
    +
    + +
    +

    System Recipes Search

    +
    +
    +
    +
    +

    Recipes can be built into the system and when added through this operator each ingrediant will be added to the list at the recipes quantity and UOM.

    +
    +
    + +
    +
    + +
    +
    + Select a Recipe from the system... + + + + + + + + + +
    NameOperations
    +
    +
    +
    +
    +
    + +
    +
    + +
    +

    System Shopping Lists Search

    +
    +
    +
    +
    +

    This operator allows you to select other shopping lists that already exist to have their + items added onto this lists items.

    +
    +
    + +
    +
    + +
    +
    + Select a List from the system... + + + + + + + + + +
    NameOperations
    +
    +
    +
    +
    +
    +
    +
    + + + + diff --git a/application/shoppinglists/templates/lists.html b/application/shoppinglists/templates/lists.html index 2d883d1..e08af13 100644 --- a/application/shoppinglists/templates/lists.html +++ b/application/shoppinglists/templates/lists.html @@ -3,6 +3,8 @@ + + @@ -111,6 +113,7 @@ diff --git a/application/shoppinglists/templates/view.html b/application/shoppinglists/templates/view.html index 12b8aae..2e35eac 100644 --- a/application/shoppinglists/templates/view.html +++ b/application/shoppinglists/templates/view.html @@ -3,6 +3,7 @@ + diff --git a/application/site_management/templates/site_management.html b/application/site_management/templates/site_management.html index 3a7c6f4..10322e3 100644 --- a/application/site_management/templates/site_management.html +++ b/application/site_management/templates/site_management.html @@ -3,6 +3,7 @@ + diff --git a/celerybeat-schedule-wal b/celerybeat-schedule-wal index fbba1cc..236fe37 100644 Binary files a/celerybeat-schedule-wal and b/celerybeat-schedule-wal differ diff --git a/logs/database.log b/logs/database.log index e7765f1..535c6db 100644 --- a/logs/database.log +++ b/logs/database.log @@ -538,4 +538,28 @@ sql='WITH sum_cte AS ( SELECT mi.id, SUM(mil.quantity_on_hand)::FLOAT8 AS total_sum FROM tet_item_locations mil JOIN tet_items mi ON mil.part_id = mi.id GROUP BY mi.id )SELECT item.id, item.description, item.item_name, sum_cte.total_sum as total_qoh, u.fullnameFROM tet_items itemLEFT JOIN sum_cte ON item.id = sum_cte.idLEFT JOIN tet_item_info item_info ON item.item_info_id = item_info.idLEFT JOIN units u ON item_info.uom = u.idWHERE item.search_string LIKE '%%' || %s || '%%'ORDER BY item.id ASCLIMIT %s OFFSET %s;') 2025-08-14 21:02:40.167097 --- ERROR --- DatabaseError(message='invalid input syntax for type uuid: "target='_blank'"', payload=("target='_blank'",), - sql='WITH passed_uuid AS (SELECT %s AS passed_uuid), cte_sl_items AS ( SELECT items.*, (SELECT COALESCE(row_to_json(un), '{}') FROM units un WHERE un.id = items.uom LIMIT 1) AS uom FROM test2_shopping_list_items items WHERE items.list_uuid = (SELECT passed_uuid::uuid FROM passed_uuid) )SELECT (SELECT passed_uuid FROM passed_uuid) AS passed_uuid, test2_shopping_lists.*, logins.username as author, (SELECT COALESCE(array_agg(row_to_json(slis)), '{}') FROM cte_sl_items slis) AS sl_items FROM test2_shopping_listsJOIN logins ON test2_shopping_lists.author = logins.idWHERE test2_shopping_lists.list_uuid=(SELECT passed_uuid::uuid FROM passed_uuid)') \ No newline at end of file + sql='WITH passed_uuid AS (SELECT %s AS passed_uuid), cte_sl_items AS ( SELECT items.*, (SELECT COALESCE(row_to_json(un), '{}') FROM units un WHERE un.id = items.uom LIMIT 1) AS uom FROM test2_shopping_list_items items WHERE items.list_uuid = (SELECT passed_uuid::uuid FROM passed_uuid) )SELECT (SELECT passed_uuid FROM passed_uuid) AS passed_uuid, test2_shopping_lists.*, logins.username as author, (SELECT COALESCE(array_agg(row_to_json(slis)), '{}') FROM cte_sl_items slis) AS sl_items FROM test2_shopping_listsJOIN logins ON test2_shopping_lists.author = logins.idWHERE test2_shopping_lists.list_uuid=(SELECT passed_uuid::uuid FROM passed_uuid)') +2025-08-15 06:32:31.728417 --- ERROR --- DatabaseError(message='column "list_item_state" of relation "main_shopping_list_items" does not existLINE 1: UPDATE main_shopping_list_items SET list_item_state = true W... ^', + payload={'uuid': '9280626f-c100-44bd-a50d-35ee5d43d855', 'update': {'list_item_state': True}}, + sql='UPDATE main_shopping_list_items SET list_item_state = %s WHERE list_item_uuid=%s::uuid RETURNING *;') +2025-08-16 11:06:43.660699 --- ERROR --- DatabaseError(message='column main_shopping_list_items.sl_id does not existLINE 40: ...n_shopping_list_items ON main_shopping_lists.id = main_shopp... ^', + payload=(196,), + sql='WITH passed_id AS (SELECT %s AS passed_id), logistics_id AS (SELECT logistics_info_id FROM main_items WHERE id=(SELECT passed_id FROM passed_id)), info_id AS (SELECT item_info_id FROM main_items WHERE id=(SELECT passed_id FROM passed_id)), cte_conversions AS ( SELECT main_conversions.id as conv_id, main_conversions.conv_factor as conv_factor, units.* as uom FROM main_conversions LEFT JOIN units ON main_conversions.uom_id = units.id WHERE main_conversions.item_id = (SELECT passed_id FROM passed_id) ), cte_item_info AS ( SELECT main_item_info.*, row_to_json(units.*) as uom, COALESCE((SELECT json_agg(convs) FROM cte_conversions convs), '[]'::json) AS conversions, COALESCE((SELECT json_agg(p.*) FROM main_sku_prefix as p WHERE p.id = ANY(main_item_info.prefixes)), '[]'::json) as prefixes FROM main_item_info LEFT JOIN units ON main_item_info.uom = units.id WHERE main_item_info.id = (SELECT item_info_id FROM info_id) ), cte_groups AS ( SELECT main_groups.*, main_group_items.uuid, main_group_items.item_type, main_group_items.qty FROM main_groups JOIN main_group_items ON main_groups.id = main_group_items.gr_id WHERE main_group_items.item_id = (SELECT passed_id FROM passed_id) ), cte_shopping_lists AS ( SELECT main_shopping_lists.*, main_shopping_list_items.uuid, main_shopping_list_items.item_type, main_shopping_list_items.qty FROM main_shopping_lists JOIN main_shopping_list_items ON main_shopping_lists.id = main_shopping_list_items.sl_id WHERE main_shopping_list_items.item_id = (SELECT passed_id FROM passed_id) ), cte_itemlinks AS ( SELECT * FROM main_itemlinks WHERE link=(SELECT passed_id FROM passed_id) ), cte_item_locations AS ( SELECT * FROM main_item_locations LEFT JOIN main_locations ON main_locations.id = main_item_locations.location_id WHERE part_id = (SELECT passed_id FROM passed_id) ), cte_logistics_info AS ( SELECT li.*, row_to_json(pl) AS primary_location, row_to_json(ail) AS auto_issue_location, row_to_json(pz) AS primary_zone, row_to_json(aiz) AS auto_issue_zone FROM main_logistics_info AS li LEFT JOIN main_locations AS pl ON li.primary_location = pl.id LEFT JOIN main_locations AS ail ON li.auto_issue_location = ail.id LEFT JOIN main_zones AS pz ON li.primary_zone = pz.id LEFT JOIN main_zones AS aiz ON li.auto_issue_zone = aiz.id WHERE li.id=(SELECT logistics_info_id FROM logistics_id) )SELECT (SELECT passed_id FROM passed_id) AS passed_id, main_items.*, (SELECT COALESCE(row_to_json(logis), '{}') FROM cte_logistics_info logis) AS logistics_info, row_to_json(main_food_info.*) as food_info, row_to_json(main_brands.*) as brand, (SELECT COALESCE(row_to_json(ii), '{}') FROM cte_item_info ii) AS item_info, (SELECT COALESCE(array_agg(row_to_json(g)), '{}') FROM cte_groups g) AS item_groups, (SELECT COALESCE(array_agg(row_to_json(sl)), '{}') FROM cte_shopping_lists sl) AS item_shopping_lists, (SELECT COALESCE(array_agg(row_to_json(il)), '{}') FROM cte_itemlinks il) AS linked_items, (SELECT COALESCE(array_agg(row_to_json(ils)), '{}') FROM cte_item_locations ils) AS item_locationsFROM main_items LEFT JOIN main_item_info ON main_items.item_info_id = main_item_info.id LEFT JOIN main_food_info ON main_items.food_info_id = main_food_info.id LEFT JOIN main_brands ON main_items.brand = main_brands.id LEFT JOIN units ON main_item_info.uom = units.id LEFT JOIN cte_groups ON main_items.id = cte_groups.id LEFT JOIN cte_shopping_lists ON main_items.id = cte_shopping_lists.idWHERE main_items.id=(SELECT passed_id FROM passed_id)GROUP BY main_items.id, main_item_info.id, main_food_info.id, main_brands.id;') +2025-08-16 11:07:04.722280 --- ERROR --- DatabaseError(message='column main_shopping_list_items.sl_id does not existLINE 40: ...n_shopping_list_items ON main_shopping_lists.id = main_shopp... ^', + payload=(196,), + sql='WITH passed_id AS (SELECT %s AS passed_id), logistics_id AS (SELECT logistics_info_id FROM main_items WHERE id=(SELECT passed_id FROM passed_id)), info_id AS (SELECT item_info_id FROM main_items WHERE id=(SELECT passed_id FROM passed_id)), cte_conversions AS ( SELECT main_conversions.id as conv_id, main_conversions.conv_factor as conv_factor, units.* as uom FROM main_conversions LEFT JOIN units ON main_conversions.uom_id = units.id WHERE main_conversions.item_id = (SELECT passed_id FROM passed_id) ), cte_item_info AS ( SELECT main_item_info.*, row_to_json(units.*) as uom, COALESCE((SELECT json_agg(convs) FROM cte_conversions convs), '[]'::json) AS conversions, COALESCE((SELECT json_agg(p.*) FROM main_sku_prefix as p WHERE p.id = ANY(main_item_info.prefixes)), '[]'::json) as prefixes FROM main_item_info LEFT JOIN units ON main_item_info.uom = units.id WHERE main_item_info.id = (SELECT item_info_id FROM info_id) ), cte_groups AS ( SELECT main_groups.*, main_group_items.uuid, main_group_items.item_type, main_group_items.qty FROM main_groups JOIN main_group_items ON main_groups.id = main_group_items.gr_id WHERE main_group_items.item_id = (SELECT passed_id FROM passed_id) ), cte_shopping_lists AS ( SELECT main_shopping_lists.*, main_shopping_list_items.uuid, main_shopping_list_items.item_type, main_shopping_list_items.qty FROM main_shopping_lists JOIN main_shopping_list_items ON main_shopping_lists.id = main_shopping_list_items.sl_id WHERE main_shopping_list_items.item_id = (SELECT passed_id FROM passed_id) ), cte_itemlinks AS ( SELECT * FROM main_itemlinks WHERE link=(SELECT passed_id FROM passed_id) ), cte_item_locations AS ( SELECT * FROM main_item_locations LEFT JOIN main_locations ON main_locations.id = main_item_locations.location_id WHERE part_id = (SELECT passed_id FROM passed_id) ), cte_logistics_info AS ( SELECT li.*, row_to_json(pl) AS primary_location, row_to_json(ail) AS auto_issue_location, row_to_json(pz) AS primary_zone, row_to_json(aiz) AS auto_issue_zone FROM main_logistics_info AS li LEFT JOIN main_locations AS pl ON li.primary_location = pl.id LEFT JOIN main_locations AS ail ON li.auto_issue_location = ail.id LEFT JOIN main_zones AS pz ON li.primary_zone = pz.id LEFT JOIN main_zones AS aiz ON li.auto_issue_zone = aiz.id WHERE li.id=(SELECT logistics_info_id FROM logistics_id) )SELECT (SELECT passed_id FROM passed_id) AS passed_id, main_items.*, (SELECT COALESCE(row_to_json(logis), '{}') FROM cte_logistics_info logis) AS logistics_info, row_to_json(main_food_info.*) as food_info, row_to_json(main_brands.*) as brand, (SELECT COALESCE(row_to_json(ii), '{}') FROM cte_item_info ii) AS item_info, (SELECT COALESCE(array_agg(row_to_json(g)), '{}') FROM cte_groups g) AS item_groups, (SELECT COALESCE(array_agg(row_to_json(sl)), '{}') FROM cte_shopping_lists sl) AS item_shopping_lists, (SELECT COALESCE(array_agg(row_to_json(il)), '{}') FROM cte_itemlinks il) AS linked_items, (SELECT COALESCE(array_agg(row_to_json(ils)), '{}') FROM cte_item_locations ils) AS item_locationsFROM main_items LEFT JOIN main_item_info ON main_items.item_info_id = main_item_info.id LEFT JOIN main_food_info ON main_items.food_info_id = main_food_info.id LEFT JOIN main_brands ON main_items.brand = main_brands.id LEFT JOIN units ON main_item_info.uom = units.id LEFT JOIN cte_groups ON main_items.id = cte_groups.id LEFT JOIN cte_shopping_lists ON main_items.id = cte_shopping_lists.idWHERE main_items.id=(SELECT passed_id FROM passed_id)GROUP BY main_items.id, main_item_info.id, main_food_info.id, main_brands.id;') +2025-08-16 11:23:59.868804 --- ERROR --- DatabaseError(message='column main_shopping_list_items.sl_id does not existLINE 40: ...n_shopping_list_items ON main_shopping_lists.id = main_shopp... ^', + payload=(196,), + sql='WITH passed_id AS (SELECT %s AS passed_id), logistics_id AS (SELECT logistics_info_id FROM main_items WHERE id=(SELECT passed_id FROM passed_id)), info_id AS (SELECT item_info_id FROM main_items WHERE id=(SELECT passed_id FROM passed_id)), cte_conversions AS ( SELECT main_conversions.id as conv_id, main_conversions.conv_factor as conv_factor, units.* as uom FROM main_conversions LEFT JOIN units ON main_conversions.uom_id = units.id WHERE main_conversions.item_id = (SELECT passed_id FROM passed_id) ), cte_item_info AS ( SELECT main_item_info.*, row_to_json(units.*) as uom, COALESCE((SELECT json_agg(convs) FROM cte_conversions convs), '[]'::json) AS conversions, COALESCE((SELECT json_agg(p.*) FROM main_sku_prefix as p WHERE p.id = ANY(main_item_info.prefixes)), '[]'::json) as prefixes FROM main_item_info LEFT JOIN units ON main_item_info.uom = units.id WHERE main_item_info.id = (SELECT item_info_id FROM info_id) ), cte_groups AS ( SELECT main_groups.*, main_group_items.uuid, main_group_items.item_type, main_group_items.qty FROM main_groups JOIN main_group_items ON main_groups.id = main_group_items.gr_id WHERE main_group_items.item_id = (SELECT passed_id FROM passed_id) ), cte_shopping_lists AS ( SELECT main_shopping_lists.*, main_shopping_list_items.uuid, main_shopping_list_items.item_type, main_shopping_list_items.qty FROM main_shopping_lists JOIN main_shopping_list_items ON main_shopping_lists.id = main_shopping_list_items.sl_id WHERE main_shopping_list_items.item_id = (SELECT passed_id FROM passed_id) ), cte_itemlinks AS ( SELECT * FROM main_itemlinks WHERE link=(SELECT passed_id FROM passed_id) ), cte_item_locations AS ( SELECT * FROM main_item_locations LEFT JOIN main_locations ON main_locations.id = main_item_locations.location_id WHERE part_id = (SELECT passed_id FROM passed_id) ), cte_logistics_info AS ( SELECT li.*, row_to_json(pl) AS primary_location, row_to_json(ail) AS auto_issue_location, row_to_json(pz) AS primary_zone, row_to_json(aiz) AS auto_issue_zone FROM main_logistics_info AS li LEFT JOIN main_locations AS pl ON li.primary_location = pl.id LEFT JOIN main_locations AS ail ON li.auto_issue_location = ail.id LEFT JOIN main_zones AS pz ON li.primary_zone = pz.id LEFT JOIN main_zones AS aiz ON li.auto_issue_zone = aiz.id WHERE li.id=(SELECT logistics_info_id FROM logistics_id) )SELECT (SELECT passed_id FROM passed_id) AS passed_id, main_items.*, (SELECT COALESCE(row_to_json(logis), '{}') FROM cte_logistics_info logis) AS logistics_info, row_to_json(main_food_info.*) as food_info, row_to_json(main_brands.*) as brand, (SELECT COALESCE(row_to_json(ii), '{}') FROM cte_item_info ii) AS item_info, (SELECT COALESCE(array_agg(row_to_json(g)), '{}') FROM cte_groups g) AS item_groups, (SELECT COALESCE(array_agg(row_to_json(sl)), '{}') FROM cte_shopping_lists sl) AS item_shopping_lists, (SELECT COALESCE(array_agg(row_to_json(il)), '{}') FROM cte_itemlinks il) AS linked_items, (SELECT COALESCE(array_agg(row_to_json(ils)), '{}') FROM cte_item_locations ils) AS item_locationsFROM main_items LEFT JOIN main_item_info ON main_items.item_info_id = main_item_info.id LEFT JOIN main_food_info ON main_items.food_info_id = main_food_info.id LEFT JOIN main_brands ON main_items.brand = main_brands.id LEFT JOIN units ON main_item_info.uom = units.id LEFT JOIN cte_groups ON main_items.id = cte_groups.id LEFT JOIN cte_shopping_lists ON main_items.id = cte_shopping_lists.idWHERE main_items.id=(SELECT passed_id FROM passed_id)GROUP BY main_items.id, main_item_info.id, main_food_info.id, main_brands.id;') +2025-08-16 17:04:26.272090 --- ERROR --- DatabaseError(message='invalid reference to FROM-clause entry for table "main_items"LINE 7: WHERE main_items.search_string LIKE '%' || '' || '%' ^HINT: Perhaps you meant to reference the table alias "items".', + payload=('', 50, 0), + sql='SELECT items.item_uuid, items.item_name, units.fullnameFROM main_items itemsLEFT JOIN main_item_info item_info ON item_info.id = items.item_info_idLEFT JOIN units ON item_info.uom = units.idWHERE main_items.search_string LIKE '%%' || %s || '%%' ORDER BY items.item_name LIMIT %s OFFSET %s;') +2025-08-17 19:21:27.673759 --- ERROR --- DatabaseError(message='duplicate key value violates unique constraint "main_logistics_info_barcode_key"DETAIL: Key (barcode)=(%%) already exists.', + payload=('%%', 1, 1, 1, 1), + sql='INSERT INTO main_logistics_info(barcode, primary_location, primary_zone, auto_issue_location, auto_issue_zone) VALUES (%s, %s, %s, %s, %s) RETURNING *;') +2025-08-17 19:21:30.048853 --- ERROR --- DatabaseError(message='duplicate key value violates unique constraint "main_logistics_info_barcode_key"DETAIL: Key (barcode)=(%%) already exists.', + payload=('%%', 1, 1, 1, 1), + sql='INSERT INTO main_logistics_info(barcode, primary_location, primary_zone, auto_issue_location, auto_issue_zone) VALUES (%s, %s, %s, %s, %s) RETURNING *;') +2025-08-17 19:22:32.824380 --- ERROR --- DatabaseError(message='duplicate key value violates unique constraint "main_item_info_barcode_key"DETAIL: Key (barcode)=(%%) already exists.', + payload=('%%', '', 1.0, 1, 0.0, 0.0, 0.0, False, '{}'), + sql='INSERT INTO main_item_info(barcode, packaging, uom_quantity, uom, cost, safety_stock, lead_time_days, ai_pick, prefixes) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING *;') \ No newline at end of file diff --git a/static/pictures/logo.png b/static/pictures/logo.png new file mode 100644 index 0000000..dbe9ea0 Binary files /dev/null and b/static/pictures/logo.png differ diff --git a/static/pictures/logo.jpg b/static/pictures/logo_old.jpg similarity index 100% rename from static/pictures/logo.jpg rename to static/pictures/logo_old.jpg diff --git a/webserver.py b/webserver.py index cda83e0..bdc22ec 100644 --- a/webserver.py +++ b/webserver.py @@ -1,4 +1,4 @@ -from flask import Flask, render_template, session, request, redirect, jsonify +from flask import Flask, render_template, session, request, redirect, jsonify, send_from_directory from flask_assets import Environment, Bundle from authlib.integrations.flask_client import OAuth import config, psycopg2, main @@ -105,6 +105,12 @@ def create_push_subscription(): def subscribe(): return render_template("subscribe.html") +@app.route('/favicon.ico') +def favicon(): + return send_from_directory( + app.static_folder, 'pictures/favicon.ico', mimetype='image/vnd.microsoft.icon' + ) + @app.route("/") @access_api.login_required def home():