diff --git a/application/shoppinglists/shoplist_api.py b/application/shoppinglists/shoplist_api.py index e17b92c..fd6a38d 100644 --- a/application/shoppinglists/shoplist_api.py +++ b/application/shoppinglists/shoplist_api.py @@ -29,26 +29,26 @@ def shopping_list(mode, id): # API CALLS +# Added to Database @shopping_list_api.route('/api/addList', methods=["POST"]) def addList(): if request.method == "POST": list_name = request.get_json()['list_name'] list_description = request.get_json()['list_description'] list_type = request.get_json()['list_type'] - database_config = config() site_name = session['selected_site'] user_id = session['user_id'] - with psycopg2.connect(**database_config) as conn: - shopping_list = MyDataclasses.ShoppingListPayload( - name=list_name, - description=list_description, - author=user_id, - type=list_type - ) - database.insertShoppingListsTuple(conn, site_name, shopping_list.payload()) + shopping_list = database_payloads.ShoppingListPayload( + name=list_name, + description=list_description, + author=user_id, + type=list_type + ) + shoplist_database.insertShoppingListsTuple(site_name, shopping_list.payload()) return jsonify({'error': False, 'message': 'List added!!'}) return jsonify({'error': True, 'message': 'These was an error with adding the list!'}) +# Added to Database @shopping_list_api.route('/api/getLists', methods=["GET"]) def getShoppingLists(): lists = [] @@ -56,42 +56,40 @@ def getShoppingLists(): page = int(request.args.get('page', 1)) limit = int(request.args.get('limit', 1)) offset = (page-1)*limit - database_config = config() + site_name = session['selected_site'] - with psycopg2.connect(**database_config) as conn: - lists, count = database.getShoppingLists(conn, site_name, (limit, offset), convert=True) + lists, count = shoplist_database.getShoppingLists(site_name, (limit, offset)) - for list in lists: - - if list['type'] == 'calculated': - items = [] - not_items = database.getItemsSafetyStock(conn, site_name, convert=True) - for item in not_items: - new_item = { - 'id': item['id'], - 'uuid': item['barcode'], - 'sl_id': 0, - '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'] - } - items.append(new_item) - list['sl_items'] = items + for list in lists: + + if list['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, + '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'] + } + items.append(new_item) + list['sl_items'] = items return jsonify({'shopping_lists': lists, 'end':math.ceil(count/limit), 'error': False, 'message': 'Lists queried successfully!'}) +# Added to Database @shopping_list_api.route('/api/getList', methods=["GET"]) def getShoppingList(): if request.method == "GET": sl_id = int(request.args.get('id', 1)) - database_config = config() site_name = session['selected_site'] - with psycopg2.connect(**database_config) as conn: - lists = database.getShoppingList(conn, site_name, (sl_id, ), convert=True) - return jsonify({'shopping_list': lists, 'error': False, 'message': 'Lists queried successfully!'}) + list = shoplist_database.getShoppingList(site_name, (sl_id, )) + return jsonify({'shopping_list': list, 'error': False, 'message': 'Lists queried successfully!'}) # Added to Database @shopping_list_api.route('/api/getListItem', methods=["GET"]) @@ -151,39 +149,38 @@ def deleteListItem(): return jsonify({"error":False, "message":"item deleted succesfully!"}) return jsonify({"error":True, "message":"There was an error with this POST statement"}) +# Added to Database @shopping_list_api.route('/api/saveListItem', methods=["POST"]) def saveListItem(): if request.method == "POST": sli_id = request.get_json()['sli_id'] update = request.get_json()['update'] site_name = session['selected_site'] - database_config = config() - with psycopg2.connect(**database_config) as conn: - database.__updateTuple(conn, site_name, f"{site_name}_shopping_list_items", {'id': sli_id, 'update': update}) + shoplist_database.updateShoppingListItemsTuple(site_name, {'id': sli_id, 'update': update}) return jsonify({"error":False, "message":"items fetched succesfully!"}) return jsonify({"error":True, "message":"There was an error with this GET statement"}) +# Added to Database @shopping_list_api.route('/api/getSKUItemsFull', methods=["GET"]) def getSKUItemsFull(): items = [] count = {'count': 0} if request.method == "GET": site_name = session['selected_site'] - database_config = config() - with psycopg2.connect(**database_config) as conn: - not_items = database.getItemsSafetyStock(conn, site_name, convert=True) - for item in not_items: - new_item = { - 'id': item['id'], - 'uuid': item['barcode'], - 'sl_id': 0, - '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'] - } - items.append(new_item) + + not_items = shoplist_database.getItemsSafetyStock(site_name) + for item in not_items: + new_item = { + 'id': item['id'], + 'uuid': item['barcode'], + 'sl_id': 0, + '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'] + } + 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"}) diff --git a/application/shoppinglists/shoplist_database.py b/application/shoppinglists/shoplist_database.py index 920f030..6f9338f 100644 --- a/application/shoppinglists/shoplist_database.py +++ b/application/shoppinglists/shoplist_database.py @@ -4,48 +4,121 @@ import psycopg2 import config from application import postsqldb +def getShoppingList(site, payload, convert=True, conn=None): + recordset = [] + self_conn = False + + with open(f"application/shoppinglists/sql/getShoppingListByID.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: + recordset = postsqldb.tupleDictionaryFactory(cur.description, rows) + if rows and not convert: + recordset = rows + + if self_conn: + conn.close() + + return recordset + + except (Exception, psycopg2.DatabaseError) as error: + raise postsqldb.DatabaseError(error, payload, sql) + +def getShoppingLists(site, payload, convert=True, conn=None): + recordset = [] + count = 0 + self_conn = False + with open(f"application/shoppinglists/sql/getShoppingLists.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] + if rows and not convert: + recordset = rows + + cur.execute(f"SELECT COUNT(*) FROM {site}_shopping_lists;") + count = cur.fetchone()[0] + + if self_conn: + conn.close() + + return recordset, count + except (Exception, psycopg2.DatabaseError) as error: + raise postsqldb.DatabaseError(error, payload, sql) + +def getItemsSafetyStock(site, convert=True, conn=None): + recordsets = [] + self_conn = False + with open(f"application/shoppinglists/sql/getItemsSafetyStock.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) + rows = cur.fetchall() + if rows and convert: + recordsets = [postsqldb.tupleDictionaryFactory(cur.description, row) for row in rows] + if rows and not convert: + recordsets = rows + + if self_conn: + conn.close() + + return recordsets + + except (Exception, psycopg2.DatabaseError) as error: + raise postsqldb.DatabaseError(error, None, sql) + def getShoppingListItem(site, payload, convert=True, conn=None): - """_summary_ + record = () + self_conn = False + with open('application/shoppinglists/sql/selectShoppingListItem.sql', 'r') as file: + sql = file.read().replace("%%site_name%%", site) + try: - Args: - conn (_type_): _description_ - site (_type_): _description_ - payload (_type_): (id, ) - convert (bool, optional): _description_. Defaults to True. + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = True + self_conn = 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) + 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 = [] @@ -115,6 +188,34 @@ def deleteShoppingListItemsTuple(site_name, payload, convert=True, conn=None): except Exception as error: raise postsqldb.DatabaseError(error, payload, sql) +def insertShoppingListsTuple(site, payload, convert=True, conn=None): + shopping_list = () + self_conn = False + with open(f"application/shoppinglists/sql/insertShoppingListsTuple.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 = postsqldb.tupleDictionaryFactory(cur.description, rows) + elif rows and not convert: + shopping_list = rows + + if self_conn: + conn.commit() + conn.close() + + return shopping_list + except Exception as error: + raise postsqldb.DatabaseError(error, payload, sql) + def insertShoppingListItemsTuple(site, payload, convert=True, conn=None): shopping_list_item = () self_conn = False @@ -144,3 +245,32 @@ def insertShoppingListItemsTuple(site, payload, convert=True, conn=None): except Exception as error: raise postsqldb.DatabaseError(error, payload, sql) + +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 *;" + 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, values) + rows = cur.fetchone() + if rows and convert: + updated = postsqldb.tupleDictionaryFactory(cur.description, rows) + elif rows and not convert: + updated = rows + + if self_conn: + conn.commit() + conn.close() + + return updated + except Exception as error: + raise postsqldb.DatabaseError(error, payload, sql) \ No newline at end of file diff --git a/application/shoppinglists/sql/getItemsSafetyStock.sql b/application/shoppinglists/sql/getItemsSafetyStock.sql new file mode 100644 index 0000000..de6a42f --- /dev/null +++ b/application/shoppinglists/sql/getItemsSafetyStock.sql @@ -0,0 +1,11 @@ +WITH sum_cte AS ( + SELECT mi.id, SUM(mil.quantity_on_hand) AS total_sum + FROM %%site_name%%_item_locations mil + JOIN %%site_name%%_items mi ON mil.part_id = mi.id + GROUP BY mi.id +) +SELECT * +FROM %%site_name%%_items +LEFT JOIN %%site_name%%_item_info ON %%site_name%%_items.item_info_id = %%site_name%%_item_info.id +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/getShoppingListByID.sql b/application/shoppinglists/sql/getShoppingListByID.sql new file mode 100644 index 0000000..de06f2e --- /dev/null +++ b/application/shoppinglists/sql/getShoppingListByID.sql @@ -0,0 +1,15 @@ +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 %%site_name%%_shopping_list_items items + WHERE items.sl_id = (SELECT passed_id FROM passed_id) + ) + +SELECT (SELECT passed_id FROM passed_id) AS passed_id, + %%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 diff --git a/application/shoppinglists/sql/getShoppingLists.sql b/application/shoppinglists/sql/getShoppingLists.sql new file mode 100644 index 0000000..acccf96 --- /dev/null +++ b/application/shoppinglists/sql/getShoppingLists.sql @@ -0,0 +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 + FROM %%site_name%%_shopping_lists LIMIT %s OFFSET %s; \ No newline at end of file diff --git a/application/shoppinglists/sql/insertShoppingListsTuple.sql b/application/shoppinglists/sql/insertShoppingListsTuple.sql new file mode 100644 index 0000000..6816ef5 --- /dev/null +++ b/application/shoppinglists/sql/insertShoppingListsTuple.sql @@ -0,0 +1,4 @@ +INSERT INTO %%site_name%%_shopping_lists +(name, description, author, creation_date, type) +VALUES (%s, %s, %s, %s, %s) +RETURNING *; \ No newline at end of file diff --git a/application/shoppinglists/static/js/shoppingListEditHandler.js b/application/shoppinglists/static/js/shoppingListEditHandler.js index e2b0d65..b390ab8 100644 --- a/application/shoppinglists/static/js/shoppingListEditHandler.js +++ b/application/shoppinglists/static/js/shoppingListEditHandler.js @@ -158,7 +158,7 @@ async function openLineEditModal(sli_id) { console.log(sl_item) document.getElementById('lineName').value = sl_item.item_name document.getElementById('lineQty').value = sl_item.qty - document.getElementById('lineUOM').value = sl_item.uom.fullname + document.getElementById('lineUOM').value = sl_item.uom.id console.log(sl_item.links) if(!sl_item.links.hasOwnProperty('main')){ diff --git a/application/shoppinglists/static/js/shoppingListViewHandler.js b/application/shoppinglists/static/js/shoppingListViewHandler.js index 1b4cf8d..b2cf155 100644 --- a/application/shoppinglists/static/js/shoppingListViewHandler.js +++ b/application/shoppinglists/static/js/shoppingListViewHandler.js @@ -60,7 +60,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(); @@ -68,7 +68,7 @@ async function fetchShoppingList() { } 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(); @@ -76,7 +76,7 @@ async function fetchSLItem(sli_id) { } async function fetchItemsFullCalculated() { - const url = new URL('/shopping-lists/getSKUItemsFull', window.location.origin); + const url = new URL('/shopping-lists/api/getSKUItemsFull', window.location.origin); const response = await fetch(url); data = await response.json(); return data.list_items; diff --git a/database.log b/database.log index b920576..0a169c5 100644 --- a/database.log +++ b/database.log @@ -1955,4 +1955,10 @@ 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 + sql='UPDATE test_shopping_list_items SET item_name = %s, qty = %s, uom = %s, links = %s WHERE id=%s RETURNING *;') +2025-07-12 09:06:37.431664 --- ERROR --- DatabaseError(message='invalid input syntax for type integer: "each"LINE 3: VALUES ('5vpg73z', '5', 'custom', 'test', 'each', 2, NULL, '... ^', + payload=('5vpg73z', '5', 'custom', 'test', 'each', 2, None, '{"main": "test"}'), + 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 09:29:10.063362 --- ERROR --- DatabaseError(message='tuple index out of range', + payload=(5,), + 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;') \ No newline at end of file