Lots of wide feature improvements
This commit is contained in:
parent
1799d00ef1
commit
3bc3655778
5
.gitignore
vendored
5
.gitignore
vendored
@ -1 +1,6 @@
|
|||||||
foodpantryserver.zip
|
foodpantryserver.zip
|
||||||
|
sites
|
||||||
|
static/css/uikit-rtl.css
|
||||||
|
static/css/uikit-rtl.min.css
|
||||||
|
static/css/uikit.css
|
||||||
|
static/css/uikit.min.css
|
||||||
@ -63,18 +63,6 @@ class FoodInfoPayload:
|
|||||||
self.default_expiration
|
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
|
@dataclass
|
||||||
class ItemsPayload:
|
class ItemsPayload:
|
||||||
@ -108,26 +96,6 @@ class ItemsPayload:
|
|||||||
self.search_string
|
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
|
@dataclass
|
||||||
class TransactionPayload:
|
class TransactionPayload:
|
||||||
@ -173,65 +141,6 @@ class CostLayerPayload:
|
|||||||
self.vendor
|
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
|
@dataclass
|
||||||
class ItemLinkPayload:
|
class ItemLinkPayload:
|
||||||
barcode: str
|
barcode: str
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
__pycache__/webpush.cpython-312.pyc
Normal file
BIN
__pycache__/webpush.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
__pycache__/workshop_api.cpython-312.pyc
Normal file
BIN
__pycache__/workshop_api.cpython-312.pyc
Normal file
Binary file not shown.
90
database.log
90
database.log
@ -1719,3 +1719,93 @@
|
|||||||
2025-04-12 19:43:45.390676 --- ERROR --- DatabaseError(message='can't adapt type 'dict'',
|
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, '{}'),
|
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 *;')
|
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')
|
||||||
@ -1597,7 +1597,10 @@ def getItemsWithQOH(conn, site, payload, convert=True):
|
|||||||
recordset = []
|
recordset = []
|
||||||
count = 0
|
count = 0
|
||||||
with open(f"sql/SELECT/getItemsWithQOH.sql", "r+") as file:
|
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:
|
try:
|
||||||
if convert:
|
if convert:
|
||||||
with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur:
|
with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur:
|
||||||
|
|||||||
@ -6,6 +6,7 @@ from threading import Thread
|
|||||||
from queue import Queue
|
from queue import Queue
|
||||||
import time, process
|
import time, process
|
||||||
from user_api import login_required
|
from user_api import login_required
|
||||||
|
import webpush
|
||||||
|
|
||||||
external_api = Blueprint('external', __name__)
|
external_api = Blueprint('external', __name__)
|
||||||
|
|
||||||
@ -22,7 +23,6 @@ def getItemLocations():
|
|||||||
database_config = config()
|
database_config = config()
|
||||||
with psycopg2.connect(**database_config) as conn:
|
with psycopg2.connect(**database_config) as conn:
|
||||||
recordset, count = database.getItemLocations(conn, site_name, (item_id, limit, offset), convert=True)
|
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":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"})
|
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()
|
database_config = config()
|
||||||
with psycopg2.connect(**database_config) as conn:
|
with psycopg2.connect(**database_config) as conn:
|
||||||
record = database.getItemAllByBarcode(conn, site_name, (item_barcode, ), convert=True)
|
record = database.getItemAllByBarcode(conn, site_name, (item_barcode, ), convert=True)
|
||||||
|
|
||||||
print(record)
|
|
||||||
if record == {}:
|
if record == {}:
|
||||||
return jsonify({"item":None, "error":True, "message":"Item either does not exist or there was a larger problem!"})
|
return jsonify({"item":None, "error":True, "message":"Item either does not exist or there was a larger problem!"})
|
||||||
else:
|
else:
|
||||||
@ -116,6 +114,7 @@ def post_receipt():
|
|||||||
data=item['item']['data']
|
data=item['item']['data']
|
||||||
)
|
)
|
||||||
database.insertReceiptItemsTuple(conn, site_name, receipt_item.payload())
|
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":False, "message":"Transaction Complete!"})
|
||||||
return jsonify({"error":True, "message":"There was an error with this POST statement"})
|
return jsonify({"error":True, "message":"There was an error with this POST statement"})
|
||||||
3
instance/application.cfg.py
Normal file
3
instance/application.cfg.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
VAPID_PUBLIC_KEY = "BIbaHOqZcTunzAeb9p1hCWBo1DeJN0NVf2WVNSrsLZ2e50vhBno5dJuRAB1NLNXIeeQYr_x-1fSifJBGfUKd6QM"
|
||||||
|
VAPID_PRIVATE_KEY = "l2wLDKWJDMkytDSN8s9iu9Y3-5qrIMy_OreUC3vDHtI"
|
||||||
|
VAPID_CLAIM_EMAIL = "jadowyne.ulve@outlook.com"
|
||||||
82
item_API.py
82
item_API.py
@ -63,14 +63,20 @@ def pagninate_items():
|
|||||||
page = int(request.args.get('page', 1))
|
page = int(request.args.get('page', 1))
|
||||||
limit = int(request.args.get('limit', 10))
|
limit = int(request.args.get('limit', 10))
|
||||||
search_string = str(request.args.get('search_text', ""))
|
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', "")
|
view = request.args.get('view', "")
|
||||||
site_name = session['selected_site']
|
site_name = session['selected_site']
|
||||||
offset = (page - 1) * limit
|
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()
|
database_config = config()
|
||||||
with psycopg2.connect(**database_config) as conn:
|
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':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!'})
|
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()
|
database_config = config()
|
||||||
with psycopg2.connect(**database_config) as conn:
|
with psycopg2.connect(**database_config) as conn:
|
||||||
payload = (limit, offset)
|
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":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"})
|
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))
|
print(count, len(zones))
|
||||||
return jsonify(zones=zones, endpage=math.ceil(count[0]/limit))
|
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'])
|
@items_api.route('/item/getLocations', methods=['get'])
|
||||||
def getLocationsByZone():
|
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))
|
page = int(request.args.get('page', 1))
|
||||||
limit = int(request.args.get('limit', 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!")
|
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"])
|
@items_api.route('/item/getLinkedItem', methods=["GET"])
|
||||||
@login_required
|
@login_required
|
||||||
def getLinkedItem():
|
def getLinkedItem():
|
||||||
@ -472,3 +528,19 @@ def refreshSearchString():
|
|||||||
|
|
||||||
return jsonify(error=False, message="Search String was updated successfully")
|
return jsonify(error=False, message="Search String was updated successfully")
|
||||||
return jsonify(error=True, message="Unable to update this search string, ERROR!")
|
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!")
|
||||||
925
postsqldb.py
925
postsqldb.py
@ -383,24 +383,7 @@ class SKUPrefixTable:
|
|||||||
raise DatabaseError(error, 'PrefixTable', sql)
|
raise DatabaseError(error, 'PrefixTable', sql)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def insert_tuple(self, conn, site: str, payload: list, convert=True):
|
def paginatePrefixes(self, conn, site: str, payload: tuple, 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):
|
|
||||||
"""_summary_
|
"""_summary_
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -434,6 +417,71 @@ class SKUPrefixTable:
|
|||||||
raise DatabaseError(error, payload, sql)
|
raise DatabaseError(error, payload, sql)
|
||||||
return recordset, count
|
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:
|
class RecipesTable:
|
||||||
@dataclass
|
@dataclass
|
||||||
class Payload:
|
class Payload:
|
||||||
@ -729,8 +777,35 @@ class RecipesTable:
|
|||||||
raise DatabaseError(error, payload, sql)
|
raise DatabaseError(error, payload, sql)
|
||||||
return updated
|
return updated
|
||||||
|
|
||||||
|
|
||||||
class ItemInfoTable:
|
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
|
@classmethod
|
||||||
def select_tuple(self, conn, site:str, payload:tuple, convert=True):
|
def select_tuple(self, conn, site:str, payload:tuple, convert=True):
|
||||||
"""_summary_
|
"""_summary_
|
||||||
@ -795,9 +870,31 @@ class ItemInfoTable:
|
|||||||
raise DatabaseError(error, payload, sql)
|
raise DatabaseError(error, payload, sql)
|
||||||
return updated
|
return updated
|
||||||
|
|
||||||
|
|
||||||
class ItemTable:
|
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
|
@classmethod
|
||||||
def getItemAllByID(self, conn, site, payload, convert=True):
|
def getItemAllByID(self, conn, site, payload, convert=True):
|
||||||
"""_summary_
|
"""_summary_
|
||||||
@ -829,6 +926,49 @@ class ItemTable:
|
|||||||
raise DatabaseError(error, payload, getItemAllByID_sql)
|
raise DatabaseError(error, payload, getItemAllByID_sql)
|
||||||
return item
|
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
|
@classmethod
|
||||||
def update_tuple(self, conn, site, payload, convert=True):
|
def update_tuple(self, conn, site, payload, convert=True):
|
||||||
"""_summary_
|
"""_summary_
|
||||||
@ -899,6 +1039,40 @@ class ReceiptTable:
|
|||||||
raise DatabaseError(error, payload, sql)
|
raise DatabaseError(error, payload, sql)
|
||||||
return updated
|
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
|
@classmethod
|
||||||
def select_tuple(self, conn, site:str, payload:tuple, convert=True):
|
def select_tuple(self, conn, site:str, payload:tuple, convert=True):
|
||||||
"""_summary_
|
"""_summary_
|
||||||
@ -928,3 +1102,714 @@ class ReceiptTable:
|
|||||||
except Exception as error:
|
except Exception as error:
|
||||||
raise DatabaseError(error, payload, sql)
|
raise DatabaseError(error, payload, sql)
|
||||||
return selected
|
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
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import database, MyDataclasses, psycopg2, datetime,json
|
import database, MyDataclasses, psycopg2, datetime,json
|
||||||
from config import config
|
from config import config
|
||||||
|
import postsqldb
|
||||||
|
|
||||||
def dropSiteTables(conn, site_manager: MyDataclasses.SiteManager):
|
def dropSiteTables(conn, site_manager: MyDataclasses.SiteManager):
|
||||||
try:
|
try:
|
||||||
@ -148,7 +149,7 @@ def postNewBlankItem(conn, site_name: str, user_id: int, data: dict):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# create item info
|
# create item info
|
||||||
item_info = MyDataclasses.ItemInfoPayload(data['barcode'])
|
item_info = postsqldb.ItemInfoTable.Payload(data['barcode'])
|
||||||
|
|
||||||
# create Food Info
|
# create Food Info
|
||||||
food_info = MyDataclasses.FoodInfoPayload()
|
food_info = MyDataclasses.FoodInfoPayload()
|
||||||
@ -190,7 +191,7 @@ def postNewBlankItem(conn, site_name: str, user_id: int, data: dict):
|
|||||||
location_id = cur.fetchone()[0]
|
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())
|
database.insertItemLocationsTuple(conn, site_name, item_location.payload())
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
107
receipts_API.py
107
receipts_API.py
@ -6,6 +6,7 @@ import openfoodfacts
|
|||||||
import postsqldb
|
import postsqldb
|
||||||
import mimetypes, os
|
import mimetypes, os
|
||||||
import pymupdf, PIL
|
import pymupdf, PIL
|
||||||
|
import webpush
|
||||||
|
|
||||||
|
|
||||||
def create_pdf_preview(pdf_path, output_path, size=(600, 400)):
|
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":False, "message":"items fetched succesfully!"})
|
||||||
return jsonify({"items":recordset, "end":math.ceil(count['count']/limit), "error":True, "message":"There was an error with this GET statement"})
|
return jsonify({"items":recordset, "end":math.ceil(count['count']/limit), "error":True, "message":"There was an error with this GET statement"})
|
||||||
|
|
||||||
|
@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"])
|
@receipt_api.route('/receipts/getReceipts', methods=["GET"])
|
||||||
def getReceipts():
|
def getReceipts():
|
||||||
recordset = []
|
recordset = []
|
||||||
@ -159,6 +192,64 @@ def saveLine():
|
|||||||
return jsonify({'error': False, "message": "Line Saved Succesfully"})
|
return jsonify({'error': False, "message": "Line Saved Succesfully"})
|
||||||
return jsonify({'error': True, "message": "Something went wrong while saving line!"})
|
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"])
|
@receipt_api.route('/receipts/resolveLine', methods=["POST"])
|
||||||
def resolveLine():
|
def resolveLine():
|
||||||
@ -248,14 +339,28 @@ def resolveLine():
|
|||||||
return jsonify({'error': False, "message": "Line Saved Succesfully"})
|
return jsonify({'error': False, "message": "Line Saved Succesfully"})
|
||||||
return jsonify({'error': True, "message": "Something went wrong while saving line!"})
|
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"])
|
@receipt_api.route('/receipts/resolveReceipt', methods=["POST"])
|
||||||
def resolveReceipt():
|
def resolveReceipt():
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
receipt_id = int(request.get_json()['receipt_id'])
|
receipt_id = int(request.get_json()['receipt_id'])
|
||||||
site_name = session['selected_site']
|
site_name = session['selected_site']
|
||||||
|
user= session['user']
|
||||||
database_config = config()
|
database_config = config()
|
||||||
with psycopg2.connect(**database_config) as conn:
|
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': False, "message": "Line Saved Succesfully"})
|
||||||
return jsonify({'error': True, "message": "Something went wrong while saving line!"})
|
return jsonify({'error': True, "message": "Something went wrong while saving line!"})
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ from config import config, sites_config
|
|||||||
from main import unfoldCostLayers
|
from main import unfoldCostLayers
|
||||||
from user_api import login_required
|
from user_api import login_required
|
||||||
import os
|
import os
|
||||||
import postsqldb
|
import postsqldb, webpush
|
||||||
|
|
||||||
recipes_api = Blueprint('recipes_api', __name__)
|
recipes_api = Blueprint('recipes_api', __name__)
|
||||||
|
|
||||||
@ -19,7 +19,6 @@ def recipes():
|
|||||||
@recipes_api.route("/recipe/<mode>/<id>")
|
@recipes_api.route("/recipe/<mode>/<id>")
|
||||||
@login_required
|
@login_required
|
||||||
def recipe(mode, id):
|
def recipe(mode, id):
|
||||||
|
|
||||||
database_config = config()
|
database_config = config()
|
||||||
with psycopg2.connect(**database_config) as conn:
|
with psycopg2.connect(**database_config) as conn:
|
||||||
units = postsqldb.UnitsTable.getAll(conn)
|
units = postsqldb.UnitsTable.getAll(conn)
|
||||||
@ -68,7 +67,8 @@ def addRecipe():
|
|||||||
author=user_id,
|
author=user_id,
|
||||||
description=recipe_description
|
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': False, 'message': 'Add Recipe successful!'})
|
||||||
return jsonify({'recipe': recipe, 'error': True, 'message': 'Add Recipe unsuccessful!'})
|
return jsonify({'recipe': recipe, 'error': True, 'message': 'Add Recipe unsuccessful!'})
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
CREATE TABLE IF NOT EXISTS %%site_name%%_zones(
|
CREATE TABLE IF NOT EXISTS %%site_name%%_zones(
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
name VARCHAR(32) NOT NULL,
|
name VARCHAR(32) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
site_id INTEGER NOT NULL,
|
site_id INTEGER NOT NULL,
|
||||||
UNIQUE(name),
|
UNIQUE(name),
|
||||||
CONSTRAINT fk_site
|
CONSTRAINT fk_site
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
INSERT INTO %%site_name%%_zones
|
INSERT INTO %%site_name%%_zones
|
||||||
(name, site_id)
|
(name, description, site_id)
|
||||||
VALUES (%s, %s)
|
VALUES (%s, %s, %s)
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
@ -13,6 +13,6 @@ FROM %%site_name%%_items
|
|||||||
LEFT JOIN sum_cte ON %%site_name%%_items.id = sum_cte.id
|
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
|
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 || '%%'
|
WHERE %%site_name%%_items.search_string LIKE '%%' || %s || '%%'
|
||||||
ORDER BY %%site_name%%_items.item_name ASC
|
ORDER BY %%sort_order%%
|
||||||
LIMIT %s OFFSET %s;
|
LIMIT %s OFFSET %s;
|
||||||
|
|
||||||
|
|||||||
5
sql/SELECT/getLocationsWithZone.sql
Normal file
5
sql/SELECT/getLocationsWithZone.sql
Normal 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;
|
||||||
15
sql/SELECT/locations/paginateLocationsBySkuZone.sql
Normal file
15
sql/SELECT/locations/paginateLocationsBySkuZone.sql
Normal 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;
|
||||||
15
sql/SELECT/locations/paginateLocationsBySkuZoneCount.sql
Normal file
15
sql/SELECT/locations/paginateLocationsBySkuZoneCount.sql
Normal 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;
|
||||||
3
sql/SELECT/selectItemLocations.sql
Normal file
3
sql/SELECT/selectItemLocations.sql
Normal 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;
|
||||||
15
sql/SELECT/zones/paginateZonesBySku.sql
Normal file
15
sql/SELECT/zones/paginateZonesBySku.sql
Normal 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;
|
||||||
|
|
||||||
13
sql/SELECT/zones/paginateZonesBySkuCount.sql
Normal file
13
sql/SELECT/zones/paginateZonesBySkuCount.sql
Normal 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;
|
||||||
@ -84,3 +84,18 @@
|
|||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
padding: 0;
|
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;
|
||||||
|
}
|
||||||
BIN
static/files/receipts/20250417_185950.jpg
Normal file
BIN
static/files/receipts/20250417_185950.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 MiB |
BIN
static/files/receipts/Order_details_-_Walmart.com_04182025.pdf
Normal file
BIN
static/files/receipts/Order_details_-_Walmart.com_04182025.pdf
Normal file
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
@ -185,7 +185,7 @@ async function updateTableElements(){
|
|||||||
let opsCell = document.createElement('th')
|
let opsCell = document.createElement('th')
|
||||||
opsCell.innerHTML = 'Operations'
|
opsCell.innerHTML = 'Operations'
|
||||||
|
|
||||||
head_row.append(nameCell, descriptionCell, opsCell)
|
head_row.append(nameCell, descriptionCell, qtyUOMCell, opsCell)
|
||||||
table_head.append(head_row)
|
table_head.append(head_row)
|
||||||
main_table.append(table_head)
|
main_table.append(table_head)
|
||||||
|
|
||||||
@ -281,12 +281,27 @@ async function updateListElements(){
|
|||||||
items_list.append(main_list)
|
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(){
|
async function getItems(){
|
||||||
|
|
||||||
const url = new URL('/item/getItemsWithQOH', window.location.origin);
|
const url = new URL('/item/getItemsWithQOH', window.location.origin);
|
||||||
url.searchParams.append('page', current_page);
|
url.searchParams.append('page', current_page);
|
||||||
url.searchParams.append('limit', limit);
|
url.searchParams.append('limit', limit);
|
||||||
url.searchParams.append('search_text', searchText);
|
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);
|
url.searchParams.append('view', view);
|
||||||
|
|
||||||
await fetch(url)
|
await fetch(url)
|
||||||
|
|||||||
@ -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 item;
|
||||||
var linked_items;
|
var linked_items;
|
||||||
var tags = new Set();
|
var tags = new Set();
|
||||||
@ -920,9 +951,10 @@ async function fetchItems() {
|
|||||||
|
|
||||||
let zones_limit = 20;
|
let zones_limit = 20;
|
||||||
async function fetchZones(){
|
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('page', current_page);
|
||||||
url.searchParams.append('limit', zones_limit);
|
url.searchParams.append('limit', zones_limit);
|
||||||
|
url.searchParams.append('item_id', item.id);
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
data = await response.json();
|
data = await response.json();
|
||||||
return data;
|
return data;
|
||||||
@ -930,13 +962,14 @@ async function fetchZones(){
|
|||||||
|
|
||||||
let locations_limit = 10;
|
let locations_limit = 10;
|
||||||
async function fetchLocations(logis) {
|
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('page', current_page);
|
||||||
url.searchParams.append('limit', locations_limit);
|
url.searchParams.append('limit', locations_limit);
|
||||||
|
url.searchParams.append('part_id', item.id);
|
||||||
if(logis=="primary_location"){
|
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"){
|
} 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);
|
const response = await fetch(url);
|
||||||
data = await response.json();
|
data = await response.json();
|
||||||
@ -1332,33 +1365,156 @@ async function updatePrefixPaginationElement() {
|
|||||||
paginationElement.append(nextElement)
|
paginationElement.append(nextElement)
|
||||||
}
|
}
|
||||||
|
|
||||||
var darkmode = false
|
// Possible Locations functions
|
||||||
function toggleDarkMode(){
|
var new_locations_current_page = 1
|
||||||
if (!darkmode){
|
var new_locations_end_page = 1
|
||||||
document.body.classList.add('dark-mode-body')
|
var new_locations_limit = 25
|
||||||
document.body.classList.add('uk-light')
|
async function fetch_new_locations() {
|
||||||
document.getElementById('navbar').classList.add('uk-light')
|
const url = new URL('/item/getPossibleLocations', window.location.origin);
|
||||||
document.getElementById('navbar').style = "background-color: #121212;"
|
url.searchParams.append('page', new_locations_current_page);
|
||||||
document.getElementById('weblinkModal').classList.add('dark-mode-element')
|
url.searchParams.append('limit', new_locations_limit);
|
||||||
document.getElementById('weblinkModalFooter').classList.add('dark-mode-element')
|
const response = await fetch(url);
|
||||||
document.getElementById('brandsModalinner').classList.add('dark-mode-element')
|
data = await response.json();
|
||||||
document.getElementById('locationsModalInner').classList.add('dark-mode-element')
|
new_locations_end_page = data.end;
|
||||||
document.getElementById('zonesModalInner').classList.add('dark-mode-element')
|
return data.locations
|
||||||
document.getElementById('modeToggle').innerHTML = "light_mode"
|
};
|
||||||
|
|
||||||
darkmode = true
|
async function postNewItemLocation(location_id) {
|
||||||
} else {
|
const response = await fetch(`/item/postNewItemLocation`, {
|
||||||
document.body.classList.remove('dark-mode-body')
|
method: 'POST',
|
||||||
document.body.classList.remove('uk-light')
|
headers: {
|
||||||
document.getElementById('navbar').classList.remove('uk-light')
|
'Content-Type': 'application/json',
|
||||||
document.getElementById('navbar').style = ""
|
},
|
||||||
document.getElementById('weblinkModal').classList.remove('dark-mode-element')
|
body: JSON.stringify({
|
||||||
document.getElementById('weblinkModalFooter').classList.remove('dark-mode-element')
|
item_id: parseInt(item_id),
|
||||||
document.getElementById('brandsModalinner').classList.remove('dark-mode-element')
|
location_id: parseInt(location_id)
|
||||||
document.getElementById('locationsModalInner').classList.remove('dark-mode-element')
|
}),
|
||||||
document.getElementById('zonesModalInner').classList.remove('dark-mode-element')
|
});
|
||||||
document.getElementById('modeToggle').innerHTML = "dark_mode"
|
|
||||||
|
|
||||||
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)
|
||||||
|
}
|
||||||
@ -1,3 +1,9 @@
|
|||||||
|
async function passwordEnter(event) {
|
||||||
|
if(event.key == "Enter"){
|
||||||
|
await loginUser()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function loginUser() {
|
async function loginUser() {
|
||||||
let username = document.getElementById('login_username').value
|
let username = document.getElementById('login_username').value
|
||||||
let password = document.getElementById('login_password').value
|
let password = document.getElementById('login_password').value
|
||||||
|
|||||||
@ -7,7 +7,6 @@ document.addEventListener('DOMContentLoaded', async function() {
|
|||||||
|
|
||||||
async function refreshReceipt() {
|
async function refreshReceipt() {
|
||||||
let receipt = await getReceipt(receipt_id)
|
let receipt = await getReceipt(receipt_id)
|
||||||
console.log(receipt)
|
|
||||||
await replenishFields(receipt)
|
await replenishFields(receipt)
|
||||||
await replenishLinesTable(receipt.receipt_items)
|
await replenishLinesTable(receipt.receipt_items)
|
||||||
await replenishFilesCards(receipt.files)
|
await replenishFilesCards(receipt.files)
|
||||||
@ -26,6 +25,14 @@ async function replenishFields(receipt) {
|
|||||||
document.getElementById('vendor_name').value = receipt.vendor.vendor_name
|
document.getElementById('vendor_name').value = receipt.vendor.vendor_name
|
||||||
document.getElementById('vendor_address').value = receipt.vendor.vendor_address
|
document.getElementById('vendor_address').value = receipt.vendor.vendor_address
|
||||||
document.getElementById('vendor_phone').value = receipt.vendor.phone_number
|
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')
|
let editOp = document.createElement('a')
|
||||||
editOp.style = "margin-right: 5px;"
|
editOp.style = "margin-right: 5px;"
|
||||||
editOp.setAttribute('class', 'uk-button uk-button-small uk-button-default')
|
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"){
|
if (receipt_items[i].type === "new sku"){
|
||||||
operationsCell.append(apiOp)
|
operationsCell.append(apiOp)
|
||||||
|
operationsCell.append(linkOp)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (receipt_items[i].type === "api"){
|
||||||
|
operationsCell.append(linkOp)
|
||||||
}
|
}
|
||||||
|
|
||||||
operationsCell.append(editOp, resolveOp, denyOp, deleteOp)
|
operationsCell.append(editOp, resolveOp, denyOp, deleteOp)
|
||||||
@ -505,3 +525,342 @@ async function updateItemsPaginationElement() {
|
|||||||
}
|
}
|
||||||
paginationElement.append(nextElement)
|
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)
|
||||||
|
}
|
||||||
@ -1,6 +1,32 @@
|
|||||||
var pagination_current = 1;
|
var pagination_current = 1;
|
||||||
var pagination_end = 10
|
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() {
|
document.addEventListener('DOMContentLoaded', async function() {
|
||||||
let receipts = await getReceipts()
|
let receipts = await getReceipts()
|
||||||
await replenishReceiptsTable(receipts)
|
await replenishReceiptsTable(receipts)
|
||||||
|
|||||||
@ -74,7 +74,7 @@ async function replenishInstructions() {
|
|||||||
innerTile.setAttribute('class', 'uk-tile uk-tile-default uk-padding-small')
|
innerTile.setAttribute('class', 'uk-tile uk-tile-default uk-padding-small')
|
||||||
|
|
||||||
let instruction = document.createElement('p')
|
let instruction = document.createElement('p')
|
||||||
instruction.innerHTML = `${recipe.instructions[i]}`
|
instruction.innerHTML = `<strong>Step ${i+1}:</strong> ${recipe.instructions[i]}`
|
||||||
|
|
||||||
|
|
||||||
innerTile.append(instruction)
|
innerTile.append(instruction)
|
||||||
|
|||||||
@ -4,6 +4,32 @@ var defaqult_limit = 2;
|
|||||||
var pagination_end = 1;
|
var pagination_end = 1;
|
||||||
var item;
|
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) {
|
async function replenishItemsTable(items) {
|
||||||
let itemsTableBody = document.getElementById("itemsTableBody")
|
let itemsTableBody = document.getElementById("itemsTableBody")
|
||||||
itemsTableBody.innerHTML = ""
|
itemsTableBody.innerHTML = ""
|
||||||
|
|||||||
1092
static/handlers/workshopHandler.js
Normal file
1092
static/handlers/workshopHandler.js
Normal file
File diff suppressed because it is too large
Load Diff
84
static/js/register_service_worker.js
Normal file
84
static/js/register_service_worker.js
Normal 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;
|
||||||
|
}
|
||||||
35
static/js/service_worker.js
Normal file
35
static/js/service_worker.js
Normal 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)
|
||||||
|
);
|
||||||
|
});
|
||||||
BIN
static/pictures/recipes/20250419_141111.jpg
Normal file
BIN
static/pictures/recipes/20250419_141111.jpg
Normal file
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
22
task_manager.py
Normal 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()
|
||||||
@ -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>
|
|
||||||
@ -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>
|
|
||||||
@ -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 />
|
<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 class="uk-nav-header">Apps</li>
|
||||||
<li><a href="/shopping-lists">Shopping Lists</a></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><a href="/recipes">Recipes</a></li>
|
||||||
<li class="uk-nav-header">Logistics</li>
|
<li class="uk-nav-header">Logistics</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/items">
|
<a href="/items">
|
||||||
<div class="uk-active">Items<div class="uk-nav-subtitle" disabled>You are currently browsing items here...</div>
|
<div class="uk-active">Items<div class="uk-nav-subtitle" disabled>You are currently browsing items here...</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a></li>
|
||||||
<li><a href="/transaction">Add Transaction</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><a href="/receipts">Receipts</a></li>
|
||||||
<li class="uk-nav-header">System Management</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 %}
|
{% if system_admin %}
|
||||||
<li><a href="/admin">Administration</a></li>
|
<li><a href="/admin">Administration</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -52,7 +65,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-navbar-left uk-margin-small">
|
<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>
|
||||||
<div class="uk-navbar-center uk-margin-small uk-visible@m">
|
<div class="uk-navbar-center uk-margin-small uk-visible@m">
|
||||||
<ul class="uk-breadcrumb">
|
<ul class="uk-breadcrumb">
|
||||||
@ -111,7 +124,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="settings" class="uk-width-1-1" hidden>
|
<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>
|
||||||
<div class="uk-width-1-1 uk-flex uk-flex-center uk-padding-small">
|
<div class="uk-width-1-1 uk-flex uk-flex-center uk-padding-small">
|
||||||
<nav aria-label="Pagination">
|
<nav aria-label="Pagination">
|
||||||
|
|||||||
@ -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 />
|
<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 class="uk-nav-header">Apps</li>
|
||||||
<li><a href="/shopping-lists">Shopping Lists</a></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><a href="/recipes">Recipes</a></li>
|
||||||
<li class="uk-nav-header">Logistics</li>
|
<li class="uk-nav-header">Logistics</li>
|
||||||
<li>
|
<li>
|
||||||
@ -58,6 +57,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<li><a href="/transaction">Add Transaction</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><a href="/receipts">Receipts</a></li>
|
||||||
<li class="uk-nav-header">System Management</li>
|
<li class="uk-nav-header">System Management</li>
|
||||||
{% if system_admin %}
|
{% if system_admin %}
|
||||||
@ -363,6 +363,8 @@
|
|||||||
<tbody id="locationsTableBody">
|
<tbody id="locationsTableBody">
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<button onclick="openPossibleLocationsModal()" class="uk-button add-button"><i class="uk-flex-middle material-symbols-outlined" style="">add_circle</i></button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Linked Items -->
|
<!-- Linked Items -->
|
||||||
@ -456,6 +458,7 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Location Select Modal -->
|
||||||
<div id="LocationsModal" uk-modal>
|
<div id="LocationsModal" uk-modal>
|
||||||
<div id="locationsModalInner" class="uk-modal-dialog uk-modal-body">
|
<div id="locationsModalInner" class="uk-modal-dialog uk-modal-body">
|
||||||
<h2 class="uk-modal-title">Select Location</h2>
|
<h2 class="uk-modal-title">Select Location</h2>
|
||||||
@ -627,6 +630,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Prefix Modal -->
|
||||||
<div id="prefixesModal" uk-modal>
|
<div id="prefixesModal" uk-modal>
|
||||||
<div id="prefixesModalInner" class="uk-modal-dialog uk-modal-body " uk-overflow-auto>
|
<div id="prefixesModalInner" class="uk-modal-dialog uk-modal-body " uk-overflow-auto>
|
||||||
<h2 class="uk-modal-title">Select Item</h2>
|
<h2 class="uk-modal-title">Select Item</h2>
|
||||||
@ -655,6 +659,37 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</body>
|
||||||
<script src="{{ url_for('static', filename='handlers/itemEditHandler.js') }}"></script>
|
<script src="{{ url_for('static', filename='handlers/itemEditHandler.js') }}"></script>
|
||||||
<script>const item_id = {{id|tojson}}</script>
|
<script>const item_id = {{id|tojson}}</script>
|
||||||
|
|||||||
@ -39,7 +39,8 @@
|
|||||||
<a href="/items">
|
<a href="/items">
|
||||||
<div class="uk-active">Items<div class="uk-nav-subtitle" disabled>You are currently editing a linked item...</div>
|
<div class="uk-active">Items<div class="uk-nav-subtitle" disabled>You are currently editing a linked item...</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a></li>
|
||||||
|
<li><a href="/workshop">Workshop</a></li>
|
||||||
<li><a href="/transaction">Add Transaction</a></li>
|
<li><a href="/transaction">Add Transaction</a></li>
|
||||||
<li><a href="/receipts">Receipts</a></li>
|
<li><a href="/receipts">Receipts</a></li>
|
||||||
<li class="uk-nav-header">System Management</li>
|
<li class="uk-nav-header">System Management</li>
|
||||||
|
|||||||
@ -41,6 +41,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="/add_transaction">Add Transaction</a></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><a href="/receipts">Receipts</a></li>
|
||||||
<li class="uk-nav-header">System Management</li>
|
<li class="uk-nav-header">System Management</li>
|
||||||
{% if system_admin %}
|
{% if system_admin %}
|
||||||
|
|||||||
@ -47,7 +47,7 @@
|
|||||||
<div class="uk-margin">
|
<div class="uk-margin">
|
||||||
<label class="uk-form-label" for="login_password">Password</label>
|
<label class="uk-form-label" for="login_password">Password</label>
|
||||||
<div class="uk-form-controls">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -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 />
|
<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 class="uk-nav-header">Apps</li>
|
||||||
<li><a href="/shopping-lists">Shopping Lists</a></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><a href="/recipes">Recipes</a></li>
|
||||||
<li class="uk-nav-header">Logistics</li>
|
<li class="uk-nav-header">Logistics</li>
|
||||||
<li><a href="/items">Items</a></li>
|
<li><a href="/items">Items</a></li>
|
||||||
@ -44,9 +43,23 @@
|
|||||||
<a href="/transaction">
|
<a href="/transaction">
|
||||||
<div class="uk-active">Add Transaction<div class="uk-nav-subtitle" disabled>You are adding transactions...</div>
|
<div class="uk-active">Add Transaction<div class="uk-nav-subtitle" disabled>You are adding transactions...</div>
|
||||||
</div></a>
|
</div></a>
|
||||||
|
</li>
|
||||||
|
<li><a href="/workshop">Workshop</a></li>
|
||||||
<li><a href="/receipts">Receipts</a></li>
|
<li><a href="/receipts">Receipts</a></li>
|
||||||
<li class="uk-nav-header">System Management</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 %}
|
{% if system_admin %}
|
||||||
<li><a href="/admin">Administration</a></li>
|
<li><a href="/admin">Administration</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -56,11 +69,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-navbar-left uk-margin-small">
|
<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>
|
||||||
<div class="uk-navbar-center uk-margin-small uk-visible@s">
|
<div class="uk-navbar-center uk-margin-small uk-visible@s">
|
||||||
<ul class="uk-breadcrumb">
|
<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 style="cursor: default; user-select: none;"><span>Logistics</span></li>
|
||||||
<li><a href="/items">Items</a></li>
|
<li><a href="/items">Items</a></li>
|
||||||
<li class="uk-disabled"><span>Add Transaction</span></li>
|
<li class="uk-disabled"><span>Add Transaction</span></li>
|
||||||
|
|||||||
485
templates/other/workshop.html
Normal file
485
templates/other/workshop.html
Normal 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>
|
||||||
@ -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 />
|
<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 class="uk-nav-header">Apps</li>
|
||||||
<li><a href="/shopping-lists">Shopping Lists</a></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><a href="/recipes">Recipes</a></li>
|
||||||
<li class="uk-nav-header">Logistics</li>
|
<li class="uk-nav-header">Logistics</li>
|
||||||
<li><a href="/items">Items</a></li>
|
<li><a href="/items">Items</a></li>
|
||||||
<li><a href="/transaction">Add Transaction</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><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 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 %}
|
{% if system_admin %}
|
||||||
<li><a href="/admin">Administration</a></li>
|
<li><a href="/admin">Administration</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -47,11 +60,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-navbar-left uk-margin-small">
|
<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>
|
||||||
<div class="uk-navbar-center uk-margin-small uk-visible@m">
|
<div class="uk-navbar-center uk-margin-small uk-visible@m">
|
||||||
<ul class="uk-breadcrumb">
|
<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 style="cursor: default; user-select: none;"><span>Logistics</span></li>
|
||||||
<li class="uk-disabled"><span>Receipts</span></li>
|
<li class="uk-disabled"><span>Receipts</span></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@ -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 />
|
<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 class="uk-nav-header">Apps</li>
|
||||||
<li><a href="/shopping-lists">Shopping Lists</a></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><a href="/recipes">Recipes</a></li>
|
||||||
<li class="uk-nav-header">Logistics</li>
|
<li class="uk-nav-header">Logistics</li>
|
||||||
<li><a href="/items">Items</a></li>
|
<li><a href="/items">Items</a></li>
|
||||||
<li><a href="/transaction">Add Transaction</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><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>
|
<li class="uk-nav-header">System Management</li>
|
||||||
{% if system_admin %}
|
{% if system_admin %}
|
||||||
@ -76,7 +76,10 @@
|
|||||||
<hr class="uk-divider-icon">
|
<hr class="uk-divider-icon">
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-width-1-1 uk-margin-remove">
|
<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>
|
||||||
<div class="uk-width-1-1" uk-grid>
|
<div class="uk-width-1-1" uk-grid>
|
||||||
<div class="uk-width-1-2@m uk-margin-remove" uk-grid>
|
<div class="uk-width-1-2@m uk-margin-remove" uk-grid>
|
||||||
@ -105,35 +108,31 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-width-1-1 uk-margin-remove">
|
<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>
|
</div>
|
||||||
<div class="uk-width-1-2@m uk-margin-remove" uk-grid>
|
<div class="uk-width-1-2@m uk-grid-small uk-margin-remove" uk-grid>
|
||||||
<div class="uk-width-1-1 uk-margin-remove" uk-grid>
|
<div id="vendorSelectDiv" class="uk-width-1-2@m">
|
||||||
<div class="uk-width-1-2 uk-padding-remove uk-margin-remove">
|
|
||||||
<label class="uk-form-label" for="vendor_id">Vendor ID</label>
|
<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="####">
|
<input class="uk-input uk-disabled" id="vendor_id" type="text" placeholder="Some text..." value="####">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div id="vendorSelectButton" class="uk-width-1-2@m uk-flex uk-flex-bottom uk-margin">
|
||||||
<div class="uk-width-1-2 uk-flex uk-flex-bottom uk-margin-remove">
|
<button onclick="openVendorsSelectModal()" class="uk-button">Select Vendor</button>
|
||||||
<button class="uk-button uk-flex uk-flex-bottom">Select Vendor</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-width-1-1 uk-margin-remove">
|
<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">
|
<div class="uk-form-controls">
|
||||||
<input class="uk-input uk-disabled uk-form-blank" id="vendor_name" type="text" placeholder="Some text..." value="####">
|
<input class="uk-input uk-disabled uk-form-blank" id="vendor_name" type="text" placeholder="Some text..." value="####">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-width-1-1 uk-margin-remove">
|
<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">
|
<div class="uk-form-controls">
|
||||||
<input class="uk-input uk-disabled uk-form-blank" id="vendor_address" type="text" placeholder="Some text..." value="####">
|
<input class="uk-input uk-disabled uk-form-blank" id="vendor_address" type="text" placeholder="Some text..." value="####">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-width-1-1 uk-margin-remove">
|
<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">
|
<div class="uk-form-controls">
|
||||||
<input class="uk-input uk-disabled uk-form-blank" id="vendor_phone" type="text" placeholder="Some text..." value="####">
|
<input class="uk-input uk-disabled uk-form-blank" id="vendor_phone" type="text" placeholder="Some text..." value="####">
|
||||||
</div>
|
</div>
|
||||||
@ -149,10 +148,12 @@
|
|||||||
<!-- Lines -->
|
<!-- Lines -->
|
||||||
<div class="uk-grid-small" uk-grid>
|
<div class="uk-grid-small" uk-grid>
|
||||||
<div class="uk-width-1-2@m">
|
<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>
|
||||||
<div class="uk-width-1-2@m uk-flex uk-flex-right@m">
|
<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">
|
<div id="addLineDropDown" uk-dropdown="mode: click">
|
||||||
<ul class="uk-nav uk-dropdown-nav">
|
<ul class="uk-nav uk-dropdown-nav">
|
||||||
<li><a onclick="openCustomModal()">Custom</a></li>
|
<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>
|
<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>
|
||||||
<div class="uk-width-1-1 uk-margin">
|
<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 type="file" aria-label="Custom controls">
|
||||||
<input class="uk-input" type="text" placeholder="Select file" aria-label="Custom controls" disabled>
|
<input class="uk-input" type="text" placeholder="Select file" aria-label="Custom controls" disabled>
|
||||||
</div>
|
</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>
|
||||||
<div class="uk-width-1-1 uk-margin">
|
<div class="uk-width-1-1 uk-margin">
|
||||||
<div id="fileCards" class="uk-child-width-1-2@m uk-grid-small" uk-grid>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -262,7 +239,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- SKU Modal -->
|
<!-- 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>
|
<div id="itemsModalInner" class="uk-modal-dialog uk-modal-body " uk-overflow-auto>
|
||||||
<h2 class="uk-modal-title">Select Item</h2>
|
<h2 class="uk-modal-title">Select Item</h2>
|
||||||
<p>Select an Item from the system...</p>
|
<p>Select an Item from the system...</p>
|
||||||
@ -291,8 +268,40 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</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-->
|
<!-- 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">
|
<div class="uk-modal-dialog uk-modal-body">
|
||||||
<h2 class="uk-modal-title">Edit Line...</h2>
|
<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>
|
<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>
|
</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>
|
</body>
|
||||||
<script src="{{ url_for('static', filename='handlers/receiptHandler.js') }}"></script>
|
<script src="{{ url_for('static', filename='handlers/receiptHandler.js') }}"></script>
|
||||||
<script>const receipt_id = {{id|tojson}}</script>
|
<script>const receipt_id = {{id|tojson}}</script>
|
||||||
|
|||||||
@ -39,8 +39,22 @@
|
|||||||
<li class="uk-nav-header">Logistics</li>
|
<li class="uk-nav-header">Logistics</li>
|
||||||
<li><a href="/items">Items</a></li>
|
<li><a href="/items">Items</a></li>
|
||||||
<li><a href="/transaction">Add Transaction</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><a href="/receipts">Receipts</a></li>
|
||||||
<li class="uk-nav-header">System Management</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 %}
|
{% if system_admin %}
|
||||||
<li><a href="/admin">Administration</a></li>
|
<li><a href="/admin">Administration</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -40,6 +40,7 @@
|
|||||||
<li class="uk-nav-header">Logistics</li>
|
<li class="uk-nav-header">Logistics</li>
|
||||||
<li><a href="/items">Items</a></li>
|
<li><a href="/items">Items</a></li>
|
||||||
<li><a href="/transaction">Add Transaction</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><a href="/receipts">Receipts</a></li>
|
||||||
<li class="uk-nav-header">System Management</li>
|
<li class="uk-nav-header">System Management</li>
|
||||||
{% if system_admin %}
|
{% if system_admin %}
|
||||||
|
|||||||
@ -47,6 +47,7 @@
|
|||||||
<li class="uk-nav-header">Logistics</li>
|
<li class="uk-nav-header">Logistics</li>
|
||||||
<li><a href="/items">Items</a></li>
|
<li><a href="/items">Items</a></li>
|
||||||
<li><a href="/transaction">Add Transaction</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><a href="/receipts">Receipts</a></li>
|
||||||
<li class="uk-nav-header">System Management</li>
|
<li class="uk-nav-header">System Management</li>
|
||||||
{% if system_admin %}
|
{% if system_admin %}
|
||||||
|
|||||||
@ -42,6 +42,7 @@
|
|||||||
<li class="uk-nav-header">Logistics</li>
|
<li class="uk-nav-header">Logistics</li>
|
||||||
<li><a href="/items">Items</a></li>
|
<li><a href="/items">Items</a></li>
|
||||||
<li><a href="/transaction">Add Transaction</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><a href="/receipts">Receipts</a></li>
|
||||||
<li class="uk-nav-header">System Management</li>
|
<li class="uk-nav-header">System Management</li>
|
||||||
{% if system_admin %}
|
{% if system_admin %}
|
||||||
|
|||||||
@ -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>
|
<div class="uk-active">Shopping Lists<div class="uk-nav-subtitle" disabled>You are currently browsing shopping lists here...</div></div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="/groups">Groups</a></li>
|
|
||||||
<li><a href="/recipes">Recipes</a></li>
|
<li><a href="/recipes">Recipes</a></li>
|
||||||
<li class="uk-nav-header">Logistics</li>
|
<li class="uk-nav-header">Logistics</li>
|
||||||
<li><a href="/items">Items</a></li>
|
<li><a href="/items">Items</a></li>
|
||||||
<li><a href="/transaction">Add Transaction</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><a href="/receipts">Receipts</a></li>
|
||||||
<li class="uk-nav-header">System Management</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 %}
|
{% if system_admin %}
|
||||||
<li><a href="/admin">Administration</a></li>
|
<li><a href="/admin">Administration</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -53,7 +66,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-navbar-left uk-margin-small">
|
<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>
|
||||||
<div class="uk-navbar-center uk-margin-small uk-visible@m">
|
<div class="uk-navbar-center uk-margin-small uk-visible@m">
|
||||||
<ul class="uk-breadcrumb">
|
<ul class="uk-breadcrumb">
|
||||||
|
|||||||
@ -42,6 +42,7 @@
|
|||||||
<li class="uk-nav-header">Logistics</li>
|
<li class="uk-nav-header">Logistics</li>
|
||||||
<li><a href="/items">Items</a></li>
|
<li><a href="/items">Items</a></li>
|
||||||
<li><a href="/transaction">Add Transaction</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><a href="/receipts">Receipts</a></li>
|
||||||
<li class="uk-nav-header">System Management</li>
|
<li class="uk-nav-header">System Management</li>
|
||||||
{% if system_admin %}
|
{% if system_admin %}
|
||||||
|
|||||||
20
templates/subscribe.html
Normal file
20
templates/subscribe.html
Normal 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
16
test.py
@ -4,18 +4,4 @@ import random, uuid, csv, postsqldb
|
|||||||
|
|
||||||
import pdf2image, os, pymupdf, PIL
|
import pdf2image, os, pymupdf, PIL
|
||||||
|
|
||||||
def create_pdf_preview(pdf_path, output_path, size=(128, 128)):
|
from pywebpush import webpush, WebPushException
|
||||||
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)
|
|
||||||
53
webpush.py
Normal file
53
webpush.py
Normal 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]
|
||||||
38
webserver.py
38
webserver.py
@ -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
|
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
|
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 user_api import login_required
|
||||||
from external_API import external_api
|
from external_API import external_api
|
||||||
|
from workshop_api import workshop_api
|
||||||
import database
|
import database
|
||||||
import postsqldb
|
import postsqldb
|
||||||
|
from webpush import trigger_push_notifications_for_subscriptions
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__, instance_relative_config=True)
|
||||||
UPLOAD_FOLDER = 'static/pictures'
|
UPLOAD_FOLDER = 'static/pictures'
|
||||||
FILES_FOLDER = 'static/files'
|
FILES_FOLDER = 'static/files'
|
||||||
|
app.config.from_pyfile('application.cfg.py')
|
||||||
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
|
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
|
||||||
app.config['FILES_FOLDER'] = FILES_FOLDER
|
app.config['FILES_FOLDER'] = FILES_FOLDER
|
||||||
|
|
||||||
|
|
||||||
assets = Environment(app)
|
assets = Environment(app)
|
||||||
app.secret_key = '11gs22h2h1a4h6ah8e413a45'
|
app.secret_key = '11gs22h2h1a4h6ah8e413a45'
|
||||||
app.register_blueprint(api.database_api)
|
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(admin.admin)
|
||||||
app.register_blueprint(item_API.items_api)
|
app.register_blueprint(item_API.items_api)
|
||||||
app.register_blueprint(external_api)
|
app.register_blueprint(external_api)
|
||||||
|
app.register_blueprint(workshop_api)
|
||||||
app.register_blueprint(receipts_API.receipt_api)
|
app.register_blueprint(receipts_API.receipt_api)
|
||||||
app.register_blueprint(shopping_list_API.shopping_list_api)
|
app.register_blueprint(shopping_list_API.shopping_list_api)
|
||||||
app.register_blueprint(group_api.groups_api)
|
app.register_blueprint(group_api.groups_api)
|
||||||
@ -80,15 +87,6 @@ def transaction():
|
|||||||
units = postsqldb.UnitsTable.getAll(conn)
|
units = postsqldb.UnitsTable.getAll(conn)
|
||||||
return render_template("other/transaction.html", units=units, current_site=session['selected_site'], sites=sites, proto={'referrer': request.referrer})
|
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")
|
@app.route("/items")
|
||||||
@login_required
|
@login_required
|
||||||
def items():
|
def items():
|
||||||
@ -97,6 +95,24 @@ def items():
|
|||||||
current_site=session['selected_site'],
|
current_site=session['selected_site'],
|
||||||
sites=sites)
|
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("/")
|
@app.route("/")
|
||||||
@login_required
|
@login_required
|
||||||
def home():
|
def home():
|
||||||
|
|||||||
269
workshop_api.py
Normal file
269
workshop_api.py
Normal 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}."})
|
||||||
Loading…
x
Reference in New Issue
Block a user