Lots of wide feature improvements

This commit is contained in:
Jadowyne Ulve 2025-04-20 10:55:47 -05:00
parent 1799d00ef1
commit 3bc3655778
71 changed files with 4250 additions and 751 deletions

5
.gitignore vendored
View File

@ -1 +1,6 @@
foodpantryserver.zip
sites
static/css/uikit-rtl.css
static/css/uikit-rtl.min.css
static/css/uikit.css
static/css/uikit.min.css

View File

@ -63,18 +63,6 @@ class FoodInfoPayload:
self.default_expiration
)
@dataclass
class BrandsPayload:
name: str
def __post_init__(self):
if not isinstance(self.name, str):
return TypeError(f"brand name should be of type str; not {type(self.name)}")
def payload(self):
return (
self.name,
)
@dataclass
class ItemsPayload:
@ -108,26 +96,6 @@ class ItemsPayload:
self.search_string
)
@dataclass
class ItemLocationPayload:
part_id: int
location_id: int
quantity_on_hand: float = 0.0
cost_layers: list = field(default_factory=list)
def __post_init__(self):
if not isinstance(self.part_id, int):
raise TypeError(f"part_id must be of type int; not {type(self.part_id)}")
if not isinstance(self.location_id, int):
raise TypeError(f"part_id must be of type int; not {type(self.part_id)}")
def payload(self):
return (
self.part_id,
self.location_id,
self.quantity_on_hand,
lst2pgarr(self.cost_layers)
)
@dataclass
class TransactionPayload:
@ -173,65 +141,6 @@ class CostLayerPayload:
self.vendor
)
@dataclass
class LocationPayload:
uuid: str
name: str
zone_id: int
def __post_init__(self):
if not isinstance(self.uuid, str):
raise TypeError(f"uuid must be of type str; not {type(self.uuid)}")
if not isinstance(self.name, str):
raise TypeError(f"Location name must be of type str; not {type(self.name)}")
if not isinstance(self.zone_id, int):
raise TypeError(f"zone_id must be of type str; not {type(self.zone_id)}")
def payload(self):
return (
self.uuid,
self.name,
self.zone_id
)
@dataclass
class ZonePayload:
name: str
site_id: int
def __post_init__(self):
if not isinstance(self.name, str):
raise TypeError(f"Zone name should be of type str; not {type(self.name)}")
def payload(self):
return (
self.name,
self.site_id
)
@dataclass
class VendorPayload:
vendor_name: str
created_by: int
vendor_address: str = ""
creation_date: datetime.datetime = field(init=False)
phone_number: str = ""
def __post_init__(self):
if not isinstance(self.vendor_name, str):
raise TypeError(f"vendor_name should be of type str; not {type(self.vendor_name)}")
self.creation_date = datetime.datetime.now()
def payload(self):
return (
self.vendor_name,
self.vendor_address,
self.creation_date,
self.created_by,
self.phone_number
)
@dataclass
class ItemLinkPayload:
barcode: str

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1719,3 +1719,93 @@
2025-04-12 19:43:45.390676 --- ERROR --- DatabaseError(message='can't adapt type 'dict'',
payload=('%024600017008%', 1, 'sku', 'Kosher salt', {'id': 1, 'plural': 'pinches', 'single': ' pinch', 'fullname': ' Pinch', 'description': ' Less than 1/8 teaspoon.'}, 1.0, 141, '{}'),
sql='INSERT INTO main_recipe_items(uuid, rp_id, item_type, item_name, uom, qty, item_id, links) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) RETURNING *;')
2025-04-13 10:06:16.130857 --- ERROR --- DatabaseError(message='unsupported format character ';' (0x3b) at index 42',
payload=(),
sql='SELECT * FROM test_zones LIMIT %s OFFSET %;')
2025-04-17 08:07:14.828153 --- ERROR --- DatabaseError(message='invalid input syntax for type integer: "ShelfA"LINE 17: @ShelfA', '6', 'ShelfA') ^',
payload=('\n \n <option value="2">KITCHEN</option>\n \n <option value="3">CLOSETS</option>\n \n <option value="4">BEDROOM A</option>\n \n <option value="5">GARAGE</option>\n \n <option value="6">BookShelves</option>\n \n <option value="1">DEFAULT</option>\n \n @ShelfA', '6', 'ShelfA'),
sql='INSERT INTO test_zones(name, description, site_id) VALUES (%s, %s, %s) RETURNING *;')
2025-04-17 08:09:29.519938 --- ERROR --- DatabaseError(message='invalid input syntax for type integer: "ShelfA"LINE 17: @ShelfA', '6', 'ShelfA') ^',
payload=('\n \n <option value="2">KITCHEN</option>\n \n <option value="3">CLOSETS</option>\n \n <option value="4">BEDROOM A</option>\n \n <option value="5">GARAGE</option>\n \n <option value="6">BookShelves</option>\n \n <option value="1">DEFAULT</option>\n \n @ShelfA', '6', 'ShelfA'),
sql='INSERT INTO test_zones(name, description, site_id) VALUES (%s, %s, %s) RETURNING *;')
2025-04-17 08:12:23.901770 --- ERROR --- DatabaseError(message='value too long for type character varying(32)',
payload=('\n \n <option value="2">KITCHEN</option>\n \n <option value="3">CLOSETS</option>\n \n <option value="4">BEDROOM A</option>\n \n <option value="5">GARAGE</option>\n \n <option value="6">BookShelves</option>\n \n <option value="1">DEFAULT</option>\n \n ', 'test', 2),
sql='INSERT INTO test_zones(name, description, site_id) VALUES (%s, %s, %s) RETURNING *;')
2025-04-17 08:13:11.019738 --- ERROR --- DatabaseError(message='value too long for type character varying(32)',
payload=('\n \n <option value="2">KITCHEN</option>\n \n <option value="3">CLOSETS</option>\n \n <option value="4">BEDROOM A</option>\n \n <option value="5">GARAGE</option>\n \n <option value="6">BookShelves</option>\n \n <option value="1">DEFAULT</option>\n \n ', 'test', 2),
sql='INSERT INTO test_zones(name, description, site_id) VALUES (%s, %s, %s) RETURNING *;')
2025-04-17 08:15:02.629959 --- ERROR --- DatabaseError(message='duplicate key value violates unique constraint "test_zones_name_key"DETAIL: Key (name)=(KITCHEN) already exists.',
payload=('KITCHEN', 'test', 2),
sql='INSERT INTO test_zones(name, description, site_id) VALUES (%s, %s, %s) RETURNING *;')
2025-04-17 08:16:00.387539 --- ERROR --- DatabaseError(message='insert or update on table "test_zones" violates foreign key constraint "fk_site"DETAIL: Key (site_id)=(2) is not present in table "sites".',
payload=('KITCHEN@test', 'test', 2),
sql='INSERT INTO test_zones(name, description, site_id) VALUES (%s, %s, %s) RETURNING *;')
2025-04-17 08:16:53.559749 --- ERROR --- DatabaseError(message='insert or update on table "test_zones" violates foreign key constraint "fk_site"DETAIL: Key (site_id)=(2) is not present in table "sites".',
payload=('KITCHEN@Fridge', 'Fridge', 2),
sql='INSERT INTO test_zones(name, description, site_id) VALUES (%s, %s, %s) RETURNING *;')
2025-04-17 13:33:53.682228 --- ERROR --- DatabaseError(message='tuple index out of range',
payload=(),
sql='INSERT INTO test_locations(uuid, name, zone_id) VALUES (%s, %s, %s) RETURNING *;')
2025-04-17 17:35:40.178418 --- ERROR --- DatabaseError(message='syntax error at or near "20"LINE 1: WITH 20 AS passed_id, ^',
payload=(20, 20, 0),
sql='WITH %s AS passed_id, cte_item_locations AS ( SELECT DISTINCT ils.zone_id FROM test_item_locations ils WHERE ils.part_id = passed_id; )SELECT DISTINCT zone.* FROM cte_item_locations cilJOIN test_zones zone ON cil.zone_id = zone.idLIMIT %s OFFSET %s;')
2025-04-17 17:36:49.614637 --- ERROR --- DatabaseError(message='syntax error at or near ";"LINE 4: WHERE ils.part_id = passed_id; ^',
payload=(20, 20, 0),
sql='WITH passed_id AS (SELECT %s as passed_id), cte_item_locations AS ( SELECT DISTINCT ils.zone_id FROM test_item_locations ils WHERE ils.part_id = passed_id; )SELECT DISTINCT zone.* FROM cte_item_locations cilJOIN test_zones zone ON cil.zone_id = zone.idLIMIT %s OFFSET %s;')
2025-04-17 17:37:05.249411 --- ERROR --- DatabaseError(message='column ils.zone_id does not existLINE 3: SELECT DISTINCT ils.zone_id FROM test_item_locations... ^',
payload=(20, 20, 0),
sql='WITH passed_id AS (SELECT %s as passed_id), cte_item_locations AS ( SELECT DISTINCT ils.zone_id FROM test_item_locations ils WHERE ils.part_id = passed_id )SELECT DISTINCT zone.* FROM cte_item_locations cilJOIN test_zones zone ON cil.zone_id = zone.idLIMIT %s OFFSET %s;')
2025-04-17 17:48:36.064425 --- ERROR --- DatabaseError(message='column "passed_id" does not existLINE 4: WHERE ils.part_id = passed_id ^',
payload=(20, 20, 0),
sql='WITH passed_id AS (SELECT %s as passed_id), cte_item_locations AS ( SELECT DISTINCT ils.location_id FROM test_item_locations ils WHERE ils.part_id = passed_id ), cte_locations AS ( SELECT DISTINCT locations.zone_id FROM test_locations locations WHERE locations.id IN (SELECT location_id FROM cte_item_locations) )SELECT DISTINCT zone.* FROM cte_locations cilJOIN test_zones zone ON cil.zone_id = zone.idLIMIT %s OFFSET %s;')
2025-04-17 17:48:59.073548 --- ERROR --- DatabaseError(message='syntax error at or near "20"LINE 1: WITH 20 AS passed_id, ^',
payload=(20, 20, 0),
sql='WITH passed_id AS (SELECT %s as passed_id), cte_item_locations AS ( SELECT DISTINCT ils.location_id FROM test_item_locations ils WHERE ils.part_id = (SELECT passed_id FROM passed_id) ), cte_locations AS ( SELECT DISTINCT locations.zone_id FROM test_locations locations WHERE locations.id IN (SELECT location_id FROM cte_item_locations) )SELECT DISTINCT zone.* FROM cte_locations cilJOIN test_zones zone ON cil.zone_id = zone.idLIMIT %s OFFSET %s;')
2025-04-17 17:50:47.491564 --- ERROR --- DatabaseError(message='syntax error at or near "20"LINE 1: WITH 20 AS passed_id, ^',
payload=(20, 20, 0),
sql='WITH passed_id AS (SELECT %s AS passed_id), cte_item_locations AS ( SELECT DISTINCT ils.location_id FROM test_item_locations ils WHERE ils.part_id = (SELECT passed_id FROM passed_id) ), cte_locations AS ( SELECT DISTINCT locations.zone_id FROM test_locations locations WHERE locations.id IN (SELECT location_id FROM cte_item_locations) )SELECT DISTINCT zone.* FROM cte_locations cilJOIN test_zones zone ON cil.zone_id = zone.idLIMIT %s OFFSET %s;')
2025-04-19 15:30:08.126535 --- ERROR --- DatabaseError(message='duplicate key value violates unique constraint "test_receipts_receipt_id_key"DETAIL: Key (receipt_id)=(SIR-00000012) already exists.',
payload=('SIR-00000012', 'Unresolved', datetime.datetime(2025, 4, 19, 15, 29, 27, 691584), 1, 1, '{}'),
sql='INSERT INTO test_receipts(receipt_id, receipt_status, date_submitted, submitted_by, vendor_id, files) VALUES (%s, %s, %s, %s, %s, %s) RETURNING *;')
2025-04-19 16:06:21.543923 --- ERROR --- DatabaseError(message=''int' object is not iterable',
payload=(25, 0),
sql='SELECT * FROM test_items WHERE row_type = 'list' LIMIT %s OFFSET %s;')
2025-04-19 18:57:04.630250 --- ERROR --- DatabaseError(message='duplicate key value violates unique constraint "test_logistics_info_barcode_key"DETAIL: Key (barcode)=(%6111031005064%) already exists.',
payload=('%6111031005064%', 1, 1, 1, 1),
sql='INSERT INTO test_logistics_info(barcode, primary_location, primary_zone, auto_issue_location, auto_issue_zone) VALUES (%s, %s, %s, %s, %s) RETURNING *;')
2025-04-19 19:30:10.634447 --- ERROR --- DatabaseError(message='syntax error at or near "'total_qoh'"LINE 16: ORDER BY test_items.'total_qoh' ASC ^',
payload=('', 'total_qoh', 50, 200),
sql='WITH sum_cte AS ( SELECT mi.id, SUM(mil.quantity_on_hand)::FLOAT8 AS total_sum FROM test_item_locations mil JOIN test_items mi ON mil.part_id = mi.id GROUP BY mi.id )SELECT test_items.*, row_to_json(test_item_info.*) as item_info, sum_cte.total_sum as total_qoh, (SELECT COALESCE(row_to_json(u), '{}') FROM units as u WHERE u.id=test_item_info.uom) as uomFROM test_itemsLEFT JOIN sum_cte ON test_items.id = sum_cte.idLEFT JOIN test_item_info ON test_items.item_info_id = test_item_info.idWHERE test_items.search_string LIKE '%%' || %s || '%%'ORDER BY test_items.%s ASCLIMIT %s OFFSET %s;')
2025-04-19 19:30:46.113871 --- ERROR --- DatabaseError(message='not all arguments converted during string formatting',
payload=('', 'id', 50, 0),
sql='WITH sum_cte AS ( SELECT mi.id, SUM(mil.quantity_on_hand)::FLOAT8 AS total_sum FROM test_item_locations mil JOIN test_items mi ON mil.part_id = mi.id GROUP BY mi.id )SELECT test_items.*, row_to_json(test_item_info.*) as item_info, sum_cte.total_sum as total_qoh, (SELECT COALESCE(row_to_json(u), '{}') FROM units as u WHERE u.id=test_item_info.uom) as uomFROM test_itemsLEFT JOIN sum_cte ON test_items.id = sum_cte.idLEFT JOIN test_item_info ON test_items.item_info_id = test_item_info.idWHERE test_items.search_string LIKE '%%' || %s || '%%'ORDER BY test_items.? ASCLIMIT %s OFFSET %s;')
2025-04-19 19:32:19.050185 --- ERROR --- DatabaseError(message='syntax error at or near "'id'"LINE 16: ORDER BY test_items.'id' ASC ^',
payload=('', 'id', 50, 0),
sql='WITH sum_cte AS ( SELECT mi.id, SUM(mil.quantity_on_hand)::FLOAT8 AS total_sum FROM test_item_locations mil JOIN test_items mi ON mil.part_id = mi.id GROUP BY mi.id )SELECT test_items.*, row_to_json(test_item_info.*) as item_info, sum_cte.total_sum as total_qoh, (SELECT COALESCE(row_to_json(u), '{}') FROM units as u WHERE u.id=test_item_info.uom) as uomFROM test_itemsLEFT JOIN sum_cte ON test_items.id = sum_cte.idLEFT JOIN test_item_info ON test_items.item_info_id = test_item_info.idWHERE test_items.search_string LIKE '%%' || %s || '%%'ORDER BY test_items.%s ASCLIMIT %s OFFSET %s;')
2025-04-19 19:34:18.066682 --- ERROR --- DatabaseError(message='not all arguments converted during string formatting',
payload=('', 50, 0, 'id'),
sql='WITH sum_cte AS ( SELECT mi.id, SUM(mil.quantity_on_hand)::FLOAT8 AS total_sum FROM test_item_locations mil JOIN test_items mi ON mil.part_id = mi.id GROUP BY mi.id )SELECT test_items.*, row_to_json(test_item_info.*) as item_info, sum_cte.total_sum as total_qoh, (SELECT COALESCE(row_to_json(u), '{}') FROM units as u WHERE u.id=test_item_info.uom) as uomFROM test_itemsLEFT JOIN sum_cte ON test_items.id = sum_cte.idLEFT JOIN test_item_info ON test_items.item_info_id = test_item_info.idWHERE test_items.search_string LIKE '%%' || %s || '%%'ORDER BY test_items.id ASCLIMIT %s OFFSET %s;')
2025-04-19 19:35:13.155663 --- ERROR --- DatabaseError(message='string index out of range',
payload=id,
sql='WITH sum_cte AS ( SELECT mi.id, SUM(mil.quantity_on_hand)::FLOAT8 AS total_sum FROM test_item_locations mil JOIN test_items mi ON mil.part_id = mi.id GROUP BY mi.id )SELECT test_items.*, row_to_json(test_item_info.*) as item_info, sum_cte.total_sum as total_qoh, (SELECT COALESCE(row_to_json(u), '{}') FROM units as u WHERE u.id=test_item_info.uom) as uomFROM test_itemsLEFT JOIN sum_cte ON test_items.id = sum_cte.idLEFT JOIN test_item_info ON test_items.item_info_id = test_item_info.idWHERE test_items.search_string LIKE '%%' || %s || '%%'ORDER BY test_items.id ASCLIMIT %s OFFSET %s;')
2025-04-19 19:35:48.262338 --- ERROR --- DatabaseError(message='string index out of range',
payload=id,
sql='WITH sum_cte AS ( SELECT mi.id, SUM(mil.quantity_on_hand)::FLOAT8 AS total_sum FROM test_item_locations mil JOIN test_items mi ON mil.part_id = mi.id GROUP BY mi.id )SELECT test_items.*, row_to_json(test_item_info.*) as item_info, sum_cte.total_sum as total_qoh, (SELECT COALESCE(row_to_json(u), '{}') FROM units as u WHERE u.id=test_item_info.uom) as uomFROM test_itemsLEFT JOIN sum_cte ON test_items.id = sum_cte.idLEFT JOIN test_item_info ON test_items.item_info_id = test_item_info.idWHERE test_items.search_string LIKE '%%' || %s || '%%'ORDER BY test_items.id ASCLIMIT %s OFFSET %s;')
2025-04-19 19:36:33.407176 --- ERROR --- DatabaseError(message='column test_items.total_qoh does not existLINE 16: ORDER BY test_items.total_qoh ASC ^',
payload=['', 50, 100],
sql='WITH sum_cte AS ( SELECT mi.id, SUM(mil.quantity_on_hand)::FLOAT8 AS total_sum FROM test_item_locations mil JOIN test_items mi ON mil.part_id = mi.id GROUP BY mi.id )SELECT test_items.*, row_to_json(test_item_info.*) as item_info, sum_cte.total_sum as total_qoh, (SELECT COALESCE(row_to_json(u), '{}') FROM units as u WHERE u.id=test_item_info.uom) as uomFROM test_itemsLEFT JOIN sum_cte ON test_items.id = sum_cte.idLEFT JOIN test_item_info ON test_items.item_info_id = test_item_info.idWHERE test_items.search_string LIKE '%%' || %s || '%%'ORDER BY test_items.total_qoh ASCLIMIT %s OFFSET %s;')
2025-04-19 19:37:57.421608 --- ERROR --- DatabaseError(message='column test_items.total_qoh does not existLINE 16: ORDER BY test_items.total_qoh ASC ^',
payload=['', 50, 50],
sql='WITH sum_cte AS ( SELECT mi.id, SUM(mil.quantity_on_hand)::FLOAT8 AS total_sum FROM test_item_locations mil JOIN test_items mi ON mil.part_id = mi.id GROUP BY mi.id )SELECT test_items.*, row_to_json(test_item_info.*) as item_info, sum_cte.total_sum as total_qoh, (SELECT COALESCE(row_to_json(u), '{}') FROM units as u WHERE u.id=test_item_info.uom) as uomFROM test_itemsLEFT JOIN sum_cte ON test_items.id = sum_cte.idLEFT JOIN test_item_info ON test_items.item_info_id = test_item_info.idWHERE test_items.search_string LIKE '%%' || %s || '%%'ORDER BY test_items.total_qoh ASCLIMIT %s OFFSET %s;')
2025-04-19 20:14:49.445587 --- ERROR --- DatabaseError(message='syntax error at or near "item_name"LINE 16: ORDER BY main_items.id item_name ^',
payload=['', 50, 0],
sql='WITH sum_cte AS ( SELECT mi.id, SUM(mil.quantity_on_hand)::FLOAT8 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.*, row_to_json(main_item_info.*) as item_info, sum_cte.total_sum as total_qoh, (SELECT COALESCE(row_to_json(u), '{}') FROM units as u WHERE u.id=main_item_info.uom) as uomFROM main_itemsLEFT JOIN sum_cte ON main_items.id = sum_cte.idLEFT JOIN main_item_info ON main_items.item_info_id = main_item_info.idWHERE main_items.search_string LIKE '%%' || %s || '%%'ORDER BY main_items.id item_nameLIMIT %s OFFSET %s;')
2025-04-19 20:16:09.867711 --- ERROR --- DatabaseError(message='syntax error at or near "total_qoh"LINE 16: ORDER BY main_items.id total_qoh ^',
payload=['', 50, 0],
sql='WITH sum_cte AS ( SELECT mi.id, SUM(mil.quantity_on_hand)::FLOAT8 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.*, row_to_json(main_item_info.*) as item_info, sum_cte.total_sum as total_qoh, (SELECT COALESCE(row_to_json(u), '{}') FROM units as u WHERE u.id=main_item_info.uom) as uomFROM main_itemsLEFT JOIN sum_cte ON main_items.id = sum_cte.idLEFT JOIN main_item_info ON main_items.item_info_id = main_item_info.idWHERE main_items.search_string LIKE '%%' || %s || '%%'ORDER BY main_items.id total_qohLIMIT %s OFFSET %s;')
2025-04-19 20:16:38.335617 --- ERROR --- DatabaseError(message='syntax error at or near "item_name"LINE 16: ORDER BY main_items.id item_name ^',
payload=['', 50, 0],
sql='WITH sum_cte AS ( SELECT mi.id, SUM(mil.quantity_on_hand)::FLOAT8 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.*, row_to_json(main_item_info.*) as item_info, sum_cte.total_sum as total_qoh, (SELECT COALESCE(row_to_json(u), '{}') FROM units as u WHERE u.id=main_item_info.uom) as uomFROM main_itemsLEFT JOIN sum_cte ON main_items.id = sum_cte.idLEFT JOIN main_item_info ON main_items.item_info_id = main_item_info.idWHERE main_items.search_string LIKE '%%' || %s || '%%'ORDER BY main_items.id item_nameLIMIT %s OFFSET %s;')
2025-04-20 09:54:33.948670 --- ERROR --- DatabaseError(message=''int' object is not iterable',
payload=(),
sql='SELECT * FROM sites')

View File

@ -1597,7 +1597,10 @@ def getItemsWithQOH(conn, site, payload, convert=True):
recordset = []
count = 0
with open(f"sql/SELECT/getItemsWithQOH.sql", "r+") as file:
sql = file.read().replace("%%site_name%%", site)
sql = file.read().replace("%%site_name%%", site).replace("%%sort_order%%", payload[3])
payload = list(payload)
payload.pop(3)
try:
if convert:
with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur:

View File

@ -6,6 +6,7 @@ from threading import Thread
from queue import Queue
import time, process
from user_api import login_required
import webpush
external_api = Blueprint('external', __name__)
@ -22,7 +23,6 @@ def getItemLocations():
database_config = config()
with psycopg2.connect(**database_config) as conn:
recordset, count = database.getItemLocations(conn, site_name, (item_id, limit, offset), convert=True)
print(count)
return jsonify({"locations":recordset, "end":math.ceil(count/limit), "error":False, "message":"item fetched succesfully!"})
return jsonify({"locations":recordset, "end": math.ceil(count/limit), "error":True, "message":"There was an error with this GET statement"})
@ -47,8 +47,6 @@ def getItemBarcode():
database_config = config()
with psycopg2.connect(**database_config) as conn:
record = database.getItemAllByBarcode(conn, site_name, (item_barcode, ), convert=True)
print(record)
if record == {}:
return jsonify({"item":None, "error":True, "message":"Item either does not exist or there was a larger problem!"})
else:
@ -116,6 +114,7 @@ def post_receipt():
data=item['item']['data']
)
database.insertReceiptItemsTuple(conn, site_name, receipt_item.payload())
#webpush.push_notifications('New Receipt', f"Receipt {receipt['receipt_id']} was added to Site -> {site_name}!")
webpush.push_ntfy('New Receipt', f"Receipt {receipt['receipt_id']} was added to Site -> {site_name}!")
return jsonify({"error":False, "message":"Transaction Complete!"})
return jsonify({"error":True, "message":"There was an error with this POST statement"})

View File

@ -0,0 +1,3 @@
VAPID_PUBLIC_KEY = "BIbaHOqZcTunzAeb9p1hCWBo1DeJN0NVf2WVNSrsLZ2e50vhBno5dJuRAB1NLNXIeeQYr_x-1fSifJBGfUKd6QM"
VAPID_PRIVATE_KEY = "l2wLDKWJDMkytDSN8s9iu9Y3-5qrIMy_OreUC3vDHtI"
VAPID_CLAIM_EMAIL = "jadowyne.ulve@outlook.com"

View File

@ -63,14 +63,20 @@ def pagninate_items():
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 10))
search_string = str(request.args.get('search_text', ""))
sort_order = request.args.get('sort_order', "")
sort = request.args.get('sort', "")
order = request.args.get('order', "")
view = request.args.get('view', "")
site_name = session['selected_site']
offset = (page - 1) * limit
if sort == 'total_qoh':
sort_order = f"{sort} {order}"
else:
sort_order = f"{site_name}_items.{sort} {order}"
print(sort_order)
database_config = config()
with psycopg2.connect(**database_config) as conn:
pantry_inventory, count = database.getItemsWithQOH(conn, site_name, (search_string, limit, offset), convert=True)
pantry_inventory, count = database.getItemsWithQOH(conn, site_name, (search_string, limit, offset, sort_order), convert=True)
return jsonify({'items': pantry_inventory, "end": math.ceil(count['count']/limit), 'error':False, 'message': 'Items Loaded Successfully!'})
return jsonify({'items': pantry_inventory, "end": math.ceil(count['count']/limit), 'error':True, 'message': 'There was a problem loading the items!'})
@ -106,7 +112,7 @@ def getModalPrefixes():
database_config = config()
with psycopg2.connect(**database_config) as conn:
payload = (limit, offset)
recordset, count = postsqldb.SKUPrefixTable.getPrefixes(conn, site_name, payload, convert=True)
recordset, count = postsqldb.SKUPrefixTable.paginatePrefixes(conn, site_name, payload, convert=True)
return jsonify({"prefixes":recordset, "end":math.ceil(count/limit), "error":False, "message":"items fetched succesfully!"})
return jsonify({"prefixes":recordset, "end":math.ceil(count/limit), "error":True, "message":"There was an error with this GET statement"})
@ -126,9 +132,46 @@ def getZones():
print(count, len(zones))
return jsonify(zones=zones, endpage=math.ceil(count[0]/limit))
@items_api.route('/item/getZonesBySku', methods=["GET"])
def getZonesbySku():
if request.method == "GET":
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 1))
item_id = int(request.args.get('item_id'))
database_config = config()
site_name = session['selected_site']
zones = []
offset = (page - 1) * limit
payload = (item_id, limit, offset)
count = 0
with psycopg2.connect(**database_config) as conn:
zones, count = postsqldb.ZonesTable.paginateZonesBySku(conn, site_name, payload)
print(zones, count)
return jsonify(zones=zones, endpage=math.ceil(count/limit))
@items_api.route('/item/getLocationsBySkuZone', methods=['get'])
def getLocationsBySkuZone():
zone_id = int(request.args.get('zone_id', 1))
part_id = int(request.args.get('part_id', 1))
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 1))
offset = (page-1)*limit
database_config = config()
site_name = session['selected_site']
locations = []
count=0
with psycopg2.connect(**database_config) as conn:
payload = (part_id, zone_id, limit, offset)
locations, count = postsqldb.LocationsTable.paginateLocationsBySkuZone(conn, site_name, payload)
return jsonify(locations=locations, endpage=math.ceil(count/limit))
@items_api.route('/item/getLocations', methods=['get'])
def getLocationsByZone():
zone_id = int(request.args.get('id', 1))
zone_id = int(request.args.get('zone_id', 1))
part_id = int(request.args.get('part_id', 1))
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 1))
@ -248,6 +291,19 @@ def updateItemLink():
return jsonify(error=True, message="Unable to save this change, ERROR!")
@items_api.route('/item/getPossibleLocations', methods=["GET"])
@login_required
def getPossibleLocations():
if request.method == "GET":
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 1))
offset = (page-1)*limit
database_config = config()
site_name = session['selected_site']
with psycopg2.connect(**database_config) as conn:
locations, count = postsqldb.LocationsTable.paginateLocationsWithZone(conn, site_name, (limit, offset))
return jsonify(locations=locations, end=math.ceil(count/limit))
@items_api.route('/item/getLinkedItem', methods=["GET"])
@login_required
def getLinkedItem():
@ -472,3 +528,19 @@ def refreshSearchString():
return jsonify(error=False, message="Search String was updated successfully")
return jsonify(error=True, message="Unable to update this search string, ERROR!")
@items_api.route('/item/postNewItemLocation', methods=['POST'])
def postNewItemLocation():
if request.method == "POST":
item_id = request.get_json()['item_id']
location_id = request.get_json()['location_id']
database_config = config()
site_name = session['selected_site']
with psycopg2.connect(**database_config) as conn:
item_location = postsqldb.ItemLocationsTable.Payload(
item_id,
location_id
)
postsqldb.ItemLocationsTable.insert_tuple(conn, site_name, item_location.payload())
return jsonify(error=False, message="Location was added successfully")
return jsonify(error=True, message="Unable to save this location, ERROR!")

View File

@ -383,24 +383,7 @@ class SKUPrefixTable:
raise DatabaseError(error, 'PrefixTable', sql)
@classmethod
def insert_tuple(self, conn, site: str, payload: list, convert=True):
record = ()
with open(f"sql/INSERT/insertSKUPrefixTuple.sql", "r+") as file:
sql = file.read().replace("%%site_name%%", site)
try:
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchone()
if rows and convert:
record = tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
record = rows
except Exception as error:
raise DatabaseError(error, payload, sql)
return record
@classmethod
def getPrefixes(self, conn, site: str, payload: tuple, convert=True):
def paginatePrefixes(self, conn, site: str, payload: tuple, convert=True):
"""_summary_
Args:
@ -434,6 +417,71 @@ class SKUPrefixTable:
raise DatabaseError(error, payload, sql)
return recordset, count
@classmethod
def insert_tuple(self, conn, site, payload, convert=True):
"""insert payload into zones table of site
Args:
conn (_T_connector@connect): Postgresql Connector
site (str):
payload (tuple): (name[str],)
convert (bool, optional): Determines if to return tuple as dictionary. Defaults to False.
Raises:
DatabaseError:
Returns:
tuple or dict: inserted tuple
"""
prefix = ()
with open(f"sql/INSERT/insertSKUPrefixTuple.sql", "r+") as file:
sql = file.read().replace("%%site_name%%", site)
try:
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchone()
if rows and convert:
prefix = tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
prefix = rows
except Exception as error:
raise DatabaseError(error, payload, sql)
return prefix
@classmethod
def update_tuple(self, conn, site, payload, convert=True):
"""_summary_
Args:
conn (_T_connector@connect): Postgresql Connector
site (str):
table (str):
payload (dict): {'id': row_id, 'update': {... column_to_update: value_to_update_to...}}
convert (bool, optional): determines if to return tuple as dictionary. Defaults to False.
Raises:
DatabaseError:
Returns:
tuple or dict: updated tuple
"""
updated = ()
set_clause, values = updateStringFactory(payload['update'])
values.append(payload['id'])
sql = f"UPDATE {site}_sku_prefix SET {set_clause} WHERE id=%s RETURNING *;"
try:
with conn.cursor() as cur:
cur.execute(sql, values)
rows = cur.fetchone()
if rows and convert:
updated = tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
updated = rows
except Exception as error:
raise DatabaseError(error, payload, sql)
return updated
class RecipesTable:
@dataclass
class Payload:
@ -729,8 +777,35 @@ class RecipesTable:
raise DatabaseError(error, payload, sql)
return updated
class ItemInfoTable:
@dataclass
class Payload:
barcode: str
packaging: str = ""
uom_quantity: float = 1.0
uom: int = 1
cost: float = 0.0
safety_stock: float = 0.0
lead_time_days: float = 0.0
ai_pick: bool = False
prefixes: list = field(default_factory=list)
def __post_init__(self):
if not isinstance(self.barcode, str):
raise TypeError(f"barcode must be of type str; not {type(self.barcode)}")
def payload(self):
return (
self.barcode,
self.packaging,
self.uom_quantity,
self.uom,
self.cost,
self.safety_stock,
self.lead_time_days,
self.ai_pick,
lst2pgarr(self.prefixes)
)
@classmethod
def select_tuple(self, conn, site:str, payload:tuple, convert=True):
"""_summary_
@ -795,9 +870,31 @@ class ItemInfoTable:
raise DatabaseError(error, payload, sql)
return updated
class ItemTable:
@classmethod
def paginateLinkedLists(self, conn, site:str, payload:tuple, convert=True):
records = []
count = 0
sql = f"SELECT * FROM {site}_items WHERE row_type = 'list' LIMIT %s OFFSET %s;"
sql_count = f"SELECT COUNT(*) FROM {site}_items WHERE row_type = 'list' LIMIT %s OFFSET %s;"
try:
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchall()
if rows and convert:
records = [tupleDictionaryFactory(cur.description, row) for row in rows]
if rows and not convert:
records = rows
cur.execute(sql_count, payload)
count = cur.fetchone()[0]
except (Exception, psycopg2.DatabaseError) as error:
raise DatabaseError(error, payload, sql)
return records, count
@classmethod
def getItemAllByID(self, conn, site, payload, convert=True):
"""_summary_
@ -829,6 +926,49 @@ class ItemTable:
raise DatabaseError(error, payload, getItemAllByID_sql)
return item
@classmethod
def getLinkedItemByBarcode(self, conn, site, payload, convert=True):
item = ()
sql = f"SELECT * FROM {site}_itemlinks WHERE barcode=%s;"
if convert:
item = {}
try:
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchone()
if rows and convert:
item = tupleDictionaryFactory(cur.description, rows)
if rows and not convert:
item = rows
except (Exception, psycopg2.DatabaseError) as error:
raise DatabaseError(error, payload, sql)
return item
@classmethod
def getItemAllByBarcode(self, conn, site, payload, convert=True):
item = ()
if convert:
item = {}
linked_item = self.getLinkedItemByBarcode(conn, site, (payload[0],))
if len(linked_item) > 1:
item = self.getItemAllByID(conn, site, payload=(linked_item['link'], ), convert=convert)
item['item_info']['uom_quantity'] = linked_item['conv_factor']
else:
with open(f"sql/SELECT/getItemAllByBarcode.sql", "r+") as file:
getItemAllByBarcode_sql = file.read().replace("%%site_name%%", site)
try:
with conn.cursor() as cur:
cur.execute(getItemAllByBarcode_sql, payload)
rows = cur.fetchone()
if rows and convert:
item = tupleDictionaryFactory(cur.description, rows)
if rows and not convert:
item = rows
except (Exception, psycopg2.DatabaseError) as error:
raise DatabaseError(error, payload, getItemAllByBarcode_sql)
return item
@classmethod
def update_tuple(self, conn, site, payload, convert=True):
"""_summary_
@ -899,6 +1039,40 @@ class ReceiptTable:
raise DatabaseError(error, payload, sql)
return updated
@classmethod
def update_receipt_item(self, conn, site:str, payload:dict, convert=True):
"""_summary_
Args:
conn (_T_connector@connect): Postgresql Connector
site (str):
table (str):
payload (dict): {'id': row_id, 'update': {... column_to_update: value_to_update_to...}}
convert (bool, optional): determines if to return tuple as dictionary. Defaults to False.
Raises:
DatabaseError:
Returns:
tuple or dict: updated tuple
"""
updated = ()
set_clause, values = updateStringFactory(payload['update'])
values.append(payload['id'])
sql = f"UPDATE {site}_receipt_items SET {set_clause} WHERE id=%s RETURNING *;"
try:
with conn.cursor() as cur:
cur.execute(sql, values)
rows = cur.fetchone()
if rows and convert:
updated = tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
updated = rows
except Exception as error:
raise DatabaseError(error, payload, sql)
return updated
@classmethod
def select_tuple(self, conn, site:str, payload:tuple, convert=True):
"""_summary_
@ -928,3 +1102,714 @@ class ReceiptTable:
except Exception as error:
raise DatabaseError(error, payload, sql)
return selected
@classmethod
def select_item_tuple(self, conn, site:str, payload:tuple, convert=True):
"""_summary_
Args:
conn (_type_): _description_
site (_type_): _description_
payload (_type_): (receipt_id,)
convert (bool, optional): _description_. Defaults to True.
Raises:
DatabaseError: _description_
Returns:
_type_: _description_
"""
selected = ()
sql = f"SELECT * FROM {site}_receipt_items WHERE id=%s;"
try:
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchone()
if rows and convert:
selected = tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
selected = rows
except Exception as error:
raise DatabaseError(error, payload, sql)
return selected
class ZonesTable:
@dataclass
class Payload:
name: str
site_id: int
description: str = ""
def __post_init__(self):
if not isinstance(self.name, str):
raise TypeError(f"Zone name should be of type str; not {type(self.name)}")
def payload(self):
return (
self.name,
self.description,
self.site_id
)
@classmethod
def insert_tuple(self, conn, site, payload, convert=True):
"""insert payload into zones table of site
Args:
conn (_T_connector@connect): Postgresql Connector
site (str):
payload (tuple): (name[str],)
convert (bool, optional): Determines if to return tuple as dictionary. Defaults to False.
Raises:
DatabaseError:
Returns:
tuple or dict: inserted tuple
"""
zone = ()
with open(f"sql/INSERT/insertZonesTuple.sql", "r+") as file:
sql = file.read().replace("%%site_name%%", site)
try:
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchone()
if rows and convert:
zone = tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
zone = rows
except Exception as error:
raise DatabaseError(error, payload, sql)
return zone
@classmethod
def update_tuple(self, conn, site, payload, convert=True):
"""_summary_
Args:
conn (_T_connector@connect): Postgresql Connector
site (str):
table (str):
payload (dict): {'id': row_id, 'update': {... column_to_update: value_to_update_to...}}
convert (bool, optional): determines if to return tuple as dictionary. Defaults to False.
Raises:
DatabaseError:
Returns:
tuple or dict: updated tuple
"""
updated = ()
set_clause, values = updateStringFactory(payload['update'])
values.append(payload['id'])
sql = f"UPDATE {site}_zones SET {set_clause} WHERE id=%s RETURNING *;"
try:
with conn.cursor() as cur:
cur.execute(sql, values)
rows = cur.fetchone()
if rows and convert:
updated = tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
updated = rows
except Exception as error:
raise DatabaseError(error, payload, sql)
return updated
@classmethod
def paginateZones(self, conn, site:str, payload:tuple, convert=True):
"""_summary_
Args:
conn (_type_): _description_
site (str): _description_
payload (tuple): (limit, offset)
convert (bool, optional): _description_. Defaults to True.
Raises:
DatabaseError: _description_
Returns:
_type_: _description_
"""
recordset = ()
count = 0
sql = f"SELECT * FROM {site}_zones LIMIT %s OFFSET %s;"
try:
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchall()
if rows and convert:
recordset = [tupleDictionaryFactory(cur.description, row) for row in rows]
elif rows and not convert:
recordset = rows
cur.execute(f"SELECT COUNT(*) FROM {site}_zones;")
count = cur.fetchone()[0]
except Exception as error:
raise DatabaseError(error, (), sql)
return recordset, count
@classmethod
def paginateZonesBySku(self, conn, site: str, payload: tuple, convert=True):
zones = ()
count = 0
with open(f"sql/SELECT/zones/paginateZonesBySku.sql", "r+") as file:
sql = file.read().replace("%%site_name%%", site)
with open(f"sql/SELECT/zones/paginateZonesBySkuCount.sql", "r+") as file:
sql_count = file.read().replace("%%site_name%%", site)
try:
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchall()
if rows and convert:
zones = [tupleDictionaryFactory(cur.description, row) for row in rows]
elif rows and not convert:
zones = rows
cur.execute(sql_count, payload)
count = cur.fetchone()[0]
except Exception as error:
raise DatabaseError(error, payload, sql)
return zones, count
class LocationsTable:
@dataclass
class Payload:
uuid: str
name: str
zone_id: int
def __post_init__(self):
if not isinstance(self.uuid, str):
raise TypeError(f"uuid must be of type str; not {type(self.uuid)}")
if not isinstance(self.name, str):
raise TypeError(f"Location name must be of type str; not {type(self.name)}")
if not isinstance(self.zone_id, int):
raise TypeError(f"zone_id must be of type str; not {type(self.zone_id)}")
def payload(self):
return (
self.uuid,
self.name,
self.zone_id
)
@classmethod
def paginateLocations(self, conn, site:str, payload:tuple, convert=True):
"""_summary_
Args:
conn (_type_): _description_
site (str): _description_
payload (tuple): (limit, offset)
convert (bool, optional): _description_. Defaults to True.
Raises:
DatabaseError: _description_
Returns:
_type_: _description_
"""
recordset = ()
count = 0
sql = f"SELECT * FROM {site}_locations LIMIT %s OFFSET %s;"
try:
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchall()
if rows and convert:
recordset = [tupleDictionaryFactory(cur.description, row) for row in rows]
elif rows and not convert:
recordset = rows
cur.execute(f"SELECT COUNT(*) FROM {site}_locations;")
count = cur.fetchone()[0]
except Exception as error:
raise DatabaseError(error, (), sql)
return recordset, count
@classmethod
def paginateLocationsWithZone(self, conn, site:str, payload:tuple, convert=True):
"""_summary_
Args:
conn (_type_): _description_
site (str): _description_
payload (tuple): (limit, offset)
convert (bool, optional): _description_. Defaults to True.
Raises:
DatabaseError: _description_
Returns:
_type_: _description_
"""
recordset = ()
count = 0
with open(f"sql/SELECT/getLocationsWithZone.sql", "r+") as file:
sql = file.read().replace("%%site_name%%", site)
try:
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchall()
if rows and convert:
recordset = [tupleDictionaryFactory(cur.description, row) for row in rows]
elif rows and not convert:
recordset = rows
cur.execute(f"SELECT COUNT(*) FROM {site}_locations;")
count = cur.fetchone()[0]
except Exception as error:
raise DatabaseError(error, (), sql)
return recordset, count
@classmethod
def paginateLocationsBySkuZone(self, conn, site: str, payload: tuple, convert=True):
"""_summary_
Args:
conn (_type_): _description_
site (str): _description_
payload (tuple): (item_id, zone_id, limit, offset)
convert (bool, optional): _description_. Defaults to True.
Raises:
DatabaseError: _description_
Returns:
_type_: _description_
"""
locations = ()
count = 0
with open(f"sql/SELECT/locations/paginateLocationsBySkuZone.sql", "r+") as file:
sql = file.read().replace("%%site_name%%", site)
with open(f"sql/SELECT/locations/paginateLocationsBySkuZoneCount.sql", "r+") as file:
sql_count = file.read().replace("%%site_name%%", site)
try:
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchall()
if rows and convert:
locations = [tupleDictionaryFactory(cur.description, row) for row in rows]
elif rows and not convert:
locations = rows
cur.execute(sql_count, payload)
count = cur.fetchone()[0]
except Exception as error:
raise DatabaseError(error, payload, sql)
return locations, count
@classmethod
def insert_tuple(self, conn, site, payload, convert=True):
"""insert payload into zones table of site
Args:
conn (_T_connector@connect): Postgresql Connector
site (str):
payload (tuple): (name[str],)
convert (bool, optional): Determines if to return tuple as dictionary. Defaults to False.
Raises:
DatabaseError:
Returns:
tuple or dict: inserted tuple
"""
zone = ()
with open(f"sql/INSERT/insertLocationsTuple.sql", "r+") as file:
sql = file.read().replace("%%site_name%%", site)
try:
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchone()
if rows and convert:
zone = tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
zone = rows
except Exception as error:
raise DatabaseError(error, payload, sql)
return zone
class VendorsTable:
@dataclass
class Payload:
vendor_name: str
created_by: int
vendor_address: str = ""
creation_date: datetime.datetime = field(init=False)
phone_number: str = ""
def __post_init__(self):
if not isinstance(self.vendor_name, str):
raise TypeError(f"vendor_name should be of type str; not {type(self.vendor_name)}")
self.creation_date = datetime.datetime.now()
def payload(self):
return (
self.vendor_name,
self.vendor_address,
self.creation_date,
self.created_by,
self.phone_number
)
@classmethod
def paginateVendors(self, conn, site:str, payload:tuple, convert=True):
"""_summary_
Args:
conn (_type_): _description_
site (str): _description_
payload (tuple): (limit, offset)
convert (bool, optional): _description_. Defaults to True.
Raises:
DatabaseError: _description_
Returns:
_type_: _description_
"""
recordset = ()
count = 0
sql = f"SELECT * FROM {site}_vendors LIMIT %s OFFSET %s;"
try:
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchall()
if rows and convert:
recordset = [tupleDictionaryFactory(cur.description, row) for row in rows]
elif rows and not convert:
recordset = rows
cur.execute(f"SELECT COUNT(*) FROM {site}_vendors;")
count = cur.fetchone()[0]
except Exception as error:
raise DatabaseError(error, (), sql)
return recordset, count
@classmethod
def insert_tuple(self, conn, site, payload, convert=True):
"""insert payload into zones table of site
Args:
conn (_T_connector@connect): Postgresql Connector
site (str):
payload (tuple): (name[str],)
convert (bool, optional): Determines if to return tuple as dictionary. Defaults to False.
Raises:
DatabaseError:
Returns:
tuple or dict: inserted tuple
"""
zone = ()
with open(f"sql/INSERT/insertVendorsTuple.sql", "r+") as file:
sql = file.read().replace("%%site_name%%", site)
try:
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchone()
if rows and convert:
zone = tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
zone = rows
except Exception as error:
raise DatabaseError(error, payload, sql)
return zone
@classmethod
def update_tuple(self, conn, site, payload, convert=True):
"""_summary_
Args:
conn (_T_connector@connect): Postgresql Connector
site (str):
table (str):
payload (dict): {'id': row_id, 'update': {... column_to_update: value_to_update_to...}}
convert (bool, optional): determines if to return tuple as dictionary. Defaults to False.
Raises:
DatabaseError:
Returns:
tuple or dict: updated tuple
"""
updated = ()
set_clause, values = updateStringFactory(payload['update'])
values.append(payload['id'])
sql = f"UPDATE {site}_vendors SET {set_clause} WHERE id=%s RETURNING *;"
try:
with conn.cursor() as cur:
cur.execute(sql, values)
rows = cur.fetchone()
if rows and convert:
updated = tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
updated = rows
except Exception as error:
raise DatabaseError(error, payload, sql)
return updated
class BrandsTable:
@dataclass
class Payload:
name: str
def __post_init__(self):
if not isinstance(self.name, str):
return TypeError(f"brand name should be of type str; not {type(self.name)}")
def payload(self):
return (
self.name,
)
@classmethod
def paginateBrands(self, conn, site:str, payload:tuple, convert=True):
"""_summary_
Args:
conn (_type_): _description_
site (str): _description_
payload (tuple): (limit, offset)
convert (bool, optional): _description_. Defaults to True.
Raises:
DatabaseError: _description_
Returns:
_type_: _description_
"""
recordset = ()
count = 0
sql = f"SELECT * FROM {site}_brands LIMIT %s OFFSET %s;"
try:
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchall()
if rows and convert:
recordset = [tupleDictionaryFactory(cur.description, row) for row in rows]
elif rows and not convert:
recordset = rows
cur.execute(f"SELECT COUNT(*) FROM {site}_brands;")
count = cur.fetchone()[0]
except Exception as error:
raise DatabaseError(error, (), sql)
return recordset, count
@classmethod
def insert_tuple(self, conn, site, payload, convert=True):
"""insert payload into zones table of site
Args:
conn (_T_connector@connect): Postgresql Connector
site (str):
payload (tuple): (name[str],)
convert (bool, optional): Determines if to return tuple as dictionary. Defaults to False.
Raises:
DatabaseError:
Returns:
tuple or dict: inserted tuple
"""
brand = ()
with open(f"sql/INSERT/insertBrandsTuple.sql", "r+") as file:
sql = file.read().replace("%%site_name%%", site)
try:
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchone()
if rows and convert:
brand = tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
brand = rows
except Exception as error:
raise DatabaseError(error, payload, sql)
return brand
@classmethod
def update_tuple(self, conn, site, payload, convert=True):
"""_summary_
Args:
conn (_T_connector@connect): Postgresql Connector
site (str):
table (str):
payload (dict): {'id': row_id, 'update': {... column_to_update: value_to_update_to...}}
convert (bool, optional): determines if to return tuple as dictionary. Defaults to False.
Raises:
DatabaseError:
Returns:
tuple or dict: updated tuple
"""
updated = ()
set_clause, values = updateStringFactory(payload['update'])
values.append(payload['id'])
sql = f"UPDATE {site}_brands SET {set_clause} WHERE id=%s RETURNING *;"
try:
with conn.cursor() as cur:
cur.execute(sql, values)
rows = cur.fetchone()
if rows and convert:
updated = tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
updated = rows
except Exception as error:
raise DatabaseError(error, payload, sql)
return updated
class ItemLocationsTable:
@dataclass
class Payload:
part_id: int
location_id: int
quantity_on_hand: float = 0.0
cost_layers: list = field(default_factory=list)
def __post_init__(self):
if not isinstance(self.part_id, int):
raise TypeError(f"part_id must be of type int; not {type(self.part_id)}")
if not isinstance(self.location_id, int):
raise TypeError(f"part_id must be of type int; not {type(self.part_id)}")
def payload(self):
return (
self.part_id,
self.location_id,
self.quantity_on_hand,
lst2pgarr(self.cost_layers)
)
@classmethod
def insert_tuple(self, conn, site, payload, convert=True):
"""insert payload into zones table of site
Args:
conn (_T_connector@connect): Postgresql Connector
site (str):
payload (tuple): (name[str],)
convert (bool, optional): Determines if to return tuple as dictionary. Defaults to False.
Raises:
DatabaseError:
Returns:
tuple or dict: inserted tuple
"""
item_location = ()
with open(f"sql/INSERT/insertItemLocationsTuple.sql", "r+") as file:
sql = file.read().replace("%%site_name%%", site)
try:
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchone()
if rows and convert:
item_location = tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
item_location = rows
except Exception as error:
raise DatabaseError(error, payload, sql)
return item_location
@classmethod
def select_by_id(self, conn, site: str, payload: tuple, convert=True):
item_locations = ()
with open(f"sql/SELECT/selectItemLocations", "r+") as file:
sql = file.read().replace("%%site_name%%", site)
try:
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchall()
if rows and convert:
item_locations = [tupleDictionaryFactory(cur.description, row) for row in rows]
elif rows and not convert:
item_locations = rows
except Exception as error:
raise DatabaseError(error, payload, sql)
return item_locations
class ItemLinksTable:
@dataclass
class Payload:
barcode: str
link: int
data: dict = field(default_factory=dict)
conv_factor: float = 1
def __post_init__(self):
if not isinstance(self.barcode, str):
raise TypeError(f"barcode must be of type str; not {type(self.barocde)}")
if not isinstance(self.link, int):
raise TypeError(f"link must be of type str; not {type(self.link)}")
def payload(self):
return (
self.barcode,
self.link,
json.dumps(self.data),
self.conv_factor
)
@classmethod
def insert_tuple(self, conn, site:str, payload:tuple, convert=True):
"""insert payload into itemlinks table of site
Args:
conn (_T_connector@connect): Postgresql Connector
site (str):
payload (tuple): (barcode[str], link[int], data[jsonb], conv_factor[float])
convert (bool, optional): Determines if to return tuple as dictionary. Defaults to False.
Raises:
DatabaseError:
Returns:
tuple or dict: inserted tuple
"""
link = ()
with open(f"sql/INSERT/insertItemLinksTuple.sql", "r+") as file:
sql = file.read().replace("%%site_name%%", site)
try:
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchone()
if rows and convert:
link = tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
link = rows
except Exception as error:
raise DatabaseError(error, payload, sql)
return link
class CycleCountsTable:
@dataclass
class Payload:
pass
class SitesTable:
@classmethod
def selectTuples(self, conn, convert=True):
recordsets = []
sql = f"SELECT * FROM sites"
try:
with conn.cursor() as cur:
cur.execute(sql)
rows = cur.fetchall()
if rows and convert:
recordsets = [tupleDictionaryFactory(cur.description, row) for row in rows]
elif rows and not convert:
recordsets = rows
except Exception as error:
raise DatabaseError(error, (), sql)
return recordsets

View File

@ -1,5 +1,6 @@
import database, MyDataclasses, psycopg2, datetime,json
from config import config
import postsqldb
def dropSiteTables(conn, site_manager: MyDataclasses.SiteManager):
try:
@ -148,7 +149,7 @@ def postNewBlankItem(conn, site_name: str, user_id: int, data: dict):
)
# create item info
item_info = MyDataclasses.ItemInfoPayload(data['barcode'])
item_info = postsqldb.ItemInfoTable.Payload(data['barcode'])
# create Food Info
food_info = MyDataclasses.FoodInfoPayload()
@ -190,7 +191,7 @@ def postNewBlankItem(conn, site_name: str, user_id: int, data: dict):
location_id = cur.fetchone()[0]
item_location = MyDataclasses.ItemLocationPayload(item['id'], location_id)
item_location = postsqldb.ItemLocationsTable.Payload(item['id'], location_id)
database.insertItemLocationsTuple(conn, site_name, item_location.payload())

View File

@ -6,6 +6,7 @@ import openfoodfacts
import postsqldb
import mimetypes, os
import pymupdf, PIL
import webpush
def create_pdf_preview(pdf_path, output_path, size=(600, 400)):
@ -53,6 +54,38 @@ def getItems():
return jsonify({"items":recordset, "end":math.ceil(count['count']/limit), "error":False, "message":"items fetched succesfully!"})
return jsonify({"items":recordset, "end":math.ceil(count['count']/limit), "error":True, "message":"There was an error with this GET statement"})
@receipt_api.route('/receipt/getVendors', methods=["GET"])
def getVendors():
recordset = []
count = 0
if request.method == "GET":
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 10))
site_name = session['selected_site']
offset = (page - 1) * limit
database_config = config()
with psycopg2.connect(**database_config) as conn:
payload = (limit, offset)
recordset, count = postsqldb.VendorsTable.paginateVendors(conn, site_name, payload)
return jsonify({"vendors":recordset, "end":math.ceil(count/limit), "error":False, "message":"items fetched succesfully!"})
return jsonify({"vendors":recordset, "end":math.ceil(count/limit), "error":True, "message":"There was an error with this GET statement"})
@receipt_api.route('/receipt/getLinkedLists', methods=["GET"])
def getLinkedLists():
recordset = []
count = 0
if request.method == "GET":
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 10))
site_name = session['selected_site']
offset = (page - 1) * limit
database_config = config()
with psycopg2.connect(**database_config) as conn:
payload = (limit, offset)
recordset, count = postsqldb.ItemTable.paginateLinkedLists(conn, site_name, payload)
return jsonify({"items":recordset, "end":math.ceil(count/limit), "error":False, "message":"items fetched succesfully!"})
return jsonify({"items":recordset, "end":math.ceil(count/limit), "error":True, "message":"There was an error with this GET statement"})
@receipt_api.route('/receipts/getReceipts', methods=["GET"])
def getReceipts():
recordset = []
@ -159,6 +192,64 @@ def saveLine():
return jsonify({'error': False, "message": "Line Saved Succesfully"})
return jsonify({'error': True, "message": "Something went wrong while saving line!"})
@receipt_api.route('/receipt/postLinkedItem', methods=["POST"])
def postLinkedItem():
if request.method == "POST":
receipt_item_id = int(request.get_json()['receipt_item_id'])
link_list_id = int(request.get_json()['link_list_id'])
conv_factor = float(request.get_json()['conv_factor'])
site_name = session['selected_site']
user_id = session['user_id']
database_config = config()
with psycopg2.connect(**database_config) as conn:
receipt_item = postsqldb.ReceiptTable.select_item_tuple(conn, site_name, (receipt_item_id,))
# get link list item
linked_list = postsqldb.ItemTable.getItemAllByID(conn, site_name, (link_list_id, ))
# add item to database
if receipt_item['type'] == 'api':
data = {
'barcode': receipt_item['barcode'],
'name': receipt_item['name'],
'subtype': 'FOOD'
}
process.postNewBlankItem(conn, site_name, user_id, data)
name = receipt_item['name']
if receipt_item['name'] == "unknown":
name = linked_list['item_name']
if receipt_item['type'] == "new sku":
data = {
'barcode': receipt_item['barcode'],
'name': name,
'subtype': 'FOOD'
}
process.postNewBlankItem(conn, site_name, user_id, data)
new_item = postsqldb.ItemTable.getItemAllByBarcode(conn, site_name, (receipt_item['barcode'], ))
new_item = postsqldb.ItemTable.update_tuple(conn, site_name, {'id': new_item['id'], 'update':{'row_type': 'link'}})
# add item to link list
item_link = postsqldb.ItemLinksTable.Payload(
new_item['barcode'],
linked_list['id'],
new_item,
conv_factor
)
postsqldb.ItemLinksTable.insert_tuple(conn, site_name, item_link.payload())
# update line item with link list name and item_link with link list id
payload = {'id': receipt_item['id'], 'update': {
'barcode': linked_list['barcode'],
'name': linked_list['item_name'],
'uom': linked_list['item_info']['uom']['id'],
'qty': float(receipt_item['qty']*conv_factor),
'type': 'sku'
}}
postsqldb.ReceiptTable.update_receipt_item(conn, site_name, payload)
return jsonify({'error': False, "message": "Line Saved Succesfully"})
return jsonify({'error': True, "message": "Something went wrong while saving line!"})
@receipt_api.route('/receipts/resolveLine', methods=["POST"])
def resolveLine():
@ -248,14 +339,28 @@ def resolveLine():
return jsonify({'error': False, "message": "Line Saved Succesfully"})
return jsonify({'error': True, "message": "Something went wrong while saving line!"})
@receipt_api.route('/receipt/postVendorUpdate', methods=["POST"])
def postVendorUpdate():
if request.method == "POST":
receipt_id = int(request.get_json()['receipt_id'])
vendor_id = int(request.get_json()['vendor_id'])
site_name = session['selected_site']
database_config = config()
with psycopg2.connect(**database_config) as conn:
postsqldb.ReceiptTable.update_receipt(conn, site_name, {'id': receipt_id, 'update': {'vendor_id': vendor_id}})
return jsonify({'error': False, "message": "Line Saved Succesfully"})
return jsonify({'error': True, "message": "Something went wrong while saving line!"})
@receipt_api.route('/receipts/resolveReceipt', methods=["POST"])
def resolveReceipt():
if request.method == "POST":
receipt_id = int(request.get_json()['receipt_id'])
site_name = session['selected_site']
user= session['user']
database_config = config()
with psycopg2.connect(**database_config) as conn:
postsqldb.ReceiptTable.update_receipt(conn, site_name, {'id': receipt_id, 'update': {'receipt_status': 'Resolved'}})
receipt = postsqldb.ReceiptTable.update_receipt(conn, site_name, {'id': receipt_id, 'update': {'receipt_status': 'Resolved'}})
webpush.push_ntfy(title=f"Receipt '{receipt['receipt_id']}' Resolved", body=f"Receipt {receipt['receipt_id']} was completed by {user['username']}.")
return jsonify({'error': False, "message": "Line Saved Succesfully"})
return jsonify({'error': True, "message": "Something went wrong while saving line!"})

View File

@ -4,7 +4,7 @@ from config import config, sites_config
from main import unfoldCostLayers
from user_api import login_required
import os
import postsqldb
import postsqldb, webpush
recipes_api = Blueprint('recipes_api', __name__)
@ -19,7 +19,6 @@ def recipes():
@recipes_api.route("/recipe/<mode>/<id>")
@login_required
def recipe(mode, id):
database_config = config()
with psycopg2.connect(**database_config) as conn:
units = postsqldb.UnitsTable.getAll(conn)
@ -68,7 +67,8 @@ def addRecipe():
author=user_id,
description=recipe_description
)
postsqldb.RecipesTable.insert_tuple(conn, site_name, recipe.payload())
recipe = postsqldb.RecipesTable.insert_tuple(conn, site_name, recipe.payload())
webpush.push_ntfy('New Recipe', f"New Recipe added to {site_name}; {recipe_name}!{recipe_description} http://test.treehousefullofstars.com/recipe/view/{recipe['id']} http://test.treehousefullofstars.com/recipe/edit/{recipe['id']}")
return jsonify({'recipe': recipe, 'error': False, 'message': 'Add Recipe successful!'})
return jsonify({'recipe': recipe, 'error': True, 'message': 'Add Recipe unsuccessful!'})

View File

@ -1,6 +1,7 @@
CREATE TABLE IF NOT EXISTS %%site_name%%_zones(
id SERIAL PRIMARY KEY,
name VARCHAR(32) NOT NULL,
description TEXT,
site_id INTEGER NOT NULL,
UNIQUE(name),
CONSTRAINT fk_site

View File

@ -1,4 +1,4 @@
INSERT INTO %%site_name%%_zones
(name, site_id)
VALUES (%s, %s)
(name, description, site_id)
VALUES (%s, %s, %s)
RETURNING *;

View File

@ -13,6 +13,6 @@ FROM %%site_name%%_items
LEFT JOIN sum_cte ON %%site_name%%_items.id = sum_cte.id
LEFT JOIN %%site_name%%_item_info ON %%site_name%%_items.item_info_id = %%site_name%%_item_info.id
WHERE %%site_name%%_items.search_string LIKE '%%' || %s || '%%'
ORDER BY %%site_name%%_items.item_name ASC
ORDER BY %%sort_order%%
LIMIT %s OFFSET %s;

View File

@ -0,0 +1,5 @@
SELECT %%site_name%%_locations.*,
row_to_json(%%site_name%%_zones.*) as zone
FROM %%site_name%%_locations
LEFT JOIN %%site_name%%_zones ON %%site_name%%_zones.id = %%site_name%%_locations.zone_id
LIMIT %s OFFSET %s;

View File

@ -0,0 +1,15 @@
WITH passed_id AS (SELECT %s AS passed_id),
cte_item_locations AS (
SELECT DISTINCT ils.location_id FROM %%site_name%%_item_locations ils
WHERE ils.part_id = (SELECT passed_id FROM passed_id)
),
cte_locations AS (
SELECT DISTINCT locations.zone_id FROM %%site_name%%_locations locations
WHERE locations.id IN (SELECT location_id FROM cte_item_locations)
)
SELECT DISTINCT location.* FROM cte_item_locations cil
JOIN %%site_name%%_locations location ON cil.location_id = location.id
WHERE location.zone_id = %s
LIMIT %s OFFSET %s;

View File

@ -0,0 +1,15 @@
WITH passed_id AS (SELECT %s AS passed_id),
cte_item_locations AS (
SELECT DISTINCT ils.location_id FROM %%site_name%%_item_locations ils
WHERE ils.part_id = (SELECT passed_id FROM passed_id)
),
cte_locations AS (
SELECT DISTINCT locations.zone_id FROM %%site_name%%_locations locations
WHERE locations.id IN (SELECT location_id FROM cte_item_locations)
)
SELECT COUNT(DISTINCT location.*) FROM cte_item_locations cil
JOIN %%site_name%%_locations location ON cil.location_id = location.id
WHERE location.zone_id = %s
LIMIT %s OFFSET %s;

View File

@ -0,0 +1,3 @@
SELECT il.* FROM %%site_name%%_item_locations il
LEFT JOIN %%site_name%%_zones zone ON zone.id = il.zone_id
WHERE il.id = %s;

View File

@ -0,0 +1,15 @@
WITH passed_id AS (SELECT %s AS passed_id),
cte_item_locations AS (
SELECT DISTINCT ils.location_id FROM %%site_name%%_item_locations ils
WHERE ils.part_id = (SELECT passed_id FROM passed_id)
),
cte_locations AS (
SELECT DISTINCT locations.zone_id FROM %%site_name%%_locations locations
WHERE locations.id IN (SELECT location_id FROM cte_item_locations)
)
SELECT DISTINCT zone.* FROM cte_locations cil
JOIN %%site_name%%_zones zone ON cil.zone_id = zone.id
LIMIT %s OFFSET %s;

View File

@ -0,0 +1,13 @@
WITH passed_id AS (SELECT %s AS passed_id),
cte_item_locations AS (
SELECT DISTINCT ils.location_id FROM %%site_name%%_item_locations ils
WHERE ils.part_id = (SELECT passed_id FROM passed_id)
),
cte_locations AS (
SELECT DISTINCT locations.zone_id FROM %%site_name%%_locations locations
WHERE locations.id IN (SELECT location_id FROM cte_item_locations)
)
SELECT COUNT(DISTINCT zone.*) FROM cte_locations cil
JOIN %%site_name%%_zones zone ON cil.zone_id = zone.id
LIMIT %s OFFSET %s;

View File

@ -84,3 +84,18 @@
list-style-type: none;
padding: 0;
}
.add-button{
width: 100%;
border-radius: 10px;
margin-top: 10px;
background-color: transparent;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
height: 40px;
}
.add-button:hover{
background-color: whitesmoke;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -185,7 +185,7 @@ async function updateTableElements(){
let opsCell = document.createElement('th')
opsCell.innerHTML = 'Operations'
head_row.append(nameCell, descriptionCell, opsCell)
head_row.append(nameCell, descriptionCell, qtyUOMCell, opsCell)
table_head.append(head_row)
main_table.append(table_head)
@ -281,12 +281,27 @@ async function updateListElements(){
items_list.append(main_list)
}
let sort = "id"
async function setSort(sort_string) {
sort = sort_string
await getItems()
await reloadCards()
}
let order = "ASC"
async function setOrder(order_string) {
order = order_string
await getItems()
await reloadCards()
}
async function getItems(){
const url = new URL('/item/getItemsWithQOH', window.location.origin);
url.searchParams.append('page', current_page);
url.searchParams.append('limit', limit);
url.searchParams.append('search_text', searchText);
url.searchParams.append('sort_order', sort_order);
url.searchParams.append('sort', sort);
url.searchParams.append('order', order);
url.searchParams.append('view', view);
await fetch(url)

View File

@ -1,3 +1,34 @@
var darkmode = false
function toggleDarkMode(){
if (!darkmode){
document.body.classList.add('dark-mode-body')
document.body.classList.add('uk-light')
document.getElementById('navbar').classList.add('uk-light')
document.getElementById('navbar').style = "background-color: #121212;"
document.getElementById('weblinkModal').classList.add('dark-mode-element')
document.getElementById('weblinkModalFooter').classList.add('dark-mode-element')
document.getElementById('brandsModalinner').classList.add('dark-mode-element')
document.getElementById('locationsModalInner').classList.add('dark-mode-element')
document.getElementById('zonesModalInner').classList.add('dark-mode-element')
document.getElementById('modeToggle').innerHTML = "light_mode"
darkmode = true
} else {
document.body.classList.remove('dark-mode-body')
document.body.classList.remove('uk-light')
document.getElementById('navbar').classList.remove('uk-light')
document.getElementById('navbar').style = ""
document.getElementById('weblinkModal').classList.remove('dark-mode-element')
document.getElementById('weblinkModalFooter').classList.remove('dark-mode-element')
document.getElementById('brandsModalinner').classList.remove('dark-mode-element')
document.getElementById('locationsModalInner').classList.remove('dark-mode-element')
document.getElementById('zonesModalInner').classList.remove('dark-mode-element')
document.getElementById('modeToggle').innerHTML = "dark_mode"
darkmode=false
}
}
var item;
var linked_items;
var tags = new Set();
@ -920,9 +951,10 @@ async function fetchItems() {
let zones_limit = 20;
async function fetchZones(){
const url = new URL('/item/getZones', window.location.origin);
const url = new URL('/item/getZonesBySku', window.location.origin);
url.searchParams.append('page', current_page);
url.searchParams.append('limit', zones_limit);
url.searchParams.append('item_id', item.id);
const response = await fetch(url);
data = await response.json();
return data;
@ -930,13 +962,14 @@ async function fetchZones(){
let locations_limit = 10;
async function fetchLocations(logis) {
const url = new URL('/item/getLocations', window.location.origin);
const url = new URL('/item/getLocationsBySkuZone', window.location.origin);
url.searchParams.append('page', current_page);
url.searchParams.append('limit', locations_limit);
url.searchParams.append('part_id', item.id);
if(logis=="primary_location"){
url.searchParams.append('id', primary_zone_id);
url.searchParams.append('zone_id', primary_zone_id);
} else if (logis=="auto_issue_location"){
url.searchParams.append('id', auto_zone_id);
url.searchParams.append('zone_id', auto_zone_id);
}
const response = await fetch(url);
data = await response.json();
@ -1332,33 +1365,156 @@ async function updatePrefixPaginationElement() {
paginationElement.append(nextElement)
}
var darkmode = false
function toggleDarkMode(){
if (!darkmode){
document.body.classList.add('dark-mode-body')
document.body.classList.add('uk-light')
document.getElementById('navbar').classList.add('uk-light')
document.getElementById('navbar').style = "background-color: #121212;"
document.getElementById('weblinkModal').classList.add('dark-mode-element')
document.getElementById('weblinkModalFooter').classList.add('dark-mode-element')
document.getElementById('brandsModalinner').classList.add('dark-mode-element')
document.getElementById('locationsModalInner').classList.add('dark-mode-element')
document.getElementById('zonesModalInner').classList.add('dark-mode-element')
document.getElementById('modeToggle').innerHTML = "light_mode"
// Possible Locations functions
var new_locations_current_page = 1
var new_locations_end_page = 1
var new_locations_limit = 25
async function fetch_new_locations() {
const url = new URL('/item/getPossibleLocations', window.location.origin);
url.searchParams.append('page', new_locations_current_page);
url.searchParams.append('limit', new_locations_limit);
const response = await fetch(url);
data = await response.json();
new_locations_end_page = data.end;
return data.locations
};
darkmode = true
} else {
document.body.classList.remove('dark-mode-body')
document.body.classList.remove('uk-light')
document.getElementById('navbar').classList.remove('uk-light')
document.getElementById('navbar').style = ""
document.getElementById('weblinkModal').classList.remove('dark-mode-element')
document.getElementById('weblinkModalFooter').classList.remove('dark-mode-element')
document.getElementById('brandsModalinner').classList.remove('dark-mode-element')
document.getElementById('locationsModalInner').classList.remove('dark-mode-element')
document.getElementById('zonesModalInner').classList.remove('dark-mode-element')
document.getElementById('modeToggle').innerHTML = "dark_mode"
async function postNewItemLocation(location_id) {
const response = await fetch(`/item/postNewItemLocation`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
item_id: parseInt(item_id),
location_id: parseInt(location_id)
}),
});
darkmode=false
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
});
await fetchItem()
await updateLocationsTable()
}
async function replenishPossibleLocationsTableBody(locations){
let NewLocationsModalTableBody = document.getElementById('NewLocationsModalTableBody')
NewLocationsModalTableBody.innerHTML = ""
for(let i =0; i <locations.length; i++){
let tableRow = document.createElement('tr')
let zoneCell = document.createElement('td')
zoneCell.innerHTML = locations[i].zone.name
let locationCell = document.createElement('td')
locationCell.innerHTML = locations[i].name
tableRow.onclick = async function() {
await postNewItemLocation(locations[i].id)
}
tableRow.append(zoneCell, locationCell)
NewLocationsModalTableBody.append(tableRow)
}
}
async function openPossibleLocationsModal() {
let locations = await fetch_new_locations()
await replenishPossibleLocationsTableBody(locations)
await updateNewLocationsPaginationElement()
UIkit.modal(document.getElementById('NewLocationsModal')).show()
}
async function setNewLocationPage(pageNumber){
new_locations_current_page = pageNumber;
let locations = await fetch_new_locations()
await updatePrefixModalTableBody(locations)
await updateNewLocationsPaginationElement()
}
async function updateNewLocationsPaginationElement() {
let paginationElement = document.getElementById('NewLocationsModalPage');
paginationElement.innerHTML = "";
// previous
let previousElement = document.createElement('li')
if(new_locations_current_page<=1){
previousElement.innerHTML = `<a><span uk-pagination-previous></span></a>`;
previousElement.classList.add('uk-disabled');
}else {
previousElement.innerHTML = `<a onclick="setNewLocationPage(${new_locations_current_page-1})"><span uk-pagination-previous></span></a>`;
}
paginationElement.append(previousElement)
//first
let firstElement = document.createElement('li')
if(new_locations_current_page<=1){
firstElement.innerHTML = `<a><strong>1</strong></a>`;
firstElement.classList.add('uk-disabled');
}else {
firstElement.innerHTML = `<a onclick="setNewLocationPage(1)">1</a>`;
}
paginationElement.append(firstElement)
// ...
if(new_locations_current_page-2>1){
let firstDotElement = document.createElement('li')
firstDotElement.classList.add('uk-disabled')
firstDotElement.innerHTML = `<span>…</span>`;
paginationElement.append(firstDotElement)
}
// last
if(new_locations_current_page-2>0){
let lastElement = document.createElement('li')
lastElement.innerHTML = `<a onclick="setNewLocationPage(${new_locations_current_page-1})">${new_locations_current_page-1}</a>`
paginationElement.append(lastElement)
}
// current
if(new_locations_current_page!=1 && new_locations_current_page != new_locations_end_page){
let currentElement = document.createElement('li')
currentElement.innerHTML = `<li class="uk-active"><span aria-current="page"><strong>${new_locations_current_page}</strong></span></li>`
paginationElement.append(currentElement)
}
// next
if(new_locations_current_page+2<new_locations_end_page+1){
let nextElement = document.createElement('li')
nextElement.innerHTML = `<a onclick="setNewLocationPage(${new_locations_current_page+1})">${new_locations_current_page+1}</a>`
paginationElement.append(nextElement)
}
// ...
if(new_locations_current_page+2<=new_locations_end_page){
let secondDotElement = document.createElement('li')
secondDotElement.classList.add('uk-disabled')
secondDotElement.innerHTML = `<span>…</span>`;
paginationElement.append(secondDotElement)
}
//end
let endElement = document.createElement('li')
if(new_locations_current_page>=new_locations_end_page){
endElement.innerHTML = `<a><strong>${new_locations_end_page}</strong></a>`;
endElement.classList.add('uk-disabled');
}else {
endElement.innerHTML = `<a onclick="setNewLocationPage(${new_locations_end_page})">${new_locations_end_page}</a>`;
}
paginationElement.append(endElement)
//next button
let nextElement = document.createElement('li')
if(new_locations_current_page>=new_locations_end_page){
nextElement.innerHTML = `<a><span uk-pagination-next></span></a>`;
nextElement.classList.add('uk-disabled');
}else {
nextElement.innerHTML = `<a onclick="setNewLocationPage(${new_locations_current_page+1})"><span uk-pagination-next></span></a>`;
}
paginationElement.append(nextElement)
}

View File

@ -1,3 +1,9 @@
async function passwordEnter(event) {
if(event.key == "Enter"){
await loginUser()
}
}
async function loginUser() {
let username = document.getElementById('login_username').value
let password = document.getElementById('login_password').value

View File

@ -7,7 +7,6 @@ document.addEventListener('DOMContentLoaded', async function() {
async function refreshReceipt() {
let receipt = await getReceipt(receipt_id)
console.log(receipt)
await replenishFields(receipt)
await replenishLinesTable(receipt.receipt_items)
await replenishFilesCards(receipt.files)
@ -26,6 +25,14 @@ async function replenishFields(receipt) {
document.getElementById('vendor_name').value = receipt.vendor.vendor_name
document.getElementById('vendor_address').value = receipt.vendor.vendor_address
document.getElementById('vendor_phone').value = receipt.vendor.phone_number
if(receipt.receipt_status=="Resolved"){
document.getElementById('resolveReceiptButton').hidden = true
document.getElementById('lineAddButton').hidden = true
document.getElementById('fileUploadButton').hidden = true
document.getElementById('fileUploadForm').hidden = true
document.getElementById('vendorSelectDiv').hidden = true
document.getElementById('vendorSelectButton').hidden = true
}
}
}
@ -105,6 +112,14 @@ async function replenishLinesTable(receipt_items) {
}
}
let linkOp = document.createElement('a')
linkOp.style = "margin-right: 5px;"
linkOp.setAttribute('class', 'uk-button uk-button-small uk-button-default')
linkOp.setAttribute('uk-icon', 'icon: link')
linkOp.onclick = async function () {
await openLinksSelectModal(receipt_items[i].id)
}
let editOp = document.createElement('a')
editOp.style = "margin-right: 5px;"
editOp.setAttribute('class', 'uk-button uk-button-small uk-button-default')
@ -139,6 +154,11 @@ async function replenishLinesTable(receipt_items) {
if (receipt_items[i].type === "new sku"){
operationsCell.append(apiOp)
operationsCell.append(linkOp)
}
if (receipt_items[i].type === "api"){
operationsCell.append(linkOp)
}
operationsCell.append(editOp, resolveOp, denyOp, deleteOp)
@ -505,3 +525,342 @@ async function updateItemsPaginationElement() {
}
paginationElement.append(nextElement)
}
// Select Vedor functions
let vendor_limit = 25
let vendor_current_page = 1
let vendor_end_page = 10
async function getVendors() {
const url = new URL('/receipt/getVendors', window.location.origin);
url.searchParams.append('page', vendor_current_page);
url.searchParams.append('limit', vendor_limit);
const response = await fetch(url);
data = await response.json();
vendor_end_page = data.end
return data.vendors;
}
async function postVendorUpdate(vendor_id) {
const response = await fetch(`/receipt/postVendorUpdate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
receipt_id: receipt_id,
vendor_id: vendor_id
}),
});
data = await response.json()
message_type = "primary"
if(data.error){
message_type = "danger"
}
UIkit.notification({
message: data.message,
status: message_type,
pos: 'top-right',
timeout: 5000
});
await refreshReceipt()
UIkit.modal(document.getElementById("vendorsModal")).hide();
}
async function openVendorsSelectModal() {
let vendors = await getVendors();
await replenishVendorsTableBody(vendors);
await updateVendorsPaginationElement()
UIkit.modal(document.getElementById("vendorsModal")).show();
}
async function replenishVendorsTableBody(vendors) {
let vendorsTableBody = document.getElementById('vendorsTableBody')
vendorsTableBody.innerHTML = ""
for(let i=0; i < vendors.length; i++){
let tableRow = document.createElement('tr')
let idCell = document.createElement('td')
idCell.innerHTML = vendors[i].id
let nameCell = document.createElement('td')
nameCell.innerHTML = vendors[i].vendor_name
let phoneCell = document.createElement('td')
phoneCell.innerHTML = vendors[i].phone_number
let addressCell = document.createElement('td')
addressCell.innerHTML = vendors[i].vendor_address
tableRow.onclick = async function() {
await postVendorUpdate(vendors[i].id)
}
tableRow.append(idCell,nameCell,phoneCell, addressCell)
vendorsTableBody.append(tableRow)
}
}
async function setVendorPage(pageNumber) {
vendor_current_page = pageNumber;
let vendors = await getVendors()
await updateVendorsPaginationElement()
await replenishVendorsTableBody(vendors)
}
async function updateVendorsPaginationElement() {
let paginationElement = document.getElementById("vendorsPage");
paginationElement.innerHTML = "";
// previous
let previousElement = document.createElement('li')
if(vendor_current_page<=1){
previousElement.innerHTML = `<a><span uk-pagination-previous></span></a>`;
previousElement.classList.add('uk-disabled');
}else {
previousElement.innerHTML = `<a onclick="setVendorPage(${vendor_current_page-1})"><span uk-pagination-previous></span></a>`;
}
paginationElement.append(previousElement)
//first
let firstElement = document.createElement('li')
if(vendor_current_page<=1){
firstElement.innerHTML = `<a><strong>1</strong></a>`;
firstElement.classList.add('uk-disabled');
}else {
firstElement.innerHTML = `<a onclick="setVendorPage(1)">1</a>`;
}
paginationElement.append(firstElement)
// ...
if(vendor_current_page-2>1){
let firstDotElement = document.createElement('li')
firstDotElement.classList.add('uk-disabled')
firstDotElement.innerHTML = `<span>…</span>`;
paginationElement.append(firstDotElement)
}
// last
if(vendor_current_page-2>0){
let lastElement = document.createElement('li')
lastElement.innerHTML = `<a onclick="setVendorPage(${vendor_current_page-1})">${vendor_current_page-1}</a>`
paginationElement.append(lastElement)
}
// current
if(vendor_current_page!=1 && vendor_current_page != vendor_end_page){
let currentElement = document.createElement('li')
currentElement.innerHTML = `<li class="uk-active"><span aria-current="page"><strong>${vendor_current_page}</strong></span></li>`
paginationElement.append(currentElement)
}
// next
if(vendor_current_page+2<vendor_end_page+1){
let nextElement = document.createElement('li')
nextElement.innerHTML = `<a onclick="setVendorPage(${vendor_current_page+1})">${vendor_current_page+1}</a>`
paginationElement.append(nextElement)
}
// ...
if(vendor_current_page+2<=vendor_end_page){
let secondDotElement = document.createElement('li')
secondDotElement.classList.add('uk-disabled')
secondDotElement.innerHTML = `<span>…</span>`;
paginationElement.append(secondDotElement)
}
//end
let endElement = document.createElement('li')
if(vendor_current_page>=vendor_end_page){
endElement.innerHTML = `<a><strong>${vendor_end_page}</strong></a>`;
endElement.classList.add('uk-disabled');
}else {
endElement.innerHTML = `<a onclick="setVendorPage(${vendor_end_page})">${vendor_end_page}</a>`;
}
paginationElement.append(endElement)
//next button
let nextElement = document.createElement('li')
if(vendor_current_page>=vendor_end_page){
nextElement.innerHTML = `<a><span uk-pagination-next></span></a>`;
nextElement.classList.add('uk-disabled');
}else {
nextElement.innerHTML = `<a onclick="setVendorPage(${vendor_current_page+1})"><span uk-pagination-next></span></a>`;
console.log(nextElement.innerHTML)
}
paginationElement.append(nextElement)
}
// Select Vedor functions
let links_limit = 25
let links_current_page = 1
let links_end_page = 10
async function getLinkedLists() {
const url = new URL('/receipt/getLinkedLists', window.location.origin);
url.searchParams.append('page', vendor_current_page);
url.searchParams.append('limit', vendor_limit);
const response = await fetch(url);
data = await response.json();
links_end_page = data.end
return data.items;
}
async function postLinkedItem(receipt_item_id, link_list_id, conv_factor) {
const response = await fetch(`/receipt/postLinkedItem`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
receipt_item_id: receipt_item_id,
link_list_id: link_list_id,
conv_factor: conv_factor
}),
});
data = await response.json()
message_type = "primary"
if(data.error){
message_type = "danger"
}
UIkit.notification({
message: data.message,
status: message_type,
pos: 'top-right',
timeout: 5000
});
await refreshReceipt()
UIkit.modal(document.getElementById("linksModal")).hide();
}
async function openLinksSelectModal(receipt_item_id) {
let links = await getLinkedLists();
await replenishLinksTableBody(links, receipt_item_id);
await updateLinksPaginationElement()
UIkit.modal(document.getElementById("linksModal")).show();
}
async function replenishLinksTableBody(links, receipt_item_id) {
let linksTableBody = document.getElementById('linksTableBody')
linksTableBody.innerHTML = ""
for(let i=0; i < links.length; i++){
let tableRow = document.createElement('tr')
let idCell = document.createElement('td')
idCell.innerHTML = links[i].id
let barcodeCell = document.createElement('td')
barcodeCell.innerHTML = links[i].barcode
let nameCell = document.createElement('td')
nameCell.innerHTML = links[i].item_name
let convFactorCell = document.createElement('td')
let conv_factor_input = document.createElement('input')
conv_factor_input.setAttribute('class', 'uk-input')
conv_factor_input.setAttribute('id', `${links[i].id}_conv_factor`)
convFactorCell.append(conv_factor_input)
let addCell = document.createElement('td')
let addbutton = document.createElement('button')
addbutton.setAttribute('class', 'uk-button')
addbutton.innerHTML = "Select"
addbutton.onclick = async function() {
let conv = document.getElementById(`${links[i].id}_conv_factor`)
if (!conv.value == ""){
conv.classList.remove('uk-form-danger')
let conv_factor = parseFloat(conv.value)
await postLinkedItem(receipt_item_id, links[i].id, conv_factor)
} else {
conv.classList.add('uk-form-danger')
}
}
addCell.append(addbutton)
tableRow.append(idCell, barcodeCell, nameCell, convFactorCell, addCell)
linksTableBody.append(tableRow)
}
}
async function setLinksPage(pageNumber) {
links_current_page = pageNumber;
let links = await getLinkedLists()
await updateLinksPaginationElement()
await replenishLinksTableBody(links)
}
async function updateLinksPaginationElement() {
let paginationElement = document.getElementById("linksPage");
paginationElement.innerHTML = "";
// previous
let previousElement = document.createElement('li')
if(links_current_page<=1){
previousElement.innerHTML = `<a><span uk-pagination-previous></span></a>`;
previousElement.classList.add('uk-disabled');
}else {
previousElement.innerHTML = `<a onclick="setLinksPage(${links_current_page-1})"><span uk-pagination-previous></span></a>`;
}
paginationElement.append(previousElement)
//first
let firstElement = document.createElement('li')
if(links_current_page<=1){
firstElement.innerHTML = `<a><strong>1</strong></a>`;
firstElement.classList.add('uk-disabled');
}else {
firstElement.innerHTML = `<a onclick="setLinksPage(1)">1</a>`;
}
paginationElement.append(firstElement)
// ...
if(links_current_page-2>1){
let firstDotElement = document.createElement('li')
firstDotElement.classList.add('uk-disabled')
firstDotElement.innerHTML = `<span>…</span>`;
paginationElement.append(firstDotElement)
}
// last
if(links_current_page-2>0){
let lastElement = document.createElement('li')
lastElement.innerHTML = `<a onclick="setLinksPage(${links_current_page-1})">${links_current_page-1}</a>`
paginationElement.append(lastElement)
}
// current
if(links_current_page!=1 && links_current_page != links_end_page){
let currentElement = document.createElement('li')
currentElement.innerHTML = `<li class="uk-active"><span aria-current="page"><strong>${links_current_page}</strong></span></li>`
paginationElement.append(currentElement)
}
// next
if(links_current_page+2<links_end_page+1){
let nextElement = document.createElement('li')
nextElement.innerHTML = `<a onclick="setLinksPage(${links_current_page+1})">${links_current_page+1}</a>`
paginationElement.append(nextElement)
}
// ...
if(links_current_page+2<=links_end_page){
let secondDotElement = document.createElement('li')
secondDotElement.classList.add('uk-disabled')
secondDotElement.innerHTML = `<span>…</span>`;
paginationElement.append(secondDotElement)
}
//end
let endElement = document.createElement('li')
if(links_current_page>=links_end_page){
endElement.innerHTML = `<a><strong>${links_end_page}</strong></a>`;
endElement.classList.add('uk-disabled');
}else {
endElement.innerHTML = `<a onclick="setLinksPage(${links_end_page})">${links_end_page}</a>`;
}
paginationElement.append(endElement)
//next button
let nextElement = document.createElement('li')
if(links_current_page>=links_end_page){
nextElement.innerHTML = `<a><span uk-pagination-next></span></a>`;
nextElement.classList.add('uk-disabled');
}else {
nextElement.innerHTML = `<a onclick="setLinksPage(${links_current_page+1})"><span uk-pagination-next></span></a>`;
console.log(nextElement.innerHTML)
}
paginationElement.append(nextElement)
}

View File

@ -1,6 +1,32 @@
var pagination_current = 1;
var pagination_end = 10
async function changeSite(site){
console.log(site)
const response = await fetch(`/changeSite`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
site: site,
}),
});
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
});
location.reload(true)
}
document.addEventListener('DOMContentLoaded', async function() {
let receipts = await getReceipts()
await replenishReceiptsTable(receipts)

View File

@ -74,7 +74,7 @@ async function replenishInstructions() {
innerTile.setAttribute('class', 'uk-tile uk-tile-default uk-padding-small')
let instruction = document.createElement('p')
instruction.innerHTML = `${recipe.instructions[i]}`
instruction.innerHTML = `<strong>Step ${i+1}:</strong> ${recipe.instructions[i]}`
innerTile.append(instruction)

View File

@ -4,6 +4,32 @@ var defaqult_limit = 2;
var pagination_end = 1;
var item;
async function changeSite(site){
console.log(site)
const response = await fetch(`/changeSite`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
site: site,
}),
});
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
});
location.reload(true)
}
async function replenishItemsTable(items) {
let itemsTableBody = document.getElementById("itemsTableBody")
itemsTableBody.innerHTML = ""

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,84 @@
'use strict';
function urlB64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
function updateSubscriptionOnServer(subscription, apiEndpoint) {
// TODO: Send subscription to application server
return fetch(apiEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
subscription_json: JSON.stringify(subscription)
})
});
}
async function subscribeUser(swRegistration, applicationServerPublicKey, apiEndpoint) {
if (swRegistration.active) {
console.log("executing Sub.")
return executeSubscription(swRegistration, applicationServerPublicKey, apiEndpoint);
} else {
swRegistration.addEventListener('statechange', function(event) {
if (event.target.state === 'activated') {
console.log('waiting and sub.')
return executeSubscription(swRegistration, applicationServerPublicKey, apiEndpoint);
}
});
}
}
async function executeSubscription(swRegistration, applicationServerPublicKey, apiEndpoint) {
const applicationServerKey = urlB64ToUint8Array(applicationServerPublicKey);
try {
const subscription = await swRegistration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: applicationServerKey
});
console.log('User is subscribed.');
const response = await updateSubscriptionOnServer(subscription, apiEndpoint);
if (!response.ok) throw new Error('Bad status code from server.');
const responseData = await response.json();
if (responseData.status !== "success") throw new Error('Bad response from server.');
console.log(responseData);
} catch (err) {
console.log('Failed to subscribe the user: ', err);
console.log(err.stack);
}
}
async function registerServiceWorker(serviceWorkerUrl, applicationServerPublicKey, apiEndpoint){
let swRegistration = null;
if ('serviceWorker' in navigator && 'PushManager' in window) {
console.log('Service Worker and Push is supported');
try {
const swReg = await navigator.serviceWorker.register(serviceWorkerUrl);
console.log('Service Worker is registered', swReg);
await subscribeUser(swReg, applicationServerPublicKey, apiEndpoint);
swRegistration = swReg;
} catch (error) {
console.error('Service Worker Error', error);
}
} else {
console.warn('Push messaging is not supported');
}
return swRegistration;
}

View File

@ -0,0 +1,35 @@
'use strict';
/* eslint-enable max-len */
self.addEventListener('install', function(event) {
console.log('Service Worker installing.');
});
self.addEventListener('activate', function(event) {
console.log('Service Worker activating.');
});
self.addEventListener('push', function(event) {
console.log('[Service Worker] Push Received.');
const pushData = event.data.text();
console.log(`[Service Worker] Push received this data - "${pushData}"`);
let data, title, body;
try {
data = JSON.parse(pushData);
title = data.title;
body = data.body;
} catch(e) {
title = "Untitled";
body = pushData;
}
const options = {
body: body
};
console.log(title, options);
event.waitUntil(
self.registration.showNotification(title, options)
);
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 874 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

22
task_manager.py Normal file
View File

@ -0,0 +1,22 @@
import schedule, time, psycopg2
import postsqldb
from config import config
def createCycleCount():
print("task is running")
database_config = config()
with psycopg2.connect(**database_config) as conn:
sites = postsqldb.SitesTable.selectTuples(conn)
print(sites)
conn.rollback()
def start_schedule():
schedule.every(1).minutes.do(createCycleCount)
while True:
schedule.run_pending()
time.sleep(60)
createCycleCount()

View File

@ -1,352 +0,0 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title>My Pantry - Groups</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/css/materialize.min.css" />
<!-- 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" />
<script src="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/js/materialize.min.js"></script>
</head>
<style>
header, main, footer, body {
padding-left: 300px;
}
@media only screen and (max-width : 992px) {
header, main, footer, body {
padding-left: 0;
}
}
.dropdown-disabled {
pointer-events: none;
opacity: 0.5; /* or your desired degree of transparency */
}
</style>
<ul id='dropdown1' class='dropdown-content'>
{% for site in sites %}
<li><button class="btn transparent black-text z-depth-0" onclick="changeSite('{{ site }}')">{{site}}</button></li>
{% endfor %}
</ul>
<ul id="slide-out" class="sidenav sidenav-fixed purple lighten-4" style="width: 250px;">
<li>
<div class="user-view">
<!-- <div class="background">
<img src="images/office.jpg">
</div> -->
<!-- <a href="#user"><img class="circle" src="images/yuna.jpg"></a> -->
<a href="#name"><span class="black-text name">John Doe</span></a>
<a href="#email"><span class="black-text email">jdoe@example.com</span></a>
</div>
</li>
<li><a class="dropdown-trigger dropdown-disabled" data-target="dropdown1">Current Site > {{current_site}}<i class="material-icons right">arrow_drop_down</i></a></li>
<li><div class="divider grey darken-1" style="margin-left: 5px; margin-right: 10px;"></div></li>
<li><a href="/items">Site Items</a></li>
<li><a href="/groups">Site Groups</a></li>
<li><a href="/shopping-lists">Site Shopping Lists</a></li>
</ul>
<body>
<div class="container">
<div class="section">
<div class="row">
<div class="col s12">
<div class="row">
<div class="col s2">
<h3><a href="#" data-target="slide-out" class="sidenav-trigger hide-on-large-only left"><i class="material-icons">menu</i></a></h3>
</div>
<div class="col s8 purple lighten-4" style="border-radius: 10px;">
<h3 id="group_name" class="center"></h3>
</div>
<div class="col s2">
</div>
</div>
</div>
<div class="col s12 p-2">
<h5 id="database_id"></h5>
</div>
<div class="col s12 m6 p-2">
<label for="group_type">Group Type</label>
<select id="group_type" class="browser-default">
<option value="plain">Plain</option>
<option value="calculated">Calculated</option>
</select>
</div>
<div class="input-field col s12 p-2">
<textarea id="description" class="materialize-textarea" placeholder=" "></textarea>
<label for="description">Group Description</label>
</div>
<div class="divider col s12"></div>
<div class="col s3 p-2">
<button class="btn filled icon-left modal-trigger purple lighten-4 black-text z-depth-0" data-target="item_modal">Add Item</button>
</div>
<div id="table_div" class="col s12 p-2">
</div>
</div>
</div>
</div>
<div class="fixed-action-btn">
<button class="btn-floating btn-large" onclick="saveGroup()">
<i class="large material-icons">save</i>
</button>
</div>
<div id="item_modal" class="modal">
<div class="modal-content">
<div class="row" style="gap: 5px;">
<div class="col s12">
<h4>Add Items...</h4>
</div>
<div class="col s12">
<div class="card-panel purple lighten-4 z-depth-0">
<span class="black-text"> Here is where you can search, add, and remove items from this group by checking or unchecking the items below. You can select
multiple at a time or simply one. Utilize the search bar to filter down quickly to items you need or simply scroll to your hearts content.
<br><br>
<b>WARNING:</b> clicking the checkbox will not save the changes right off the bat! You <b>MUST</b> click the Update Items button!
<br><br>
</span>
</div>
</div>
<div class="col s9 m6 offset-m3 input-field outlined align-center">
<i class="material-icons prefix">search</i>
<input style="border-radius: 20px; border: 1px solid #ccc;" id="search" name="search" type="search" placeholder="Search Items" value="">
</div>
<div class="col s12 center">
<a id="back" class="btn icon-left purple lighten-4 black-text z-depth-0"><i class="material-icons">chevron_left</i></a>
<a id="update_items" class="btn purple lighten-4 black-text z-depth-0">Update Items</a>
<a id="forward" class="btn icon-right purple lighten-4 black-text z-depth-0"><i class="material-icons">chevron_right</i></a>
</div>
<div class="divider col s12"></div>
<div id="checkbox-container" class="col s12">
</div>
</div>
</div>
</div>
</body>
<script>
const group_id = {{id|tojson}}
let site = {{ current_site|tojson }}
let group;
let current_page = 1
let limit = 25
let checked_items = new Array();
let search_text = "";
document.addEventListener("DOMContentLoaded", async function(){
await fetchGroup()
var elems = document.querySelectorAll('select');
var instances = M.FormSelect.init(elems, {});
var elems = document.querySelectorAll('.modal');
var instances = M.Modal.init(elems, {});
var elems = document.querySelectorAll('.fixed-action-btn');
var instances = M.FloatingActionButton.init(elems, {});
var elems = document.querySelectorAll('.sidenav');
var instances = M.Sidenav.init(elems, {});
var elems = document.querySelectorAll('.dropdown-trigger');
var instances = M.Dropdown.init(elems, {});
var elems = document.querySelectorAll('.collapsible');
var instances = M.Collapsible.init(elems, {});
M.AutoInit();
await populateInfo()
await populateReferences(group[3])
update_list()
})
document.getElementById("search").addEventListener('keydown', function(event){
if(event.key === 'Enter'){
console.log(document.getElementById("search").value);
search_text = document.getElementById("search").value;
update_list()
}
})
async function fetchGroup(){
const url = new URL('/getGroup', window.location.origin);
url.searchParams.append('id', group_id);
const response = await fetch(url);
data = await response.json();
group = data.group;
}
async function populateInfo(){
var itemName = document.getElementById('group_name');
var databaseID = document.getElementById('database_id');
var selectElement = document.getElementById('group_type');
var description = document.getElementById('description');
itemName.innerHTML = group[1];
databaseID.innerHTML = `Database ID: ${group[0]}`;
selectElement.value = group[4];
selectElement.dispatchEvent(new Event('change'));
description.value = group[2];
M.Forms.textareaAutoResize(description);
checked_items = [];
for(let i=0; i < group[3].length; i++){
checked_items.push(group[3][i][0]);
}
}
async function populateReferences(references){
var table_div = document.getElementById('table_div')
table_div.innerHTML = ""
var references_table = document.createElement('table')
references_table.classList.add("striped")
var tbl_header = document.createElement('thead')
tbl_header.innerHTML = `<tr><th>Database ID</th><th>Barcode</th><th>Item Name</th><th>QOH</th></tr>`
var tbl_body = document.createElement('tbody')
for (let i=0; i < references.length; i++){
var row = document.createElement('tr')
row.innerHTML = `
<td>${references[i][0]}</td>
<td>${references[i][1]}</td>
<td>${references[i][2]}</td>
<td>${references[i][3]}</td>
`
tbl_body.appendChild(row)
};
references_table.appendChild(tbl_header)
references_table.appendChild(tbl_body)
table_div.appendChild(references_table)
}
function save_checked(){
let checkboxes = document.querySelectorAll('.checkbox-class');
checkboxes.forEach((checkbox) => {
if (checked_items.includes(Number(checkbox.id)) && !checkbox.checked){
checked_items.splice(checked_items.indexOf(Number(Number(checkbox.id))), 1);
}
if (!checked_items.includes(Number(checkbox.id)) && checkbox.checked){
checked_items.push(Number(checkbox.id))
}
});
}
function update_list(){
if (current_page == 1){
document.getElementById('back').classList.add("disabled")
}else{
document.getElementById('back').classList.remove("disabled")
};
const url = new URL('/getItems', window.location.origin);
url.searchParams.append('page', current_page);
url.searchParams.append('limit', limit);
url.searchParams.append('search_text', search_text);
let container = document.getElementById('checkbox-container')
fetch(url)
.then(response => response.json())
.then(data => {
if (data.items.length < limit){
document.getElementById('forward').classList.add("disabled")
} else {
document.getElementById('forward').classList.remove("disabled")
}
const divi = document.createElement('div')
data.items.forEach(item => {
const checkboxHTML = document.createElement('p')
if (checked_items.includes(item[0])){
checkboxHTML.innerHTML = `<label>
<input class="checkbox-class" id=${item[0]} name=${item[0]} checked="checked" type="checkbox" />
<span>${item[1]} - ${item[2]}</span>
</label>`;
} else {
checkboxHTML.innerHTML = `<div class="col s12"><label>
<input class="checkbox-class" id=${item[0]} name=${item[0]} type="checkbox" />
<span>${item[1]} - ${item[2]}</span>
</label></div>`;
}
divi.appendChild(checkboxHTML)
})
container.innerHTML = divi.innerHTML
})
}
async function saveGroup(){
save_checked()
var itemName = document.getElementById('group_name');
var selectElement = document.getElementById('group_type');
var description = document.getElementById('description');
await fetch(`/updateGroup`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
id: group[0],
name: itemName.innerHTML,
description:description.value,
items: checked_items,
group_type:selectElement.value,
}),
});
M.toast({text: "Group Saved!"})
}
document.getElementById('forward').addEventListener('click', () =>{
current_page++
save_checked()
update_list()
})
document.getElementById('back').addEventListener('click', () =>{
current_page--
save_checked()
update_list()
})
document.getElementById('update_items').addEventListener('click', async function(){
save_checked()
var itemName = document.getElementById('group_name');
var selectElement = document.getElementById('group_type');
var description = document.getElementById('description');
await fetch(`/updateGroup`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
id: group[0],
name: itemName.innerHTML,
description:description.value,
items: checked_items,
group_type:selectElement.value,
}),
})
var modalElem = document.querySelector('#item_modal');
var instance = M.Modal.getInstance(modalElem);
instance.close();
M.toast({text: "Group Saved!"})
await fetchGroup()
await populateInfo()
await populateReferences(group[3])
})
</script>
</html>

View File

@ -1,140 +0,0 @@
<!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">Groups</title>
<!-- 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') }}"/>
<link id="dark-mode" rel="stylesheet" href="{{ url_for('static', filename='css/dark-mode.css') }}" disabled/>
<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>
<div uk-sticky="sel-target: .uk-navbar-container; cls-active: uk-navbar-sticky">
<nav id="navbar" class="uk-navbar-container">
<div class="uk-container uk-container-expand">
<div uk-navbar="dropbar: true">
<div id="offcanvas-slide" uk-offcanvas="mode: slide; overlay: true">
<div class="uk-offcanvas-bar uk-flex uk-flex-column">
<ul class="uk-nav uk-nav-secondary">
<img class="uk-align-center uk-border-circle" data-src="{{ url_for('static', filename='pictures/logo.jpg') }}" style="width: 150px; height: auto;" uk-img />
<li class="uk-nav-header">Apps</li>
<li><a href="/shopping-lists">Shopping Lists</a></li>
<li>
<a href="/groups">
<div class="uk-active">Groups<div class="uk-nav-subtitle" disabled>You are currently browsing groups here...</div></div>
</a>
<li><a href="/recipes">Recipes</a></li>
<li class="uk-nav-header">Logistics</li>
<li><a href="/items">Items</a></li>
<li><a href="/transaction">Add Transaction</a></li>
<li><a href="/receipts">Receipts</a></li>
<li class="uk-nav-header">System Management</li>
{% if system_admin %}
<li><a href="/admin">Administration</a></li>
{% endif %}
<li><a href="" class="">{{username}}</a></li>
</ul>
<button class="uk-button uk-margin-small uk-position-top-right" uk-icon="icon: close" href=""></button>
</div>
</div>
<div class="uk-navbar-left uk-margin-small">
<a href="#offcanvas-slide" class="uk-button uk-button-default uk-button-small" uk-icon="icon: menu" uk-toggle></a>
</div>
<div class="uk-navbar-center uk-margin-small uk-visible@m">
<ul class="uk-breadcrumb">
<li style="cursor: default;"><span><strong>{{current_site}}</strong></span>
<div uk-dropdown="mode: hover">
<ul class="uk-nav uk-dropdown-nav">
{% 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;"><span>Logistics</span></li>
<li class="uk-disabled"><span>Items</span></li>
</ul>
</div>
<div class="uk-navbar-right">
<div>
<a onclick="toggleDarkMode()" class="uk-button uk-button-small"><span id="modeToggle" class="uk-flex material-symbols-outlined">dark_mode</span></a>
</div>
<div>
<a href="" class="" uk-icon="icon: user" uk-toggle>{{username}}</a>
</div>
</div>
</div>
</div>
</nav>
</div>
<div class="uk-container">
<div class="uk-section">
<div uk-grid>
<div class="uk-width-1-1@m">
<ul class="uk-iconnav uk-flex-center uk-flex-left@m">
<li><a onclick="" uk-icon="icon: plus">Add List</a></li>
<li><a href="#" uk-icon="icon: cloud-download">download</a></li>
</ul>
<ul class="uk-iconnav uk-flex-center uk-flex-right@m">
<li><a id="view_mode_toggle" onclick="setViewMode()" uk-icon="icon: thumbnails">Cards</a></li>
</ul>
</div>
<div class="uk-width-1-1 uk-flex uk-flex-center uk-padding-small">
<nav aria-label="Pagination">
<ul id="paginationElement" class="uk-pagination" 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">
<div id="groups_list" class="uk-child-width-1-1">
<!-- populates through javascript -->
</div>
<a class="uk-margin" href="" uk-totop uk-scroll></a>
</div>
</div>
</div>
</div>
<!-- This is the modal -->
<div id="viewGroupModal" uk-modal>
<div class="uk-modal-dialog uk-modal-body">
<h2 class="uk-modal-title" id="groupName"></h2>
<p id="groupDescription"></p>
<p class="uk-text-right">
<button class="uk-button uk-button-default uk-modal-close" type="button">Cancel</button>
<button class="uk-button uk-button-primary" type="button">Save</button>
</p>
</div>
</div>
</body>
<script>
const session = {{session|tojson}}
</script>
<script src="{{ url_for('static', filename='handlers/groupListHandler.js') }}"></script>
</html>

View File

@ -32,17 +32,30 @@
<img class="uk-align-center uk-border-circle" data-src="{{ url_for('static', filename='pictures/logo.jpg') }}" style="width: 150px; height: auto;" uk-img />
<li class="uk-nav-header">Apps</li>
<li><a href="/shopping-lists">Shopping Lists</a></li>
<li><a href="/groups">Groups</a></li>
<li><a href="/recipes">Recipes</a></li>
<li class="uk-nav-header">Logistics</li>
<li>
<a href="/items">
<div class="uk-active">Items<div class="uk-nav-subtitle" disabled>You are currently browsing items here...</div>
</div>
</a>
</a></li>
<li><a href="/transaction">Add Transaction</a></li>
<li><a href="/workshop">Workshop</a></li>
<li><a href="/receipts">Receipts</a></li>
<li class="uk-nav-header">System Management</li>
<li><a><div>{{current_site}}<div class="uk-nav-subtitle">This is the current site you are viewing...</div></div></a>
<div uk-dropdown="mode: click">
<ul class="uk-nav uk-dropdown-nav">
{% 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>
{% if system_admin %}
<li><a href="/admin">Administration</a></li>
{% endif %}
@ -52,7 +65,7 @@
</div>
</div>
<div class="uk-navbar-left uk-margin-small">
<a href="#offcanvas-slide" class="uk-button uk-button-default uk-button-small" uk-icon="icon: menu" uk-toggle></a>
<a href="#offcanvas-slide" class="uk-button uk-button-default uk-button-small" uk-icon="icon: menu" uk-toggle> Menu</a>
</div>
<div class="uk-navbar-center uk-margin-small uk-visible@m">
<ul class="uk-breadcrumb">
@ -111,7 +124,23 @@
</div>
</div>
<div id="settings" class="uk-width-1-1" hidden>
This is some test text
<div uk-grid>
<div class="uk-width-1-1">
<div class="uk-margin uk-grid-small uk-child-width-auto uk-grid">
<p class="uk-text-meta">Order Items By</p>
<label><input onchange="setSort('id')" class="uk-radio" type="radio" name="radio2" checked>ID</label>
<label><input onchange="setSort('item_name')" class="uk-radio" type="radio" name="radio2">Name</label>
<label><input onchange="setSort('total_qoh')" class="uk-radio" type="radio" name="radio2">Quantity On Hand</label>
</div>
</div>
<div class="uk-width-1-1">
<div class="uk-margin uk-grid-small uk-child-width-auto uk-grid">
<p class="uk-text-meta">Sort Item Direction</p>
<label><input onchange="setOrder('ASC')" class="uk-radio" type="radio" name="order" checked>Ascending</label>
<label><input onchange="setOrder('DESC')" class="uk-radio" type="radio" name="order">Descending</label>
</div>
</div>
</div>
</div>
<div class="uk-width-1-1 uk-flex uk-flex-center uk-padding-small">
<nav aria-label="Pagination">

View File

@ -49,7 +49,6 @@
<img class="uk-align-center uk-border-circle" data-src="{{ url_for('static', filename='pictures/logo.jpg') }}" style="width: 150px; height: auto;" uk-img />
<li class="uk-nav-header">Apps</li>
<li><a href="/shopping-lists">Shopping Lists</a></li>
<li><a href="/groups">Groups</a></li>
<li><a href="/recipes">Recipes</a></li>
<li class="uk-nav-header">Logistics</li>
<li>
@ -58,6 +57,7 @@
</div>
</a>
<li><a href="/transaction">Add Transaction</a></li>
<li><a href="/workshop">Workshop</a></li>
<li><a href="/receipts">Receipts</a></li>
<li class="uk-nav-header">System Management</li>
{% if system_admin %}
@ -363,6 +363,8 @@
<tbody id="locationsTableBody">
</tbody>
</table>
<button onclick="openPossibleLocationsModal()" class="uk-button add-button"><i class="uk-flex-middle material-symbols-outlined" style="">add_circle</i></button>
</div>
</div>
<!-- Linked Items -->
@ -456,6 +458,7 @@
</table>
</div>
</div>
<!-- Location Select Modal -->
<div id="LocationsModal" uk-modal>
<div id="locationsModalInner" class="uk-modal-dialog uk-modal-body">
<h2 class="uk-modal-title">Select Location</h2>
@ -627,6 +630,7 @@
</div>
</div>
</div>
<!-- Prefix Modal -->
<div id="prefixesModal" uk-modal>
<div id="prefixesModalInner" class="uk-modal-dialog uk-modal-body " uk-overflow-auto>
<h2 class="uk-modal-title">Select Item</h2>
@ -655,6 +659,37 @@
</table>
</div>
</div>
<!-- Prefix Modal -->
<div id="NewLocationsModal" uk-modal>
<div id="NewLocationsModalInner" class="uk-modal-dialog uk-modal-body " uk-overflow-auto>
<h2 class="uk-modal-title">Select Item</h2>
<p>Select an Prefix from the system...</p>
<nav aria-label="Pagination">
<ul id="NewLocationsModalPage" 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>Zone</th>
<th>Name</th>
</tr>
</thead>
<tbody id="NewLocationsModalTableBody">
</tbody>
</table>
</div>
</div>
</body>
<script src="{{ url_for('static', filename='handlers/itemEditHandler.js') }}"></script>
<script>const item_id = {{id|tojson}}</script>

View File

@ -39,7 +39,8 @@
<a href="/items">
<div class="uk-active">Items<div class="uk-nav-subtitle" disabled>You are currently editing a linked item...</div>
</div>
</a>
</a></li>
<li><a href="/workshop">Workshop</a></li>
<li><a href="/transaction">Add Transaction</a></li>
<li><a href="/receipts">Receipts</a></li>
<li class="uk-nav-header">System Management</li>

View File

@ -39,8 +39,9 @@
<div class="uk-active">Items<div class="uk-nav-subtitle" disabled>You are currently viewing transactions...</div>
</div>
</a>
</li>
</li>
<li><a href="/add_transaction">Add Transaction</a></li>
<li><a href="/workshop">Workshop</a></li>
<li><a href="/receipts">Receipts</a></li>
<li class="uk-nav-header">System Management</li>
{% if system_admin %}

View File

View File

@ -47,7 +47,7 @@
<div class="uk-margin">
<label class="uk-form-label" for="login_password">Password</label>
<div class="uk-form-controls">
<input class="uk-input" id="login_password" type="password">
<input onkeydown="passwordEnter(event)" class="uk-input" id="login_password" type="password">
</div>
</div>
</div>

View File

@ -36,7 +36,6 @@
<img class="uk-align-center uk-border-circle" data-src="{{ url_for('static', filename='pictures/logo.jpg') }}" style="width: 150px; height: auto;" uk-img />
<li class="uk-nav-header">Apps</li>
<li><a href="/shopping-lists">Shopping Lists</a></li>
<li><a href="/groups">Groups</a></li>
<li><a href="/recipes">Recipes</a></li>
<li class="uk-nav-header">Logistics</li>
<li><a href="/items">Items</a></li>
@ -44,9 +43,23 @@
<a href="/transaction">
<div class="uk-active">Add Transaction<div class="uk-nav-subtitle" disabled>You are adding transactions...</div>
</div></a>
</li>
<li><a href="/workshop">Workshop</a></li>
<li><a href="/receipts">Receipts</a></li>
<li class="uk-nav-header">System Management</li>
<li><a><div>{{current_site}}<div class="uk-nav-subtitle">This is the current site you are viewing...</div></div></a>
<div uk-dropdown="mode: click">
<ul class="uk-nav uk-dropdown-nav">
{% 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>
{% if system_admin %}
<li><a href="/admin">Administration</a></li>
{% endif %}
@ -56,11 +69,23 @@
</div>
</div>
<div class="uk-navbar-left uk-margin-small">
<a href="#offcanvas-slide" class="uk-button uk-button-default uk-button-small" uk-icon="icon: menu" uk-toggle></a>
<a href="#offcanvas-slide" class="uk-button uk-button-default uk-button-small" uk-icon="icon: menu" uk-toggle> Menu</a>
</div>
<div class="uk-navbar-center uk-margin-small uk-visible@s">
<ul class="uk-breadcrumb">
<li style="cursor: default; user-select: none;"><span><strong>{{current_site}}</strong></span></li>
<li style="cursor: default;"><span><strong>{{current_site}}</strong></span>
<div uk-dropdown="mode: hover">
<ul class="uk-nav uk-dropdown-nav">
{% 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;"><span>Logistics</span></li>
<li><a href="/items">Items</a></li>
<li class="uk-disabled"><span>Add Transaction</span></li>

View File

@ -0,0 +1,485 @@
<!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>
<!-- 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 rel="stylesheet" href="{{ url_for('static', filename='css/uikit.min.css') }}"/>
<link rel="stylesheet" href="{{ url_for('static', filename='css/pantry.css') }}"/>
<link id="dark-mode" rel="stylesheet" href="{{ url_for('static', filename='css/dark-mode.css') }}" disabled/>
</head>
<body>
<div uk-sticky="sel-target: .uk-navbar-container; cls-active: uk-navbar-sticky">
<!-- to color the navbar i have to stlye this element the nav element -->
<nav id="navbar" class="uk-navbar-container">
<div class="uk-container uk-container-expand">
<div uk-navbar="dropbar: true">
<div id="offcanvas-slide" uk-offcanvas="mode: slide; overlay: true">
<div class="uk-offcanvas-bar uk-flex uk-flex-column">
<ul class="uk-nav uk-nav-secondary">
<img class="uk-align-center uk-border-circle" data-src="{{ url_for('static', filename='pictures/logo.jpg') }}" style="width: 150px; height: auto;" uk-img />
<li class="uk-nav-header">Apps</li>
<li><a href="/shopping-lists">Shopping Lists</a></li>
<li><a href="/recipes">Recipes</a></li>
<li class="uk-nav-header">Logistics</li>
<li><a href="/items">Items</a></li>
<li><a href="/transaction">Add Transaction</a></li>
<li>
<a href="/workshop">
<div class="uk-active">Workshop<div class="uk-nav-subtitle" disabled>Building in the workshop...</div>
</div></a>
</li>
<li><a href="/receipts">Receipts</a></li>
<li class="uk-nav-header">System Management</li>
<li><a><div>{{current_site}}<div class="uk-nav-subtitle">This is the current site you are viewing...</div></div></a>
<div uk-dropdown="mode: click">
<ul class="uk-nav uk-dropdown-nav">
{% 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>
{% if system_admin %}
<li><a href="/admin">Administration</a></li>
{% endif %}
<li><a href="" class="">{{username}}</a></li>
</ul>
<button class="uk-button uk-margin-small uk-position-top-right" uk-icon="icon: close" href=""></button>
</div>
</div>
<div class="uk-navbar-left uk-margin-small">
<a href="#offcanvas-slide" class="uk-button uk-button-default uk-button-small" uk-icon="icon: menu" uk-toggle> Menu</a>
</div>
<div class="uk-navbar-center uk-margin-small uk-visible@s">
<ul class="uk-breadcrumb">
<li style="cursor: default;"><span><strong>{{current_site}}</strong></span>
<div uk-dropdown="mode: hover">
<ul class="uk-nav uk-dropdown-nav">
{% 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;"><span>Logistics</span></li>
<li class="uk-disabled"><span>Workshop</span></li>
</ul>
</div>
<div class="uk-navbar-right">
<div>
<a onclick="toggleDarkMode()" class="uk-button uk-button-small"><span id="modeToggle" class="uk-flex material-symbols-outlined">dark_mode</span></a>
</div>
<div>
<a href="" class="" uk-icon="icon: user" uk-toggle>{{username}}</a>
</div>
</div>
</div>
</div>
</nav>
</div>
<div class="uk-section uk-margin-left">
<div class="uk-child-width-1-1" uk-grid>
<div>
<div uk-grid>
<div class="uk-width-auto">
<ul class="uk-tab-left" uk-tab="connect: #component-tab-left; animation: uk-animation-fade">
<li><a href="#">Zones</a></li>
<li><a href="#">Locations</a></li>
<li><a href="#">Vendors</a></li>
<li><a href="#">Brands</a></li>
<li><a href="#">Prefixes</a></li>
</ul>
</div>
<div class="uk-width-expand">
<div id="component-tab-left" class="uk-switcher">
<!-- Zones -->
<div class="uk-container">
<div uk-grid>
<div class="uk-width-1-1 uk-flex uk-flex-center">
<nav aria-label="Pagination">
<ul id="zonesPagination" class="uk-pagination" 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 class="uk-text-meta">Here are all the Zones that have been set up for the site <strong>{{current_site}}</strong></caption>
<table style="margin-bottom: 0px;" class="uk-table uk-table-striped">
<thead>
<tr>
<th>ID</th>
<th>Zone Name</th>
<th>Description</th>
<th>Operations</th>
</tr>
</thead>
<tbody id="zonesTableBody">
</tbody>
</table>
<button onclick="openAddZoneModal()" class="uk-button add-button"><i class="uk-flex-middle material-symbols-outlined" style="">add_circle</i></button>
</div>
</div>
</div>
<!-- Locations -->
<div class="uk-container">
<div uk-grid>
<div class="uk-width-1-1 uk-flex uk-flex-center">
<nav aria-label="Pagination">
<ul id="locationsPagination" class="uk-pagination" 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 class="uk-text-meta">Here are all the locations that have been set up for the site <strong>{{current_site}}</strong></caption>
<table style="margin-bottom: 0px;" class="uk-table uk-table-striped">
<thead>
<tr>
<th>ID</th>
<th>UUID</th>
<th>Location Name</th>
<th>Description</th>
<th>Operations</th>
</tr>
</thead>
<tbody id="locationsTableBody">
</tbody>
</table>
<button onclick="openAddLocationModal()" class="uk-button add-button"><i class="uk-flex-middle material-symbols-outlined" style="">add_circle</i></button>
</div>
</div>
</div>
<!-- Vendors -->
<div class="uk-container">
<div uk-grid>
<div class="uk-width-1-1 uk-flex uk-flex-center">
<nav aria-label="Pagination">
<ul id="vendorsPagination" class="uk-pagination" 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 class="uk-text-meta">Here are all the locations that have been set up for the site <strong>{{current_site}}</strong></caption>
<table style="margin-bottom: 0px;" class="uk-table uk-table-striped">
<thead>
<tr>
<th>ID</th>
<th>Vendor Name</th>
<th>Created By</th>
<th>Operations</th>
</tr>
</thead>
<tbody id="vendorsTableBody">
</tbody>
</table>
<button onclick="openAddVendorsModal()" class="uk-button add-button"><i class="uk-flex-middle material-symbols-outlined" style="">add_circle</i></button>
</div>
</div>
</div>
<!-- Brands -->
<div class="uk-container">
<div uk-grid>
<div class="uk-width-1-1 uk-flex uk-flex-center">
<nav aria-label="Pagination">
<ul id="brandsPagination" class="uk-pagination" 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 class="uk-text-meta">Here are all the locations that have been set up for the site <strong>{{current_site}}</strong></caption>
<table style="margin-bottom: 0px;" class="uk-table uk-table-striped">
<thead>
<tr>
<th>ID</th>
<th>Brand Name</th>
<th>Operations</th>
</tr>
</thead>
<tbody id="brandsTableBody">
</tbody>
</table>
<button onclick="openAddBrandsModal()" class="uk-button add-button"><i class="uk-flex-middle material-symbols-outlined" style="">add_circle</i></button>
</div>
</div>
</div>
<!-- Prefixes -->
<div class="uk-container">
<div uk-grid>
<div class="uk-width-1-1 uk-flex uk-flex-center">
<nav aria-label="Pagination">
<ul id="prefixesPagination" class="uk-pagination" 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 class="uk-text-meta">Here are all the locations that have been set up for the site <strong>{{current_site}}</strong></caption>
<table style="margin-bottom: 0px;" class="uk-table uk-table-striped">
<thead>
<tr>
<th>ID</th>
<th>UUID</th>
<th>Name</th>
<th>Description</th>
<th>Operations</th>
</tr>
</thead>
<tbody id="prefixesTableBody">
</tbody>
</table>
<button onclick="openAddPrefixModal()" class="uk-button add-button"><i class="uk-flex-middle material-symbols-outlined" style="">add_circle</i></button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Modals -->
<!-- Zones ADD/EDIT -->
<div id="ZonesModal" uk-modal>
<div class="uk-modal-dialog">
<button class="uk-modal-close-default" type="button" uk-close></button>
<div class="uk-modal-header">
<h2 id="ZonesModalHeader" class="uk-modal-title"></h2>
</div>
<div class="uk-modal-body">
<div class="uk-grid-small" uk-grid>
<div class="uk-width-1-1">
</div>
<div class="uk-width-1-1">
<div class="uk-margin">
<label class="uk-form-label" for="ZoneName">Zone Name</label>
<input class="uk-input" id="ZoneName" type="text" placeholder="">
</div>
</div>
<div class="uk-width-1-1">
<div class="uk-margin">
<label class="uk-form-label" for="ZoneDescription">Text</label>
<textarea id="ZoneDescription" class="uk-textarea" rows="5" placeholder="" aria-label="Textarea"></textarea>
</div>
</div>
</div>
</div>
<div class="uk-modal-footer uk-text-right">
<button class="uk-button uk-button-default uk-modal-close" type="button">Cancel</button>
<button id="ZonesModalSubmitButton" class="uk-button uk-button-primary" type="button"></button>
</div>
</div>
</div>
<!-- Locations ADD/EDIT -->
<div id="LocationsModal" uk-modal>
<div class="uk-modal-dialog">
<button class="uk-modal-close-default" type="button" uk-close></button>
<div class="uk-modal-header">
<h2 id="LocationsModalHeader" class="uk-modal-title"></h2>
</div>
<div class="uk-modal-body">
<div class="uk-grid-small" uk-grid>
<div class="uk-width-1-1">
</div>
<div class="uk-width-1-1">
<div class="uk-margin">
<label class="uk-form-label" for="LocationZoneId">Location Zone</label>
<select id="LocationZoneId" class="uk-select" aria-label="Select">
{% for zone in zones %}
<option id="locationzone_{{zone[0]}}" value="{{zone[0]}}">{{zone[1]}}</option>
{% endfor %}
</select>
</div>
</div>
<div class="uk-width-1-1">
<div class="uk-margin">
<label class="uk-form-label" for="LocationName">Location Name</label>
<input id="LocationName" class="uk-input" type="text">
</div>
</div>
<div class="uk-width-1-1">
<div class="uk-margin">
<label class="uk-form-label" for="LocationUUID">Location UUID</label>
<input class="uk-input uk-disabled" id="LocationUUID" type="text" placeholder="">
</div>
</div>
</div>
</div>
<div class="uk-modal-footer uk-text-right">
<button class="uk-button uk-button-default uk-modal-close" type="button">Cancel</button>
<button id="LocationsModalSubmitButton" class="uk-button uk-button-primary" type="button"></button>
</div>
</div>
</div>
<!-- Vendors ADD/EDIT -->
<div id="VendorsModal" uk-modal>
<div class="uk-modal-dialog">
<button class="uk-modal-close-default" type="button" uk-close></button>
<div class="uk-modal-header">
<h2 id="VendorsModalHeader" class="uk-modal-title"></h2>
</div>
<div class="uk-modal-body">
<div class="uk-grid-small" uk-grid>
<div class="uk-width-1-1">
</div>
<div class="uk-width-1-1">
<div class="uk-margin">
<label class="uk-form-label" for="VendorName">Vendor Name</label>
<input id="VendorName" class="uk-input" type="text">
</div>
</div>
<div class="uk-width-1-1">
<div class="uk-margin">
<label class="uk-form-label" for="VendorPhoneNumber">Vendor Phone Number</label>
<input class="uk-input" id="VendorPhoneNumber" type="text" placeholder="">
</div>
</div>
<div class="uk-width-1-1">
<div class="uk-margin">
<label class="uk-form-label" for="VendorAddress">Vendor Address</label>
<textarea id="VendorAddress" class="uk-textarea" rows="5" placeholder="" aria-label="Textarea"></textarea>
</div>
</div>
<div class="uk-width-1-1">
<div class="uk-margin">
<label class="uk-form-label" for="vendor_created">Vendor Created</label>
<input class="uk-input uk-disabled" id="vendor_created" type="text" placeholder="">
</div>
</div>
<div class="uk-width-1-1">
<div class="uk-margin">
<label class="uk-form-label" for="vendor_created_by">Vendor Created By</label>
<input class="uk-input uk-disabled" id="vendor_created_by" type="text" placeholder="">
</div>
</div>
</div>
</div>
<div class="uk-modal-footer uk-text-right">
<button class="uk-button uk-button-default uk-modal-close" type="button">Cancel</button>
<button id="VendorsModalSubmitButton" class="uk-button uk-button-primary" type="button"></button>
</div>
</div>
</div>
<!-- Brands ADD/EDIT -->
<div id="BrandsModal" uk-modal>
<div class="uk-modal-dialog">
<button class="uk-modal-close-default" type="button" uk-close></button>
<div class="uk-modal-header">
<h2 id="BrandsModalHeader" class="uk-modal-title"></h2>
</div>
<div class="uk-modal-body">
<div class="uk-grid-small" uk-grid>
<div class="uk-width-1-1">
</div>
<div class="uk-width-1-1">
<div class="uk-margin">
<label class="uk-form-label" for="BrandName">Brand Name</label>
<input id="BrandName" class="uk-input" type="text">
</div>
</div>
</div>
</div>
<div class="uk-modal-footer uk-text-right">
<button class="uk-button uk-button-default uk-modal-close" type="button">Cancel</button>
<button id="BrandsModalSubmitButton" class="uk-button uk-button-primary" type="button"></button>
</div>
</div>
</div>
<!-- Prefixes ADD/EDIT -->
<div id="PrefixModal" uk-modal>
<div class="uk-modal-dialog">
<button class="uk-modal-close-default" type="button" uk-close></button>
<div class="uk-modal-header">
<h2 id="PrefixModalHeader" class="uk-modal-title"></h2>
</div>
<div class="uk-modal-body">
<div class="uk-grid-small" uk-grid>
<div class="uk-width-1-1">
</div>
<div class="uk-width-1-1">
<div class="uk-margin">
<label class="uk-form-label" for="PrefixUUID">Prefix UUID</label>
<input id="PrefixUUID" class="uk-input" type="text">
</div>
</div>
<div class="uk-width-1-1">
<div class="uk-margin">
<label class="uk-form-label" for="PrefixName">Prefix Name</label>
<input id="PrefixName" class="uk-input" type="text">
</div>
</div>
<div class="uk-width-1-1">
<div class="uk-margin">
<label class="uk-form-label" for="PrefixDescription">Prefix Description</label>
<textarea id="PrefixDescription" class="uk-textarea" rows="5" placeholder="" aria-label="Textarea"></textarea>
</div>
</div>
</div>
</div>
<div class="uk-modal-footer uk-text-right">
<button class="uk-button uk-button-default uk-modal-close" type="button">Cancel</button>
<button id="PrefixModalSubmitButton" class="uk-button uk-button-primary" type="button"></button>
</div>
</div>
</div>
</body>
{% assets "js_all" %}
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
{% endassets %}
<script>const session = {{session|tojson}}</script>
<script src="{{ url_for('static', filename='handlers/workshopHandler.js') }}"></script>
</html>

View File

@ -31,13 +31,26 @@
<img class="uk-align-center uk-border-circle" data-src="{{ url_for('static', filename='pictures/logo.jpg') }}" style="width: 150px; height: auto;" uk-img />
<li class="uk-nav-header">Apps</li>
<li><a href="/shopping-lists">Shopping Lists</a></li>
<li><a href="/groups">Groups</a></li>
<li><a href="/recipes">Recipes</a></li>
<li class="uk-nav-header">Logistics</li>
<li><a href="/items">Items</a></li>
<li><a href="/transaction">Add Transaction</a></li>
<li><a href="/workshop">Workshop</a></li>
<li><a href="/receipts"><div class="uk-active">Receipts<div class="uk-nav-subtitle" disabled>You are currently viewing Receipts...</div></div></a></li>
<li class="uk-nav-header">System Management</li>
<li><a><div>{{current_site}}<div class="uk-nav-subtitle">This is the current site you are viewing...</div></div></a>
<div uk-dropdown="mode: click">
<ul class="uk-nav uk-dropdown-nav">
{% 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>
{% if system_admin %}
<li><a href="/admin">Administration</a></li>
{% endif %}
@ -47,11 +60,23 @@
</div>
</div>
<div class="uk-navbar-left uk-margin-small">
<a href="#offcanvas-slide" class="uk-button uk-button-default uk-button-small" uk-icon="icon: menu" uk-toggle></a>
<a href="#offcanvas-slide" class="uk-button uk-button-default uk-button-small" uk-icon="icon: menu" uk-toggle>Menu</a>
</div>
<div class="uk-navbar-center uk-margin-small uk-visible@m">
<ul class="uk-breadcrumb">
<li style="cursor: default; user-select: none;"><span><strong>{{current_site}}</strong></span></li>
<li style="cursor: default;"><span><strong>{{current_site}}</strong></span>
<div uk-dropdown="mode: hover">
<ul class="uk-nav uk-dropdown-nav">
{% 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;"><span>Logistics</span></li>
<li class="uk-disabled"><span>Receipts</span></li>
</ul>

View File

@ -33,11 +33,11 @@
<img class="uk-align-center uk-border-circle" data-src="{{ url_for('static', filename='pictures/logo.jpg') }}" style="width: 150px; height: auto;" uk-img />
<li class="uk-nav-header">Apps</li>
<li><a href="/shopping-lists">Shopping Lists</a></li>
<li><a href="/groups">Groups</a></li>
<li><a href="/recipes">Recipes</a></li>
<li class="uk-nav-header">Logistics</li>
<li><a href="/items">Items</a></li>
<li><a href="/transaction">Add Transaction</a></li>
<li><a href="/workshop">Workshop</a></li>
<li><a href="/receipts"><div class="uk-active">Receipts<div class="uk-nav-subtitle" disabled>You are currently editing a Receipt...</div></div></a></li>
<li class="uk-nav-header">System Management</li>
{% if system_admin %}
@ -76,7 +76,10 @@
<hr class="uk-divider-icon">
</div>
<div class="uk-width-1-1 uk-margin-remove">
<p class="uk-text-meta">This is the basic information that is attached to your receipt.</p>
<p class="uk-text-meta">This is the basic information that is attached to your receipt. You can change the Vendor associated, manipulate your receipt lines,
and attach files to a receipt. Once a receipt has been fully resolved there is no way to reopen it without admin help so be sure to only
resolve the receipt once you are sure you no longer need to change its information.
</p>
</div>
<div class="uk-width-1-1" uk-grid>
<div class="uk-width-1-2@m uk-margin-remove" uk-grid>
@ -105,35 +108,31 @@
</div>
</div>
<div class="uk-width-1-1 uk-margin-remove">
<button onclick="resolveReceipt()" class="uk-button uk-button-default">Resolve Receipt</button>
<button id="resolveReceiptButton" onclick="resolveReceipt()" class="uk-button uk-button-default">Resolve Receipt</button>
</div>
</div>
<div class="uk-width-1-2@m uk-margin-remove" uk-grid>
<div class="uk-width-1-1 uk-margin-remove" uk-grid>
<div class="uk-width-1-2 uk-padding-remove uk-margin-remove">
<label class="uk-form-label" for="vendor_id">Vendor ID</label>
<div class="uk-form-controls">
<input class="uk-input uk-disabled" id="vendor_id" type="text" placeholder="Some text..." value="####">
</div>
</div>
<div class="uk-width-1-2 uk-flex uk-flex-bottom uk-margin-remove">
<button class="uk-button uk-flex uk-flex-bottom">Select Vendor</button>
</div>
<div class="uk-width-1-2@m uk-grid-small uk-margin-remove" uk-grid>
<div id="vendorSelectDiv" class="uk-width-1-2@m">
<label class="uk-form-label" for="vendor_id">Vendor ID</label>
<input class="uk-input uk-disabled" id="vendor_id" type="text" placeholder="Some text..." value="####">
</div>
<div id="vendorSelectButton" class="uk-width-1-2@m uk-flex uk-flex-bottom uk-margin">
<button onclick="openVendorsSelectModal()" class="uk-button">Select Vendor</button>
</div>
<div class="uk-width-1-1 uk-margin-remove">
<label class="uk-form-label" for="vendor_name">Name</label>
<label class="uk-form-label" for="vendor_name">Vendor Name</label>
<div class="uk-form-controls">
<input class="uk-input uk-disabled uk-form-blank" id="vendor_name" type="text" placeholder="Some text..." value="####">
</div>
</div>
<div class="uk-width-1-1 uk-margin-remove">
<label class="uk-form-label" for="vendor_address">Address</label>
<label class="uk-form-label" for="vendor_address">Vendor Address</label>
<div class="uk-form-controls">
<input class="uk-input uk-disabled uk-form-blank" id="vendor_address" type="text" placeholder="Some text..." value="####">
</div>
</div>
<div class="uk-width-1-1 uk-margin-remove">
<label class="uk-form-label" for="vendor_phone">Phone Number</label>
<label class="uk-form-label" for="vendor_phone">Vendor Phone Number</label>
<div class="uk-form-controls">
<input class="uk-input uk-disabled uk-form-blank" id="vendor_phone" type="text" placeholder="Some text..." value="####">
</div>
@ -149,10 +148,12 @@
<!-- Lines -->
<div class="uk-grid-small" uk-grid>
<div class="uk-width-1-2@m">
<p class="uk-text-meta">Here is where all the lines on this receipt are added, changed, manipulated, and resolved.</p>
<p class="uk-text-meta">Here is where all the lines on this receipt are added, changed, manipulated, and resolved. Once a Line
has been resolved, deleted, denied it cannot be opened again so proceed with caution.
</p>
</div>
<div class="uk-width-1-2@m uk-flex uk-flex-right@m">
<button class="uk-button uk-button-default" type="button">Add Line <span uk-icon="icon: triangle-down"></span></button>
<button id="lineAddButton" class="uk-button uk-button-default" type="button">Add Line <span uk-icon="icon: triangle-down"></span></button>
<div id="addLineDropDown" uk-dropdown="mode: click">
<ul class="uk-nav uk-dropdown-nav">
<li><a onclick="openCustomModal()">Custom</a></li>
@ -220,38 +221,14 @@
<p class="uk-text-meta">You may upload whatever files you may want to attach to a receipt to later download and view them.</p>
</div>
<div class="uk-width-1-1 uk-margin">
<div uk-form-custom="target: true">
<div id="fileUploadForm"uk-form-custom="target: true">
<input type="file" aria-label="Custom controls">
<input class="uk-input" type="text" placeholder="Select file" aria-label="Custom controls" disabled>
</div>
<button onclick="uploadFile()" class="uk-button uk-button-default">Submit</button>
<button id="fileUploadButton" onclick="uploadFile()" class="uk-button uk-button-default">Submit</button>
</div>
<div class="uk-width-1-1 uk-margin">
<div id="fileCards" class="uk-child-width-1-2@m uk-grid-small" uk-grid>
<div>
<div class="uk-card uk-card-primary">
<div class="uk-card-media-top">
<img src="file-icon.png" alt="File Preview">
</div>
<div class="uk-card-body">
<h3 class="uk-card-title">File Name</h3>
<p>Size: 1.5 MB</p>
<p>Description: A brief description of the file's contents.</p>
</div>
</div>
</div>
<div>
<div class="uk-card uk-card-primary">
<div class="uk-card-media-top">
<img src="file-icon.png" alt="File Preview">
</div>
<div class="uk-card-body">
<h3 class="uk-card-title">File Name</h3>
<p>Size: 1.5 MB</p>
<p>Description: A brief description of the file's contents.</p>
</div>
</div>
</div>
</div>
</div>
</div>
@ -262,7 +239,7 @@
</div>
</div>
<!-- SKU Modal -->
<div id="itemsModal" uk-modal>
<div id="itemsModal" class="uk-modal-container" uk-modal>
<div id="itemsModalInner" class="uk-modal-dialog uk-modal-body " uk-overflow-auto>
<h2 class="uk-modal-title">Select Item</h2>
<p>Select an Item from the system...</p>
@ -291,8 +268,40 @@
</table>
</div>
</div>
<!-- SKU LinkedList Modal -->
<div id="linksModal" class="uk-modal-container" uk-modal>
<div id="linksModalInner" class="uk-modal-dialog uk-modal-body " uk-overflow-auto>
<h2 class="uk-modal-title">Select Linked List</h2>
<p>Select an Linked List from the system...</p>
<nav aria-label="Pagination">
<ul id="linksPage" 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>ID</th>
<th>Barcode</th>
<th>Name</th>
<th>Conversion Factor</th>
<th>Operations</th>
</tr>
</thead>
<tbody id="linksTableBody">
</tbody>
</table>
</div>
</div>
<!-- Line Edit Modal-->
<div id="lineEditModal" uk-modal>
<div id="lineEditModal" class="uk-modal-container" uk-modal>
<div class="uk-modal-dialog uk-modal-body">
<h2 class="uk-modal-title">Edit Line...</h2>
<p class="uk-text-meta">Edit any fields here for the selected Line and then save them.</p>
@ -349,6 +358,37 @@
</div>
</div>
</div>
<!-- Vendors Select Modal -->
<div id="vendorsModal" class="uk-modal-container" uk-modal>
<div id="vendorsModalInner" class="uk-modal-dialog uk-modal-body " uk-overflow-auto>
<h2 class="uk-modal-title">Select Vendor</h2>
<p>Select an Vendor from the system...</p>
<nav aria-label="Pagination">
<ul id="vendorsPage" 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>ID</th>
<th>Name</th>
<th>Phone #</th>
<th>Address</th>
</tr>
</thead>
<tbody id="vendorsTableBody">
</tbody>
</table>
</div>
</div>
</body>
<script src="{{ url_for('static', filename='handlers/receiptHandler.js') }}"></script>
<script>const receipt_id = {{id|tojson}}</script>

View File

@ -39,8 +39,22 @@
<li class="uk-nav-header">Logistics</li>
<li><a href="/items">Items</a></li>
<li><a href="/transaction">Add Transaction</a></li>
<li><a href="/workshop">Workshop</a></li>
<li><a href="/receipts">Receipts</a></li>
<li class="uk-nav-header">System Management</li>
<li><a><div>{{current_site}}<div class="uk-nav-subtitle">This is the current site you are viewing...</div></div></a>
<div uk-dropdown="mode: click">
<ul class="uk-nav uk-dropdown-nav">
{% 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>
{% if system_admin %}
<li><a href="/admin">Administration</a></li>
{% endif %}

View File

@ -40,6 +40,7 @@
<li class="uk-nav-header">Logistics</li>
<li><a href="/items">Items</a></li>
<li><a href="/transaction">Add Transaction</a></li>
<li><a href="/workshop">Workshop</a></li>
<li><a href="/receipts">Receipts</a></li>
<li class="uk-nav-header">System Management</li>
{% if system_admin %}

View File

@ -47,6 +47,7 @@
<li class="uk-nav-header">Logistics</li>
<li><a href="/items">Items</a></li>
<li><a href="/transaction">Add Transaction</a></li>
<li><a href="/workshop">Workshop</a></li>
<li><a href="/receipts">Receipts</a></li>
<li class="uk-nav-header">System Management</li>
{% if system_admin %}

View File

@ -42,6 +42,7 @@
<li class="uk-nav-header">Logistics</li>
<li><a href="/items">Items</a></li>
<li><a href="/transaction">Add Transaction</a></li>
<li><a href="/workshop">Workshop</a></li>
<li><a href="/receipts">Receipts</a></li>
<li class="uk-nav-header">System Management</li>
{% if system_admin %}

View File

@ -37,13 +37,26 @@
<div class="uk-active">Shopping Lists<div class="uk-nav-subtitle" disabled>You are currently browsing shopping lists here...</div></div>
</a>
</li>
<li><a href="/groups">Groups</a></li>
<li><a href="/recipes">Recipes</a></li>
<li class="uk-nav-header">Logistics</li>
<li><a href="/items">Items</a></li>
<li><a href="/transaction">Add Transaction</a></li>
<li><a href="/workshop">Workshop</a></li>
<li><a href="/receipts">Receipts</a></li>
<li class="uk-nav-header">System Management</li>
<li><a><div>{{current_site}}<div class="uk-nav-subtitle">This is the current site you are viewing...</div></div></a>
<div uk-dropdown="mode: click">
<ul class="uk-nav uk-dropdown-nav">
{% 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>
{% if system_admin %}
<li><a href="/admin">Administration</a></li>
{% endif %}
@ -53,7 +66,7 @@
</div>
</div>
<div class="uk-navbar-left uk-margin-small">
<a href="#offcanvas-slide" class="uk-button uk-button-default uk-button-small" uk-icon="icon: menu" uk-toggle></a>
<a href="#offcanvas-slide" class="uk-button uk-button-default uk-button-small" uk-icon="icon: menu" uk-toggle> Menu</a>
</div>
<div class="uk-navbar-center uk-margin-small uk-visible@m">
<ul class="uk-breadcrumb">

View File

@ -42,6 +42,7 @@
<li class="uk-nav-header">Logistics</li>
<li><a href="/items">Items</a></li>
<li><a href="/transaction">Add Transaction</a></li>
<li><a href="/workshop">Workshop</a></li>
<li><a href="/receipts">Receipts</a></li>
<li class="uk-nav-header">System Management</li>
{% if system_admin %}

20
templates/subscribe.html Normal file
View File

@ -0,0 +1,20 @@
<html>
<head>
<link rel="icon" href="data:,">
</head>
<body>
<h1>Webpush Demo</h1>
<script
type="text/javascript"
src="/static/js/register_service_worker.js">
</script>
<script type="text/javascript">
registerServiceWorker(
"/static/js/service_worker.js",
"{{config['VAPID_PUBLIC_KEY']}}",
"/api/push-subscriptions"
);
</script>
</body>
</html>

16
test.py
View File

@ -4,18 +4,4 @@ import random, uuid, csv, postsqldb
import pdf2image, os, pymupdf, PIL
def create_pdf_preview(pdf_path, output_path, size=(128, 128)):
pdf = pymupdf.open(pdf_path)
page = pdf[0]
file_name = os.path.basename(pdf_path).replace('.pdf', "")
pix = page.get_pixmap()
img = PIL.Image.frombytes("RGB", (pix.width, pix.height), pix.samples)
output_path = output_path + file_name + '.jpg'
img.thumbnail(size)
img.save(output_path)
file_path = 'static/files/receipts/Order_details_-_Walmart.com_04122025.pdf'
output_path = "static/files/receipts/previews/"
create_pdf_preview(file_path, output_path)
from pywebpush import webpush, WebPushException

53
webpush.py Normal file
View File

@ -0,0 +1,53 @@
from pywebpush import webpush, WebPushException
import json, psycopg2,requests
from flask import current_app
import postsqldb, config
def push_ntfy(title, body):
requests.post("http://ntfy.treehousefullofstars.com/pantry",
data=body,
headers={
"Title": title,
"Priority": "default",
"Tags": "tada"
})
def push_notifications(title, body):
database_config = config.config()
subscriptions = None
with psycopg2.connect(**database_config) as conn:
with conn.cursor() as cur:
cur.execute(f"SELECT * FROM push_subscriptions;")
subscriptions = cur.fetchall()
trigger_push_notifications_for_subscriptions(subscriptions, title, body)
def trigger_push_notification(subscription, title, body):
print('sub', json.loads(subscription[1]))
try:
response = webpush(
subscription_info=json.loads(subscription[1]),
data=json.dumps({"title": title, "body": body}),
vapid_private_key=current_app.config["VAPID_PRIVATE_KEY"],
vapid_claims={
"sub": "mailto:{}".format(
current_app.config["VAPID_CLAIM_EMAIL"])
}
)
print('response', response)
return response.ok
except WebPushException as ex:
if ex.response and ex.response.json():
extra = ex.response.json()
print("Remote service replied with a {}:{}, {}",
extra.code,
extra.errno,
extra.message
)
print(ex)
return False
def trigger_push_notifications_for_subscriptions(subscriptions, title, body):
return [trigger_push_notification(subscription, title, body)
for subscription in subscriptions]

View File

@ -1,16 +1,22 @@
from flask import Flask, render_template, session, request, redirect
import celery.schedules
from flask import Flask, render_template, session, request, redirect, jsonify
from flask_assets import Environment, Bundle
import api, config, user_api, psycopg2, main, admin, item_API, receipts_API, shopping_list_API, group_api, recipes_api
from user_api import login_required
from external_API import external_api
from workshop_api import workshop_api
import database
import postsqldb
from webpush import trigger_push_notifications_for_subscriptions
app = Flask(__name__)
app = Flask(__name__, instance_relative_config=True)
UPLOAD_FOLDER = 'static/pictures'
FILES_FOLDER = 'static/files'
app.config.from_pyfile('application.cfg.py')
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['FILES_FOLDER'] = FILES_FOLDER
assets = Environment(app)
app.secret_key = '11gs22h2h1a4h6ah8e413a45'
app.register_blueprint(api.database_api)
@ -18,6 +24,7 @@ app.register_blueprint(user_api.login_app)
app.register_blueprint(admin.admin)
app.register_blueprint(item_API.items_api)
app.register_blueprint(external_api)
app.register_blueprint(workshop_api)
app.register_blueprint(receipts_API.receipt_api)
app.register_blueprint(shopping_list_API.shopping_list_api)
app.register_blueprint(group_api.groups_api)
@ -80,15 +87,6 @@ def transaction():
units = postsqldb.UnitsTable.getAll(conn)
return render_template("other/transaction.html", units=units, current_site=session['selected_site'], sites=sites, proto={'referrer': request.referrer})
@app.route("/admin")
@login_required
def workshop():
sites = [site[1] for site in main.get_sites(session['user']['sites'])]
print(session.get('user')['system_admin'])
if not session.get('user')['system_admin']:
return redirect('/logout')
return render_template("admin.html", current_site=session['selected_site'], sites=sites)
@app.route("/items")
@login_required
def items():
@ -97,6 +95,24 @@ def items():
current_site=session['selected_site'],
sites=sites)
@app.route("/api/push-subscriptions", methods=["POST"])
def create_push_subscription():
json_data = request.get_json()
database_config = config.config()
with psycopg2.connect(**database_config) as conn:
with conn.cursor() as cur:
cur.execute(f"SELECT * FROM push_subscriptions WHERE subscription_json = %s;", (json_data['subscription_json'],))
rows = cur.fetchone()
if rows is None:
cur.execute(f"INSERT INTO push_subscriptions (subscription_json) VALUES (%s);", (json_data['subscription_json'],))
return jsonify({
"status": "success"
})
@app.route("/subscribe")
def subscribe():
return render_template("subscribe.html")
@app.route("/")
@login_required
def home():

269
workshop_api.py Normal file
View File

@ -0,0 +1,269 @@
from flask import Blueprint, request, render_template, redirect, session, url_for, send_file, jsonify, Response
import psycopg2, math, json, datetime, main, copy, requests, process, database, pprint, MyDataclasses
from config import config, sites_config
from main import unfoldCostLayers
from user_api import login_required
import postsqldb
workshop_api = Blueprint('workshop_api', __name__)
@workshop_api.route("/workshop")
@login_required
def workshop():
sites = [site[1] for site in main.get_sites(session['user']['sites'])]
print(session.get('user')['system_admin'])
if not session.get('user')['system_admin']:
return redirect('/logout')
database_config = config()
site_name = session['selected_site']
with psycopg2.connect(**database_config) as conn:
with conn.cursor() as cur:
sql = f"SELECT id, name FROM {site_name}_zones;"
cur.execute(sql)
zones = cur.fetchall()
return render_template("other/workshop.html", current_site=session['selected_site'], sites=sites, zones=zones)
@workshop_api.route('/workshop/getZones', methods=['GET'])
@login_required
def getZones():
if request.method == "GET":
records = []
count = 0
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 10))
offset = (page - 1) * limit
database_config = config()
site_name = session['selected_site']
with psycopg2.connect(**database_config) as conn:
records, count = postsqldb.ZonesTable.paginateZones(conn, site_name, (limit, offset))
return jsonify({'zones': records, "end": math.ceil(count/limit), 'error':False, 'message': 'Zones Loaded Successfully!'})
return jsonify({'zones': records, "end": math.ceil(count/limit), 'error':True, 'message': 'There was a problem loading Zones!'})
@workshop_api.route('/workshop/getLocations', methods=['GET'])
@login_required
def getLocations():
if request.method == "GET":
records = []
count = 0
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 10))
offset = (page - 1) * limit
database_config = config()
site_name = session['selected_site']
with psycopg2.connect(**database_config) as conn:
records, count = postsqldb.LocationsTable.paginateLocations(conn, site_name, (limit, offset))
return jsonify({'locations': records, "end": math.ceil(count/limit), 'error':False, 'message': 'Zones Loaded Successfully!'})
return jsonify({'locations': records, "end": math.ceil(count/limit), 'error':True, 'message': 'There was a problem loading Zones!'})
@workshop_api.route('/workshop/getVendors', methods=['GET'])
@login_required
def getVendors():
if request.method == "GET":
records = []
count = 0
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 10))
offset = (page - 1) * limit
database_config = config()
site_name = session['selected_site']
with psycopg2.connect(**database_config) as conn:
records, count = postsqldb.VendorsTable.paginateVendors(conn, site_name, (limit, offset))
return jsonify({'vendors': records, "end": math.ceil(count/limit), 'error':False, 'message': 'Zones Loaded Successfully!'})
return jsonify({'vendors': records, "end": math.ceil(count/limit), 'error':True, 'message': 'There was a problem loading Zones!'})
@workshop_api.route('/workshop/getBrands', methods=['GET'])
@login_required
def getBrands():
if request.method == "GET":
records = []
count = 0
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 10))
offset = (page - 1) * limit
database_config = config()
site_name = session['selected_site']
with psycopg2.connect(**database_config) as conn:
records, count = postsqldb.BrandsTable.paginateBrands(conn, site_name, (limit, offset))
return jsonify({'brands': records, "end": math.ceil(count/limit), 'error':False, 'message': 'Zones Loaded Successfully!'})
return jsonify({'brands': records, "end": math.ceil(count/limit), 'error':True, 'message': 'There was a problem loading Zones!'})
@workshop_api.route('/workshop/getPrefixes', methods=['GET'])
@login_required
def getPrefixes():
if request.method == "GET":
records = []
count = 0
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 10))
offset = (page - 1) * limit
database_config = config()
site_name = session['selected_site']
with psycopg2.connect(**database_config) as conn:
records, count = postsqldb.SKUPrefixTable.paginatePrefixes(conn, site_name, (limit, offset))
return jsonify({'prefixes': records, "end": math.ceil(count/limit), 'error':False, 'message': 'Zones Loaded Successfully!'})
return jsonify({'prefixes': records, "end": math.ceil(count/limit), 'error':True, 'message': 'There was a problem loading Zones!'})
@workshop_api.route('/workshop/postAddZone', methods=["POST"])
def postAddZone():
if request.method == "POST":
database_config = config()
site_name = session['selected_site']
try:
with psycopg2.connect(**database_config) as conn:
with conn.cursor() as cur:
cur.execute(f"SELECT id FROM sites WHERE site_name = %s;", (site_name,))
site_id = cur.fetchone()[0]
zone = postsqldb.ZonesTable.Payload(
request.get_json()['name'],
site_id,
request.get_json()['description']
)
postsqldb.ZonesTable.insert_tuple(conn, site_name, zone.payload())
except Exception as error:
conn.rollback()
return jsonify({'error': True, 'message': error})
return jsonify({'error': False, 'message': f"Zone added to {site_name}."})
return jsonify({'error': True, 'message': f"These was an error with adding this Zone to {site_name}."})
@workshop_api.route('/workshop/postEditZone', methods=["POST"])
def postEditZone():
if request.method == "POST":
database_config = config()
site_name = session['selected_site']
try:
with psycopg2.connect(**database_config) as conn:
payload = {'id': request.get_json()['zone_id'],
'update': request.get_json()['update']}
zone = postsqldb.ZonesTable.update_tuple(conn, site_name, payload)
except Exception as error:
conn.rollback()
return jsonify({'error': True, 'message': error})
return jsonify({'error': False, 'message': f"{zone['name']} edited in site {site_name}."})
return jsonify({'error': True, 'message': f"These was an error with editing Zone {zone['name']} in {site_name}."})
@workshop_api.route('/workshop/postAddLocation', methods=["POST"])
def postAddLocation():
if request.method == "POST":
database_config = config()
site_name = session['selected_site']
try:
with psycopg2.connect(**database_config) as conn:
location = postsqldb.LocationsTable.Payload(
request.get_json()['uuid'],
request.get_json()['name'],
request.get_json()['zone_id']
)
print(request.get_json())
postsqldb.LocationsTable.insert_tuple(conn, site_name, location.payload())
except Exception as error:
conn.rollback()
return jsonify({'error': True, 'message': error})
return jsonify({'error': False, 'message': f"Zone added to {site_name}."})
return jsonify({'error': True, 'message': f"These was an error with adding this Zone to {site_name}."})
@workshop_api.route('/workshop/postAddVendor', methods=["POST"])
def postAddVendor():
if request.method == "POST":
database_config = config()
site_name = session['selected_site']
try:
with psycopg2.connect(**database_config) as conn:
vendor = postsqldb.VendorsTable.Payload(
request.get_json()['vendor_name'],
session['user_id'],
request.get_json()['vendor_address'],
request.get_json()['vendor_phone_number'],
)
postsqldb.VendorsTable.insert_tuple(conn, site_name, vendor.payload())
except Exception as error:
conn.rollback()
return jsonify({'error': True, 'message': error})
return jsonify({'error': False, 'message': f"Zone added to {site_name}."})
return jsonify({'error': True, 'message': f"These was an error with adding this Zone to {site_name}."})
@workshop_api.route('/workshop/postEditVendor', methods=["POST"])
def postEditVendor():
if request.method == "POST":
database_config = config()
site_name = session['selected_site']
try:
with psycopg2.connect(**database_config) as conn:
payload = {'id': request.get_json()['vendor_id'],
'update': request.get_json()['update']}
vendor = postsqldb.VendorsTable.update_tuple(conn, site_name, payload)
except Exception as error:
conn.rollback()
return jsonify({'error': True, 'message': error})
return jsonify({'error': False, 'message': f"{vendor['vendor_name']} edited in site {site_name}."})
return jsonify({'error': True, 'message': f"These was an error with editing Zone {vendor['vendor_name']} in {site_name}."})
@workshop_api.route('/workshop/postAddBrand', methods=["POST"])
def postAddBrand():
if request.method == "POST":
database_config = config()
site_name = session['selected_site']
try:
with psycopg2.connect(**database_config) as conn:
brand = postsqldb.BrandsTable.Payload(
request.get_json()['brand_name']
)
postsqldb.BrandsTable.insert_tuple(conn, site_name, brand.payload())
except Exception as error:
conn.rollback()
return jsonify({'error': True, 'message': error})
return jsonify({'error': False, 'message': f"Brand added to {site_name}."})
return jsonify({'error': True, 'message': f"These was an error with adding this Zone to {site_name}."})
@workshop_api.route('/workshop/postEditBrand', methods=["POST"])
def postEditBrand():
if request.method == "POST":
database_config = config()
site_name = session['selected_site']
try:
with psycopg2.connect(**database_config) as conn:
payload = {'id': request.get_json()['brand_id'],
'update': request.get_json()['update']}
brand = postsqldb.BrandsTable.update_tuple(conn, site_name, payload)
except Exception as error:
conn.rollback()
return jsonify({'error': True, 'message': error})
return jsonify({'error': False, 'message': f"{brand['name']} edited in site {site_name}."})
return jsonify({'error': True, 'message': f"These was an error with editing Zone {brand['name']} in {site_name}."})
@workshop_api.route('/workshop/postAddPrefix', methods=["POST"])
def postAddPrefix():
if request.method == "POST":
database_config = config()
site_name = session['selected_site']
try:
with psycopg2.connect(**database_config) as conn:
prefix = postsqldb.SKUPrefixTable.Payload(
request.get_json()['prefix_uuid'],
request.get_json()['prefix_name'],
request.get_json()['prefix_description']
)
postsqldb.SKUPrefixTable.insert_tuple(conn, site_name, prefix.payload())
except Exception as error:
conn.rollback()
return jsonify({'error': True, 'message': error})
return jsonify({'error': False, 'message': f"Prefix added to {site_name}."})
return jsonify({'error': True, 'message': f"These was an error with adding this Prefix to {site_name}."})
@workshop_api.route('/workshop/postEditPrefix', methods=["POST"])
def postEditPrefix():
if request.method == "POST":
database_config = config()
site_name = session['selected_site']
try:
with psycopg2.connect(**database_config) as conn:
payload = {'id': request.get_json()['prefix_id'],
'update': request.get_json()['update']}
prefix = postsqldb.SKUPrefixTable.update_tuple(conn, site_name, payload)
except Exception as error:
conn.rollback()
return jsonify({'error': True, 'message': error})
return jsonify({'error': False, 'message': f"{prefix['name']} edited in site {site_name}."})
return jsonify({'error': True, 'message': f"These was an error with editing Zone {prefix['name']} in {site_name}."})