From ce8e63b596ab6486b9da33eac4aab48c0a22cae7 Mon Sep 17 00:00:00 2001 From: Jadowyne Ulve Date: Sat, 12 Jul 2025 08:54:27 -0500 Subject: [PATCH] Migrating Shoppinglist Module to new schema --- application/poe/poe_database.py | 111 +------------ application/shoppinglists/__init__.py | 0 .../shoppinglists/shoplist_api.py | 81 +++++----- .../shoppinglists/shoplist_database.py | 146 ++++++++++++++++++ .../shoppinglists/shoplist_processess.py | 0 .../shoppinglists/sql/getItemsWithQOH.sql | 18 +++ .../sql/insertShoppingListItemsTuple.sql | 4 + .../sql/selectShoppingListItem.sql | 4 + .../static/js}/shoppingListEditHandler.js | 12 +- .../static/js}/shoppingListViewHandler.js | 0 .../static/js}/shoppingListsHandler.js | 8 +- .../shoppinglists/templates}/edit.html | 2 +- .../shoppinglists/templates/lists.html | 2 +- .../shoppinglists/templates}/view.html | 2 +- database.log | 14 +- webserver.py | 5 +- 16 files changed, 244 insertions(+), 165 deletions(-) create mode 100644 application/shoppinglists/__init__.py rename shopping_list_API.py => application/shoppinglists/shoplist_api.py (74%) create mode 100644 application/shoppinglists/shoplist_database.py create mode 100644 application/shoppinglists/shoplist_processess.py create mode 100644 application/shoppinglists/sql/getItemsWithQOH.sql create mode 100644 application/shoppinglists/sql/insertShoppingListItemsTuple.sql create mode 100644 application/shoppinglists/sql/selectShoppingListItem.sql rename {static/handlers => application/shoppinglists/static/js}/shoppingListEditHandler.js (96%) rename {static/handlers => application/shoppinglists/static/js}/shoppingListViewHandler.js (100%) rename {static/handlers => application/shoppinglists/static/js}/shoppingListsHandler.js (97%) rename {templates/shopping-lists => application/shoppinglists/templates}/edit.html (99%) rename templates/shopping-lists/index.html => application/shoppinglists/templates/lists.html (98%) rename {templates/shopping-lists => application/shoppinglists/templates}/view.html (98%) diff --git a/application/poe/poe_database.py b/application/poe/poe_database.py index d7440b2..fc8df36 100644 --- a/application/poe/poe_database.py +++ b/application/poe/poe_database.py @@ -3,14 +3,6 @@ import config from application import postsqldb def request_receipt_id(conn, site_name): - """gets the next id for receipts_id, currently returns a 8 digit number - - Args: - site (str): site to get the next id for - - Returns: - json: receipt_id, message, error keys - """ next_receipt_id = None sql = f"SELECT receipt_id FROM {site_name}_receipts ORDER BY id DESC LIMIT 1;" try: @@ -34,17 +26,6 @@ def request_receipt_id(conn, site_name): return next_receipt_id def selectItemLocationsTuple(site_name, payload, convert=True): - """select a single tuple from ItemLocations table for site_name - - Args: - conn (_T_connector@connect): - site_name (str): - payload (tuple): [item_id, location_id] - convert (bool): defaults to False, used to determine return of tuple/dict - - Returns: - tuple: the row that was returned from the table - """ item_locations = () database_config = config.config() select_item_location_sql = f"SELECT * FROM {site_name}_item_locations WHERE part_id = %s AND location_id = %s;" @@ -62,17 +43,6 @@ def selectItemLocationsTuple(site_name, payload, convert=True): return error def selectCostLayersTuple(site_name, payload, convert=True): - """select a single or series of cost layers from the database for site_name - - Args: - conn (_T_connector@connect): - site_name (str): - payload (tuple): (item_locations_id, ) - convert (bool): defaults to False, used for determining return as tuple/dict - - Returns: - list: list of tuples/dict from the cost_layers table for site_name - """ cost_layers = () database_config = config.config() select_cost_layers_sql = f"SELECT cl.* FROM {site_name}_item_locations il JOIN {site_name}_cost_layers cl ON cl.id = ANY(il.cost_layers) where il.id=%s;" @@ -118,17 +88,6 @@ def selectLocationsTuple(site, payload, convert=True, conn=None): raise postsqldb.DatabaseError(error, payload, sql) def selectItemLocationsTuple(site_name, payload, convert=True, conn=None): - """select a single tuple from ItemLocations table for site_name - - Args: - conn (_T_connector@connect): - site_name (str): - payload (tuple): [item_id, location_id] - convert (bool): defaults to False, used to determine return of tuple/dict - - Returns: - tuple: the row that was returned from the table - """ item_locations = () self_conn = False select_item_location_sql = f"SELECT * FROM {site_name}_item_locations WHERE part_id = %s AND location_id = %s;" @@ -276,21 +235,7 @@ def insertCostLayersTuple(site, payload, convert=True, conn=None): raise postsqldb.DatabaseError(error, payload, sql) def insertTransactionsTuple(site, payload, convert=True, conn=None): - """insert payload into transactions table for site - - Args: - conn (_T_connector@connect): Postgresql Connector - site (str): - payload (tuple): (timestamp[timestamp], logistics_info_id[int], barcode[str], name[str], - transaction_type[str], quantity[float], description[str], user_id[int], data[jsonb]) - convert (bool, optional): Determines if to return tuple as dictionary. Defaults to False. - - Raises: - DatabaseError: - - Returns: - tuple or dict: inserted tuple - """ + # payload (tuple): (timestamp[timestamp], logistics_info_id[int], barcode[str], name[str], transaction = () self_conn = False with open(f"application/poe/sql/insertTransactionsTuple.sql", "r+") as file: @@ -319,20 +264,6 @@ def insertTransactionsTuple(site, payload, convert=True, conn=None): return transaction def insertReceiptsTuple(site, payload, convert=True, conn=None): - """insert payload into receipt table of site - - Args: - conn (_T_connector@connect): Postgresql Connector - site (str): - payload (tuple): - convert (bool, optional): Determines if to return tuple as dictionary. Defaults to False. - - Raises: - DatabaseError: - - Returns: - tuple or dict: inserted tuple - """ receipt = () self_conn = False with open(f"application/poe/sql/insertReceiptsTuple.sql", "r+") as file: @@ -362,21 +293,6 @@ def insertReceiptsTuple(site, payload, convert=True, conn=None): raise postsqldb.DatabaseError(error, payload, sql) def insertReceiptItemsTuple(site, payload, convert=True, conn=None): - """insert payload into receipt_items table of site - - Args: - conn (_T_connector@connect): Postgresql Connector - site (str): - payload (tuple): (type[str], receipt_id[int], barcode[str], name[str], - qty[float], data[jsonb], status[str]) - convert (bool, optional): Determines if to return tuple as dictionary. Defaults to False. - - Raises: - DatabaseError: - - Returns: - tuple or dict: inserted tuple - """ receipt_item = () self_conn = False @@ -407,16 +323,6 @@ def insertReceiptItemsTuple(site, payload, convert=True, conn=None): raise postsqldb.DatabaseError(error, payload, sql) def updateCostLayersTuple(site, payload, convert=True, conn=None): - """_summary_ - - Args: - conn (_type_): _description_ - site (_type_): _description_ - payload (_type_): {'id': cost_layer_id, 'update': {column: data...}} - - Returns: - _type_: _description_ - """ cost_layer = () self_conn = False @@ -478,21 +384,6 @@ def updateItemLocation(site, payload, convert=True, conn=None): def deleteCostLayersTuple(site, payload, convert=True, conn=None): - """This is a basic funtion to delete a tuple from a table in site with an id. All - tables in this database has id's associated with them. - - Args: - conn (_T_connector@connect): Postgresql Connector - site_name (str): - payload (tuple): (tuple_id,) - convert (bool, optional): Determines if to return tuple as dictionary. Defaults to True. - - Raises: - DatabaseError: - - Returns: - tuple or dict: deleted tuple - """ deleted = () self_conn = False sql = f"WITH deleted_rows AS (DELETE FROM {site}_cost_layers WHERE id IN ({','.join(['%s'] * len(payload))}) RETURNING *) SELECT * FROM deleted_rows;" diff --git a/application/shoppinglists/__init__.py b/application/shoppinglists/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/shopping_list_API.py b/application/shoppinglists/shoplist_api.py similarity index 74% rename from shopping_list_API.py rename to application/shoppinglists/shoplist_api.py index 5becc3e..e17b92c 100644 --- a/shopping_list_API.py +++ b/application/shoppinglists/shoplist_api.py @@ -4,26 +4,32 @@ from config import config, sites_config from main import unfoldCostLayers from user_api import login_required import postsqldb +from application.shoppinglists import shoplist_database +from application import database_payloads -shopping_list_api = Blueprint('shopping_list_API', __name__) +shopping_list_api = Blueprint('shopping_list_API', __name__, template_folder="templates", static_folder="static") -@shopping_list_api.route("/shopping-lists") + +# ROOT TEMPLATE CALLS +@shopping_list_api.route("/") @login_required def shopping_lists(): sites = [site[1] for site in main.get_sites(session['user']['sites'])] - return render_template("shopping-lists/index.html", current_site=session['selected_site'], sites=sites) + return render_template("lists.html", current_site=session['selected_site'], sites=sites) -@shopping_list_api.route("/shopping-list//") +@shopping_list_api.route("//") @login_required def shopping_list(mode, id): sites = [site[1] for site in main.get_sites(session['user']['sites'])] if mode == "view": - return render_template("shopping-lists/view.html", id=id, current_site=session['selected_site'], sites=sites) + return render_template("view.html", id=id, current_site=session['selected_site'], sites=sites) if mode == "edit": - return render_template("shopping-lists/edit.html", id=id, current_site=session['selected_site'], sites=sites) + return render_template("edit.html", id=id, current_site=session['selected_site'], sites=sites) return redirect("/") -@shopping_list_api.route('/shopping-lists/addList', methods=["POST"]) + +# API CALLS +@shopping_list_api.route('/api/addList', methods=["POST"]) def addList(): if request.method == "POST": list_name = request.get_json()['list_name'] @@ -43,7 +49,7 @@ def addList(): return jsonify({'error': False, 'message': 'List added!!'}) return jsonify({'error': True, 'message': 'These was an error with adding the list!'}) -@shopping_list_api.route('/shopping-lists/getLists', methods=["GET"]) +@shopping_list_api.route('/api/getLists', methods=["GET"]) def getShoppingLists(): lists = [] if request.method == "GET": @@ -77,7 +83,7 @@ def getShoppingLists(): return jsonify({'shopping_lists': lists, 'end':math.ceil(count/limit), 'error': False, 'message': 'Lists queried successfully!'}) -@shopping_list_api.route('/shopping-lists/getList', methods=["GET"]) +@shopping_list_api.route('/api/getList', methods=["GET"]) def getShoppingList(): if request.method == "GET": sl_id = int(request.args.get('id', 1)) @@ -87,19 +93,19 @@ def getShoppingList(): lists = database.getShoppingList(conn, site_name, (sl_id, ), convert=True) return jsonify({'shopping_list': lists, 'error': False, 'message': 'Lists queried successfully!'}) -@shopping_list_api.route('/shopping-lists/getListItem', methods=["GET"]) +# Added to Database +@shopping_list_api.route('/api/getListItem', methods=["GET"]) def getShoppingListItem(): list_item = {} if request.method == "GET": sli_id = int(request.args.get('sli_id', 1)) - database_config = config() site_name = session['selected_site'] - with psycopg2.connect(**database_config) as conn: - list_item = postsqldb.ShoppingListsTable.getItem(conn, site_name, (sli_id, )) + list_item = shoplist_database.getShoppingListItem(site_name, (sli_id, )) 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!'}) -@shopping_list_api.route('/shopping-lists/getItems', methods=["GET"]) +# Added to database +@shopping_list_api.route('/api/getItems', methods=["GET"]) def getItems(): recordset = [] count = {'count': 0} @@ -108,47 +114,44 @@ def getItems(): limit = int(request.args.get('limit', 10)) search_string = request.args.get('search_string', 10) site_name = session['selected_site'] - offset = (page - 1) * limit - database_config = config() - with psycopg2.connect(**database_config) as conn: - payload = (search_string, limit, offset) - recordset, count = database.getItemsWithQOH(conn, site_name, payload, convert=True) + offset = (page - 1) * limit + sort_order = "ID ASC" + payload = (search_string, limit, offset, sort_order) + recordset, count = shoplist_database.getItemsWithQOH(site_name, payload, convert=True) 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('/shopping-lists/postListItem', methods=["POST"]) +# Added to database +@shopping_list_api.route('/api/postListItem', methods=["POST"]) def postListItem(): if request.method == "POST": data = request.get_json()['data'] site_name = session['selected_site'] - database_config = config() - with psycopg2.connect(**database_config) as conn: - sl_item = MyDataclasses.ShoppingListItemPayload( - uuid = data['uuid'], - sl_id = data['sl_id'], - item_type=data['item_type'], - item_name=data['item_name'], - uom=data['uom'], - qty=data['qty'], - item_id=data['item_id'], - links=data['links'] - ) - database.insertShoppingListItemsTuple(conn, site_name, sl_item.payload()) + sl_item = database_payloads.ShoppingListItemPayload( + uuid = data['uuid'], + sl_id = data['sl_id'], + item_type=data['item_type'], + item_name=data['item_name'], + uom=data['uom'], + qty=data['qty'], + item_id=data['item_id'], + 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('/shopping-lists/deleteListItem', methods=["POST"]) +# Added to Database +@shopping_list_api.route('/api/deleteListItem', methods=["POST"]) def deleteListItem(): if request.method == "POST": sli_id = request.get_json()['sli_id'] site_name = session['selected_site'] - database_config = config() - with psycopg2.connect(**database_config) as conn: - database.deleteShoppingListItemsTuple(conn, site_name, (sli_id, )) + shoplist_database.deleteShoppingListItemsTuple(site_name, (sli_id, )) return jsonify({"error":False, "message":"item deleted succesfully!"}) return jsonify({"error":True, "message":"There was an error with this POST statement"}) -@shopping_list_api.route('/shopping-lists/saveListItem', methods=["POST"]) +@shopping_list_api.route('/api/saveListItem', methods=["POST"]) def saveListItem(): if request.method == "POST": sli_id = request.get_json()['sli_id'] @@ -160,7 +163,7 @@ def saveListItem(): 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('/shopping-lists/getSKUItemsFull', methods=["GET"]) +@shopping_list_api.route('/api/getSKUItemsFull', methods=["GET"]) def getSKUItemsFull(): items = [] count = {'count': 0} diff --git a/application/shoppinglists/shoplist_database.py b/application/shoppinglists/shoplist_database.py new file mode 100644 index 0000000..920f030 --- /dev/null +++ b/application/shoppinglists/shoplist_database.py @@ -0,0 +1,146 @@ + +import psycopg2 + +import config +from application import postsqldb + +def getShoppingListItem(site, payload, convert=True, conn=None): + """_summary_ + + Args: + conn (_type_): _description_ + site (_type_): _description_ + payload (_type_): (id, ) + convert (bool, optional): _description_. Defaults to True. + + Raises: + DatabaseError: _description_ + + Returns: + _type_: _description_ + """ + record = () + self_conn = False + with open('application/shoppinglists/sql/selectShoppingListItem.sql', 'r') as file: + sql = file.read().replace("%%site_name%%", site) + try: + + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = True + self_conn = True + + with conn.cursor() as cur: + cur.execute(sql, payload) + rows = cur.fetchone() + if rows and convert: + record = postsqldb.tupleDictionaryFactory(cur.description, rows) + elif rows and not convert: + record = rows + + if self_conn: + conn.close() + + return record + except Exception as error: + raise postsqldb.DatabaseError(error, payload, sql) + + +def getItemsWithQOH(site, payload, convert=True, conn=None): + recordset = [] + count = 0 + self_conn = False + + with open(f"application/shoppinglists/sql/getItemsWithQOH.sql", "r+") as file: + sql = file.read().replace("%%site_name%%", site).replace("%%sort_order%%", payload[3]) + + payload = list(payload) + payload.pop(3) + try: + + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = True + self_conn = True + + if convert: + with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur: + cur.execute(sql, payload) + recordset = cur.fetchall() + recordset = [dict(record) for record in recordset] + cur.execute(f"SELECT COUNT(*) FROM {site}_items WHERE search_string LIKE '%%' || %s || '%%';", (payload[0], )) + count = cur.fetchone() + else: + with conn.cursor() as cur: + cur.execute(sql, payload) + recordset = cur.fetchall() + cur.execute(f"SELECT COUNT(*) FROM {site}_items WHERE search_string LIKE '%%' || %s || '%%';", (payload[0], )) + count = cur.fetchone() + + if self_conn: + conn.close() + + return recordset, 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;" + try: + + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = True + self_conn = True + + with conn.cursor() as cur: + cur.execute(sql, payload) + rows = cur.fetchall() + if rows and convert: + deleted = [postsqldb.tupleDictionaryFactory(cur.description, r) for r in rows] + elif rows and not convert: + deleted = rows + + if self_conn: + conn.commit() + conn.close() + + return deleted + except Exception as error: + raise postsqldb.DatabaseError(error, payload, sql) + +def insertShoppingListItemsTuple(site, payload, convert=True, conn=None): + shopping_list_item = () + self_conn = False + with open(f"application/shoppinglists/sql/insertShoppingListItemsTuple.sql", "r+") as file: + sql = file.read().replace("%%site_name%%", site) + try: + + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = True + self_conn = True + + with conn.cursor() as cur: + cur.execute(sql, payload) + rows = cur.fetchone() + if rows and convert: + shopping_list_item = postsqldb.tupleDictionaryFactory(cur.description, rows) + elif rows and not convert: + shopping_list_item = rows + + if self_conn: + conn.commit() + conn.close() + + return shopping_list_item + + except Exception as error: + raise postsqldb.DatabaseError(error, payload, sql) diff --git a/application/shoppinglists/shoplist_processess.py b/application/shoppinglists/shoplist_processess.py new file mode 100644 index 0000000..e69de29 diff --git a/application/shoppinglists/sql/getItemsWithQOH.sql b/application/shoppinglists/sql/getItemsWithQOH.sql new file mode 100644 index 0000000..e86c053 --- /dev/null +++ b/application/shoppinglists/sql/getItemsWithQOH.sql @@ -0,0 +1,18 @@ +WITH sum_cte AS ( + SELECT mi.id, SUM(mil.quantity_on_hand)::FLOAT8 AS total_sum + FROM %%site_name%%_item_locations mil + JOIN %%site_name%%_items mi ON mil.part_id = mi.id + GROUP BY mi.id + ) + +SELECT %%site_name%%_items.*, + row_to_json(%%site_name%%_item_info.*) as item_info, + sum_cte.total_sum as total_qoh, + (SELECT COALESCE(row_to_json(u), '{}') FROM units as u WHERE u.id=%%site_name%%_item_info.uom) as uom +FROM %%site_name%%_items +LEFT JOIN sum_cte ON %%site_name%%_items.id = sum_cte.id +LEFT JOIN %%site_name%%_item_info ON %%site_name%%_items.item_info_id = %%site_name%%_item_info.id +WHERE %%site_name%%_items.search_string LIKE '%%' || %s || '%%' +ORDER BY %%sort_order%% +LIMIT %s OFFSET %s; + diff --git a/application/shoppinglists/sql/insertShoppingListItemsTuple.sql b/application/shoppinglists/sql/insertShoppingListItemsTuple.sql new file mode 100644 index 0000000..0e45d8c --- /dev/null +++ b/application/shoppinglists/sql/insertShoppingListItemsTuple.sql @@ -0,0 +1,4 @@ +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) +RETURNING *; \ No newline at end of file diff --git a/application/shoppinglists/sql/selectShoppingListItem.sql b/application/shoppinglists/sql/selectShoppingListItem.sql new file mode 100644 index 0000000..64a029f --- /dev/null +++ b/application/shoppinglists/sql/selectShoppingListItem.sql @@ -0,0 +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 diff --git a/static/handlers/shoppingListEditHandler.js b/application/shoppinglists/static/js/shoppingListEditHandler.js similarity index 96% rename from static/handlers/shoppingListEditHandler.js rename to application/shoppinglists/static/js/shoppingListEditHandler.js index 2f71022..e2b0d65 100644 --- a/static/handlers/shoppingListEditHandler.js +++ b/application/shoppinglists/static/js/shoppingListEditHandler.js @@ -88,7 +88,7 @@ async function replenishLineTable(sl_items){ } async function fetchShoppingList() { - const url = new URL('/shopping-lists/getList', window.location.origin); + const url = new URL('/shopping-lists/api/getList', window.location.origin); url.searchParams.append('id', sl_id); const response = await fetch(url); data = await response.json(); @@ -277,7 +277,7 @@ async function updateItemsPaginationElement() { let items_limit = 25; async function fetchItems() { - const url = new URL('/shopping-lists/getItems', window.location.origin); + const url = new URL('/shopping-lists/api/getItems', window.location.origin); url.searchParams.append('page', pagination_current); url.searchParams.append('limit', items_limit); url.searchParams.append('search_string', search_string); @@ -288,7 +288,7 @@ async function fetchItems() { } async function fetchSLItem(sli_id) { - const url = new URL('/shopping-lists/getListItem', window.location.origin); + const url = new URL('/shopping-lists/api/getListItem', window.location.origin); url.searchParams.append('sli_id', sli_id); const response = await fetch(url); data = await response.json(); @@ -320,7 +320,7 @@ async function addCustomItem() { } async function submitItemToList(newItem) { - const response = await fetch(`/shopping-lists/postListItem`, { + const response = await fetch(`/shopping-lists/api/postListItem`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -345,7 +345,7 @@ async function submitItemToList(newItem) { } async function deleteLineItem(sli_id) { - const response = await fetch(`/shopping-lists/deleteListItem`, { + const response = await fetch(`/shopping-lists/api/deleteListItem`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -374,7 +374,7 @@ async function deleteLineItem(sli_id) { } async function saveLineItem(sli_id, update) { - const response = await fetch(`/shopping-lists/saveListItem`, { + const response = await fetch(`/shopping-lists/api/saveListItem`, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/static/handlers/shoppingListViewHandler.js b/application/shoppinglists/static/js/shoppingListViewHandler.js similarity index 100% rename from static/handlers/shoppingListViewHandler.js rename to application/shoppinglists/static/js/shoppingListViewHandler.js diff --git a/static/handlers/shoppingListsHandler.js b/application/shoppinglists/static/js/shoppingListsHandler.js similarity index 97% rename from static/handlers/shoppingListsHandler.js rename to application/shoppinglists/static/js/shoppingListsHandler.js index a3166bd..a4e4603 100644 --- a/static/handlers/shoppingListsHandler.js +++ b/application/shoppinglists/static/js/shoppingListsHandler.js @@ -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-list/edit/${lists[i].id}` + editOp.href = `/shopping-lists/edit/${lists[i].id}` let viewOp = document.createElement('a') viewOp.setAttribute('class', 'uk-button uk-button-small uk-button-default') viewOp.innerHTML = ' View' - viewOp.href = `/shopping-list/view/${lists[i].id}` + viewOp.href = `/shopping-lists/view/${lists[i].id}` //viewOp.style = "margin-right: 20px;" @@ -83,7 +83,7 @@ async function openAddListModal() { var listLimit = 5; async function getShoppingLists(){ console.log(pagination_current) - const url = new URL('/shopping-lists/getLists', window.location.origin); + const url = new URL('/shopping-lists/api/getLists', window.location.origin); url.searchParams.append('page', pagination_current); url.searchParams.append('limit', listLimit); response = await fetch(url) @@ -98,7 +98,7 @@ async function addList() { let list_description = document.getElementById('addListDescription').value let list_type = document.getElementById('list_type').value - const response = await fetch(`/shopping-lists/addList`, { + const response = await fetch(`/shopping-lists/api/addList`, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/templates/shopping-lists/edit.html b/application/shoppinglists/templates/edit.html similarity index 99% rename from templates/shopping-lists/edit.html rename to application/shoppinglists/templates/edit.html index 16333a2..0b40b38 100644 --- a/templates/shopping-lists/edit.html +++ b/application/shoppinglists/templates/edit.html @@ -304,6 +304,6 @@ - + \ No newline at end of file diff --git a/templates/shopping-lists/index.html b/application/shoppinglists/templates/lists.html similarity index 98% rename from templates/shopping-lists/index.html rename to application/shoppinglists/templates/lists.html index 8b34b82..f7d415c 100644 --- a/templates/shopping-lists/index.html +++ b/application/shoppinglists/templates/lists.html @@ -156,5 +156,5 @@ - + \ No newline at end of file diff --git a/templates/shopping-lists/view.html b/application/shoppinglists/templates/view.html similarity index 98% rename from templates/shopping-lists/view.html rename to application/shoppinglists/templates/view.html index 9023292..9edeb6f 100644 --- a/templates/shopping-lists/view.html +++ b/application/shoppinglists/templates/view.html @@ -116,6 +116,6 @@ - + \ No newline at end of file diff --git a/database.log b/database.log index edaa2c5..b920576 100644 --- a/database.log +++ b/database.log @@ -1943,4 +1943,16 @@ sql='INSERT INTO test_receipt_items(type, receipt_id, barcode, name, qty, uom, data, status) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) RETURNING *;') 2025-07-04 08:19:34.889657 --- ERROR --- DatabaseError(message='invalid input syntax for type integer: "{"cost": 1.99, "expires": false}"LINE 3: ...41789001314%', 'Chicken Ramen Noodle Soup', 1, 5, '{"cost": ... ^', payload=('sku', 23, '%041789001314%', 'Chicken Ramen Noodle Soup', 1, 5, '{"cost": 1.99, "expires": false}', 'Unresolved'), - sql='INSERT INTO test_recipe_items(uuid, rp_id, item_type, item_name, uom, qty, item_id, links) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) RETURNING *;') \ No newline at end of file + sql='INSERT INTO test_recipe_items(uuid, rp_id, item_type, item_name, uom, qty, item_id, links) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) RETURNING *;') +2025-07-12 07:48:13.460875 --- ERROR --- DatabaseError(message='invalid input syntax for type integer: "each"LINE 3: VALUES ('5g89bj2', '5', 'custom', 'test', 'each', 1, NULL, '... ^', + payload=('5g89bj2', '5', 'custom', 'test', 'each', 1, None, '{"main": ""}'), + sql='INSERT INTO test_shopping_list_items(uuid, sl_id, item_type, item_name, uom, qty, item_id, links) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) RETURNING *;') +2025-07-12 07:57:23.925776 --- ERROR --- DatabaseError(message='syntax error at or near "ASC"LINE 16: ORDER BY ASC ^', + payload=['', 25, 0], + sql='WITH 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 )SELECT test_items.*, row_to_json(test_item_info.*) as item_info, sum_cte.total_sum as total_qoh, (SELECT COALESCE(row_to_json(u), '{}') FROM units as u WHERE u.id=test_item_info.uom) as uomFROM test_itemsLEFT JOIN sum_cte ON test_items.id = sum_cte.idLEFT JOIN test_item_info ON test_items.item_info_id = test_item_info.idWHERE test_items.search_string LIKE '%%' || %s || '%%'ORDER BY ASCLIMIT %s OFFSET %s;') +2025-07-12 07:58:21.551161 --- ERROR --- DatabaseError(message='syntax error at or near "ASC"LINE 16: ORDER BY ASC ^', + payload=['', 25, 0], + sql='WITH 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 )SELECT test_items.*, row_to_json(test_item_info.*) as item_info, sum_cte.total_sum as total_qoh, (SELECT COALESCE(row_to_json(u), '{}') FROM units as u WHERE u.id=test_item_info.uom) as uomFROM test_itemsLEFT JOIN sum_cte ON test_items.id = sum_cte.idLEFT JOIN test_item_info ON test_items.item_info_id = test_item_info.idWHERE test_items.search_string LIKE '%%' || %s || '%%' ORDER BY ASC LIMIT %s OFFSET %s;') +2025-07-12 08:43:43.017720 --- ERROR --- DatabaseError(message='invalid input syntax for type integer: " Pinch"LINE 1: ... = 'Acai-Blueberry-Pomegranate', qty = '1', uom = ' Pinch', ... ^', + payload={'id': 12, 'update': {'item_name': 'Acai-Blueberry-Pomegranate', 'qty': '1', 'uom': ' Pinch', 'links': {'main': 'test'}}}, + sql='UPDATE test_shopping_list_items SET item_name = %s, qty = %s, uom = %s, links = %s WHERE id=%s RETURNING *;') \ No newline at end of file diff --git a/webserver.py b/webserver.py index f9347f9..9937fe6 100644 --- a/webserver.py +++ b/webserver.py @@ -1,7 +1,7 @@ import celery.schedules from flask import Flask, render_template, session, request, redirect, jsonify from flask_assets import Environment, Bundle -import api, config, user_api, psycopg2, main, api_admin, receipts_API, shopping_list_API, group_api +import api, config, user_api, psycopg2, main, api_admin, receipts_API, group_api from user_api import login_required, update_session_user from workshop_api import workshop_api import database @@ -10,6 +10,7 @@ from webpush import trigger_push_notifications_for_subscriptions from application.recipes import recipes_api from application.items import items_API from application.poe import poe_api +from application.shoppinglists import shoplist_api from flasgger import Swagger @@ -31,7 +32,7 @@ app.register_blueprint(items_API.items_api, url_prefix='/items') app.register_blueprint(poe_api.point_of_ease, url_prefix='/poe') app.register_blueprint(workshop_api) app.register_blueprint(receipts_API.receipt_api) -app.register_blueprint(shopping_list_API.shopping_list_api) +app.register_blueprint(shoplist_api.shopping_list_api, url_prefix="/shopping-lists") app.register_blueprint(group_api.groups_api) app.register_blueprint(recipes_api.recipes_api, url_prefix='/recipes')