Implemented recipes being added to shopping lists

This commit is contained in:
Jadowyne Ulve 2025-08-14 18:25:48 -05:00
parent c9674b93b9
commit 86e40f70f1
23 changed files with 558 additions and 99 deletions

View File

@ -1,20 +1,11 @@
CREATE TABLE IF NOT EXISTS %%site_name%%_shopping_list_items ( CREATE TABLE IF NOT EXISTS %%site_name%%_shopping_list_items (
id SERIAL PRIMARY KEY, list_item_uuid UUID DEFAULT uuid_generate_v4() NOT NULL PRIMARY KEY,
uuid VARCHAR(32) NOT NULL, list_uuid UUID NOT NULL,
sl_id INTEGER NOT NULL,
item_type VARCHAR(32) NOT NULL, item_type VARCHAR(32) NOT NULL,
item_name TEXT NOT NULL, item_name TEXT NOT NULL,
uom INTEGER NOT NULL, uom INTEGER NOT NULL,
qty FLOAT8 NOT NULL, qty FLOAT8 NOT NULL,
item_id INTEGER DEFAULT NULL, item_uuid UUID DEFAULT NULL,
links JSONB DEFAULT '{"main": ""}', links JSONB DEFAULT '{"main": ""}',
UNIQUE(uuid, sl_id), UNIQUE(list_uuid, item_uuid)
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
); );

View File

@ -1,9 +1,11 @@
CREATE TABLE IF NOT EXISTS %%site_name%%_shopping_lists ( CREATE TABLE IF NOT EXISTS %%site_name%%_shopping_lists (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
list_uuid UUID DEFAULT uuid_generate_v4() NOT NULL,
list_type VARCHAR(32),
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
description TEXT, description TEXT,
author INTEGER, author INTEGER,
creation_date TIMESTAMP, creation_date TIMESTAMP,
type VARCHAR(64), sub_type VARCHAR(64),
UNIQUE(name) UNIQUE(list_uuid, name)
); );

View File

@ -305,24 +305,22 @@ class ReceiptPayload:
@dataclass @dataclass
class ShoppingListItemPayload: class ShoppingListItemPayload:
uuid: str list_uuid: str
sl_id: int
item_type: str item_type: str
item_name: str item_name: str
uom: str uom: int
qty: float qty: float
item_id: int = None item_uuid: str = None
links: dict = field(default_factory=dict) links: dict = field(default_factory=dict)
def payload(self): def payload(self):
return ( return (
self.uuid, self.list_uuid,
self.sl_id,
self.item_type, self.item_type,
self.item_name, self.item_name,
self.uom, self.uom,
self.qty, self.qty,
self.item_id, self.item_uuid,
json.dumps(self.links) json.dumps(self.links)
) )

View File

@ -7,7 +7,7 @@ import math
# APPLICATION IMPORTS # APPLICATION IMPORTS
from application import postsqldb, database_payloads from application import postsqldb, database_payloads
from application.access_module import access_api 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") 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'])] sites = [site[1] for site in postsqldb.get_sites(session['user']['sites'])]
return render_template("lists.html", current_site=session['selected_site'], sites=sites) return render_template("lists.html", current_site=session['selected_site'], sites=sites)
@shopping_list_api.route("/<mode>/<id>") @shopping_list_api.route("/<mode>/<list_uuid>")
@access_api.login_required @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'])] sites = [site[1] for site in postsqldb.get_sites(session['user']['sites'])]
if mode == "view": 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": 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("/") return redirect("/")
# API CALLS # API CALLS
@ -65,20 +65,19 @@ def getShoppingLists():
for list in lists: for list in lists:
if list['type'] == 'calculated': if list['sub_type'] == 'calculated':
items = [] items = []
not_items = shoplist_database.getItemsSafetyStock(site_name) not_items = shoplist_database.getItemsSafetyStock(site_name)
for item in not_items: for item in not_items:
new_item = { new_item = {
'id': item['id'], 'list_item_uuid': 0,
'uuid': item['barcode'], 'list_uuid': list['list_uuid'],
'sl_id': 0,
'item_type': 'sku', 'item_type': 'sku',
'item_name': item['item_name'], 'item_name': item['item_name'],
'uom': item['uom'], 'uom': item['item_info']['uom'],
'qty': float(float(item['safety_stock']) - float(item['total_sum'])), 'qty': float(float(item['item_info']['safety_stock']) - float(item['total_sum'])),
'item_id': item['id'], 'links': item['links'],
'links': item['links'] 'uom_fullname': ['uom_fullname']
} }
items.append(new_item) items.append(new_item)
list['sl_items'] = items list['sl_items'] = items
@ -90,9 +89,9 @@ def getShoppingLists():
@access_api.login_required @access_api.login_required
def getShoppingList(): def getShoppingList():
if request.method == "GET": 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'] 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!'}) return jsonify({'shopping_list': list, 'error': False, 'message': 'Lists queried successfully!'})
# Added to Database # Added to Database
@ -101,9 +100,9 @@ def getShoppingList():
def getShoppingListItem(): def getShoppingListItem():
list_item = {} list_item = {}
if request.method == "GET": 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'] 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': False, 'message': 'Lists Items queried successfully!'})
return jsonify({'list_item': list_item, 'error': True, 'message': 'List Items queried unsuccessfully!'}) 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":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"}) 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 # Added to database
@shopping_list_api.route('/api/postListItem', methods=["POST"]) @shopping_list_api.route('/api/postListItem', methods=["POST"])
@access_api.login_required @access_api.login_required
@ -133,6 +150,27 @@ def postListItem():
data = request.get_json()['data'] data = request.get_json()['data']
site_name = session['selected_site'] site_name = session['selected_site']
sl_item = database_payloads.ShoppingListItemPayload( 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'], uuid = data['uuid'],
sl_id = data['sl_id'], sl_id = data['sl_id'],
item_type=data['item_type'], item_type=data['item_type'],
@ -142,7 +180,9 @@ def postListItem():
item_id=data['item_id'], item_id=data['item_id'],
links=data['links'] 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":False, "message":"items fetched succesfully!"})
return jsonify({"error":True, "message":"There was an error with this GET statement"}) return jsonify({"error":True, "message":"There was an error with this GET statement"})
@ -162,10 +202,10 @@ def deleteListItem():
@access_api.login_required @access_api.login_required
def saveListItem(): def saveListItem():
if request.method == "POST": 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'] update = request.get_json()['update']
site_name = session['selected_site'] 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":False, "message":"items fetched succesfully!"})
return jsonify({"error":True, "message":"There was an error with this GET statement"}) return jsonify({"error":True, "message":"There was an error with this GET statement"})
@ -179,6 +219,7 @@ def getSKUItemsFull():
site_name = session['selected_site'] site_name = session['selected_site']
not_items = shoplist_database.getItemsSafetyStock(site_name) not_items = shoplist_database.getItemsSafetyStock(site_name)
print(not_items)
for item in not_items: for item in not_items:
new_item = { new_item = {
'id': item['id'], 'id': item['id'],
@ -186,10 +227,11 @@ def getSKUItemsFull():
'sl_id': 0, 'sl_id': 0,
'item_type': 'sku', 'item_type': 'sku',
'item_name': item['item_name'], 'item_name': item['item_name'],
'uom': item['uom'], 'uom': item['item_info']['uom'],
'qty': float(float(item['safety_stock']) - float(item['total_sum'])), 'qty': float(float(item['item_info']['safety_stock']) - float(item['total_sum'])),
'item_id': item['id'], 'item_id': item['id'],
'links': item['links'] 'links': item['links'],
'uom_fullname': item['uom_fullname']
} }
items.append(new_item) items.append(new_item)
return jsonify({"list_items":items, "error":False, "message":"items fetched succesfully!"}) return jsonify({"list_items":items, "error":False, "message":"items fetched succesfully!"})

View File

@ -122,6 +122,35 @@ def getShoppingListItem(site, payload, convert=True, conn=None):
except Exception as error: except Exception as error:
raise postsqldb.DatabaseError(error, payload, sql) 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): def getItemsWithQOH(site, payload, convert=True, conn=None):
recordset = [] recordset = []
count = 0 count = 0
@ -162,10 +191,46 @@ def getItemsWithQOH(site, payload, convert=True, conn=None):
except Exception as error: except Exception as error:
raise postsqldb.DatabaseError(error, payload, sql) 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): def deleteShoppingListItemsTuple(site_name, payload, convert=True, conn=None):
deleted = () deleted = ()
self_conn = False 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: try:
if not conn: if not conn:
@ -252,8 +317,8 @@ def updateShoppingListItemsTuple(site, payload, convert=True, conn=None):
updated = () updated = ()
self_conn = False self_conn = False
set_clause, values = postsqldb.updateStringFactory(payload['update']) set_clause, values = postsqldb.updateStringFactory(payload['update'])
values.append(payload['id']) values.append(payload['uuid'])
sql = f"UPDATE {site}_shopping_list_items SET {set_clause} WHERE id=%s RETURNING *;" sql = f"UPDATE {site}_shopping_list_items SET {set_clause} WHERE list_item_uuid=%s::uuid RETURNING *;"
try: try:
if not conn: if not conn:
database_config = config.config() database_config = config.config()

View File

@ -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()

View File

@ -4,8 +4,12 @@ WITH sum_cte AS (
JOIN %%site_name%%_items mi ON mil.part_id = mi.id JOIN %%site_name%%_items mi ON mil.part_id = mi.id
GROUP BY 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 FROM %%site_name%%_items
LEFT JOIN %%site_name%%_item_info ON %%site_name%%_items.item_info_id = %%site_name%%_item_info.id 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 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); WHERE %%site_name%%_item_info.safety_stock > COALESCE(sum_cte.total_sum, 0);

View File

@ -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);

View File

@ -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 ( cte_sl_items AS (
SELECT items.*, SELECT items.*,
(SELECT COALESCE(row_to_json(un), '{}') FROM units un WHERE un.id = items.uom LIMIT 1) AS uom (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 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.*, %%site_name%%_shopping_lists.*,
logins.username as author, logins.username as author,
(SELECT COALESCE(array_agg(row_to_json(slis)), '{}') FROM cte_sl_items slis) AS sl_items (SELECT COALESCE(array_agg(row_to_json(slis)), '{}') FROM cte_sl_items slis) AS sl_items
FROM %%site_name%%_shopping_lists FROM %%site_name%%_shopping_lists
JOIN logins ON %%site_name%%_shopping_lists.author = logins.id JOIN logins ON %%site_name%%_shopping_lists.author = logins.id
WHERE %%site_name%%_shopping_lists.id=(SELECT passed_id FROM passed_id) WHERE %%site_name%%_shopping_lists.list_uuid=(SELECT passed_uuid::uuid FROM passed_uuid)

View File

@ -1,3 +1,3 @@
SELECT *, 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; FROM %%site_name%%_shopping_lists LIMIT %s OFFSET %s;

View File

@ -1,4 +1,6 @@
INSERT INTO %%site_name%%_shopping_list_items INSERT INTO %%site_name%%_shopping_list_items
(uuid, sl_id, item_type, item_name, uom, qty, item_id, links) (list_uuid, item_type, item_name, uom, qty, item_uuid, links)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s) 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 *; RETURNING *;

View File

@ -1,4 +1,4 @@
SELECT items.*, SELECT items.*,
(SELECT COALESCE(row_to_json(un), '{}') FROM units un WHERE un.id = items.uom LIMIT 1) AS uom (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 FROM %%site_name%%_shopping_list_items items
WHERE items.id = %s; WHERE items.list_item_uuid = %s::uuid;

View File

@ -10,7 +10,7 @@ async function replenishForm(shopping_list){
document.getElementById('list_creation_date').value = shopping_list.creation_date document.getElementById('list_creation_date').value = shopping_list.creation_date
document.getElementById('list_description').value = shopping_list.description document.getElementById('list_description').value = shopping_list.description
document.getElementById('list_author').value = shopping_list.author 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"){ if(shopping_list.type == "calculated"){
document.getElementById('addLineButton').classList.add("uk-disabled") document.getElementById('addLineButton').classList.add("uk-disabled")
@ -43,7 +43,7 @@ async function replenishLineTable(sl_items){
typeCell.innerHTML = sl_items[i].item_type typeCell.innerHTML = sl_items[i].item_type
let uuidCell = document.createElement('td') let uuidCell = document.createElement('td')
uuidCell.innerHTML = sl_items[i].uuid uuidCell.innerHTML = sl_items[i].list_item_uuid
let nameCell = document.createElement('td') let nameCell = document.createElement('td')
nameCell.innerHTML = sl_items[i].item_name nameCell.innerHTML = sl_items[i].item_name
@ -55,14 +55,14 @@ async function replenishLineTable(sl_items){
editOp.innerHTML = `<span uk-icon="icon: pencil"></span>` editOp.innerHTML = `<span uk-icon="icon: pencil"></span>`
editOp.style = 'margin-right: 5px;' editOp.style = 'margin-right: 5px;'
editOp.onclick = async function () { editOp.onclick = async function () {
await openLineEditModal(sl_items[i].id) await openLineEditModal(sl_items[i].list_item_uuid)
} }
let deleteOp = document.createElement('a') let deleteOp = document.createElement('a')
deleteOp.setAttribute('class', 'uk-button uk-button-default uk-button-small') deleteOp.setAttribute('class', 'uk-button uk-button-default uk-button-small')
deleteOp.innerHTML = `<span uk-icon="icon: trash"></span>` deleteOp.innerHTML = `<span uk-icon="icon: trash"></span>`
deleteOp.onclick = async function () { deleteOp.onclick = async function () {
await deleteLineItem(sl_items[i].id) await deleteLineItem(sl_items[i].list_item_uuid)
} }
opCell.append(editOp, deleteOp) opCell.append(editOp, deleteOp)
@ -74,7 +74,7 @@ async function replenishLineTable(sl_items){
async function fetchShoppingList() { async function fetchShoppingList() {
const url = new URL('/shopping-lists/api/getList', window.location.origin); 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); const response = await fetch(url);
data = await response.json(); data = await response.json();
return data.shopping_list; return data.shopping_list;
@ -91,26 +91,25 @@ async function updateItemsModalTable(items) {
for(let i=0; i < items.length; i++){ for(let i=0; i < items.length; i++){
let tableRow = document.createElement('tr') 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') let nameCell = document.createElement('td')
nameCell.innerHTML = `${items[i].item_name}` nameCell.innerHTML = `${items[i].item_name}`
tableRow.id = items[i].id let opCell = document.createElement('td')
tableRow.onclick = async function(){
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 = { let newItem = {
uuid: items[i].barcode, list_uuid: list_uuid,
sl_id: sl_id,
item_type: 'sku', item_type: 'sku',
item_name: items[i].item_name, item_name: items[i].item_name,
uom: items[i].item_info.uom, uom: items[i].item_info.uom,
qty: items[i].item_info.uom_quantity, qty: items[i].item_info.uom_quantity,
item_id: items[i].id, item_uuid: items[i].item_uuid,
links: {'main': items[i].links['main']} links: {'main': items[i].links['main']}
} }
@ -122,7 +121,9 @@ async function updateItemsModalTable(items) {
UIkit.modal(itemsModal).hide(); UIkit.modal(itemsModal).hide();
} }
tableRow.append(idCell, barcodeCell, nameCell)
opCell.append(selectButton)
tableRow.append(nameCell, opCell)
itemsTableBody.append(tableRow) itemsTableBody.append(tableRow)
} }
} }
@ -138,13 +139,11 @@ async function openSKUModal() {
UIkit.modal(itemsModal).show(); UIkit.modal(itemsModal).show();
} }
async function openLineEditModal(sli_id) { async function openLineEditModal(list_item_uuid) {
let sl_item = await fetchSLItem(sli_id) let sl_item = await fetchSLItem(list_item_uuid)
console.log(sl_item)
document.getElementById('lineName').value = sl_item.item_name document.getElementById('lineName').value = sl_item.item_name
document.getElementById('lineQty').value = sl_item.qty document.getElementById('lineQty').value = sl_item.qty
document.getElementById('lineUOM').value = sl_item.uom.id document.getElementById('lineUOM').value = sl_item.uom.id
console.log(sl_item.links)
if(!sl_item.links.hasOwnProperty('main')){ if(!sl_item.links.hasOwnProperty('main')){
sl_item.links.main = '' sl_item.links.main = ''
@ -161,7 +160,7 @@ async function openLineEditModal(sli_id) {
uom: document.getElementById('lineUOM').value, uom: document.getElementById('lineUOM').value,
links: links 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')).hide();
} }
UIkit.modal(document.getElementById('lineEditModal')).show(); UIkit.modal(document.getElementById('lineEditModal')).show();
@ -272,9 +271,10 @@ async function fetchItems() {
return data.items; 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); 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); const response = await fetch(url);
data = await response.json(); data = await response.json();
return data.list_item; return data.list_item;
@ -284,16 +284,14 @@ async function addCustomItem() {
let customModal = document.getElementById('customModal') let customModal = document.getElementById('customModal')
UIkit.modal(customModal).hide(); UIkit.modal(customModal).hide();
uuid = `${sl_id}${Math.random().toString(36).substring(2, 8)}`
let newItem = { let newItem = {
uuid: uuid, list_uuid: list_uuid,
sl_id: sl_id,
item_type: 'custom', item_type: 'custom',
item_name: document.getElementById('customName').value, item_name: document.getElementById('customName').value,
uom: document.getElementById('customUOM').value, uom: document.getElementById('customUOM').value,
qty: parseFloat(document.getElementById('customQty').value), qty: parseFloat(document.getElementById('customQty').value),
item_id: null, item_uuid: null,
links: {'main': document.getElementById('customLink').value} links: {'main': document.getElementById('customLink').value}
} }
@ -358,14 +356,14 @@ async function deleteLineItem(sli_id) {
await replenishLineTable(shopping_list.sl_items) 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`, { const response = await fetch(`/shopping-lists/api/saveListItem`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
body: JSON.stringify({ body: JSON.stringify({
sli_id: sli_id, list_item_uuid: list_item_uuid,
update: update update: update
}), }),
}); });
@ -387,3 +385,177 @@ async function saveLineItem(sli_id, update) {
await replenishForm(shopping_list) await replenishForm(shopping_list)
await replenishLineTable(shopping_list.sl_items) 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 = `<a><span uk-pagination-previous></span></a>`;
previousElement.classList.add('uk-disabled');
}else {
previousElement.innerHTML = `<a onclick="setRecipesPage(${recipes_pagination_current-1})"><span uk-pagination-previous></span></a>`;
}
paginationElement.append(previousElement)
//first
let firstElement = document.createElement('li')
if(recipes_pagination_current<=1){
firstElement.innerHTML = `<a><strong>1</strong></a>`;
firstElement.classList.add('uk-disabled');
}else {
firstElement.innerHTML = `<a onclick="setRecipesPage(1)">1</a>`;
}
paginationElement.append(firstElement)
// ...
if(recipes_pagination_current-2>1){
let firstDotElement = document.createElement('li')
firstDotElement.classList.add('uk-disabled')
firstDotElement.innerHTML = `<span>…</span>`;
paginationElement.append(firstDotElement)
}
// last
if(recipes_pagination_current-2>0){
let lastElement = document.createElement('li')
lastElement.innerHTML = `<a onclick="setRecipesPage(${recipes_pagination_current-1})">${recipes_pagination_current-1}</a>`
paginationElement.append(lastElement)
}
// current
if(recipes_pagination_current!=1 && recipes_pagination_current != recipes_pagination_end){
let currentElement = document.createElement('li')
currentElement.innerHTML = `<li class="uk-active"><span aria-current="page"><strong>${recipes_pagination_current}</strong></span></li>`
paginationElement.append(currentElement)
}
// next
if(recipes_pagination_current+2<recipes_pagination_end+1){
let nextElement = document.createElement('li')
nextElement.innerHTML = `<a onclick="setRecipesPage(${recipes_pagination_current+1})">${recipes_pagination_current+1}</a>`
paginationElement.append(nextElement)
}
// ...
if(recipes_pagination_current+2<=recipes_pagination_end){
let secondDotElement = document.createElement('li')
secondDotElement.classList.add('uk-disabled')
secondDotElement.innerHTML = `<span>…</span>`;
paginationElement.append(secondDotElement)
}
//end
let endElement = document.createElement('li')
if(recipes_pagination_current>=recipes_pagination_end){
endElement.innerHTML = `<a><strong>${recipes_pagination_end}</strong></a>`;
endElement.classList.add('uk-disabled');
}else {
endElement.innerHTML = `<a onclick="setRecipesPage(${recipes_pagination_end})">${recipes_pagination_end}</a>`;
}
paginationElement.append(endElement)
//next button
let nextElement = document.createElement('li')
if(recipes_pagination_current>=recipes_pagination_end){
nextElement.innerHTML = `<a><span uk-pagination-next></span></a>`;
nextElement.classList.add('uk-disabled');
}else {
nextElement.innerHTML = `<a onclick="setRecipesPage(${recipes_pagination_current+1})"><span uk-pagination-next></span></a>`;
}
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
});
}

View File

@ -3,7 +3,7 @@ document.addEventListener('DOMContentLoaded', async function() {
await replenishForm(shopping_list) await replenishForm(shopping_list)
list_items = shopping_list.sl_items list_items = shopping_list.sl_items
if(shopping_list.type == "calculated"){ if(shopping_list.sub_type == "calculated"){
list_items = await fetchItemsFullCalculated() list_items = await fetchItemsFullCalculated()
} }
@ -37,7 +37,7 @@ async function replenishLineTable(sl_items){
nameCell.innerHTML = namefield nameCell.innerHTML = namefield
let qtyuomCell = document.createElement('td') 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) tableRow.append(checkboxCell, nameCell, qtyuomCell)
listItemsTableBody.append(tableRow) listItemsTableBody.append(tableRow)
@ -46,7 +46,7 @@ async function replenishLineTable(sl_items){
async function fetchShoppingList() { async function fetchShoppingList() {
const url = new URL('/shopping-lists/api/getList', window.location.origin); 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); const response = await fetch(url);
data = await response.json(); data = await response.json();
return data.shopping_list; return data.shopping_list;

View File

@ -13,7 +13,7 @@ document.addEventListener('DOMContentLoaded', async function() {
async function replenishShoppingListCards(lists) { async function replenishShoppingListCards(lists) {
let shopping_list_lists = document.getElementById('shopping_list_lists') let shopping_list_lists = document.getElementById('shopping_list_lists')
shopping_list_lists.innerHTML = "" shopping_list_lists.innerHTML = ""
console.log(lists)
for(let i=0; i < lists.length; i++){ for(let i=0; i < lists.length; i++){
console.log(lists[i]) console.log(lists[i])
let main_div = document.createElement('div') let main_div = document.createElement('div')
@ -25,7 +25,7 @@ async function replenishShoppingListCards(lists) {
let badge_div_dos = document.createElement('div') let badge_div_dos = document.createElement('div')
badge_div_dos.setAttribute('class', 'uk-card-badge uk-label') 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;" 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') 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.setAttribute('class', 'uk-button uk-button-small uk-button-default')
editOp.innerHTML = '<span uk-icon="icon: pencil"></span> Edit' editOp.innerHTML = '<span uk-icon="icon: pencil"></span> Edit'
editOp.style = "margin-right: 10px;" 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') let viewOp = document.createElement('a')
viewOp.setAttribute('class', 'uk-button uk-button-small uk-button-default') viewOp.setAttribute('class', 'uk-button uk-button-small uk-button-default')
viewOp.innerHTML = '<span uk-icon="icon: eye"></span> View' viewOp.innerHTML = '<span uk-icon="icon: eye"></span> View'
viewOp.href = `/shopping-lists/view/${lists[i].id}` viewOp.href = `/shopping-lists/view/${lists[i].list_uuid}`
//viewOp.style = "margin-right: 20px;" //viewOp.style = "margin-right: 20px;"

View File

@ -160,6 +160,7 @@
<li class="uk-nav-header">Line Type</li> <li class="uk-nav-header">Line Type</li>
<li><a href="#customModal" uk-toggle>Custom</a></li> <li><a href="#customModal" uk-toggle>Custom</a></li>
<li><a onclick="openSKUModal()">SKU</a></li> <li><a onclick="openSKUModal()">SKU</a></li>
<li><a onclick="openRecipesModal()">Recipes</a></li>
</ul> </ul>
</div> </div>
</div> </div>
@ -209,6 +210,39 @@
</div> </div>
</div> </div>
<!-- This is the modal --> <!-- This is the modal -->
<!-- Recipes modal lookup -->
<div id="recipesModal" uk-modal>
<div id="recipesModalInner" class="uk-modal-dialog uk-modal-body " uk-overflow-auto>
<h2 class="uk-modal-title">Select Item</h2>
<p>Select a Recipe from the system...</p>
<div id="searchRecipesForm" onkeydown="searchRecipesTable(event)" class="uk-search uk-search-default uk-align-center">
<input id="searchRecipesInput" class="uk-border-pill uk-search-input" type="search" placeholder="" aria-label="">
<span class="uk-search-icon-flip" uk-search-icon></span>
</div>
<nav aria-label="Pagination">
<ul id="recipesPage" class="uk-pagination uk-flex-center" uk-margin>
<li><a href="#"><span uk-pagination-previous></span></a></li>
<li><a href="#">1</a></li>
<li class="uk-disabled"><span></span></li>
<li><a href="#">5</a></li>
<li><a href="#">6</a></li>
<li class="uk-active"><span aria-current="page">7</span></li>
<li><a href="#">8</a></li>
<li><a href="#"><span uk-pagination-next></span></a></li>
</ul>
</nav>
<table class="uk-table uk-table-striped uk-table-hover">
<thead>
<tr>
<th>Name</th>
<th>Operations</th>
</tr>
</thead>
<tbody id="receipesTableBody">
</tbody>
</table>
</div>
</div>
<!-- Items modal lookup --> <!-- Items modal lookup -->
<div id="itemsModal" uk-modal> <div id="itemsModal" uk-modal>
<div id="itemsModalInner" class="uk-modal-dialog uk-modal-body " uk-overflow-auto> <div id="itemsModalInner" class="uk-modal-dialog uk-modal-body " uk-overflow-auto>
@ -230,12 +264,11 @@
<li><a href="#"><span uk-pagination-next></span></a></li> <li><a href="#"><span uk-pagination-next></span></a></li>
</ul> </ul>
</nav> </nav>
<table class="uk-table uk-table-striped uk-table-hover"> <table class="uk-table uk-table-striped">
<thead> <thead>
<tr> <tr>
<th>ID</th>
<th>Barcode</th>
<th>Name</th> <th>Name</th>
<th>Operations</th>
</tr> </tr>
</thead> </thead>
<tbody id="itemsTableBody"> <tbody id="itemsTableBody">
@ -324,5 +357,5 @@
</div> </div>
</body> </body>
<script src="{{ url_for('shopping_list_API.static', filename='js/shoppingListEditHandler.js') }}"></script> <script src="{{ url_for('shopping_list_API.static', filename='js/shoppingListEditHandler.js') }}"></script>
<script>const sl_id = {{id|tojson}}</script> <script>const list_uuid = {{list_uuid|tojson}}</script>
</html> </html>

View File

@ -136,5 +136,5 @@
</div> </div>
</body> </body>
<script src="{{ url_for('shopping_list_API.static', filename='js/shoppingListViewHandler.js') }}"></script> <script src="{{ url_for('shopping_list_API.static', filename='js/shoppingListViewHandler.js') }}"></script>
<script>const sl_id = {{id|tojson}}</script> <script>const list_uuid = {{list_uuid|tojson}}</script>
</html> </html>

View File

@ -306,3 +306,96 @@
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).', 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), 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 *;') 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);')

7
run-server.sh Normal file
View File

@ -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" &