Working on new Generation of shopping lists

This commit is contained in:
Jadowyne Ulve 2025-08-18 17:18:43 -05:00
parent 7003836890
commit a22faeb7a8
46 changed files with 1987 additions and 23 deletions

3
.gitignore vendored
View File

@ -7,4 +7,5 @@ static/css/uikit.min.css
instance/application.cfg.py
test.py
.VScodeCounter
celerybeat-schedule
celerybeat-schedule
instance/application.cfg.py

View File

@ -3,6 +3,7 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title id="title">Login</title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<!-- Material Icons -->
@ -15,19 +16,20 @@
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Sharp" rel="stylesheet" />
<link rel="stylesheet" href="{{ url_for('static', filename='css/uikit.min.css') }}"/>
<link id="dark-mode" rel="stylesheet" href="{{ url_for('access_api.static', filename='css/logins-dark.css') }}"/>
<!--link id="dark-mode" rel="stylesheet" href="{{ url_for('access_api.static', filename='css/logins-dark.css') }}"/-->
<script src="{{ url_for('static', filename='js/uikit.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/uikit-icons.min.js') }}"></script>
</head>
<body class="uk-light">
<body>
<div class="uk-container">
<div class="uk-section">
<div class="uk-flex-center" uk-grid>
<div class="uk-card uk-card-default uk-card-body uk-width-1-2@m uk-flex-center">
<img class="uk-align-center" src="{{ url_for('static', filename='pictures/logo.jpg') }}" style="width: 200px; border-radius: 50%;">
<img class="uk-align-center" src="{{ url_for('static', filename='pictures/logo.png') }}" style="width: 200px;">
<h1 class="uk-text-center">PantryTrack</h1>
<ul uk-tab>
<li><a href="#">Login</a></li>
{% if instance_settings['signup_enabled'] %}

View File

@ -3,6 +3,7 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title id="title"></title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<!-- Material Icons -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
<!-- Material Symbols - Outlined Set -->

View File

@ -3,6 +3,7 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title id="title"></title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<!-- Material Icons -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
<!-- Material Symbols - Outlined Set -->

View File

@ -3,6 +3,7 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title>My Pantry - Setup</title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/css/materialize.min.css" />
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/js/materialize.min.js"></script>

View File

@ -3,6 +3,7 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title id="title"></title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<!-- Material Icons -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
<!-- Material Symbols - Outlined Set -->

View File

@ -3,6 +3,7 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title id="title">User</title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<!-- Material Icons -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
<!-- Material Symbols - Outlined Set -->

View File

@ -3,7 +3,7 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title id="title"></title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<!-- Material Icons -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />

View File

@ -3,7 +3,7 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title id="title"></title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<!-- Material Icons -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />

View File

@ -3,6 +3,7 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title id="title"></title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<!-- Material Icons -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
<!-- Material Symbols - Outlined Set -->

View File

@ -3,6 +3,7 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title id="title"></title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<!-- Material Icons -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
<!-- Material Symbols - Outlined Set -->

View File

@ -3,6 +3,7 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title id="title"></title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<!-- Material Icons -->

View File

@ -3,6 +3,7 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title id="title"></title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<!-- Material Icons -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
<!-- Material Symbols - Outlined Set -->

View File

@ -3,6 +3,7 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title id="title"></title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<!-- Material Icons -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
<!-- Material Symbols - Outlined Set -->

View File

@ -3,6 +3,7 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title id="title"></title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<!-- Material Icons -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
<!-- Material Symbols - Outlined Set -->

View File

@ -123,7 +123,7 @@ def addSKULine():
if request.method == "POST":
item_id = int(request.get_json()['item_id'])
receipt_id = int(request.get_json()['receipt_id'])
print(item_id, receipt_id)
site_name = session['selected_site']
item = receipts_database.getItemAllByID(site_name, (item_id, ))
data = {

View File

@ -1,4 +1,4 @@
WITH passed_id AS (SELECT %s AS passed_id),
WITH passed_id AS (SELECT id AS passed_id, item_uuid as passed_uuid FROM %%site_name%%_items WHERE id=%s),
logistics_id AS (SELECT logistics_info_id FROM %%site_name%%_items WHERE id=(SELECT passed_id FROM passed_id)),
info_id AS (SELECT item_info_id FROM %%site_name%%_items WHERE id=(SELECT passed_id FROM passed_id)),
cte_conversions AS (
@ -33,12 +33,11 @@ WITH passed_id AS (SELECT %s AS passed_id),
cte_shopping_lists AS (
SELECT
%%site_name%%_shopping_lists.*,
%%site_name%%_shopping_list_items.uuid,
%%site_name%%_shopping_list_items.item_type,
%%site_name%%_shopping_list_items.qty
FROM %%site_name%%_shopping_lists
JOIN %%site_name%%_shopping_list_items ON %%site_name%%_shopping_lists.id = %%site_name%%_shopping_list_items.sl_id
WHERE %%site_name%%_shopping_list_items.item_id = (SELECT passed_id FROM passed_id)
JOIN %%site_name%%_shopping_list_items ON %%site_name%%_shopping_lists.list_uuid = %%site_name%%_shopping_list_items.list_uuid
WHERE %%site_name%%_shopping_list_items.item_uuid = (SELECT passed_uuid FROM passed_id)
),
cte_itemlinks AS (
SELECT * FROM %%site_name%%_itemlinks WHERE link=(SELECT passed_id FROM passed_id)

View File

@ -3,6 +3,7 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title id="title"></title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<!-- Material Icons -->

View File

@ -3,6 +3,7 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title id="title"></title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<!-- Material Icons -->

View File

@ -309,6 +309,8 @@ def selectItemTupleByUUID(site, payload, convert=True, conn=None):
def selectConversionTuple(site, payload, convert=True, conn=None):
"""payload=(item_id, uom_id)"""
selected = ()
if convert:
selected = {}
self_conn = False
sql = f"SELECT conversions.conv_factor FROM {site}_conversions conversions WHERE item_id = %s AND uom_id = %s;"
try:

View File

@ -112,7 +112,9 @@ def process_recipe_receipt(site_name, user_id, data:dict, conn=None):
rp_item_uom = item['uom']['id']
item_stuff = database_recipes.selectItemTupleByUUID(site_name, (item['item_uuid'],), conn=conn)
conv_factor = database_recipes.selectConversionTuple(site_name, (item_stuff['item_id'], rp_item_uom))
qty = float(item['qty']) / float(conv_factor['conv_factor'])
print(conv_factor)
conversion = conv_factor.get('conv_factor', 1)
qty = float(item['qty']) / float(conversion)
payload = {
'item_id': item_stuff['item_id'],
'logistics_info_id': item_stuff['logistics_info_id'],
@ -164,7 +166,7 @@ def postNewSkuFromRecipe(site_name: str, user_id: int, data: dict, conn=None):
)
# create item info
item_info = database_payloads.ItemInfoPayload(barcode=None)
item_info = database_payloads.ItemInfoPayload(barcode=None, uom=data['uom_id'], cost=data['cost'])
# create Food Info
food_info = database_payloads.FoodInfoPayload()
@ -184,6 +186,7 @@ def postNewSkuFromRecipe(site_name: str, user_id: int, data: dict, conn=None):
links = {'main': data['main_link']}
search_string = f"&&{name}&&"
print(item_info)
item = database_payloads.ItemsPayload(
barcode=None,

View File

@ -369,14 +369,14 @@ async function deleteInstruction(index){
async function openNewSKUModal() {
let itemsModal = document.getElementById('addNewSKUItem')
let addNewSKUItem = document.getElementById('addNewSKUItem')
document.getElementById('newSKUName').value = ""
document.getElementById('newSKUSubtype').value = "FOOD"
document.getElementById('newSKUQty').value = 1
document.getElementById('newSKUUOM').value = "1"
document.getElementById('newWeblink').value = ""
document.getElementById('newSKUCost').value = 0.00
UIkit.modal(itemsModal).show();
UIkit.modal(addNewSKUItem).show();
}

View File

@ -3,6 +3,7 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title id="title">Recipes</title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<!-- Material Icons -->

View File

@ -3,6 +3,7 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title id="title">Recipes</title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<!-- Material Icons -->

View File

@ -3,6 +3,7 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title id="title">Recipes</title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<!-- Material Icons -->

View File

@ -29,6 +29,13 @@ def shopping_list(mode, list_uuid):
return render_template("edit.html", list_uuid=list_uuid, current_site=session['selected_site'], sites=sites)
return redirect("/")
@shopping_list_api.route("/generate")
@access_api.login_required
def generateList():
sites = [site[1] for site in postsqldb.get_sites(session['user']['sites'])]
units = postsqldb.get_units_of_measure()
return render_template("generate.html", current_site=session['selected_site'], sites=sites, units=units)
# API CALLS
# Added to Database
@shopping_list_api.route('/api/addList', methods=["POST"])
@ -107,7 +114,7 @@ def getShoppingListItem():
return jsonify({'list_item': list_item, 'error': True, 'message': 'List Items queried unsuccessfully!'})
# Added to database
@shopping_list_api.route('/api/getItems', methods=["GET"])
@shopping_list_api.route('/api/getItemstwo', methods=["GET"])
@access_api.login_required
def getItems():
recordset = []
@ -141,6 +148,40 @@ def getRecipesModal():
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!")
@shopping_list_api.route('/api/getListsModal', methods=["GET"])
@access_api.login_required
def getListsModal():
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.getListsModal(site_name, payload)
return jsonify(status=201, lists=recordsets, end=math.ceil(count/limit), message=f"Recipes fetched successfully!")
return jsonify(status=405, lists=recordsets, end=math.ceil(count/limit), message=f"{request.method} is not an accepted method on this endpoint!")
@shopping_list_api.route("/api/getItems", methods=["GET"])
@access_api.login_required
def getItemsModal():
items = []
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)
items, count = shoplist_database.getItemsModal(site_name, payload)
return jsonify(status=201, items=items, end=math.ceil(count/limit), message=f"Items fetched successfully!")
return jsonify(status=405, items=items, 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"])
@ -231,8 +272,24 @@ def getSKUItemsFull():
'qty': float(float(item['item_info']['safety_stock']) - float(item['total_sum'])),
'item_id': item['id'],
'links': item['links'],
'uom_fullname': item['uom_fullname']
'uom_fullname': item['uom_fullname'],
'list_item_state': False
}
items.append(new_item)
return jsonify({"list_items":items, "error":False, "message":"items fetched succesfully!"})
return jsonify({"list_items":items, "error":True, "message":"There was an error with this GET statement"})
# Added to Database
@shopping_list_api.route('/api/setListItemState', methods=["POST"])
@access_api.login_required
def setListItemState():
items = []
count = {'count': 0}
if request.method == "POST":
site_name = session['selected_site']
print(request.get_json())
shoplist_database.updateShoppingListItemsTuple(site_name, {'uuid': request.get_json()['list_item_uuid'], 'update': {'list_item_state': request.get_json()['list_item_state']}})
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"})

View File

@ -227,6 +227,77 @@ def getRecipesModal(site, payload, convert=True, conn=None):
except Exception as error:
raise postsqldb.DatabaseError(error, payload, sql)
def getListsModal(site, payload, convert=True, conn=None):
recordsets = []
count = 0
self_conn = False
sql = f"SELECT lists.list_uuid, lists.name FROM {site}_shopping_lists lists WHERE lists.name LIKE '%%' || %s || '%%' LIMIT %s OFFSET %s;"
sql_count = f"SELECT COUNT(*) FROM {site}_shopping_lists lists WHERE lists.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 getItemsModal(site, payload, convert=True, conn=None):
recordsets = []
count = 0
self_conn = False
with open(f"application/shoppinglists/sql/getItemsForModal.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:
recordsets = [postsqldb.tupleDictionaryFactory(cur.description, row) for row in rows]
if rows and not convert:
recordsets = rows
cur.execute(f"SELECT COUNT(*) FROM {site}_items WHERE search_string LIKE '%%' || %s || '%%';", (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

View File

@ -0,0 +1,11 @@
SELECT items.item_uuid as item_uuid,
items.item_name as item_name,
units.fullname AS fullname,
units.id AS unit_id,
items.links AS links
FROM %%site_name%%_items items
LEFT JOIN %%site_name%%_item_info item_info ON item_info.id = items.item_info_id
LEFT JOIN units ON item_info.uom = units.id
WHERE items.search_string LIKE '%%' || %s || '%%'
ORDER BY items.item_name
LIMIT %s OFFSET %s;

File diff suppressed because it is too large Load Diff

View File

@ -21,13 +21,17 @@ async function replenishForm(shopping_list){
async function replenishLineTable(sl_items){
let listItemsTableBody = document.getElementById('listItemsTableBody')
listItemsTableBody.innerHTML = ""
console.log(sl_items)
for(let i = 0; i < sl_items.length; i++){
let tableRow = document.createElement('tr')
let checkboxCell = document.createElement('td')
checkboxCell.innerHTML = `<label><input class="uk-checkbox" type="checkbox"></label>`
checkboxCell.innerHTML = `<label><input class="uk-checkbox" type="checkbox" ${sl_items[i].list_item_state ? 'checked' : ''}></label>`
checkboxCell.onclick = async function (event) {
await updateListItemState(sl_items[i].list_item_uuid, event.target.checked)
}
namefield = sl_items[i].item_name
if(sl_items[i].links.hasOwnProperty('main')){
namefield = `<a href=${sl_items[i].links.main} target='_blank'>${sl_items[i].item_name}</a>`
@ -37,8 +41,9 @@ 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}`
checkboxCell.checked = sl_items[i].list_item_state
tableRow.append(checkboxCell, nameCell, qtyuomCell)
listItemsTableBody.append(tableRow)
}
@ -65,4 +70,18 @@ async function fetchItemsFullCalculated() {
const response = await fetch(url);
data = await response.json();
return data.list_items;
}
async function updateListItemState(list_item_uuid, state){
console.log(list_item_uuid, state)
const response = await fetch(`/shopping-lists/api/setListItemState`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
list_item_uuid: list_item_uuid,
list_item_state: state
}),
});
}

View File

@ -0,0 +1,10 @@
{
"name": "MRP - %DATE%",
"description": "This is a shopping list generated using Full Calculated SKUs Operator Only! Generated on %DATE%",
"custom_items": [],
"uncalculated_items": [],
"calculated_items": [],
"recipes": [],
"planner": [],
"full_sku_enabled": true
}

View File

@ -3,7 +3,8 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title id="title"></title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<!-- Material Icons -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />

View File

@ -0,0 +1,660 @@
<!DOCTYPE html>
<html lang="en" dir="ltr" id="main_html">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title id="title"></title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<!-- Material Icons -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
<!-- Material Symbols - Outlined Set -->
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" rel="stylesheet" />
<!-- Material Symbols - Rounded Set -->
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded" rel="stylesheet" />
<!-- Material Symbols - Sharp Set -->
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Sharp" rel="stylesheet" />
<link rel="stylesheet" href="{{ url_for('static', filename='css/uikit.min.css') }}"/>
{% if session['user']['flags']['darkmode'] %}
<link id="dark-mode" rel="stylesheet" href="{{ url_for('static', filename='css/dark-mode.css') }}"/>
{% endif %}
<script src="{{ url_for('static', filename='js/uikit.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/uikit-icons.min.js') }}"></script>
</head>
<style>
:root {
--primary-color: {{ session['user']['flags']['styles']['primary_color']}};
}
</style>
{% if session['user']['flags']['darkmode'] %}
<body class="uk-light">
{% else %}
<body>
{% endif %}
<nav class="uk-navbar-container">
<div class="uk-container uk-container-expand">
<div class="uk-navbar uk-navbar-primary">
<!-- Application Navigation-->
<div class="uk-navbar-left">
<ul class="uk-navbar-nav">
<li>
<a href>Apps</a>
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
<ul class="uk-nav uk-navbar-dropdown-nav">
<li><a href="/planner">Planner</a></li>
<li><a href="/recipes">Recipes</a></li>
<li><a href="/shopping-lists">Shopping Lists</a></li>
<li class="uk-nav-header">Logistics</li>
<li><a href="/items">Items</a></li>
<li><a href="/items/transaction">Transaction</a></li>
<li><a href="/receipts">Receipts</a></li>
<li class="uk-nav-header">Points of Ease</li>
<li><a href="/poe/scanner">Transaction Scanner</a></li>
<li><a href="/poe/receipts">Receipts Scanner</a></li>
</ul>
</div>
</li>
</ul>
</div>
<!-- Breadcrumbs Navigation -->
<div class="uk-navbar-center uk-visible@m">
<ul class="uk-breadcrumb uk-margin-remove">
<li class="uk-disabled" style="cursor: pointer;"><span><strong>{{current_site}}</strong></span>
<div uk-dropdown="mode: hover">
<ul class="uk-nav uk-dropdown-nav">
<li class="uk-nav-header">Select Site</li>
<li class="uk-nav-divider"></li>
{% for site in sites %}
{% if site == current_site %}
<li><a class="uk-disabled" href="#">{{site}}</a></li>
{% else %}
<li><a onclick="changeSite('{{site}}')">{{site}}</a></li>
{% endif %}
{% endfor %}
</ul>
</div>
</li>
<li style="cursor: default; user-select: none;" class="uk-disabled"><span>Modules</span></li>
<li class="uk-disabled"><span>Shopping Lists</span></li>
<li class="uk-disabled"><span>Editing Shopping List</span></li>
</ul>
</div>
<!-- Profile/Management Navigation-->
<div class="uk-navbar-right">
<ul class="uk-navbar-nav">
<li>
<a href="#">
<img src="{{session['user']['profile_pic_url']}}" alt="Profile Picture" class="profile-pic uk-visible@m" style="width: 40px; height: 40px; border-radius: 50%; margin-right: 5px;">
{{username}}
</a>
<div class="uk-navbar-dropdown" uk-drop="mode: click; multi:false">
<ul class="uk-nav uk-navbar-dropdown-nav">
<li><a href="/profile">Profile</a></li>
<li><a onclick="toggleDarkMode()">Dark Mode</a></li>
<li><a href="/site-management">Site Management</a></li>
<li><a href="/administration">System Management</a></li>
<li><a href="/access/logout">Logout</a></li>
</ul>
</div>
</li>
</ul>
</div>
</div>
</div>
</nav>
<div class="uk-container">
<div class="uk-section">
<div class="uk-grid-small" uk-grid>
<div class="uk-width-1-1">
<h1 class="uk-heading-medium uk-heading-divider">Generate Shopping List</h1>
</div>
<div class="uk-width-1-1">
<p class="uk-text-small">Generating a shopping list is as powerful as you wish to make it. In this form you will make the choices of what and when you want to
generate the list for. You will be adding specific items in the system, even can add all the safety stock items or specific ones, recipes, even other shopping lists
can be added to this one list. This is a snapshot of a moment though and any calculated safety stocks become static and can change if there is a long wait.
</p>
</div>
<!-- type section -->
<div class="uk-width-1-1">
<h1 class="uk-heading-xsmall uk-heading-divider">Shopping List Type</h1>
</div>
<div class="uk-width-1-1">
<p class="uk-text-small">Choosing the shopping list type informs the system of your intent when actually using the list when viewed. It will inform the system if it should
completely delete the list (known as a "Temporary" list) or if it should just change the states of all items to new (known as a "Permanent" list). Permanent lists would be used
for reoccuring lists that never change and can be reused. Temporary lists will generally be used when generating a huge one time list, or something small that you just want to
track quickly.
</p>
</div>
<div class="uk-width-1-1">
<label class="uk-form-label" for="generated_list_type">Select List Type</label>
<select id="generated_list_type" class="uk-select" aria-label="Select">
<option value="temporary" selected>Temporary</option>
<option value="permanent">Permanent</option>
</select>
</div>
<!-- basic info section -->
<div class="uk-width-1-1">
<h1 class="uk-heading-xsmall uk-heading-divider">Shopping List Type</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.
The shopping list name must be unique!
</p>
</div>
<div class="uk-width-1-1">
<label class="uk-form-label" for="generated_list_name">Shopping List Name</label>
<div class="uk-form-controls">
<input class="uk-input" id="generated_list_name" type="text" placeholder="Enter list name here...">
</div>
</div>
<div class="uk-width-1-1">
<label class="uk-form-label" for="generated_list_name">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>
</div>
</div>
<!-- Part section -->
<div class="uk-width-1-1">
<h1 class="uk-heading-xsmall uk-heading-divider">Shopping List Operators</h1>
</div>
<div class="uk-width-1-1">
<p class="uk-text-small"> Operators are what drives the engine that is a shopping list. You can configure each on specifically for the list you
want to generate and each will operate in their own manner behind the scenes.
</p>
</div>
<div class="uk-width-1-1">
<button class="uk-button uk-button-primary" type="button">Add Operator</button>
<div uk-dropdown>
<ul class="uk-nav uk-dropdown-nav">
<li class="uk-nav-header">Active Operators</li>
<li class="uk-nav-divider"></li>
<li><a onclick="addCustomItemsCard()" uk-tooltip="title: Create Custom items to add into the generated list.; pos: right">Custom Items</a></li>
<li><a onclick="addUncalculatedItemsCard()" uk-tooltip="title: Add items from the system that take a static quantity into the generated list.; pos: right">Non-Calculated System Items</a></li>
<li><a onclick="addCalculatedItemsCard()" uk-tooltip="title: Add items from the system that calculate quantity using quantity on hand and a set safety stocks into the generated list.; pos: right">Calculated System Items</a></li>
<li><a onclick="addRecipesCard()" uk-tooltip="title: Add Recipes that will take all the ingrediants and add them into the generated list.; pos: right">System Recipes</a></li>
<li><a onclick="addFullSKUCard()" uk-tooltip="title: Takes a full safety stock count from the system and adds any quantities below their safety stocks into the generated list.; pos: right">Full System Calculated</a></li>
<li><a class="uk-disabled" uk-tooltip="title: Uses a date range and selected planners for each to add any planned recipes into the generated list.; pos: right">Site Planners</a></li>
<li><a onclick="addListsCard()" uk-tooltip="title: Combine already made lists into this one; pos: right">Shopping Lists</a></li>
</ul>
</div>
</div>
<div class="uk-width-1-1">
<div id="operators_deck" class="uk-grid-small uk-child-width-1-1" uk-grid>
<!-- Items Custom -->
<div id="customItemsCard" hidden>
<div class="uk-card uk-card-default uk-card-small uk-card-body">
<h3>Custom Items Operator
<span class="">
<button onclick="changeCustomZoneState()" class="uk-button uk-button-small" title="Show/Hide the card body." uk-tooltip>Show/Hide</button>
</span>
<span class="uk-align-right">
<button onclick="removeCustomItemsCard()" class="uk-button uk-button-small" title="Will remove the custom items card and data from the list." uk-tooltip>Remove</button>
</span>
</h3>
<p class="uk-text-meta">A custom Item is an item that does not exist in your system but you would like to show up on your shopping list. This is useful for items
you dont often have to buy or do not want to keep track of but give the users the ability to see it.
</p>
<div id="customCardZone">
<table class="uk-table uk-table-striped">
<thead>
<tr>
<th>Name</th>
<th>Qty/UOM</th>
<th>Operations</th>
</tr>
</thead>
<tbody id="customItemsTableBody"></tbody>
</table>
<button onclick="openCustomItemsModal()" class="uk-button uk-button-secondary uk-width-1-1">Add Item</button>
</div>
</div>
</div>
<!-- Items Uncalculated -->
<div id="uncalcedItemsCard" hidden>
<div class="uk-card uk-card-default uk-card-small uk-card-body">
<h3>Un-Calculated System Item Operator
<span class="">
<button onclick="changeUncalculatedZoneState()" class="uk-button uk-button-small" title="Show/Hide the card body." uk-tooltip>Show/Hide</button>
</span>
<span class="uk-align-right">
<button onclick="removeUncalcedItemsCard()" class="uk-button uk-button-small" title="Will remove the uncalculated system items card and data from the list." uk-tooltip >Remove</button>
</span>
</h3>
<p class="uk-text-meta"> An Uncalulated System Item is an item choosen from the configured SKUs in your
system. The uncalculated part means the quantity asked for in the list will always remain to be what
you set it to during configuration of the list or while editing.
</p>
<div id="uncalculatedCardZone">
<table class="uk-table uk-table-striped">
<thead>
<tr>
<th>Name</th>
<th>Qty/UOM</th>
<th>Operations</th>
</tr>
</thead>
<tbody id="uncalculatedItemsTableBody"></tbody>
</table>
<button onclick="openUncalculatedItemsModal()" class="uk-button uk-button-secondary uk-width-1-1">Add Item</button>
</div>
</div>
</div>
<!-- Items Calculated -->
<div id="calculatedItemsCard" hidden>
<div class="uk-card uk-card-default uk-card-small uk-card-body">
<h3>Calculated System Item Operator
<span class="">
<button onclick="changeCalculatedZoneState()" class="uk-button uk-button-small" title="Show/Hide the card body." uk-tooltip>Show/Hide</button>
</span>
<span class="uk-align-right">
<button onclick="removeCalculatedItemsCard()" class="uk-button uk-button-small" title="Will remove the calculated system items card and data from the list." uk-tooltip >Remove</button>
</span>
</h3>
<p class="uk-text-meta"> A Calculated System Item is an item choosen from the configured SKUs in your
system. The calculated part means the quantity asked for in the list will be calculated at the time
that the list is generated based on set safety stock and quantity on hand.
</p>
<div id="calculatedCardZone">
<table class="uk-table uk-table-striped">
<thead>
<tr>
<th>Name</th>
<th>Operations</th>
</tr>
</thead>
<tbody id="calculatedItemsTableBody"></tbody>
</table>
<button onclick="openCalculatedItemsModal()" class="uk-button uk-button-secondary uk-width-1-1">Add Item</button>
</div>
</div>
</div>
<!-- Recipes -->
<div id="recipesCard" hidden>
<div class="uk-card uk-card-default uk-card-small uk-card-body">
<h3>Recipes Operator
<span class="">
<button onclick="changeRecipesZoneState()" class="uk-button uk-button-small" title="Show/Hide the card body." uk-tooltip>Show/Hide</button>
</span>
<span class="uk-align-right">
<button onclick="removeRecipesItemsCard()" class="uk-button uk-button-small" title="Will remove the Recipes card and data from the list." uk-tooltip >Remove</button>
</span>
</h3>
<p class="uk-text-meta">Recipes can be built into the system and when added through this operator each ingrediant will be added to the list at the recipes quantity and UOM.</p>
<div id="recipesCardZone">
<table class="uk-table uk-table-striped">
<thead>
<tr>
<th>Name</th>
<th>Operations</th>
</tr>
</thead>
<tbody id="recipesTableBody"></tbody>
</table>
<button onclick="openRecipesModal()" class="uk-button uk-button-secondary uk-width-1-1">Add Item</button>
</div>
</div>
</div>
<!-- Shopping List -->
<div id="listsCard" hidden>
<div class="uk-card uk-card-default uk-card-small uk-card-body">
<h3>Shopping Lists Operator
<span class="">
<button onclick="changeListsZoneState()" class="uk-button uk-button-small" title="Show/Hide the card body." uk-tooltip>Show/Hide</button>
</span>
<span class="uk-align-right">
<button onclick="removeListsItemsCard()" class="uk-button uk-button-small" title="Will remove the Recipes card and data from the list." uk-tooltip >Remove</button>
</span>
</h3>
<p class="uk-text-meta">This operator allows you to select other shopping lists that already exist to have their
items added onto this lists items.
</p>
<div id="listsCardZone">
<table class="uk-table uk-table-striped">
<thead>
<tr>
<th>Name</th>
<th>Operations</th>
</tr>
</thead>
<tbody id="listsTableBody"></tbody>
</table>
<button onclick="openListsModal()" class="uk-button uk-button-secondary uk-width-1-1">Add Item</button>
</div>
</div>
</div>
<!-- Full Calculated SKU -->
<div id="fullSKUCard" hidden>
<div class="uk-card uk-card-default uk-card-small uk-card-body">
<h3>Full Calculation Operator
<span class="uk-align-right">
<button onclick="removeFullSKUCard()" class="uk-button uk-button-small" title="Will remove the Recipes card and data from the list." uk-tooltip >Remove</button>
</span>
</h3>
<p class="uk-text-meta">Full SKU calculations will go through your whole system and grab all items that have safety stocks.
It will then use the safety stocks and quantity on hand to determine if you need to purchase more to add to the list. With
this in mind you CANNOT have full SKU calculation enabled and also have the Calculated Items Operator.
</p>
<div id="fullSKUZone" uk-grid>
<div class="uk-width-1-1">
<label><input autocomplete="off" id="fullSKUCheckbox" onclick="fullSKUEnabledChange(event)" class="uk-checkbox" type="checkbox"> Enabled</label>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="modals_deck">
<!-- Custom Item Modal -->
<div id="CustomItemModal" class="uk-modal">
<div class="uk-modal-dialog uk-modal-body">
<h2 class="uk-modal-title">Add Custom Line...</h2>
<p class="uk-text-small">A custom Item is an item that does not exist in your system but you would like to show up on your shopping list. This is useful for items
you dont often have to buy or do not want to keep track of but give the users the ability to see it.
</p>
<table class="uk-table uk-table-responsive uk-table-striped">
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>Name</td>
<td><input id="customName" class="uk-input" type="text"></td>
</tr>
<tr>
<td>QTY</td>
<td><input id="customQty" class="uk-input" type="number"></td>
</tr>
<tr>
<td>UOM</td>
<td>
<select id="customUnit" class="uk-select" aria-label="Select">
{% for unit in units %}
<option value={{unit['id']}} data-fullname={{unit['fullname']}}>{{unit['fullname']}}</option>
{% endfor %}
</select>
</td>
</tr>
<tr>
<td>Main Link</td>
<td><input id="customLink" class="uk-input" type="text"></td>
</tr>
</tbody>
</table>
<p class="uk-text-right">
<button class="uk-button uk-button-default uk-modal-close" type="button">Cancel</button>
<button id="customItemModalButton" onclick="addCustomItem()" class="uk-button uk-button-primary" type="button">Add Line</button>
</p>
</div>
</div>
<!-- Uncalculated Item Modals -->
<div id="uncalculatedItemModal" class="uk-flex-top" uk-modal>
<div class="uk-modal-dialog">
<button class="uk-modal-close-default" type="button" uk-close></button>
<div class="uk-modal-header">
<h2 class="uk-modal-title">System Item Search</h2>
</div>
<div id="paginationModalBody" class="uk-modal-body">
<div class="uk-grid-small" uk-grid>
<div class="uk-width-1-1">
<p class="uk-text-small"> An Uncalulated System Item is an item choosen from the configured SKUs in your
system. The uncalculated part means the quantity asked for in the list will always remain to be what
you set it to during configuration of the list or while editing.
</p>
</div>
<div class="uk-width-1-1">
<div id="searchItemsForm" onkeydown="searchItemTable(event)" class="uk-search uk-search-default uk-align-center">
<input id="searchItemsInput" class="uk-border-pill uk-search-input" type="search" placeholder="" aria-label="">
<span class="uk-search-icon-flip" uk-search-icon></span>
</div>
</div>
<div class="uk-width-1-1">
<nav aria-label="Pagination">
<ul id="itemsPage" 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>
</div>
<div class="uk-width-1-1">
<caption>Select an Item from the system...</caption>
<table class="uk-table uk-table-striped">
<thead>
<tr>
<th>Name</th>
<th>Qty</th>
<th>UOM</th>
<th>Operations</th>
</tr>
</thead>
<tbody id="uncalculatedItemsModalTableBody">
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div id="uncalculatedItemModalEdit" class="uk-modal">
<div class="uk-modal-dialog uk-modal-body">
<h2 class="uk-modal-title">Edit Uncalulated Item...</h2>
<p class="uk-text-small"> An Uncalulated System Item is an item choosen from the configured SKUs in your
system. The uncalculated part means the quantity asked for in the list will always remain to be what
you set it to during configuration of the list or while editing.
</p>
<div class="uk-alert uk-alert-warning">While editing System Items the <strong>Name</strong> and <strong>UOM</strong> inputs will be Disabled!</div>
<table class="uk-table uk-table-responsive uk-table-striped">
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>Name</td>
<td><input id="uncalculatedItemName" class="uk-input uk-disabled" type="text"></td>
</tr>
<tr>
<td>QTY</td>
<td><input id="uncalculatedItemQty" class="uk-input" type="number"></td>
</tr>
<tr>
<td>UOM</td>
<td>
<select id="uncalculatedItemUnit" class="uk-select uk-disabled" aria-label="Select">
{% for unit in units %}
<option value={{unit['id']}} data-fullname={{unit['fullname']}}>{{unit['fullname']}}</option>
{% endfor %}
</select>
</td>
</tr>
<tr>
<td>Main Link</td>
<td><input id="uncalculatedItemLink" class="uk-input" type="text"></td>
</tr>
</tbody>
</table>
<p class="uk-text-right">
<button class="uk-button uk-button-default uk-modal-close" type="button">Cancel</button>
<button id="uncalculatedItemModalEditButton" class="uk-button uk-button-primary" type="button">Add Line</button>
</p>
</div>
</div>
<!-- Calculated Item Modals -->
<div id="calculatedItemModal" class="uk-flex-top" uk-modal>
<div class="uk-modal-dialog">
<button class="uk-modal-close-default" type="button" uk-close></button>
<div class="uk-modal-header">
<h2 class="uk-modal-title">System Item Search</h2>
</div>
<div class="uk-modal-body">
<div class="uk-grid-small" uk-grid>
<div class="uk-width-1-1">
<p class="uk-text-small"> A Calculated System Item is an item choosen from the configured SKUs in your
system. The calculated part means the quantity asked for in the list will be calculated at the time
that the list is generated based on set safety stock and quantity on hand.
</p>
</div>
<div class="uk-width-1-1">
<div id="searcCalculatedItemsForm" onkeydown="searchCalculatedItemTable(event)" class="uk-search uk-search-default uk-align-center">
<input id="searchCalculatedItemsInput" class="uk-border-pill uk-search-input" type="search" placeholder="" aria-label="">
<span class="uk-search-icon-flip" uk-search-icon></span>
</div>
</div>
<div class="uk-width-1-1">
<nav aria-label="Pagination">
<ul id="itemsCalculatedPage" 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>
</div>
<div class="uk-width-1-1">
<caption>Select an Item from the system...</caption>
<table class="uk-table uk-table-striped">
<thead>
<tr>
<th>Name</th>
<th>Operations</th>
</tr>
</thead>
<tbody id="calculatedItemsModalTableBody">
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Recipes Modals -->
<div id="recipesModal" class="uk-flex-top" uk-modal>
<div class="uk-modal-dialog">
<button class="uk-modal-close-default" type="button" uk-close></button>
<div class="uk-modal-header">
<h2 class="uk-modal-title">System Recipes Search</h2>
</div>
<div class="uk-modal-body">
<div class="uk-grid-small" uk-grid>
<div class="uk-width-1-1">
<p class="uk-text-small">Recipes can be built into the system and when added through this operator each ingrediant will be added to the list at the recipes quantity and UOM.</p>
</div>
<div class="uk-width-1-1">
<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>
</div>
<div class="uk-width-1-1">
<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>
</div>
<div class="uk-width-1-1">
<caption>Select a Recipe from the system...</caption>
<table class="uk-table uk-table-striped">
<thead>
<tr>
<th>Name</th>
<th>Operations</th>
</tr>
</thead>
<tbody id="recipesModalTableBody">
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Shopping Lists Modals -->
<div id="listsModal" class="uk-flex-top" uk-modal>
<div class="uk-modal-dialog">
<button class="uk-modal-close-default" type="button" uk-close></button>
<div class="uk-modal-header">
<h2 class="uk-modal-title">System Shopping Lists Search</h2>
</div>
<div class="uk-modal-body">
<div class="uk-grid-small" uk-grid>
<div class="uk-width-1-1">
<p class="uk-text-small">This operator allows you to select other shopping lists that already exist to have their
items added onto this lists items.</p>
</div>
<div class="uk-width-1-1">
<div id="searchListsForm" onkeydown="searchListsTable(event)" class="uk-search uk-search-default uk-align-center">
<input id="searchListsInput" class="uk-border-pill uk-search-input" type="search" placeholder="" aria-label="">
<span class="uk-search-icon-flip" uk-search-icon></span>
</div>
</div>
<div class="uk-width-1-1">
<nav aria-label="Pagination">
<ul id="listsPage" 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>
</div>
<div class="uk-width-1-1">
<caption>Select a List from the system...</caption>
<table class="uk-table uk-table-striped">
<thead>
<tr>
<th>Name</th>
<th>Operations</th>
</tr>
</thead>
<tbody id="listsModalTableBody">
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
<script>const units = {{units|tojson}}</script>
<script src="{{ url_for('shopping_list_API.static', filename='js/shoppingListGeneratorHandler.js') }}"></script>
</html>

View File

@ -3,6 +3,8 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title id="title"></title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<!-- Material Icons -->
@ -111,6 +113,7 @@
<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 href="/shopping-lists/generate" uk-icon="icon: plus">Generate List</a></li>
<li><a href="#" uk-icon="icon: cloud-download">download</a></li>
</ul>
</div>

View File

@ -3,6 +3,7 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title id="title"></title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<!-- Material Icons -->

View File

@ -3,6 +3,7 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title id="title"></title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<!-- Material Icons -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
<!-- Material Symbols - Outlined Set -->

Binary file not shown.

View File

@ -538,4 +538,28 @@
sql='WITH sum_cte AS ( SELECT mi.id, SUM(mil.quantity_on_hand)::FLOAT8 AS total_sum FROM tet_item_locations mil JOIN tet_items mi ON mil.part_id = mi.id GROUP BY mi.id )SELECT item.id, item.description, item.item_name, sum_cte.total_sum as total_qoh, u.fullnameFROM tet_items itemLEFT JOIN sum_cte ON item.id = sum_cte.idLEFT JOIN tet_item_info item_info ON item.item_info_id = item_info.idLEFT JOIN units u ON item_info.uom = u.idWHERE item.search_string LIKE '%%' || %s || '%%'ORDER BY item.id ASCLIMIT %s OFFSET %s;')
2025-08-14 21:02:40.167097 --- 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 test2_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, test2_shopping_lists.*, logins.username as author, (SELECT COALESCE(array_agg(row_to_json(slis)), '{}') FROM cte_sl_items slis) AS sl_items FROM test2_shopping_listsJOIN logins ON test2_shopping_lists.author = logins.idWHERE test2_shopping_lists.list_uuid=(SELECT passed_uuid::uuid FROM passed_uuid)')
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 test2_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, test2_shopping_lists.*, logins.username as author, (SELECT COALESCE(array_agg(row_to_json(slis)), '{}') FROM cte_sl_items slis) AS sl_items FROM test2_shopping_listsJOIN logins ON test2_shopping_lists.author = logins.idWHERE test2_shopping_lists.list_uuid=(SELECT passed_uuid::uuid FROM passed_uuid)')
2025-08-15 06:32:31.728417 --- ERROR --- DatabaseError(message='column "list_item_state" of relation "main_shopping_list_items" does not existLINE 1: UPDATE main_shopping_list_items SET list_item_state = true W... ^',
payload={'uuid': '9280626f-c100-44bd-a50d-35ee5d43d855', 'update': {'list_item_state': True}},
sql='UPDATE main_shopping_list_items SET list_item_state = %s WHERE list_item_uuid=%s::uuid RETURNING *;')
2025-08-16 11:06:43.660699 --- ERROR --- DatabaseError(message='column main_shopping_list_items.sl_id does not existLINE 40: ...n_shopping_list_items ON main_shopping_lists.id = main_shopp... ^',
payload=(196,),
sql='WITH passed_id AS (SELECT %s AS passed_id), logistics_id AS (SELECT logistics_info_id FROM main_items WHERE id=(SELECT passed_id FROM passed_id)), info_id AS (SELECT item_info_id FROM main_items WHERE id=(SELECT passed_id FROM passed_id)), cte_conversions AS ( SELECT main_conversions.id as conv_id, main_conversions.conv_factor as conv_factor, units.* as uom FROM main_conversions LEFT JOIN units ON main_conversions.uom_id = units.id WHERE main_conversions.item_id = (SELECT passed_id FROM passed_id) ), cte_item_info AS ( SELECT main_item_info.*, row_to_json(units.*) as uom, COALESCE((SELECT json_agg(convs) FROM cte_conversions convs), '[]'::json) AS conversions, COALESCE((SELECT json_agg(p.*) FROM main_sku_prefix as p WHERE p.id = ANY(main_item_info.prefixes)), '[]'::json) as prefixes FROM main_item_info LEFT JOIN units ON main_item_info.uom = units.id WHERE main_item_info.id = (SELECT item_info_id FROM info_id) ), cte_groups AS ( SELECT main_groups.*, main_group_items.uuid, main_group_items.item_type, main_group_items.qty FROM main_groups JOIN main_group_items ON main_groups.id = main_group_items.gr_id WHERE main_group_items.item_id = (SELECT passed_id FROM passed_id) ), cte_shopping_lists AS ( SELECT main_shopping_lists.*, main_shopping_list_items.uuid, main_shopping_list_items.item_type, main_shopping_list_items.qty FROM main_shopping_lists JOIN main_shopping_list_items ON main_shopping_lists.id = main_shopping_list_items.sl_id WHERE main_shopping_list_items.item_id = (SELECT passed_id FROM passed_id) ), cte_itemlinks AS ( SELECT * FROM main_itemlinks WHERE link=(SELECT passed_id FROM passed_id) ), cte_item_locations AS ( SELECT * FROM main_item_locations LEFT JOIN main_locations ON main_locations.id = main_item_locations.location_id WHERE part_id = (SELECT passed_id FROM passed_id) ), cte_logistics_info AS ( SELECT li.*, row_to_json(pl) AS primary_location, row_to_json(ail) AS auto_issue_location, row_to_json(pz) AS primary_zone, row_to_json(aiz) AS auto_issue_zone FROM main_logistics_info AS li LEFT JOIN main_locations AS pl ON li.primary_location = pl.id LEFT JOIN main_locations AS ail ON li.auto_issue_location = ail.id LEFT JOIN main_zones AS pz ON li.primary_zone = pz.id LEFT JOIN main_zones AS aiz ON li.auto_issue_zone = aiz.id WHERE li.id=(SELECT logistics_info_id FROM logistics_id) )SELECT (SELECT passed_id FROM passed_id) AS passed_id, main_items.*, (SELECT COALESCE(row_to_json(logis), '{}') FROM cte_logistics_info logis) AS logistics_info, row_to_json(main_food_info.*) as food_info, row_to_json(main_brands.*) as brand, (SELECT COALESCE(row_to_json(ii), '{}') FROM cte_item_info ii) AS item_info, (SELECT COALESCE(array_agg(row_to_json(g)), '{}') FROM cte_groups g) AS item_groups, (SELECT COALESCE(array_agg(row_to_json(sl)), '{}') FROM cte_shopping_lists sl) AS item_shopping_lists, (SELECT COALESCE(array_agg(row_to_json(il)), '{}') FROM cte_itemlinks il) AS linked_items, (SELECT COALESCE(array_agg(row_to_json(ils)), '{}') FROM cte_item_locations ils) AS item_locationsFROM main_items LEFT JOIN main_item_info ON main_items.item_info_id = main_item_info.id LEFT JOIN main_food_info ON main_items.food_info_id = main_food_info.id LEFT JOIN main_brands ON main_items.brand = main_brands.id LEFT JOIN units ON main_item_info.uom = units.id LEFT JOIN cte_groups ON main_items.id = cte_groups.id LEFT JOIN cte_shopping_lists ON main_items.id = cte_shopping_lists.idWHERE main_items.id=(SELECT passed_id FROM passed_id)GROUP BY main_items.id, main_item_info.id, main_food_info.id, main_brands.id;')
2025-08-16 11:07:04.722280 --- ERROR --- DatabaseError(message='column main_shopping_list_items.sl_id does not existLINE 40: ...n_shopping_list_items ON main_shopping_lists.id = main_shopp... ^',
payload=(196,),
sql='WITH passed_id AS (SELECT %s AS passed_id), logistics_id AS (SELECT logistics_info_id FROM main_items WHERE id=(SELECT passed_id FROM passed_id)), info_id AS (SELECT item_info_id FROM main_items WHERE id=(SELECT passed_id FROM passed_id)), cte_conversions AS ( SELECT main_conversions.id as conv_id, main_conversions.conv_factor as conv_factor, units.* as uom FROM main_conversions LEFT JOIN units ON main_conversions.uom_id = units.id WHERE main_conversions.item_id = (SELECT passed_id FROM passed_id) ), cte_item_info AS ( SELECT main_item_info.*, row_to_json(units.*) as uom, COALESCE((SELECT json_agg(convs) FROM cte_conversions convs), '[]'::json) AS conversions, COALESCE((SELECT json_agg(p.*) FROM main_sku_prefix as p WHERE p.id = ANY(main_item_info.prefixes)), '[]'::json) as prefixes FROM main_item_info LEFT JOIN units ON main_item_info.uom = units.id WHERE main_item_info.id = (SELECT item_info_id FROM info_id) ), cte_groups AS ( SELECT main_groups.*, main_group_items.uuid, main_group_items.item_type, main_group_items.qty FROM main_groups JOIN main_group_items ON main_groups.id = main_group_items.gr_id WHERE main_group_items.item_id = (SELECT passed_id FROM passed_id) ), cte_shopping_lists AS ( SELECT main_shopping_lists.*, main_shopping_list_items.uuid, main_shopping_list_items.item_type, main_shopping_list_items.qty FROM main_shopping_lists JOIN main_shopping_list_items ON main_shopping_lists.id = main_shopping_list_items.sl_id WHERE main_shopping_list_items.item_id = (SELECT passed_id FROM passed_id) ), cte_itemlinks AS ( SELECT * FROM main_itemlinks WHERE link=(SELECT passed_id FROM passed_id) ), cte_item_locations AS ( SELECT * FROM main_item_locations LEFT JOIN main_locations ON main_locations.id = main_item_locations.location_id WHERE part_id = (SELECT passed_id FROM passed_id) ), cte_logistics_info AS ( SELECT li.*, row_to_json(pl) AS primary_location, row_to_json(ail) AS auto_issue_location, row_to_json(pz) AS primary_zone, row_to_json(aiz) AS auto_issue_zone FROM main_logistics_info AS li LEFT JOIN main_locations AS pl ON li.primary_location = pl.id LEFT JOIN main_locations AS ail ON li.auto_issue_location = ail.id LEFT JOIN main_zones AS pz ON li.primary_zone = pz.id LEFT JOIN main_zones AS aiz ON li.auto_issue_zone = aiz.id WHERE li.id=(SELECT logistics_info_id FROM logistics_id) )SELECT (SELECT passed_id FROM passed_id) AS passed_id, main_items.*, (SELECT COALESCE(row_to_json(logis), '{}') FROM cte_logistics_info logis) AS logistics_info, row_to_json(main_food_info.*) as food_info, row_to_json(main_brands.*) as brand, (SELECT COALESCE(row_to_json(ii), '{}') FROM cte_item_info ii) AS item_info, (SELECT COALESCE(array_agg(row_to_json(g)), '{}') FROM cte_groups g) AS item_groups, (SELECT COALESCE(array_agg(row_to_json(sl)), '{}') FROM cte_shopping_lists sl) AS item_shopping_lists, (SELECT COALESCE(array_agg(row_to_json(il)), '{}') FROM cte_itemlinks il) AS linked_items, (SELECT COALESCE(array_agg(row_to_json(ils)), '{}') FROM cte_item_locations ils) AS item_locationsFROM main_items LEFT JOIN main_item_info ON main_items.item_info_id = main_item_info.id LEFT JOIN main_food_info ON main_items.food_info_id = main_food_info.id LEFT JOIN main_brands ON main_items.brand = main_brands.id LEFT JOIN units ON main_item_info.uom = units.id LEFT JOIN cte_groups ON main_items.id = cte_groups.id LEFT JOIN cte_shopping_lists ON main_items.id = cte_shopping_lists.idWHERE main_items.id=(SELECT passed_id FROM passed_id)GROUP BY main_items.id, main_item_info.id, main_food_info.id, main_brands.id;')
2025-08-16 11:23:59.868804 --- ERROR --- DatabaseError(message='column main_shopping_list_items.sl_id does not existLINE 40: ...n_shopping_list_items ON main_shopping_lists.id = main_shopp... ^',
payload=(196,),
sql='WITH passed_id AS (SELECT %s AS passed_id), logistics_id AS (SELECT logistics_info_id FROM main_items WHERE id=(SELECT passed_id FROM passed_id)), info_id AS (SELECT item_info_id FROM main_items WHERE id=(SELECT passed_id FROM passed_id)), cte_conversions AS ( SELECT main_conversions.id as conv_id, main_conversions.conv_factor as conv_factor, units.* as uom FROM main_conversions LEFT JOIN units ON main_conversions.uom_id = units.id WHERE main_conversions.item_id = (SELECT passed_id FROM passed_id) ), cte_item_info AS ( SELECT main_item_info.*, row_to_json(units.*) as uom, COALESCE((SELECT json_agg(convs) FROM cte_conversions convs), '[]'::json) AS conversions, COALESCE((SELECT json_agg(p.*) FROM main_sku_prefix as p WHERE p.id = ANY(main_item_info.prefixes)), '[]'::json) as prefixes FROM main_item_info LEFT JOIN units ON main_item_info.uom = units.id WHERE main_item_info.id = (SELECT item_info_id FROM info_id) ), cte_groups AS ( SELECT main_groups.*, main_group_items.uuid, main_group_items.item_type, main_group_items.qty FROM main_groups JOIN main_group_items ON main_groups.id = main_group_items.gr_id WHERE main_group_items.item_id = (SELECT passed_id FROM passed_id) ), cte_shopping_lists AS ( SELECT main_shopping_lists.*, main_shopping_list_items.uuid, main_shopping_list_items.item_type, main_shopping_list_items.qty FROM main_shopping_lists JOIN main_shopping_list_items ON main_shopping_lists.id = main_shopping_list_items.sl_id WHERE main_shopping_list_items.item_id = (SELECT passed_id FROM passed_id) ), cte_itemlinks AS ( SELECT * FROM main_itemlinks WHERE link=(SELECT passed_id FROM passed_id) ), cte_item_locations AS ( SELECT * FROM main_item_locations LEFT JOIN main_locations ON main_locations.id = main_item_locations.location_id WHERE part_id = (SELECT passed_id FROM passed_id) ), cte_logistics_info AS ( SELECT li.*, row_to_json(pl) AS primary_location, row_to_json(ail) AS auto_issue_location, row_to_json(pz) AS primary_zone, row_to_json(aiz) AS auto_issue_zone FROM main_logistics_info AS li LEFT JOIN main_locations AS pl ON li.primary_location = pl.id LEFT JOIN main_locations AS ail ON li.auto_issue_location = ail.id LEFT JOIN main_zones AS pz ON li.primary_zone = pz.id LEFT JOIN main_zones AS aiz ON li.auto_issue_zone = aiz.id WHERE li.id=(SELECT logistics_info_id FROM logistics_id) )SELECT (SELECT passed_id FROM passed_id) AS passed_id, main_items.*, (SELECT COALESCE(row_to_json(logis), '{}') FROM cte_logistics_info logis) AS logistics_info, row_to_json(main_food_info.*) as food_info, row_to_json(main_brands.*) as brand, (SELECT COALESCE(row_to_json(ii), '{}') FROM cte_item_info ii) AS item_info, (SELECT COALESCE(array_agg(row_to_json(g)), '{}') FROM cte_groups g) AS item_groups, (SELECT COALESCE(array_agg(row_to_json(sl)), '{}') FROM cte_shopping_lists sl) AS item_shopping_lists, (SELECT COALESCE(array_agg(row_to_json(il)), '{}') FROM cte_itemlinks il) AS linked_items, (SELECT COALESCE(array_agg(row_to_json(ils)), '{}') FROM cte_item_locations ils) AS item_locationsFROM main_items LEFT JOIN main_item_info ON main_items.item_info_id = main_item_info.id LEFT JOIN main_food_info ON main_items.food_info_id = main_food_info.id LEFT JOIN main_brands ON main_items.brand = main_brands.id LEFT JOIN units ON main_item_info.uom = units.id LEFT JOIN cte_groups ON main_items.id = cte_groups.id LEFT JOIN cte_shopping_lists ON main_items.id = cte_shopping_lists.idWHERE main_items.id=(SELECT passed_id FROM passed_id)GROUP BY main_items.id, main_item_info.id, main_food_info.id, main_brands.id;')
2025-08-16 17:04:26.272090 --- ERROR --- DatabaseError(message='invalid reference to FROM-clause entry for table "main_items"LINE 7: WHERE main_items.search_string LIKE '%' || '' || '%' ^HINT: Perhaps you meant to reference the table alias "items".',
payload=('', 50, 0),
sql='SELECT items.item_uuid, items.item_name, units.fullnameFROM main_items itemsLEFT JOIN main_item_info item_info ON item_info.id = items.item_info_idLEFT JOIN units ON item_info.uom = units.idWHERE main_items.search_string LIKE '%%' || %s || '%%' ORDER BY items.item_name LIMIT %s OFFSET %s;')
2025-08-17 19:21:27.673759 --- ERROR --- DatabaseError(message='duplicate key value violates unique constraint "main_logistics_info_barcode_key"DETAIL: Key (barcode)=(%%) already exists.',
payload=('%%', 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 *;')
2025-08-17 19:21:30.048853 --- ERROR --- DatabaseError(message='duplicate key value violates unique constraint "main_logistics_info_barcode_key"DETAIL: Key (barcode)=(%%) already exists.',
payload=('%%', 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 *;')
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 *;')

BIN
static/pictures/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 KiB

View File

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 89 KiB

View File

@ -1,4 +1,4 @@
from flask import Flask, render_template, session, request, redirect, jsonify
from flask import Flask, render_template, session, request, redirect, jsonify, send_from_directory
from flask_assets import Environment, Bundle
from authlib.integrations.flask_client import OAuth
import config, psycopg2, main
@ -105,6 +105,12 @@ def create_push_subscription():
def subscribe():
return render_template("subscribe.html")
@app.route('/favicon.ico')
def favicon():
return send_from_directory(
app.static_folder, 'pictures/favicon.ico', mimetype='image/vnd.microsoft.icon'
)
@app.route("/")
@access_api.login_required
def home():