Added the ability to generate shopping lists
dynamically
This commit is contained in:
parent
a22faeb7a8
commit
faafa75422
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -238,6 +238,18 @@ def deleteListItem():
|
||||
return jsonify({"error":False, "message":"item deleted succesfully!"})
|
||||
return jsonify({"error":True, "message":"There was an error with this POST statement"})
|
||||
|
||||
@shopping_list_api.route('/api/deleteList', methods=["POST"])
|
||||
@access_api.login_required
|
||||
def deleteList():
|
||||
if request.method == "POST":
|
||||
shopping_list_uuid = request.get_json()['shopping_list_uuid']
|
||||
site_name = session['selected_site']
|
||||
user_id = session['user_id']
|
||||
shoplist_processess.deleteShoppingList(site_name, {'shopping_list_uuid': shopping_list_uuid}, user_id)
|
||||
return jsonify({"error":False, "message":"List Deleted succesfully!"})
|
||||
return jsonify({"error":True, "message":"There was an error with this POST statement"})
|
||||
|
||||
|
||||
# Added to Database
|
||||
@shopping_list_api.route('/api/saveListItem', methods=["POST"])
|
||||
@access_api.login_required
|
||||
@ -293,3 +305,15 @@ def setListItemState():
|
||||
|
||||
return jsonify({"list_items":items, "error":False, "message":"items fetched succesfully!"})
|
||||
return jsonify({"list_items":items, "error":True, "message":"There was an error with this GET statement"})
|
||||
|
||||
|
||||
@shopping_list_api.route('/api/postGeneratedList', methods=["POST"])
|
||||
@access_api.login_required
|
||||
def postGeneratedList():
|
||||
if request.method == "POST":
|
||||
payload: dict = request.get_json()
|
||||
site_name: str = session['selected_site']
|
||||
user_id: int = session['user_id']
|
||||
shoplist_processess.postNewGeneratedList(site_name, payload, user_id)
|
||||
return jsonify(status=201, message=f"List Generated successfully!")
|
||||
return jsonify(status=405, message=f"{request.method} is not an accepted method on this endpoint!")
|
||||
@ -1,7 +1,6 @@
|
||||
# 3rd Party imports
|
||||
import psycopg2
|
||||
|
||||
|
||||
# applications imports
|
||||
import config
|
||||
from application import postsqldb
|
||||
@ -150,7 +149,6 @@ def getRecipeItemsByUUID(site, payload, convert=True, conn=None):
|
||||
except Exception as error:
|
||||
raise postsqldb.DatabaseError(error, payload, sql)
|
||||
|
||||
|
||||
def getItemsWithQOH(site, payload, convert=True, conn=None):
|
||||
recordset = []
|
||||
count = 0
|
||||
@ -263,7 +261,6 @@ def getListsModal(site, payload, convert=True, conn=None):
|
||||
except Exception as error:
|
||||
raise postsqldb.DatabaseError(error, payload, sql)
|
||||
|
||||
|
||||
def getItemsModal(site, payload, convert=True, conn=None):
|
||||
recordsets = []
|
||||
count = 0
|
||||
@ -298,6 +295,63 @@ def getItemsModal(site, payload, convert=True, conn=None):
|
||||
except Exception as error:
|
||||
raise postsqldb.DatabaseError(error, payload, sql)
|
||||
|
||||
def getItemByUUID(site, payload:dict, convert=True, conn=None):
|
||||
""" payload: dict = {'item_uuid'}"""
|
||||
record = ()
|
||||
self_conn = False
|
||||
with open('application/shoppinglists/sql/getItemByUUID.sql', 'r') as file:
|
||||
sql = file.read().replace("%%site_name%%", site)
|
||||
try:
|
||||
|
||||
if not conn:
|
||||
database_config = config.config()
|
||||
conn = psycopg2.connect(**database_config)
|
||||
conn.autocommit = True
|
||||
self_conn = True
|
||||
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(sql, payload)
|
||||
rows = cur.fetchone()
|
||||
if rows and convert:
|
||||
record = postsqldb.tupleDictionaryFactory(cur.description, rows)
|
||||
elif rows and not convert:
|
||||
record = rows
|
||||
|
||||
if self_conn:
|
||||
conn.close()
|
||||
|
||||
return record
|
||||
except Exception as error:
|
||||
raise postsqldb.DatabaseError(error, payload, sql)
|
||||
|
||||
def deleteShoppingListsTuple(site_name, payload, convert=True, conn=None):
|
||||
deleted = ()
|
||||
self_conn = False
|
||||
sql = f"WITH deleted_rows AS (DELETE FROM {site_name}_shopping_lists WHERE {site_name}_shopping_lists.list_uuid IN ({','.join(['%s'] * len(payload))}) RETURNING *) SELECT * FROM deleted_rows;"
|
||||
try:
|
||||
|
||||
if not conn:
|
||||
database_config = config.config()
|
||||
conn = psycopg2.connect(**database_config)
|
||||
conn.autocommit = True
|
||||
self_conn = True
|
||||
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(sql, payload)
|
||||
rows = cur.fetchall()
|
||||
if rows and convert:
|
||||
deleted = [postsqldb.tupleDictionaryFactory(cur.description, r) for r in rows]
|
||||
elif rows and not convert:
|
||||
deleted = rows
|
||||
|
||||
if self_conn:
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
return deleted
|
||||
except Exception as error:
|
||||
raise postsqldb.DatabaseError(error, payload, sql)
|
||||
|
||||
def deleteShoppingListItemsTuple(site_name, payload, convert=True, conn=None):
|
||||
deleted = ()
|
||||
self_conn = False
|
||||
|
||||
@ -37,3 +37,151 @@ def addRecipeItemsToList(site:str, data:dict, user_id: int, conn=None):
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def postNewGeneratedList(site: str, data: dict, user_id: int, conn=None):
|
||||
"""data={'list_type', 'list_name', 'list_description', 'custom_items', 'uncalculated_items', 'calculated_items', 'recipes', 'full_system_calculated', 'shopping_lists'}"""
|
||||
list_type: str = data['list_type']
|
||||
list_name: str = data['list_name']
|
||||
list_description: str = data['list_description']
|
||||
custom_items: list = data['custom_items']
|
||||
uncalculated_items: list = data['uncalculated_items']
|
||||
calculated_items: list = data['calculated_items']
|
||||
recipes: list = data['recipes']
|
||||
full_system_calculated: list = data['full_system_calculated']
|
||||
shopping_lists: list = data['shopping_lists']
|
||||
|
||||
|
||||
self_conn=False
|
||||
|
||||
if not conn:
|
||||
database_config = config.config()
|
||||
conn = psycopg2.connect(**database_config)
|
||||
conn.autocommit = False
|
||||
self_conn = True
|
||||
|
||||
shopping_list = database_payloads.ShoppingListPayload(
|
||||
name=list_name,
|
||||
description=list_description,
|
||||
author=int(user_id),
|
||||
sub_type="plain",
|
||||
list_type=list_type
|
||||
)
|
||||
shopping_list = shoplist_database.insertShoppingListsTuple(site, shopping_list.payload(), conn=conn)
|
||||
|
||||
items_to_add_to_system = []
|
||||
# start by checcking if i should iterate full sku calc
|
||||
if full_system_calculated:
|
||||
safety_stock_items = shoplist_database.getItemsSafetyStock(site, conn=conn)
|
||||
for item in safety_stock_items:
|
||||
qty = float(item['item_info']['safety_stock']-float(item['total_sum']))
|
||||
temp_item = database_payloads.ShoppingListItemPayload(
|
||||
list_uuid=shopping_list['list_uuid'],
|
||||
item_type='calculated sku',
|
||||
item_name=item['item_name'],
|
||||
uom=item['item_info']['uom'],
|
||||
qty=qty,
|
||||
item_uuid=item['item_uuid'],
|
||||
links=item['links']
|
||||
)
|
||||
items_to_add_to_system.append(temp_item)
|
||||
|
||||
if calculated_items and not full_system_calculated:
|
||||
for item_uuid in calculated_items:
|
||||
item = shoplist_database.getItemByUUID(site, {'item_uuid': item_uuid}, conn=conn)
|
||||
qty = float(item['item_info']['safety_stock']-float(item['total_sum']))
|
||||
temp_item = database_payloads.ShoppingListItemPayload(
|
||||
list_uuid=shopping_list['list_uuid'],
|
||||
item_type='calculated sku',
|
||||
item_name=item['item_name'],
|
||||
uom=item['item_info']['uom'],
|
||||
qty=qty,
|
||||
item_uuid=item['item_uuid'],
|
||||
links=item['links']
|
||||
)
|
||||
items_to_add_to_system.append(temp_item)
|
||||
|
||||
|
||||
if custom_items:
|
||||
for item in custom_items:
|
||||
temp_item = database_payloads.ShoppingListItemPayload(
|
||||
list_uuid=shopping_list['list_uuid'],
|
||||
item_type='custom',
|
||||
item_name=item['item_name'],
|
||||
uom=item['uom'],
|
||||
qty=float(item['qty']),
|
||||
item_uuid=None,
|
||||
links={'main': item['link']}
|
||||
)
|
||||
items_to_add_to_system.append(temp_item)
|
||||
|
||||
if uncalculated_items:
|
||||
for item in uncalculated_items:
|
||||
temp_item = database_payloads.ShoppingListItemPayload(
|
||||
list_uuid=shopping_list['list_uuid'],
|
||||
item_type='uncalculated sku',
|
||||
item_name=item['item_name'],
|
||||
uom=item['uom'],
|
||||
qty=float(item['qty']),
|
||||
item_uuid=None,
|
||||
links={'main': item['link']}
|
||||
)
|
||||
items_to_add_to_system.append(temp_item)
|
||||
|
||||
|
||||
if recipes:
|
||||
for recipe_uuid in recipes:
|
||||
recipe_items = shoplist_database.getRecipeItemsByUUID(site, (recipe_uuid,), conn=conn)
|
||||
for item in recipe_items:
|
||||
temp_item = database_payloads.ShoppingListItemPayload(
|
||||
list_uuid=shopping_list['list_uuid'],
|
||||
item_type='recipe',
|
||||
item_name=item['item_name'],
|
||||
uom=item['uom'],
|
||||
qty=float(item['qty']),
|
||||
item_uuid=item['item_uuid'],
|
||||
links=item['links']
|
||||
)
|
||||
items_to_add_to_system.append(temp_item)
|
||||
|
||||
if shopping_lists:
|
||||
for shopping_list_uuid in shopping_lists:
|
||||
shopping_list_items = shoplist_database.getShoppingList(site, (shopping_list_uuid,), conn=conn)['sl_items']
|
||||
for item in shopping_list_items:
|
||||
temp_item = database_payloads.ShoppingListItemPayload(
|
||||
list_uuid=shopping_list['list_uuid'],
|
||||
item_type=item['item_type'],
|
||||
item_name=item['item_name'],
|
||||
uom=item['uom']['id'],
|
||||
qty=float(item['qty']),
|
||||
item_uuid=item['item_uuid'],
|
||||
links=item['links']
|
||||
)
|
||||
items_to_add_to_system.append(temp_item)
|
||||
|
||||
|
||||
if items_to_add_to_system:
|
||||
for item in items_to_add_to_system:
|
||||
shoplist_database.insertShoppingListItemsTuple(site, item.payload(), conn=conn)
|
||||
|
||||
if self_conn:
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def deleteShoppingList(site: str, data: dict, user_id: int, conn=None):
|
||||
shopping_list_uuid = data['shopping_list_uuid']
|
||||
self_conn=False
|
||||
|
||||
if not conn:
|
||||
database_config = config.config()
|
||||
conn = psycopg2.connect(**database_config)
|
||||
conn.autocommit = False
|
||||
self_conn = True
|
||||
|
||||
shopping_list_items = shoplist_database.getShoppingList(site, (shopping_list_uuid, ), conn=conn)['sl_items']
|
||||
shopping_list_items = [item['list_item_uuid'] for item in shopping_list_items]
|
||||
|
||||
shoplist_database.deleteShoppingListsTuple(site, (shopping_list_uuid,), conn=conn)
|
||||
shoplist_database.deleteShoppingListItemsTuple(site, shopping_list_items, conn=conn)
|
||||
|
||||
if self_conn:
|
||||
conn.commit()
|
||||
conn.close()
|
||||
15
application/shoppinglists/sql/getItemByUUID.sql
Normal file
15
application/shoppinglists/sql/getItemByUUID.sql
Normal file
@ -0,0 +1,15 @@
|
||||
WITH sum_cte AS (
|
||||
SELECT mi.id, SUM(mil.quantity_on_hand) AS total_sum
|
||||
FROM %%site_name%%_item_locations mil
|
||||
JOIN %%site_name%%_items mi ON mil.part_id = mi.id
|
||||
GROUP BY mi.id
|
||||
)
|
||||
|
||||
SELECT items.*,
|
||||
COALESCE(row_to_json(item_info.*), '{}') AS item_info,
|
||||
COALESCE(sum_cte.total_sum, 0) AS total_sum
|
||||
FROM %%site_name%%_items items
|
||||
LEFT JOIN %%site_name%%_item_info item_info ON items.item_info_id = item_info.id
|
||||
LEFT JOIN units ON units.id = item_info.uom
|
||||
LEFT JOIN sum_cte ON items.id = sum_cte.id
|
||||
WHERE items.item_uuid = %(item_uuid)s
|
||||
@ -1075,3 +1075,27 @@ async function generateListsTable() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Generate Functions
|
||||
async function postGenerateList() {
|
||||
let data = {
|
||||
list_type: String(document.getElementById('generated_list_type').value),
|
||||
list_name: String(document.getElementById('generated_list_name').value),
|
||||
list_description: String(document.getElementById('generated_list_description').value),
|
||||
custom_items: Object.values(custom_items),
|
||||
uncalculated_items: Object.values(uncalculated_items),
|
||||
calculated_items: Object.keys(calculated_items),
|
||||
recipes: Object.keys(recipes),
|
||||
full_system_calculated: full_sku_enabled,
|
||||
shopping_lists: Object.keys(shopping_lists)
|
||||
}
|
||||
|
||||
const response = await fetch(`/shopping-lists/api/postGeneratedList`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
@ -54,11 +54,11 @@ async function replenishShoppingListCards(lists) {
|
||||
footer_div.setAttribute('class', 'uk-card-footer')
|
||||
footer_div.style = 'height: 40px; border: none;'
|
||||
|
||||
let editOp = document.createElement('a')
|
||||
editOp.setAttribute('class', 'uk-button uk-button-small uk-button-default')
|
||||
editOp.innerHTML = '<span uk-icon="icon: pencil"></span> Edit'
|
||||
editOp.style = "margin-right: 10px;"
|
||||
editOp.href = `/shopping-lists/edit/${lists[i].list_uuid}`
|
||||
//let editOp = document.createElement('a')
|
||||
//editOp.setAttribute('class', 'uk-button uk-button-small uk-button-default')
|
||||
//editOp.innerHTML = '<span uk-icon="icon: pencil"></span> Edit'
|
||||
//editOp.style = "margin-right: 10px;"
|
||||
//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')
|
||||
@ -66,8 +66,14 @@ async function replenishShoppingListCards(lists) {
|
||||
viewOp.href = `/shopping-lists/view/${lists[i].list_uuid}`
|
||||
//viewOp.style = "margin-right: 20px;"
|
||||
|
||||
let deleteOp = document.createElement('a')
|
||||
deleteOp.setAttribute('class', 'uk-button uk-button-small uk-button-default')
|
||||
deleteOp.innerHTML = '<span uk-icon="icon: eye"></span> Delete'
|
||||
deleteOp.onclick = async function(params) { await deleteList(lists[i].list_uuid)}
|
||||
//viewOp.style = "margin-right: 20px;"
|
||||
|
||||
footer_div.append(editOp, viewOp)
|
||||
|
||||
footer_div.append(viewOp, deleteOp)
|
||||
|
||||
main_div.append(card_header_div, body_div, footer_div)
|
||||
|
||||
@ -123,6 +129,35 @@ async function addList() {
|
||||
});
|
||||
}
|
||||
|
||||
async function deleteList(shopping_list_uuid) {
|
||||
const response = await fetch(`/shopping-lists/api/deleteList`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
shopping_list_uuid: shopping_list_uuid
|
||||
}),
|
||||
});
|
||||
data = await response.json();
|
||||
transaction_status = "success"
|
||||
if (data.error){
|
||||
transaction_status = "danger"
|
||||
}
|
||||
|
||||
UIkit.notification({
|
||||
message: data.message,
|
||||
status: transaction_status,
|
||||
pos: 'top-right',
|
||||
timeout: 5000
|
||||
});
|
||||
|
||||
let lists = await getShoppingLists()
|
||||
await replenishShoppingListCards(lists)
|
||||
await updatePaginationElement()
|
||||
|
||||
}
|
||||
|
||||
async function changeSite(site){
|
||||
const response = await fetch(`/changeSite`, {
|
||||
method: 'POST',
|
||||
|
||||
@ -138,7 +138,7 @@
|
||||
</div>
|
||||
<!-- basic info section -->
|
||||
<div class="uk-width-1-1">
|
||||
<h1 class="uk-heading-xsmall uk-heading-divider">Shopping List Type</h1>
|
||||
<h1 class="uk-heading-xsmall uk-heading-divider">Shopping List Info</h1>
|
||||
</div>
|
||||
<div class="uk-width-1-1">
|
||||
<p class="uk-text-small">Fill out the basic info asked for here, the description could be helpful to remind yourself and others what the list was generated for.
|
||||
@ -152,11 +152,14 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="uk-width-1-1">
|
||||
<label class="uk-form-label" for="generated_list_name">Shopping List Description</label>
|
||||
<label class="uk-form-label" for="generated_list_description">Shopping List Description</label>
|
||||
<div class="uk-form-controls">
|
||||
<textarea id="generated_list_name" class="uk-textarea" rows="5" placeholder="Enter list description here..." aria-label="Textarea"></textarea>
|
||||
<textarea id="generated_list_description" class="uk-textarea" rows="5" placeholder="Enter list description here..." aria-label="Textarea"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="uk-width-1-1">
|
||||
<button onclick="postGenerateList()" class="uk-button uk-button-primary uk-align-right">Generate List</button>
|
||||
</div>
|
||||
<!-- Part section -->
|
||||
<div class="uk-width-1-1">
|
||||
<h1 class="uk-heading-xsmall uk-heading-divider">Shopping List Operators</h1>
|
||||
|
||||
@ -112,7 +112,7 @@
|
||||
<div uk-grid>
|
||||
<div class="uk-width-1-2@m">
|
||||
<ul class="uk-iconnav uk-flex-center uk-flex-left@m">
|
||||
<li><a onclick="openAddListModal()" uk-icon="icon: plus">Add List</a></li>
|
||||
<!--li><a onclick="openAddListModal()" uk-icon="icon: plus">Add List</a></li-->
|
||||
<li><a href="/shopping-lists/generate" uk-icon="icon: plus">Generate List</a></li>
|
||||
<li><a href="#" uk-icon="icon: cloud-download">download</a></li>
|
||||
</ul>
|
||||
|
||||
@ -563,3 +563,18 @@
|
||||
2025-08-17 19:22:32.824380 --- ERROR --- DatabaseError(message='duplicate key value violates unique constraint "main_item_info_barcode_key"DETAIL: Key (barcode)=(%%) already exists.',
|
||||
payload=('%%', '', 1.0, 1, 0.0, 0.0, 0.0, False, '{}'),
|
||||
sql='INSERT INTO main_item_info(barcode, packaging, uom_quantity, uom, cost, safety_stock, lead_time_days, ai_pick, prefixes) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING *;')
|
||||
2025-08-19 14:46:59.332054 --- ERROR --- DatabaseError(message='duplicate key value violates unique constraint "main_barcodes_pkey"DETAIL: Key (barcode)=(%181945000062%) already exists.',
|
||||
payload=('%181945000062%', 'faecba1e-8817-4e19-9ade-d43cb4602aae', '1', '1', ''),
|
||||
sql='INSERT INTO main_barcodes (barcode, item_uuid, in_exchange, out_exchange, descriptor) VALUES (%s, %s, %s, %s, %s) RETURNING *;')
|
||||
2025-08-19 15:39:25.102811 --- ERROR --- DatabaseError(message='dict is not a sequence',
|
||||
payload={'item_uuid': '392f05b5-4ccd-41da-875d-0e593f51b610'},
|
||||
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 * FROM main_items 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 items.item_uuid = %{item_uuid}s')
|
||||
2025-08-19 15:43:08.370309 --- ERROR --- DatabaseError(message='invalid reference to FROM-clause entry for table "main_items"LINE 10: LEFT JOIN main_item_info ON main_items.item_info_id = main_i... ^HINT: Perhaps you meant to reference the table alias "items".',
|
||||
payload={'item_uuid': '392f05b5-4ccd-41da-875d-0e593f51b610'},
|
||||
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 * FROM main_items 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 items.item_uuid = %(item_uuid)s')
|
||||
2025-08-19 15:45:09.890974 --- ERROR --- DatabaseError(message='syntax error at or near "FROM"LINE 11: FROM main_items items ^',
|
||||
payload={'item_uuid': '392f05b5-4ccd-41da-875d-0e593f51b610'},
|
||||
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(main_item_info.*), '{}') AS item_info, COALESCE(sum_cte.total_sum, 0) AS total_sum,FROM main_items itemsLEFT JOIN main_item_info item_info ON items.item_info_id = item_info.idLEFT JOIN units ON units.id = item_info.uomLEFT JOIN sum_cte ON items.id = sum_cte.idWHERE items.item_uuid = %(item_uuid)s')
|
||||
2025-08-19 15:45:32.184317 --- ERROR --- DatabaseError(message='syntax error at or near "FROM"LINE 11: FROM main_items items ^',
|
||||
payload={'item_uuid': '392f05b5-4ccd-41da-875d-0e593f51b610'},
|
||||
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 items.*, COALESCE(row_to_json(item_info.*), '{}') AS item_info, COALESCE(sum_cte.total_sum, 0) AS total_sum,FROM main_items itemsLEFT JOIN main_item_info item_info ON items.item_info_id = item_info.idLEFT JOIN units ON units.id = item_info.uomLEFT JOIN sum_cte ON items.id = sum_cte.idWHERE items.item_uuid = %(item_uuid)s')
|
||||
BIN
static/pictures/favicon.ico
Normal file
BIN
static/pictures/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 MiB |
Loading…
x
Reference in New Issue
Block a user