From 86e40f70f1d6efcd0ba445d2e43342f41651f994 Mon Sep 17 00:00:00 2001 From: Jadowyne Ulve Date: Thu, 14 Aug 2025 18:25:48 -0500 Subject: [PATCH] Implemented recipes being added to shopping lists --- .../database_payloads.cpython-313.pyc | Bin 28454 -> 28481 bytes .../sql/CREATE/shopping_list_items.sql | 17 +- .../sql/CREATE/shopping_lists.sql | 6 +- application/database_payloads.py | 12 +- .../__pycache__/shoplist_api.cpython-313.pyc | Bin 10919 -> 13023 bytes .../shoplist_database.cpython-313.pyc | Bin 13765 -> 16761 bytes .../shoplist_processess.cpython-313.pyc | Bin 0 -> 1598 bytes application/shoppinglists/shoplist_api.py | 88 +++++-- .../shoppinglists/shoplist_database.py | 71 +++++- .../shoppinglists/shoplist_processess.py | 39 +++ .../shoppinglists/sql/getItemsSafetyStock.sql | 6 +- .../sql/getRecipeItemsByUUID.sql | 11 + .../shoppinglists/sql/getShoppingListByID.sql | 8 +- .../shoppinglists/sql/getShoppingLists.sql | 2 +- .../sql/insertShoppingListItemsTuple.sql | 6 +- .../sql/selectShoppingListItem.sql | 2 +- .../static/js/shoppingListEditHandler.js | 230 +++++++++++++++--- .../static/js/shoppingListViewHandler.js | 6 +- .../static/js/shoppingListsHandler.js | 8 +- application/shoppinglists/templates/edit.html | 41 +++- application/shoppinglists/templates/view.html | 2 +- logs/database.log | 95 +++++++- run-server.sh | 7 + 23 files changed, 558 insertions(+), 99 deletions(-) create mode 100644 application/shoppinglists/__pycache__/shoplist_processess.cpython-313.pyc create mode 100644 application/shoppinglists/sql/getRecipeItemsByUUID.sql create mode 100644 run-server.sh diff --git a/application/__pycache__/database_payloads.cpython-313.pyc b/application/__pycache__/database_payloads.cpython-313.pyc index 82e08d3302b2037289a947a4d1fcc90854e42d2d..243c2285c0c0f72bbde2c8a245ad34b78821b1bb 100644 GIT binary patch delta 722 zcmX9*ZAep57{2eEce{7HD8r@GQd^cY67q}rT~=o6V_UoG1h?k=-2CpUlnJ^_&=o28 z{_#(d{V*{o#|cCF6hiVxfe2ZGCiFw_M-4`$5Y(9+I1kVJJkR@{b2w8Qu&@C_NDz32 zuEm=Bo`(V9P!33#k&`$s=kfR%et>mEijk?*cPJ=h!%S32N{5_Q$~~RaGY($l@<38K zc&hSy8C5(ZsYSIUG%3=AwOG;9(b?&3Z@TF1>Na&Zv{~J~tsU-qZzeFz8rMoyS5J?( zUb55qk~s_+VeqpQ%@U7=sqs8Vc?8{|_vmi$;|oo(kgZ5zq$sMTaneh)7~+7(B;z?v zAsyRwmg+ucQyo2K_M6dpI2pCsFk@84vkM4^Hq5?Im~QEX>OS@Xw)WX zbw1Nb*NR;CMH4$MU)H4iN=LF+r4Qckvcn!>=bMz=_vS?m!>8TA~Mb@PG54;y4VP8i(5$ zJRgHqr>m*|?DP`AUD98wlfxvLFxdg-s69)y$qPP!d7{6x0q~Z9#SE~BR_h~}#~o_` zKH|*f*YF9wwjs*D+a_Qghs9U$4NJ@WDSuZ!3fn}iI0EnkZ&WU^q3|vC((;IX7m9Wg`4AlQLv0kg1QL12pSRFRd_QmN{UE|{yi5D=lssYxp-dX@GuASg4tYb zkW+DQtZO=L-r}IKwUT$Va!bS*DGC_vG7FeuCYo1oUt+4V@R6guI5sTl=c0i#I%geg zMT5Nz%M3FepYED}V%3O(~2}_w-9N>v;!U7q<@Dmg0dB-Z1ctCB7-ip8GBU8R0sT173>_ zM`h_-_8gl)hGqUVKJbnI1+q*WPe`o>Mv>?Jz%;(HEjT1~K6niU(HGL7d;{mB>zQtu z#1HD>^C<93cpQu3+=)wy0!92i*#xL;j9kPbXCj|q6=SVWfi)ta?*=wgypF@pUXuk`%d9x#l8hSc^!YAsxVhTc>?7kvR QUC$GREBkHYPwxTz1Gwz33jhEB 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 01d674227fcaa66bf001cead03815fa4f9600831..e462cdfb1b972b72c891e240e0caca2c0a23c4e9 100644 GIT binary patch delta 4610 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 C<=oZ) diff --git a/application/shoppinglists/__pycache__/shoplist_database.cpython-313.pyc b/application/shoppinglists/__pycache__/shoplist_database.cpython-313.pyc index 21333794fef3206ce99788f0ac780fa205804eaa..25279d8a0f8dbf2d96cc24cd64a58c3f7290ee36 100644 GIT binary patch delta 2105 zcma)7U2Gf25#GJy-SO`DE0Q8jQsR*jO)<16iIiMhvV|(8Z>t7)JWTh z&YmQvLHp1JZswcW*>9ISGt13qzWxz*+_T#y1fy;G^{2m|UULNS&({*MWrxNF(HX2; zG>2v}5eJAf5f6wrkpM_Akrj~DL?R&3L=qs$L^2@RL^eP+6WIaTP2@O@lg`iRAtsXe zmiX+s+TtJI{=kWUOD?$9ad-W@ZjQs*`bg_EmaGuP!gudx{Z`8f9N&*nndzi!Va#U0 zl04{fxyE#(8skBu9SBurGR%Aj%jBxn2lA448UKwu9!imSoF1!yX2TF-BX^yOW$OAd zfgI0-Bw<1qfNx}JFS*J_W5v0Xm1pPji*sjdk3C;5=X2@Wa|?PeMCd$-lHNtSv%5GQ zm=KA|?L>6TTNQ7+eXpNxhLu&3I9q(J0mB-2kV4Wp{)<@n*Oy_` zHisXGB0J~6Y~~>(95VB zRV^B>avD?RHMWc#NV8q=WfDoaIqXXYC{^<4ipp8{o4w3Fzb5ZPzV z$c~+uq6>EMT41t;T(XX*{ujxM8s;Fr4}XiyM{an7BqAoUA5fT|Qu~+=L@tZ*MAtz! zg644)O$T=N*{(YK^fx;P>CVejH_Af)_K%vw8miid?elmZZBZn}37!6ek)^bz7Sux} zMK>$6mfq4c=g(J8?Rn^|&Yqo9Mv9Z;%F^V_cI3{aa%@yBsykNN3@zlf!>W>q#}mp6 zFDUUEm8Mn5kLOF&F*!1#sT2+B2{^~nf&YV+Tnz8Bmdt)iI>_Bjg#6CsC7(-=wx@wY zPg2oFMFhkx5jtz;vQDKa+HI`ju!G_N1WVt_a3EGuFHpCI{ZuV+VIt}D?>MJnWoqOYGJKF zCa(0}@Wy|lKJXz&=mwwbf4-E<-m!A6P{TS)> zKeGH79Q_x=a;^tmkM!jt?7AnF3-aqj1AsU7MZw<)a)28VDc2=5k^=SgNNOwF81SoZ zcGJxPK4}4(ok45Yd@0|7l6e_HGg_b-xt(S-+mO@vBHR}u9Ud1JNJxpZ)~rAdDHW#( z-9_u%9GX~vPx%yMiTozE2Zx9e3o%QbpuG09 z<)Z9*S1K3gHv%5O8>t@fH^Lm?MpVjm!&@j&PexJ$Y~z4m^{|^BP7TzJN55dmZ0Aq$ z2>HD8B2V8}2G=hnFIo7*fRi?YWiRPSFYrgfY{&Ii(gBPo>VN3|kYO!wK^HTLNNT(J z{`2N4rw+zz0|T^y$@`fTz^`O~#XkWg2QYa*+aKJ$)_v0wYuY@kxz)YKuFRVQZ|*UM zDu#5X?&xDNc9NFrtBoU zDaQ=7IM+8noPZWAySE{(WosCNcI}359JJ48HW-}8+WsJJglbqX<@HE=AIkCFVM^VQFuA=^+0=AAfj6SArRyI{PeN z8a(2C_{KMgb&1!hcPV?ijoa$#NY@M7=UUSNX~*j!??e(5uLKymQZJ{YmP*88MB3zx zSEzKK%D9DHH_LdnpNT~Q1`zBXeAaPa8lqgcIL+GmT!ak1>V6_!rN}2vJKN(2ahQz1 z5`)9=9iHX% za+e-xU+YdBlgU)NUq1pp_m4>UK#X@S~qG{#4&#_Op@N~)2Cnng&}$vL9&bHyY^ TQZ1|rdcR)7+o_Y{7zO_TD^RJc 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 0000000000000000000000000000000000000000..1795e96a79fe34630ac508c548551da1c5b62959 GIT binary patch literal 1598 zcmZ8h-EZ4e6u*ugf5ge$Hd$Iqp;=TGqOBd8N>LGIfHp+iQB`rnhnf+&j+c2$Z0}x2 zC?KRhX3`K(NEJx1M|kTayzwVA1*y^9q&;jy;;mKCp1H@hlZ>n6{LaVyIQMtYaX*zx zAU1xtf4cQpKFdb2w4wipoE*@kWuOHEiNnYld&^7{n^ZsN%WACwCjQ z9XcMltDezrxJHdof!Mx91qe{7MJ!ykYbMKlFjS`T?BCz#f~+}5DD$s};o2fX+k>g4 zfi_g~=*`e@;@h#vUm86jbmozM<_%PiU*(oKhu66>-+*(2p~oCyTaK(!z;R@qVjg|Q z>3p0Si)^JXEc1?77Sre>mXsrHC!^V=Gm$pLJSuZ~9MVNm4%2`;~dL?FP$B(iL_`fo6VOdBkvH0 z@p`JBDr#TN!qetfa$gs)W!j!qZME!L;bN{p8tkbH-+Z`5)sYCRBEfVWhbloG!DK3) z#;L+erCD{1rbYRd+ob$mza2V*dGUtr+#y8?1KVN1Z+jL7&anmpSi<8VCYadx6%~_I zWar(q>s0ng+jPD9Why}img&=k(eho>Z8mKmLov`y-SSJpJyuyRa9QIhScEj8X)#nDhDM+Y zu^Kn40YTcJnZo>&A&tNi8ITGVRUPZgDi97Kg>96J2L1Hf+ZEHTSryN4eB7S*v0>h+ zG!5H%(PMb5vrWUdU8e$V4mX7-B7qe=mWN%_B82@Gyf)1kwb}?(TQ2Z|Ut@>Eok)*~ zU34rWEwj7&x2ipw&iy?9!~BnzI@%*`@<^LK)MmTd(*^Y2u>t&`o%G31R zQF{3>z5LtUAI2huJH=shPd)?o_dteYKZ8 z_uxV|yU-hZdGA_xEI+`=Kk>?5y*qLKM3W~I9rfP{q)Z>lONa8(zJIi`*O&->LW1`zcTo_Y&VGkK@eUXLmzIZlkE;EYs*qBjhc-K;CIp zsUvX=pQfOqFti`#8ENeMXtQeMZT3PyAkKg|4`L4H$nP}WTB~8bi!U;heJ2sQz{D}n kaoj_sJwtCjMvD(o>O`33&hDAVh`}#H0Q=-QSixrh0f?E4mjD0& literal 0 HcmV?d00001 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