diff --git a/application/__pycache__/database_payloads.cpython-313.pyc b/application/__pycache__/database_payloads.cpython-313.pyc index 82e08d3..243c228 100644 Binary files a/application/__pycache__/database_payloads.cpython-313.pyc and b/application/__pycache__/database_payloads.cpython-313.pyc differ diff --git a/application/administration/sql/CREATE/shopping_list_items.sql b/application/administration/sql/CREATE/shopping_list_items.sql index 7dd074d..c539cfe 100644 --- a/application/administration/sql/CREATE/shopping_list_items.sql +++ b/application/administration/sql/CREATE/shopping_list_items.sql @@ -1,20 +1,11 @@ CREATE TABLE IF NOT EXISTS %%site_name%%_shopping_list_items ( - id SERIAL PRIMARY KEY, - uuid VARCHAR(32) NOT NULL, - sl_id INTEGER NOT NULL, + list_item_uuid UUID DEFAULT uuid_generate_v4() NOT NULL PRIMARY KEY, + list_uuid UUID NOT NULL, item_type VARCHAR(32) NOT NULL, item_name TEXT NOT NULL, uom INTEGER NOT NULL, qty FLOAT8 NOT NULL, - item_id INTEGER DEFAULT NULL, + item_uuid UUID DEFAULT NULL, links JSONB DEFAULT '{"main": ""}', - UNIQUE(uuid, sl_id), - CONSTRAINT fk_sl_id - FOREIGN KEY(sl_id) - REFERENCES %%site_name%%_shopping_lists(id) - ON DELETE CASCADE, - CONSTRAINT fk_item_id - FOREIGN KEY(item_id) - REFERENCES %%site_name%%_items(id) - ON DELETE CASCADE + UNIQUE(list_uuid, item_uuid) ); \ No newline at end of file diff --git a/application/administration/sql/CREATE/shopping_lists.sql b/application/administration/sql/CREATE/shopping_lists.sql index c88abb0..a4fcdb0 100644 --- a/application/administration/sql/CREATE/shopping_lists.sql +++ b/application/administration/sql/CREATE/shopping_lists.sql @@ -1,9 +1,11 @@ CREATE TABLE IF NOT EXISTS %%site_name%%_shopping_lists ( id SERIAL PRIMARY KEY, + list_uuid UUID DEFAULT uuid_generate_v4() NOT NULL, + list_type VARCHAR(32), name VARCHAR(255) NOT NULL, description TEXT, author INTEGER, creation_date TIMESTAMP, - type VARCHAR(64), - UNIQUE(name) + sub_type VARCHAR(64), + UNIQUE(list_uuid, name) ); \ No newline at end of file diff --git a/application/database_payloads.py b/application/database_payloads.py index e4254f9..6ef9049 100644 --- a/application/database_payloads.py +++ b/application/database_payloads.py @@ -305,24 +305,22 @@ class ReceiptPayload: @dataclass class ShoppingListItemPayload: - uuid: str - sl_id: int + list_uuid: str item_type: str item_name: str - uom: str + uom: int qty: float - item_id: int = None + item_uuid: str = None links: dict = field(default_factory=dict) def payload(self): return ( - self.uuid, - self.sl_id, + self.list_uuid, self.item_type, self.item_name, self.uom, self.qty, - self.item_id, + self.item_uuid, json.dumps(self.links) ) diff --git a/application/shoppinglists/__pycache__/shoplist_api.cpython-313.pyc b/application/shoppinglists/__pycache__/shoplist_api.cpython-313.pyc index 01d6742..e462cdf 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 2133379..25279d8 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 new file mode 100644 index 0000000..1795e96 Binary files /dev/null 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 061656f..39ba613 100644 --- a/application/shoppinglists/shoplist_api.py +++ b/application/shoppinglists/shoplist_api.py @@ -7,7 +7,7 @@ import math # APPLICATION IMPORTS from application import postsqldb, database_payloads from application.access_module import access_api -from application.shoppinglists import shoplist_database +from application.shoppinglists import shoplist_database, shoplist_processess shopping_list_api = Blueprint('shopping_list_API', __name__, template_folder="templates", static_folder="static") @@ -19,14 +19,14 @@ def shopping_lists(): sites = [site[1] for site in postsqldb.get_sites(session['user']['sites'])] return render_template("lists.html", current_site=session['selected_site'], sites=sites) -@shopping_list_api.route("//") +@shopping_list_api.route("//") @access_api.login_required -def shopping_list(mode, id): +def shopping_list(mode, list_uuid): sites = [site[1] for site in postsqldb.get_sites(session['user']['sites'])] if mode == "view": - return render_template("view.html", id=id, current_site=session['selected_site'], sites=sites) + return render_template("view.html", list_uuid=list_uuid, current_site=session['selected_site'], sites=sites) if mode == "edit": - return render_template("edit.html", id=id, current_site=session['selected_site'], sites=sites) + return render_template("edit.html", list_uuid=list_uuid, current_site=session['selected_site'], sites=sites) return redirect("/") # API CALLS @@ -65,20 +65,19 @@ def getShoppingLists(): for list in lists: - if list['type'] == 'calculated': + if list['sub_type'] == 'calculated': items = [] not_items = shoplist_database.getItemsSafetyStock(site_name) for item in not_items: new_item = { - 'id': item['id'], - 'uuid': item['barcode'], - 'sl_id': 0, + 'list_item_uuid': 0, + 'list_uuid': list['list_uuid'], 'item_type': 'sku', 'item_name': item['item_name'], - 'uom': item['uom'], - 'qty': float(float(item['safety_stock']) - float(item['total_sum'])), - 'item_id': item['id'], - 'links': item['links'] + 'uom': item['item_info']['uom'], + 'qty': float(float(item['item_info']['safety_stock']) - float(item['total_sum'])), + 'links': item['links'], + 'uom_fullname': ['uom_fullname'] } items.append(new_item) list['sl_items'] = items @@ -90,9 +89,9 @@ def getShoppingLists(): @access_api.login_required def getShoppingList(): if request.method == "GET": - sl_id = int(request.args.get('id', 1)) + list_uuid = request.args.get('list_uuid', 1) site_name = session['selected_site'] - list = shoplist_database.getShoppingList(site_name, (sl_id, )) + list = shoplist_database.getShoppingList(site_name, (list_uuid, )) return jsonify({'shopping_list': list, 'error': False, 'message': 'Lists queried successfully!'}) # Added to Database @@ -101,9 +100,9 @@ def getShoppingList(): def getShoppingListItem(): list_item = {} if request.method == "GET": - sli_id = int(request.args.get('sli_id', 1)) + list_item_uuid = request.args.get('list_item_uuid', '') site_name = session['selected_site'] - list_item = shoplist_database.getShoppingListItem(site_name, (sli_id, )) + list_item = shoplist_database.getShoppingListItem(site_name, (list_item_uuid, )) return jsonify({'list_item': list_item, 'error': False, 'message': 'Lists Items queried successfully!'}) return jsonify({'list_item': list_item, 'error': True, 'message': 'List Items queried unsuccessfully!'}) @@ -125,6 +124,24 @@ def getItems(): return jsonify({"items":recordset, "end":math.ceil(count['count']/limit), "error":False, "message":"items fetched succesfully!"}) return jsonify({"items":recordset, "end":math.ceil(count['count']/limit), "error":True, "message":"There was an error with this GET statement"}) +@shopping_list_api.route('/api/getRecipesModal', methods=["GET"]) +@access_api.login_required +def getRecipesModal(): + 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.getRecipesModal(site_name, payload) + 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!") + + # Added to database @shopping_list_api.route('/api/postListItem', methods=["POST"]) @access_api.login_required @@ -133,6 +150,27 @@ def postListItem(): data = request.get_json()['data'] site_name = session['selected_site'] sl_item = database_payloads.ShoppingListItemPayload( + list_uuid = data['list_uuid'], + item_type=data['item_type'], + item_name=data['item_name'], + uom=data['uom'], + qty=data['qty'], + item_uuid=data['item_uuid'], + links=data['links'] + ) + shoplist_database.insertShoppingListItemsTuple(site_name, sl_item.payload()) + return jsonify({"error":False, "message":"items fetched succesfully!"}) + return jsonify({"error":True, "message":"There was an error with this GET statement"}) + +@shopping_list_api.route('/api/postRecipeLine', methods=["POST"]) +@access_api.login_required +def postRecipeLine(): + if request.method == "POST": + data = request.get_json() + + site_name = session['selected_site'] + user_id = session['user_id'] + """sl_item = database_payloads.ShoppingListItemPayload( uuid = data['uuid'], sl_id = data['sl_id'], item_type=data['item_type'], @@ -142,7 +180,9 @@ def postListItem(): item_id=data['item_id'], links=data['links'] ) - shoplist_database.insertShoppingListItemsTuple(site_name, sl_item.payload()) + shoplist_database.insertShoppingListItemsTuple(site_name, sl_item.payload())""" + shoplist_processess.addRecipeItemsToList(site_name, data, user_id) + return jsonify({"error":False, "message":"items fetched succesfully!"}) return jsonify({"error":True, "message":"There was an error with this GET statement"}) @@ -162,10 +202,10 @@ def deleteListItem(): @access_api.login_required def saveListItem(): if request.method == "POST": - sli_id = request.get_json()['sli_id'] + list_item_uuid = request.get_json()['list_item_uuid'] update = request.get_json()['update'] site_name = session['selected_site'] - shoplist_database.updateShoppingListItemsTuple(site_name, {'id': sli_id, 'update': update}) + shoplist_database.updateShoppingListItemsTuple(site_name, {'uuid': list_item_uuid, 'update': update}) return jsonify({"error":False, "message":"items fetched succesfully!"}) return jsonify({"error":True, "message":"There was an error with this GET statement"}) @@ -179,6 +219,7 @@ def getSKUItemsFull(): site_name = session['selected_site'] not_items = shoplist_database.getItemsSafetyStock(site_name) + print(not_items) for item in not_items: new_item = { 'id': item['id'], @@ -186,10 +227,11 @@ def getSKUItemsFull(): 'sl_id': 0, 'item_type': 'sku', 'item_name': item['item_name'], - 'uom': item['uom'], - 'qty': float(float(item['safety_stock']) - float(item['total_sum'])), + 'uom': item['item_info']['uom'], + 'qty': float(float(item['item_info']['safety_stock']) - float(item['total_sum'])), 'item_id': item['id'], - 'links': item['links'] + 'links': item['links'], + 'uom_fullname': item['uom_fullname'] } items.append(new_item) return jsonify({"list_items":items, "error":False, "message":"items fetched succesfully!"}) diff --git a/application/shoppinglists/shoplist_database.py b/application/shoppinglists/shoplist_database.py index 1258824..8e596b8 100644 --- a/application/shoppinglists/shoplist_database.py +++ b/application/shoppinglists/shoplist_database.py @@ -122,6 +122,35 @@ def getShoppingListItem(site, payload, convert=True, conn=None): except Exception as error: raise postsqldb.DatabaseError(error, payload, sql) +def getRecipeItemsByUUID(site, payload, convert=True, conn=None): + recordset = () + self_conn = False + with open('application/shoppinglists/sql/getRecipeItemsByUUID.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: + recordset = [postsqldb.tupleDictionaryFactory(cur.description, row) for row in rows] + elif rows and not convert: + recordset = rows + + if self_conn: + conn.close() + + return recordset + except Exception as error: + raise postsqldb.DatabaseError(error, payload, sql) + + def getItemsWithQOH(site, payload, convert=True, conn=None): recordset = [] count = 0 @@ -161,11 +190,47 @@ def getItemsWithQOH(site, payload, convert=True, conn=None): except Exception as error: raise postsqldb.DatabaseError(error, payload, sql) + +def getRecipesModal(site, payload, convert=True, conn=None): + recordsets = [] + count = 0 + self_conn = False + + + sql = f"SELECT recipes.recipe_uuid, recipes.name FROM {site}_recipes recipes WHERE recipes.name LIKE '%%' || %s || '%%' LIMIT %s OFFSET %s;" + sql_count = f"SELECT COUNT(*) FROM {site}_recipes recipes WHERE recipes.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 deleteShoppingListItemsTuple(site_name, payload, convert=True, conn=None): deleted = () self_conn = False - sql = f"WITH deleted_rows AS (DELETE FROM {site_name}_shopping_list_items WHERE id IN ({','.join(['%s'] * len(payload))}) RETURNING *) SELECT * FROM deleted_rows;" + sql = f"WITH deleted_rows AS (DELETE FROM {site_name}_shopping_list_items WHERE {site_name}_shopping_list_items.list_item_uuid IN ({','.join(['%s'] * len(payload))}) RETURNING *) SELECT * FROM deleted_rows;" try: if not conn: @@ -252,8 +317,8 @@ def updateShoppingListItemsTuple(site, payload, convert=True, conn=None): updated = () self_conn = False set_clause, values = postsqldb.updateStringFactory(payload['update']) - values.append(payload['id']) - sql = f"UPDATE {site}_shopping_list_items SET {set_clause} WHERE id=%s RETURNING *;" + values.append(payload['uuid']) + sql = f"UPDATE {site}_shopping_list_items SET {set_clause} WHERE list_item_uuid=%s::uuid RETURNING *;" try: if not conn: database_config = config.config() diff --git a/application/shoppinglists/shoplist_processess.py b/application/shoppinglists/shoplist_processess.py index e69de29..b8dd9fe 100644 --- a/application/shoppinglists/shoplist_processess.py +++ b/application/shoppinglists/shoplist_processess.py @@ -0,0 +1,39 @@ +import psycopg2 + +from application.shoppinglists import shoplist_database +from application import postsqldb, database_payloads +import config + +def addRecipeItemsToList(site:str, data:dict, user_id: int, conn=None): + """data = {'recipe_uuid', 'sl_id'}""" + + self_conn=False + + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = False + self_conn = True + + recipe_items = shoplist_database.getRecipeItemsByUUID(site, (data['recipe_uuid'],), conn=conn) + + + # for each item build a new item payload + for recipe_item in recipe_items: + # add item to the table pointing to the list_uuid + new_sl_item = database_payloads.ShoppingListItemPayload( + list_uuid = data['list_uuid'], + item_type='recipe', + item_name=recipe_item['item_name'], + uom=recipe_item['uom'], + qty=recipe_item['qty'], + item_uuid=recipe_item['item_uuid'], + links=recipe_item['links'] + ) + shoplist_database.insertShoppingListItemsTuple(site, new_sl_item.payload(), conn=conn) + + + if self_conn: + conn.commit() + conn.close() + \ No newline at end of file diff --git a/application/shoppinglists/sql/getItemsSafetyStock.sql b/application/shoppinglists/sql/getItemsSafetyStock.sql index de6a42f..b27de79 100644 --- a/application/shoppinglists/sql/getItemsSafetyStock.sql +++ b/application/shoppinglists/sql/getItemsSafetyStock.sql @@ -4,8 +4,12 @@ WITH sum_cte AS ( JOIN %%site_name%%_items mi ON mil.part_id = mi.id GROUP BY mi.id ) -SELECT * +SELECT %%site_name%%_items.*, + COALESCE(row_to_json(%%site_name%%_item_info.*), '{}') AS item_info, + COALESCE(sum_cte.total_sum, 0) AS total_sum, + units.fullname AS uom_fullname FROM %%site_name%%_items LEFT JOIN %%site_name%%_item_info ON %%site_name%%_items.item_info_id = %%site_name%%_item_info.id +LEFT JOIN units ON units.id = %%site_name%%_item_info.uom LEFT JOIN sum_cte ON %%site_name%%_items.id = sum_cte.id WHERE %%site_name%%_item_info.safety_stock > COALESCE(sum_cte.total_sum, 0); diff --git a/application/shoppinglists/sql/getRecipeItemsByUUID.sql b/application/shoppinglists/sql/getRecipeItemsByUUID.sql new file mode 100644 index 0000000..a5e0167 --- /dev/null +++ b/application/shoppinglists/sql/getRecipeItemsByUUID.sql @@ -0,0 +1,11 @@ +WITH passed_id AS (SELECT recipes.id AS passed_id FROM %%site_name%%_recipes recipes WHERE recipes.recipe_uuid = %s::uuid) +SELECT + COALESCE(item_info.uom, recipe_items.uom) as uom, + COALESCE(items.links, recipe_items.links) as links, + COALESCE(items.item_uuid, recipe_items.item_uuid) as item_uuid, + COALESCE(items.item_name, recipe_items.item_name) as item_name, + recipe_items.qty as qty +FROM %%site_name%%_recipe_items recipe_items +LEFT JOIN %%site_name%%_items items ON items.item_uuid = recipe_items.item_uuid +LEFT JOIN %%site_name%%_item_info item_info ON item_info.id = items.item_info_id +WHERE recipe_items.rp_id=(SELECT passed_id FROM passed_id); \ No newline at end of file diff --git a/application/shoppinglists/sql/getShoppingListByID.sql b/application/shoppinglists/sql/getShoppingListByID.sql index de06f2e..ebfc81f 100644 --- a/application/shoppinglists/sql/getShoppingListByID.sql +++ b/application/shoppinglists/sql/getShoppingListByID.sql @@ -1,15 +1,15 @@ -WITH passed_id AS (SELECT %s AS passed_id), +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 %%site_name%%_shopping_list_items items - WHERE items.sl_id = (SELECT passed_id FROM passed_id) + WHERE items.list_uuid = (SELECT passed_uuid::uuid FROM passed_uuid) ) -SELECT (SELECT passed_id FROM passed_id) AS passed_id, +SELECT (SELECT passed_uuid FROM passed_uuid) AS passed_uuid, %%site_name%%_shopping_lists.*, logins.username as author, (SELECT COALESCE(array_agg(row_to_json(slis)), '{}') FROM cte_sl_items slis) AS sl_items FROM %%site_name%%_shopping_lists JOIN logins ON %%site_name%%_shopping_lists.author = logins.id -WHERE %%site_name%%_shopping_lists.id=(SELECT passed_id FROM passed_id) \ No newline at end of file +WHERE %%site_name%%_shopping_lists.list_uuid=(SELECT passed_uuid::uuid FROM passed_uuid) \ No newline at end of file diff --git a/application/shoppinglists/sql/getShoppingLists.sql b/application/shoppinglists/sql/getShoppingLists.sql index acccf96..764727d 100644 --- a/application/shoppinglists/sql/getShoppingLists.sql +++ b/application/shoppinglists/sql/getShoppingLists.sql @@ -1,3 +1,3 @@ SELECT *, - (SELECT COALESCE(array_agg(row_to_json(g)), '{}') FROM %%site_name%%_shopping_list_items g WHERE sl_id = %%site_name%%_shopping_lists.id) AS sl_items + (SELECT COALESCE(array_agg(row_to_json(g)), '{}') FROM %%site_name%%_shopping_list_items g WHERE list_uuid = %%site_name%%_shopping_lists.list_uuid) AS sl_items FROM %%site_name%%_shopping_lists LIMIT %s OFFSET %s; \ No newline at end of file diff --git a/application/shoppinglists/sql/insertShoppingListItemsTuple.sql b/application/shoppinglists/sql/insertShoppingListItemsTuple.sql index 0e45d8c..12ec926 100644 --- a/application/shoppinglists/sql/insertShoppingListItemsTuple.sql +++ b/application/shoppinglists/sql/insertShoppingListItemsTuple.sql @@ -1,4 +1,6 @@ INSERT INTO %%site_name%%_shopping_list_items -(uuid, sl_id, item_type, item_name, uom, qty, item_id, links) -VALUES (%s, %s, %s, %s, %s, %s, %s, %s) +(list_uuid, item_type, item_name, uom, qty, item_uuid, links) +VALUES (%s, %s, %s, %s, %s, %s, %s) +ON CONFLICT (list_uuid, item_uuid) DO UPDATE +SET qty = %%site_name%%_shopping_list_items.qty + EXCLUDED.qty RETURNING *; \ No newline at end of file diff --git a/application/shoppinglists/sql/selectShoppingListItem.sql b/application/shoppinglists/sql/selectShoppingListItem.sql index 64a029f..79bb086 100644 --- a/application/shoppinglists/sql/selectShoppingListItem.sql +++ b/application/shoppinglists/sql/selectShoppingListItem.sql @@ -1,4 +1,4 @@ SELECT items.*, (SELECT COALESCE(row_to_json(un), '{}') FROM units un WHERE un.id = items.uom LIMIT 1) AS uom FROM %%site_name%%_shopping_list_items items -WHERE items.id = %s; \ No newline at end of file +WHERE items.list_item_uuid = %s::uuid; \ No newline at end of file diff --git a/application/shoppinglists/static/js/shoppingListEditHandler.js b/application/shoppinglists/static/js/shoppingListEditHandler.js index b1c86dc..14150db 100644 --- a/application/shoppinglists/static/js/shoppingListEditHandler.js +++ b/application/shoppinglists/static/js/shoppingListEditHandler.js @@ -10,7 +10,7 @@ async function replenishForm(shopping_list){ document.getElementById('list_creation_date').value = shopping_list.creation_date document.getElementById('list_description').value = shopping_list.description document.getElementById('list_author').value = shopping_list.author - document.getElementById('list_type').value = shopping_list.type + document.getElementById('list_type').value = shopping_list.sub_type if(shopping_list.type == "calculated"){ document.getElementById('addLineButton').classList.add("uk-disabled") @@ -43,7 +43,7 @@ async function replenishLineTable(sl_items){ typeCell.innerHTML = sl_items[i].item_type let uuidCell = document.createElement('td') - uuidCell.innerHTML = sl_items[i].uuid + uuidCell.innerHTML = sl_items[i].list_item_uuid let nameCell = document.createElement('td') nameCell.innerHTML = sl_items[i].item_name @@ -55,14 +55,14 @@ async function replenishLineTable(sl_items){ editOp.innerHTML = `` editOp.style = 'margin-right: 5px;' editOp.onclick = async function () { - await openLineEditModal(sl_items[i].id) + await openLineEditModal(sl_items[i].list_item_uuid) } let deleteOp = document.createElement('a') deleteOp.setAttribute('class', 'uk-button uk-button-default uk-button-small') deleteOp.innerHTML = `` deleteOp.onclick = async function () { - await deleteLineItem(sl_items[i].id) + await deleteLineItem(sl_items[i].list_item_uuid) } opCell.append(editOp, deleteOp) @@ -74,7 +74,7 @@ async function replenishLineTable(sl_items){ async function fetchShoppingList() { const url = new URL('/shopping-lists/api/getList', window.location.origin); - url.searchParams.append('id', sl_id); + url.searchParams.append('list_uuid', list_uuid); const response = await fetch(url); data = await response.json(); return data.shopping_list; @@ -91,26 +91,25 @@ async function updateItemsModalTable(items) { for(let i=0; i < items.length; i++){ let tableRow = document.createElement('tr') - let idCell = document.createElement('td') - idCell.innerHTML = `${items[i].id}` - - let barcodeCell = document.createElement('td') - barcodeCell.innerHTML = `${items[i].barcode}` let nameCell = document.createElement('td') nameCell.innerHTML = `${items[i].item_name}` - tableRow.id = items[i].id - tableRow.onclick = async function(){ + let opCell = document.createElement('td') + + let selectButton = document.createElement('button') + selectButton.innerHTML = "Select" + selectButton.setAttribute('class', 'uk-button uk-button-primary uk-button-small') + + selectButton.onclick = async function(){ let newItem = { - uuid: items[i].barcode, - sl_id: sl_id, + list_uuid: list_uuid, item_type: 'sku', item_name: items[i].item_name, uom: items[i].item_info.uom, qty: items[i].item_info.uom_quantity, - item_id: items[i].id, + item_uuid: items[i].item_uuid, links: {'main': items[i].links['main']} } @@ -122,7 +121,9 @@ async function updateItemsModalTable(items) { UIkit.modal(itemsModal).hide(); } - tableRow.append(idCell, barcodeCell, nameCell) + + opCell.append(selectButton) + tableRow.append(nameCell, opCell) itemsTableBody.append(tableRow) } } @@ -138,13 +139,11 @@ async function openSKUModal() { UIkit.modal(itemsModal).show(); } -async function openLineEditModal(sli_id) { - let sl_item = await fetchSLItem(sli_id) - console.log(sl_item) +async function openLineEditModal(list_item_uuid) { + let sl_item = await fetchSLItem(list_item_uuid) document.getElementById('lineName').value = sl_item.item_name document.getElementById('lineQty').value = sl_item.qty document.getElementById('lineUOM').value = sl_item.uom.id - console.log(sl_item.links) if(!sl_item.links.hasOwnProperty('main')){ sl_item.links.main = '' @@ -161,7 +160,7 @@ async function openLineEditModal(sli_id) { uom: document.getElementById('lineUOM').value, links: links } - await saveLineItem(sl_item.id, update) + await saveLineItem(sl_item.list_item_uuid, update) UIkit.modal(document.getElementById('lineEditModal')).hide(); } UIkit.modal(document.getElementById('lineEditModal')).show(); @@ -272,9 +271,10 @@ async function fetchItems() { return data.items; } -async function fetchSLItem(sli_id) { +async function fetchSLItem(list_item_uuid) { + console.log(list_item_uuid) const url = new URL('/shopping-lists/api/getListItem', window.location.origin); - url.searchParams.append('sli_id', sli_id); + url.searchParams.append('list_item_uuid', list_item_uuid); const response = await fetch(url); data = await response.json(); return data.list_item; @@ -284,16 +284,14 @@ async function addCustomItem() { let customModal = document.getElementById('customModal') UIkit.modal(customModal).hide(); - uuid = `${sl_id}${Math.random().toString(36).substring(2, 8)}` let newItem = { - uuid: uuid, - sl_id: sl_id, + list_uuid: list_uuid, item_type: 'custom', item_name: document.getElementById('customName').value, uom: document.getElementById('customUOM').value, qty: parseFloat(document.getElementById('customQty').value), - item_id: null, + item_uuid: null, links: {'main': document.getElementById('customLink').value} } @@ -358,14 +356,14 @@ async function deleteLineItem(sli_id) { await replenishLineTable(shopping_list.sl_items) } -async function saveLineItem(sli_id, update) { +async function saveLineItem(list_item_uuid, update) { const response = await fetch(`/shopping-lists/api/saveListItem`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ - sli_id: sli_id, + list_item_uuid: list_item_uuid, update: update }), }); @@ -386,4 +384,178 @@ async function saveLineItem(sli_id, update) { let shopping_list = await fetchShoppingList() await replenishForm(shopping_list) await replenishLineTable(shopping_list.sl_items) +} + + +// Recipes Modal and Functions +var recipes_pagination_current = 1; +var recipes_pagination_end = 1; +var recipes_search_string = "" +let recipes_limit = 25; + + +async function updateRecipesModalTable(recipes) { + let receipesTableBody = document.getElementById('receipesTableBody'); + receipesTableBody.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.innerHTML = "Select" + selectButton.setAttribute('class', 'uk-button uk-button-primary uk-button-small') + + selectButton.onclick = async function(){ + await addRecipeLine(recipes[i].recipe_uuid) + } + + opCell.append(selectButton) + tableRow.append(nameCell, opCell) + receipesTableBody.append(tableRow) + } +} + +async function openRecipesModal() { + let recipesModal = document.getElementById('recipesModal') + let recipes = await fetchRecipes() + recipes_pagination_current = 1; + recipes_search_string = ''; + document.getElementById('searchRecipesInput').value = ''; + await updateRecipesModalTable(recipes) + await updateRecipesPaginationElement() + UIkit.modal(recipesModal).show(); +} + +async function searchRecipesTable(event) { + if(event.key==='Enter'){ + recipes_search_string = event.srcElement.value + let recipes = await fetchRecipes() + await updateRecipesModalTable(recipes) + await updateRecipesPaginationElement() + } +} + +async function setRecipesPage(pageNumber){ + recipes_pagination_current = pageNumber; + let recipes = await fetchRecipes() + await updateRecipesModalTable(recipes) + await updateRecipesPaginationElement() +} + +async function updateRecipesPaginationElement() { + let paginationElement = document.getElementById('recipesPage'); + paginationElement.innerHTML = ""; + // previous + let previousElement = document.createElement('li') + if(recipes_pagination_current<=1){ + previousElement.innerHTML = ``; + previousElement.classList.add('uk-disabled'); + }else { + previousElement.innerHTML = ``; + } + paginationElement.append(previousElement) + + //first + let firstElement = document.createElement('li') + if(recipes_pagination_current<=1){ + firstElement.innerHTML = `1`; + firstElement.classList.add('uk-disabled'); + }else { + firstElement.innerHTML = `1`; + } + paginationElement.append(firstElement) + + // ... + if(recipes_pagination_current-2>1){ + let firstDotElement = document.createElement('li') + firstDotElement.classList.add('uk-disabled') + firstDotElement.innerHTML = ``; + paginationElement.append(firstDotElement) + } + // last + if(recipes_pagination_current-2>0){ + let lastElement = document.createElement('li') + lastElement.innerHTML = `${recipes_pagination_current-1}` + paginationElement.append(lastElement) + } + // current + if(recipes_pagination_current!=1 && recipes_pagination_current != recipes_pagination_end){ + let currentElement = document.createElement('li') + currentElement.innerHTML = `
  • ${recipes_pagination_current}
  • ` + paginationElement.append(currentElement) + } + // next + if(recipes_pagination_current+2${recipes_pagination_current+1}` + paginationElement.append(nextElement) + } + // ... + if(recipes_pagination_current+2<=recipes_pagination_end){ + let secondDotElement = document.createElement('li') + secondDotElement.classList.add('uk-disabled') + secondDotElement.innerHTML = ``; + paginationElement.append(secondDotElement) + } + //end + let endElement = document.createElement('li') + if(recipes_pagination_current>=recipes_pagination_end){ + endElement.innerHTML = `${recipes_pagination_end}`; + endElement.classList.add('uk-disabled'); + }else { + endElement.innerHTML = `${recipes_pagination_end}`; + } + paginationElement.append(endElement) + //next button + let nextElement = document.createElement('li') + if(recipes_pagination_current>=recipes_pagination_end){ + nextElement.innerHTML = ``; + nextElement.classList.add('uk-disabled'); + }else { + nextElement.innerHTML = ``; + } + paginationElement.append(nextElement) +} + +async function fetchRecipes() { + const url = new URL('/shopping-lists/api/getRecipesModal', window.location.origin); + url.searchParams.append('page', recipes_pagination_current); + url.searchParams.append('limit', recipes_limit); + url.searchParams.append('search_string', recipes_search_string); + const response = await fetch(url); + data = await response.json(); + recipes_pagination_end = data.end + return data.recipes; +} + +async function addRecipeLine(recipe_uuid){ + const response = await fetch(`/shopping-lists/api/postRecipeLine`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + recipe_uuid: recipe_uuid, + list_uuid: list_uuid + }), + }); + + data = await response.json(); + response_status = 'success' + if (data.error){ + response_status = 'danger' + } + + UIkit.notification({ + message: data.message, + status: response_status, + pos: 'top-right', + timeout: 5000 + }); + } \ No newline at end of file diff --git a/application/shoppinglists/static/js/shoppingListViewHandler.js b/application/shoppinglists/static/js/shoppingListViewHandler.js index d1c33be..fe4ec5d 100644 --- a/application/shoppinglists/static/js/shoppingListViewHandler.js +++ b/application/shoppinglists/static/js/shoppingListViewHandler.js @@ -3,7 +3,7 @@ document.addEventListener('DOMContentLoaded', async function() { await replenishForm(shopping_list) list_items = shopping_list.sl_items - if(shopping_list.type == "calculated"){ + if(shopping_list.sub_type == "calculated"){ list_items = await fetchItemsFullCalculated() } @@ -37,7 +37,7 @@ 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}` tableRow.append(checkboxCell, nameCell, qtyuomCell) listItemsTableBody.append(tableRow) @@ -46,7 +46,7 @@ async function replenishLineTable(sl_items){ async function fetchShoppingList() { const url = new URL('/shopping-lists/api/getList', window.location.origin); - url.searchParams.append('id', sl_id); + url.searchParams.append('list_uuid', list_uuid); const response = await fetch(url); data = await response.json(); return data.shopping_list; diff --git a/application/shoppinglists/static/js/shoppingListsHandler.js b/application/shoppinglists/static/js/shoppingListsHandler.js index b711a3b..3346489 100644 --- a/application/shoppinglists/static/js/shoppingListsHandler.js +++ b/application/shoppinglists/static/js/shoppingListsHandler.js @@ -13,7 +13,7 @@ document.addEventListener('DOMContentLoaded', async function() { async function replenishShoppingListCards(lists) { let shopping_list_lists = document.getElementById('shopping_list_lists') shopping_list_lists.innerHTML = "" - + console.log(lists) for(let i=0; i < lists.length; i++){ console.log(lists[i]) let main_div = document.createElement('div') @@ -25,7 +25,7 @@ async function replenishShoppingListCards(lists) { let badge_div_dos = document.createElement('div') badge_div_dos.setAttribute('class', 'uk-card-badge uk-label') - badge_div_dos.innerHTML = lists[i].type + badge_div_dos.innerHTML = lists[i].sub_type badge_div_dos.style = "margin-top: 30px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width:150px; text-align: right;" let card_header_div = document.createElement('div') @@ -58,12 +58,12 @@ async function replenishShoppingListCards(lists) { 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].id}` + 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') viewOp.innerHTML = ' View' - viewOp.href = `/shopping-lists/view/${lists[i].id}` + viewOp.href = `/shopping-lists/view/${lists[i].list_uuid}` //viewOp.style = "margin-right: 20px;" diff --git a/application/shoppinglists/templates/edit.html b/application/shoppinglists/templates/edit.html index b50979e..76ae487 100644 --- a/application/shoppinglists/templates/edit.html +++ b/application/shoppinglists/templates/edit.html @@ -160,6 +160,7 @@
  • Line Type
  • Custom
  • SKU
  • +
  • Recipes
  • @@ -209,6 +210,39 @@ + +
    +
    +

    Select Item

    +

    Select a Recipe from the system...

    + + + + + + + + + + + +
    NameOperations
    +
    +
    @@ -230,12 +264,11 @@
  • - +
    - - + @@ -324,5 +357,5 @@ - + \ No newline at end of file diff --git a/application/shoppinglists/templates/view.html b/application/shoppinglists/templates/view.html index ad07cc2..12b8aae 100644 --- a/application/shoppinglists/templates/view.html +++ b/application/shoppinglists/templates/view.html @@ -136,5 +136,5 @@ - + \ No newline at end of file diff --git a/logs/database.log b/logs/database.log index e3a529a..42739b5 100644 --- a/logs/database.log +++ b/logs/database.log @@ -305,4 +305,97 @@ 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-13 14:48:16.893199 --- ERROR --- DatabaseError(message='null value in column "barcode" of relation "main_logistics_info" violates not-null constraintDETAIL: Failing row contains (511, null, 1, 1, 1, 1).', payload=(None, 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 *;') \ No newline at end of file + 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-13 18:11:37.556015 --- ERROR --- DatabaseError(message='missing FROM-clause entry for table "recipes"LINE 1: SELECT COUNT(*) FROM main_recipes WHERE recipes.name LIKE '%... ^', + payload=('', 25, 0), + sql='SELECT recipes.recipe_uuid, recipes.name FROM main_recipes recipes WHERE recipes.name LIKE '%%' || %s || '%%' LIMIT %s OFFSET %s;') +2025-08-13 18:13:39.194633 --- ERROR --- DatabaseError(message='missing FROM-clause entry for table "recipes"LINE 1: SELECT COUNT(*) FROM main_recipes WHERE recipes.name LIKE '%... ^', + payload=('', 25, 0), + sql='SELECT * FROM main_recipes recipes WHERE recipes.name LIKE '%%' || %s || '%%' LIMIT %s OFFSET %s;') +2025-08-14 15:19:00.050654 --- ERROR --- DatabaseError(message='column "sl_id" does not existLINE 2: ...(g)), '{}') FROM test_shopping_list_items g WHERE sl_id = te... ^', + payload=(5, 0), + sql='SELECT *, (SELECT COALESCE(array_agg(row_to_json(g)), '{}') FROM test_shopping_list_items g WHERE sl_id = test_shopping_lists.id) AS sl_items FROM test_shopping_lists LIMIT %s OFFSET %s;') +2025-08-14 15:25:02.157300 --- ERROR --- DatabaseError(message='column items.sl_id does not existLINE 6: WHERE items.sl_id = (SELECT passed_id FROM passe... ^', + payload=(12,), + sql='WITH passed_id AS (SELECT %s AS passed_id), 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 test_shopping_list_items items WHERE items.sl_id = (SELECT passed_id FROM passed_id) )SELECT (SELECT passed_id FROM passed_id) AS passed_id, test_shopping_lists.*, logins.username as author, (SELECT COALESCE(array_agg(row_to_json(slis)), '{}') FROM cte_sl_items slis) AS sl_items FROM test_shopping_listsJOIN logins ON test_shopping_lists.author = logins.idWHERE test_shopping_lists.id=(SELECT passed_id FROM passed_id)') +2025-08-14 15:28:59.416536 --- ERROR --- DatabaseError(message='operator does not exist: uuid = integerLINE 6: WHERE items.list_uuid = (SELECT passed_uuid FROM... ^HINT: No operator matches the given name and argument types. You might need to add explicit type casts.', + payload=(12,), + 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 test_shopping_list_items items WHERE items.list_uuid = (SELECT passed_uuid FROM passed_uuid) )SELECT (SELECT passed_uuid FROM passed_uuid) AS passed_uuid, test_shopping_lists.*, logins.username as author, (SELECT COALESCE(array_agg(row_to_json(slis)), '{}') FROM cte_sl_items slis) AS sl_items FROM test_shopping_listsJOIN logins ON test_shopping_lists.author = logins.idWHERE test_shopping_lists.list_uuid=(SELECT passed_uuid FROM passed_uuid)') +2025-08-14 15:29:26.536839 --- ERROR --- DatabaseError(message='operator does not exist: uuid = integerLINE 6: WHERE items.list_uuid = (SELECT passed_uuid FROM... ^HINT: No operator matches the given name and argument types. You might need to add explicit type casts.', + payload=(2,), + 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 test_shopping_list_items items WHERE items.list_uuid = (SELECT passed_uuid FROM passed_uuid) )SELECT (SELECT passed_uuid FROM passed_uuid) AS passed_uuid, test_shopping_lists.*, logins.username as author, (SELECT COALESCE(array_agg(row_to_json(slis)), '{}') FROM cte_sl_items slis) AS sl_items FROM test_shopping_listsJOIN logins ON test_shopping_lists.author = logins.idWHERE test_shopping_lists.list_uuid=(SELECT passed_uuid FROM passed_uuid)') +2025-08-14 15:32:23.856954 --- ERROR --- DatabaseError(message='operator does not exist: uuid = textLINE 6: WHERE items.list_uuid = (SELECT passed_uuid FROM... ^HINT: No operator matches the given name and argument types. You might need to add explicit type casts.', + payload=('14d8ce2f-2920-47ae-a671-2953d567383d',), + 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 test_shopping_list_items items WHERE items.list_uuid = (SELECT passed_uuid FROM passed_uuid) )SELECT (SELECT passed_uuid FROM passed_uuid) AS passed_uuid, test_shopping_lists.*, logins.username as author, (SELECT COALESCE(array_agg(row_to_json(slis)), '{}') FROM cte_sl_items slis) AS sl_items FROM test_shopping_listsJOIN logins ON test_shopping_lists.author = logins.idWHERE test_shopping_lists.list_uuid=(SELECT passed_uuid FROM passed_uuid)') +2025-08-14 15:34:21.586465 --- ERROR --- DatabaseError(message='operator does not exist: uuid = textLINE 6: WHERE items.list_uuid = (SELECT passed_uuid FROM... ^HINT: No operator matches the given name and argument types. You might need to add explicit type casts.', + payload=('14d8ce2f-2920-47ae-a671-2953d567383d',), + 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 test_shopping_list_items items WHERE items.list_uuid = (SELECT passed_uuid FROM passed_uuid) )SELECT (SELECT passed_uuid FROM passed_uuid) AS passed_uuid, test_shopping_lists.*, logins.username as author, (SELECT COALESCE(array_agg(row_to_json(slis)), '{}') FROM cte_sl_items slis) AS sl_items FROM test_shopping_listsJOIN logins ON test_shopping_lists.author = logins.idWHERE test_shopping_lists.list_uuid=(SELECT passed_uuid FROM passed_uuid)') +2025-08-14 16:30:42.515423 --- ERROR --- DatabaseError(message='column "id" does not existLINE 1: ...ws AS (DELETE FROM test_shopping_list_items WHERE id IN ('e9... ^', + payload=('e9c4ff4f-0dad-444d-a8ba-525360b14f2b',), + sql='WITH deleted_rows AS (DELETE FROM test_shopping_list_items WHERE id IN (%s) RETURNING *) SELECT * FROM deleted_rows;') +2025-08-14 16:31:21.076149 --- ERROR --- DatabaseError(message='column "list_item_uud" does not existLINE 1: ...ws AS (DELETE FROM test_shopping_list_items WHERE list_item_... ^HINT: Perhaps you meant to reference the column "test_shopping_list_items.list_item_uuid".', + payload=('e9c4ff4f-0dad-444d-a8ba-525360b14f2b',), + sql='WITH deleted_rows AS (DELETE FROM test_shopping_list_items WHERE list_item_uud IN (%s) RETURNING *) SELECT * FROM deleted_rows;') +2025-08-14 16:32:02.625093 --- ERROR --- DatabaseError(message='column test_shopping_list_items.list_item_uud does not existLINE 1: ...ws AS (DELETE FROM test_shopping_list_items WHERE test_shopp... ^HINT: Perhaps you meant to reference the column "test_shopping_list_items.list_item_uuid".', + payload=('e9c4ff4f-0dad-444d-a8ba-525360b14f2b',), + sql='WITH deleted_rows AS (DELETE FROM test_shopping_list_items WHERE test_shopping_list_items.list_item_uud IN (%s) RETURNING *) SELECT * FROM deleted_rows;') +2025-08-14 16:33:56.856394 --- ERROR --- DatabaseError(message='cannot cast type integer to uuidLINE 4: WHERE items.list_item_uuid = 1::uuid; ^', + payload=(1,), + sql='SELECT items.*, (SELECT COALESCE(row_to_json(un), '{}') FROM units un WHERE un.id = items.uom LIMIT 1) AS uomFROM test_shopping_list_items itemsWHERE items.list_item_uuid = %s::uuid; ') +2025-08-14 16:36:03.309409 --- ERROR --- DatabaseError(message='invalid input syntax for type uuid: ""LINE 4: WHERE items.list_item_uuid = ''::uuid; ^', + payload=('',), + sql='SELECT items.*, (SELECT COALESCE(row_to_json(un), '{}') FROM units un WHERE un.id = items.uom LIMIT 1) AS uomFROM test_shopping_list_items itemsWHERE items.list_item_uuid = %s::uuid; ') +2025-08-14 16:44:56.545542 --- ERROR --- DatabaseError(message='duplicate key value violates unique constraint "unique combo"DETAIL: Key (item_name, list_uuid)=(Whole grain oats, 14d8ce2f-2920-47ae-a671-2953d567383d) already exists.', + payload=('14d8ce2f-2920-47ae-a671-2953d567383d', 'sku', 'Whole grain oats', 1, 1, '9b93104e-4df3-47c4-9d56-f75548ed2c6c', '{}'), + sql='INSERT INTO test_shopping_list_items(list_uuid, item_type, item_name, uom, qty, item_uuid, links) VALUES (%s, %s, %s, %s, %s, %s, %s) RETURNING *;') +2025-08-14 16:55:30.428449 --- ERROR --- DatabaseError(message='column recipes.recipe_uuid does not existLINE 3: WHERE recipes.recipe_uuid = 'ab60ddfa-90ab-4ce0-9c98-a505873... ^', + payload=('ab60ddfa-90ab-4ce0-9c98-a505873788bd',), + sql='SELECT * FROM test_recipe_items recipesWHERE recipes.recipe_uuid = %s::uuid;') +2025-08-14 17:00:16.133750 --- ERROR --- DatabaseError(message='syntax error at or near ":"LINE 1: ....recipe_uuid = 'ab60ddfa-90ab-4ce0-9c98-a505873788bd':uuid), ^', + payload=('ab60ddfa-90ab-4ce0-9c98-a505873788bd',), + sql='WITH passed_id AS (SELECT recipe.id AS passed_id FROM test_recipes recipes WHERE recipes.recipe_uuid = %s:uuid), sum_cte AS ( SELECT mi.id, SUM(mil.quantity_on_hand)::FLOAT8 AS total_sum FROM test_item_locations mil JOIN test_items mi ON mil.part_id = mi.id GROUP BY mi.id ), cte_recipe_items AS ( SELECT items.*, /*COALESCE(test_items.barcode, items.uuid) AS uuid,*/ (SELECT COALESCE(row_to_json(units.*), '{}') FROM units WHERE units.id=test_item_info.uom) AS item_uom, COALESCE(test_items.item_name, items.item_name) AS item_name, COALESCE(test_items.links, items.links) AS links, row_to_json(units.*) as uom, (SELECT COALESCE(array_agg(jsonb_build_object('conversion', conv, 'unit', units)), '{}') FROM test_conversions conv LEFT JOIN units ON conv.uom_id = units.id WHERE conv.item_id = test_items.id) AS conversions, COALESCE(sum_cte.total_sum, 0.0) AS quantity_on_hand FROM test_recipe_items items LEFT JOIN test_items ON items.item_id = test_items.id LEFT JOIN test_item_info ON test_items.item_info_id = test_item_info.id LEFT JOIN units ON units.id = items.uom LEFT JOIN sum_cte ON test_items.id = sum_cte.id WHERE items.rp_id = (SELECT passed_id FROM passed_id) ORDER BY items.item_name ASC ) SELECT (SELECT passed_id FROM passed_id) AS passed_id, test_recipes.*, logins.username as author, (SELECT COALESCE(array_agg(row_to_json(ris)), '{}') FROM cte_recipe_items ris) AS recipe_itemsFROM test_recipesJOIN logins ON test_recipes.author = logins.idWHERE test_recipes.id=(SELECT passed_id FROM passed_id)') +2025-08-14 17:00:42.565091 --- ERROR --- DatabaseError(message='missing FROM-clause entry for table "recipe"LINE 1: WITH passed_id AS (SELECT recipe.id AS passed_id FROM test_r... ^', + payload=('ab60ddfa-90ab-4ce0-9c98-a505873788bd',), + sql='WITH passed_id AS (SELECT recipe.id AS passed_id FROM test_recipes recipes WHERE recipes.recipe_uuid = %s::uuid), sum_cte AS ( SELECT mi.id, SUM(mil.quantity_on_hand)::FLOAT8 AS total_sum FROM test_item_locations mil JOIN test_items mi ON mil.part_id = mi.id GROUP BY mi.id ), cte_recipe_items AS ( SELECT items.*, /*COALESCE(test_items.barcode, items.uuid) AS uuid,*/ (SELECT COALESCE(row_to_json(units.*), '{}') FROM units WHERE units.id=test_item_info.uom) AS item_uom, COALESCE(test_items.item_name, items.item_name) AS item_name, COALESCE(test_items.links, items.links) AS links, row_to_json(units.*) as uom, (SELECT COALESCE(array_agg(jsonb_build_object('conversion', conv, 'unit', units)), '{}') FROM test_conversions conv LEFT JOIN units ON conv.uom_id = units.id WHERE conv.item_id = test_items.id) AS conversions, COALESCE(sum_cte.total_sum, 0.0) AS quantity_on_hand FROM test_recipe_items items LEFT JOIN test_items ON items.item_id = test_items.id LEFT JOIN test_item_info ON test_items.item_info_id = test_item_info.id LEFT JOIN units ON units.id = items.uom LEFT JOIN sum_cte ON test_items.id = sum_cte.id WHERE items.rp_id = (SELECT passed_id FROM passed_id) ORDER BY items.item_name ASC ) SELECT (SELECT passed_id FROM passed_id) AS passed_id, test_recipes.*, logins.username as author, (SELECT COALESCE(array_agg(row_to_json(ris)), '{}') FROM cte_recipe_items ris) AS recipe_itemsFROM test_recipesJOIN logins ON test_recipes.author = logins.idWHERE test_recipes.id=(SELECT passed_id FROM passed_id)') +2025-08-14 17:03:48.785824 --- ERROR --- DatabaseError(message='syntax error at or near "SELECT"LINE 2: SELECT (SELECT passed_id FROM passed_id) AS passed_id, ^', + payload=('ab60ddfa-90ab-4ce0-9c98-a505873788bd',), + sql='WITH passed_id AS (SELECT recipes.id AS passed_id FROM test_recipes recipes WHERE recipes.recipe_uuid = %s::uuid),SELECT (SELECT passed_id FROM passed_id) AS passed_id, recipe_items.*,FROM test_recipe_items recipe_itemsWHERE test_recipes.id=(SELECT passed_id FROM passed_id);') +2025-08-14 17:04:26.545601 --- ERROR --- DatabaseError(message='syntax error at or near "SELECT"LINE 2: SELECT (SELECT passed_id FROM passed_id) AS passed_id, ^', + payload=('ab60ddfa-90ab-4ce0-9c98-a505873788bd',), + sql='WITH passed_id AS (SELECT recipes.id AS passed_id FROM test_recipes recipes WHERE recipes.recipe_uuid = %s::uuid),SELECT (SELECT passed_id FROM passed_id) AS passed_id, recipe_items.*FROM test_recipe_items recipe_itemsWHERE test_recipes.id=(SELECT passed_id FROM passed_id);') +2025-08-14 17:04:43.018584 --- ERROR --- DatabaseError(message='missing FROM-clause entry for table "test_recipes"LINE 5: WHERE test_recipes.id=(SELECT passed_id FROM passed_id); ^', + payload=('ab60ddfa-90ab-4ce0-9c98-a505873788bd',), + sql='WITH passed_id AS (SELECT recipes.id AS passed_id FROM test_recipes recipes WHERE recipes.recipe_uuid = %s::uuid)SELECT (SELECT passed_id FROM passed_id) AS passed_id, recipe_items.*FROM test_recipe_items recipe_itemsWHERE test_recipes.id=(SELECT passed_id FROM passed_id);') +2025-08-14 17:13:12.481521 --- ERROR --- DatabaseError(message='syntax error at or near "FROM"LINE 7: FROM test_recipe_items recipe_items ^', + payload=('ab60ddfa-90ab-4ce0-9c98-a505873788bd',), + sql='WITH passed_id AS (SELECT recipes.id AS passed_id FROM test_recipes recipes WHERE recipes.recipe_uuid = %s::uuid)SELECT COALESCE(item_info.uom, recipe_items.uom) as uom, COALESCE(items.links, recipe_items.links) as links, items.item_uuid, items.item_name,FROM test_recipe_items recipe_itemsLEFT JOIN test_items items ON items.item_uuid = recipe_items.item_uuidLEFT JOIN test_item_info item_info ON item_info.id = items.item_info_idWHERE recipe_items.rp_id=(SELECT passed_id FROM passed_id);') +2025-08-14 17:15:03.135511 --- ERROR --- DatabaseError(message='COALESCE types character varying and integer cannot be matchedLINE 3: COALESCE(units.fullname, recipe_items.uom) as uom, ^', + payload=('ab60ddfa-90ab-4ce0-9c98-a505873788bd',), + sql='WITH passed_id AS (SELECT recipes.id AS passed_id FROM test_recipes recipes WHERE recipes.recipe_uuid = %s::uuid)SELECT COALESCE(units.fullname, recipe_items.uom) as uom, COALESCE(items.links, recipe_items.links) as links, items.item_uuid, items.item_nameFROM test_recipe_items recipe_itemsLEFT JOIN test_items items ON items.item_uuid = recipe_items.item_uuidLEFT JOIN test_item_info item_info ON item_info.id = items.item_info_idLEFT JOIN units ON units.id = item_info.uomWHERE recipe_items.rp_id=(SELECT passed_id FROM passed_id);') +2025-08-14 17:16:24.328017 --- ERROR --- DatabaseError(message='COALESCE types character varying and integer cannot be matchedLINE 3: ...ullname FROM units WHERE units.id=item_info.uom), recipe_ite... ^', + payload=('ab60ddfa-90ab-4ce0-9c98-a505873788bd',), + sql='WITH passed_id AS (SELECT recipes.id AS passed_id FROM test_recipes recipes WHERE recipes.recipe_uuid = %s::uuid)SELECT COALESCE((SELECT units.fullname FROM units WHERE units.id=item_info.uom), recipe_items.uom) as uom, COALESCE(items.links, recipe_items.links) as links, items.item_uuid, items.item_nameFROM test_recipe_items recipe_itemsLEFT JOIN test_items items ON items.item_uuid = recipe_items.item_uuidLEFT JOIN test_item_info item_info ON item_info.id = items.item_info_idWHERE recipe_items.rp_id=(SELECT passed_id FROM passed_id);') +2025-08-14 17:23:17.559471 --- ERROR --- DatabaseError(message='null value in column "item_name" of relation "test_shopping_list_items" violates not-null constraintDETAIL: Failing row contains (recipe, null, 1, 55, {"main": "1"}, f3571bbb-25d3-4b9d-aafd-6be67a289068, null, 14d8ce2f-2920-47ae-a671-2953d567383d).', + payload=('14d8ce2f-2920-47ae-a671-2953d567383d', 'recipe', None, 1, 55.0, None, '{"main": "1"}'), + sql='INSERT INTO test_shopping_list_items(list_uuid, item_type, item_name, uom, qty, item_uuid, links) VALUES (%s, %s, %s, %s, %s, %s, %s) RETURNING *;') +2025-08-14 17:30:27.390620 --- ERROR --- DatabaseError(message='duplicate key value violates unique constraint "unique combo"DETAIL: Key (item_name, list_uuid)=(Torani Peppermint syrup, ad3bfe0d-3442-42fa-af16-08a6fc0a1c33) already exists.', + payload=('ad3bfe0d-3442-42fa-af16-08a6fc0a1c33', 'recipe', 'Torani Peppermint syrup', 1, 1.0, '53d52046-8e70-4451-89fb-200de48ae6d0', '{}'), + sql='INSERT INTO test_shopping_list_items(list_uuid, item_type, item_name, uom, qty, item_uuid, links) VALUES (%s, %s, %s, %s, %s, %s, %s) RETURNING *;') +2025-08-14 17:48:08.287394 --- ERROR --- DatabaseError(message='column excluded.col2 does not existLINE 5: SET qty = test_shopping_list_items.qty + EXCLUDED.col2 ^', + payload=('ad3bfe0d-3442-42fa-af16-08a6fc0a1c33', 'recipe', 'Torani Peppermint syrup', 1, 1.0, '53d52046-8e70-4451-89fb-200de48ae6d0', '{}'), + sql='INSERT INTO test_shopping_list_items(list_uuid, item_type, item_name, uom, qty, item_uuid, links) VALUES (%s, %s, %s, %s, %s, %s, %s)ON CONFLICT (list_uuid, item_name) DO UPDATESET qty = test_shopping_list_items.qty + EXCLUDED.col2RETURNING *;') +2025-08-14 17:58:26.999337 --- 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 test_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, test_shopping_lists.*, logins.username as author, (SELECT COALESCE(array_agg(row_to_json(slis)), '{}') FROM cte_sl_items slis) AS sl_items FROM test_shopping_listsJOIN logins ON test_shopping_lists.author = logins.idWHERE test_shopping_lists.list_uuid=(SELECT passed_uuid::uuid FROM passed_uuid)') +2025-08-14 17:58:32.904639 --- 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 test_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, test_shopping_lists.*, logins.username as author, (SELECT COALESCE(array_agg(row_to_json(slis)), '{}') FROM cte_sl_items slis) AS sl_items FROM test_shopping_listsJOIN logins ON test_shopping_lists.author = logins.idWHERE test_shopping_lists.list_uuid=(SELECT passed_uuid::uuid FROM passed_uuid)') +2025-08-14 17:58:36.459552 --- ERROR --- DatabaseError(message='invalid input syntax for type uuid: "1"', + payload=('1',), + 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 test_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, test_shopping_lists.*, logins.username as author, (SELECT COALESCE(array_agg(row_to_json(slis)), '{}') FROM cte_sl_items slis) AS sl_items FROM test_shopping_listsJOIN logins ON test_shopping_lists.author = logins.idWHERE test_shopping_lists.list_uuid=(SELECT passed_uuid::uuid FROM passed_uuid)') +2025-08-14 18:14:47.690481 --- ERROR --- DatabaseError(message='relation "cte_item_info" does not existLINE 8: (SELECT COALESCE(row_to_json(ii), '{}') FROM cte_item_in... ^', + payload=None, + sql='WITH sum_cte AS ( SELECT mi.id, SUM(mil.quantity_on_hand) AS total_sum FROM main_item_locations mil JOIN main_items mi ON mil.part_id = mi.id GROUP BY mi.id)SELECT main_items.*, (SELECT COALESCE(row_to_json(ii), '{}') FROM cte_item_info ii) AS item_infoFROM main_itemsLEFT JOIN main_item_info ON main_items.item_info_id = main_item_info.idLEFT JOIN units ON units.id = main_item_info.uomLEFT JOIN sum_cte ON main_items.id = sum_cte.idWHERE main_item_info.safety_stock > COALESCE(sum_cte.total_sum, 0);') +2025-08-14 18:15:24.034419 --- ERROR --- DatabaseError(message='missing FROM-clause entry for table "item_info"LINE 8: COALESCE(row_to_json(item_info.*), '{}') AS item_info ^', + payload=None, + sql='WITH sum_cte AS ( SELECT mi.id, SUM(mil.quantity_on_hand) AS total_sum FROM main_item_locations mil JOIN main_items mi ON mil.part_id = mi.id GROUP BY mi.id)SELECT main_items.*, COALESCE(row_to_json(item_info.*), '{}') AS item_infoFROM main_itemsLEFT JOIN main_item_info ON main_items.item_info_id = main_item_info.idLEFT JOIN units ON units.id = main_item_info.uomLEFT JOIN sum_cte ON main_items.id = sum_cte.idWHERE main_item_info.safety_stock > COALESCE(sum_cte.total_sum, 0);') \ No newline at end of file diff --git a/run-server.sh b/run-server.sh new file mode 100644 index 0000000..18fd966 --- /dev/null +++ b/run-server.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# Start Flask app +gnome-terminal -- bash -c "python webserver.py; exec bash" & +# Start Celery worker +gnome-terminal -- bash -c "celery -A celery_worker.celery worker --loglevel=info; exec bash" & +# Start Celery beat +gnome-terminal -- bash -c "celery -A celery_worker.celery beat --loglevel=info; exec bash" & \ No newline at end of file
    IDBarcode NameOperations