diff --git a/api.py b/api.py index 7adada4..9267eac 100644 --- a/api.py +++ b/api.py @@ -14,54 +14,6 @@ def changeSite(): session['selected_site'] = site return jsonify({'error': False, 'message': 'Site Changed!'}) - -@database_api.route("/getGroups") -def paginate_groups(): - page = int(request.args.get('page', 1)) - limit = int(request.args.get('limit', 10)) - site_name = session['selected_site'] - offset = (page - 1) * limit - - groups = [] - count = 0 - - database_config = config() - with psycopg2.connect(**database_config) as conn: - try: - with conn.cursor() as cur: - sql = f"SELECT * FROM {site_name}_groups LIMIT %s OFFSET %s;" - count = f"SELECT COUNT(*) FROM {site_name}_groups" - - cur.execute(sql, (limit, offset)) - groups = cur.fetchall() - cur.execute(count) - count = cur.fetchone()[0] - - - sql_item = f"SELECT {site_name}_items.barcode, {site_name}_items.item_name, {site_name}_logistics_info.quantity_on_hand FROM {site_name}_items LEFT JOIN {site_name}_logistics_info ON {site_name}_items.logistics_info_id = {site_name}_logistics_info.id WHERE {site_name}_items.id = %s; " - new_groups = [] - for group in groups: - qty = 0 - group = list(group) - items = [] - print(group[3]) - for item_id in group[3]: - cur.execute(sql_item, (item_id,)) - item_row = list(cur.fetchone()) - cur.execute(f"SELECT quantity_on_hand FROM {site_name}_item_locations WHERE part_id=%s;", (item_id, )) - item_locations = cur.fetchall()[0] - qty += float(sum(item_locations)) - item_row[2] = sum(item_locations) - items.append(item_row) - group[3] = items - group.append(qty) - new_groups.append(group) - except (Exception, psycopg2.DatabaseError) as error: - print(error) - - return jsonify({'groups': new_groups, "end": math.ceil(count/limit)}) - - @database_api.route("/getVendors") def get_vendors(): database_config = config() @@ -76,297 +28,4 @@ def get_vendors(): except (Exception, psycopg2.DatabaseError) as error: print(error) - return jsonify(vendors=vendors) - - -@database_api.route("/addGroup") -def addGroup(): - name = str(request.args.get('name', "")) - description = str(request.args.get('description', "")) - group_type = str(request.args.get('type', "")) - site_name = session['selected_site'] - state = "FAILED" - - database_config = config() - with psycopg2.connect(**database_config) as conn: - try: - with conn.cursor() as cur: - sql = f"INSERT INTO {site_name}_groups (name, description, included_items, group_type) VALUES (%s, %s, %s, %s);" - cur.execute(sql, (name, description, json.dumps({}), group_type)) - state = "SUCCESS" - conn.commit() - except (Exception, psycopg2.DatabaseError) as error: - print(error) - conn.rollback() - - - return jsonify({'state': state}) - -@database_api.route("/getGroup") -def get_group(): - id = int(request.args.get('id', 1)) - database_config = config() - site_name = session['selected_site'] - - group = [] - with psycopg2.connect(**database_config) as conn: - try: - with conn.cursor() as cur: - sql = f"SELECT * FROM {site_name}_groups WHERE id=%s;" - cur.execute(sql, (id, )) - group = list(cur.fetchone()) - - sql_item = f"SELECT {site_name}_items.id, {site_name}_items.barcode, {site_name}_items.item_name, {site_name}_logistics_info.quantity_on_hand FROM {site_name}_items LEFT JOIN {site_name}_logistics_info ON {site_name}_items.logistics_info_id = {site_name}_logistics_info.id WHERE {site_name}_items.id = %s;" - qty = 0 - group = list(group) - items = [] - print(group[3]) - for item_id in group[3]: - cur.execute(sql_item, (item_id,)) - item_row = cur.fetchone() - qty += float(item_row[3]) - items.append(item_row) - group[3] = items - group.append(qty) - except (Exception, psycopg2.DatabaseError) as error: - print(error) - - return jsonify(group=group) - -@database_api.route("/updateGroup", methods=["POST"]) -def update_group(): - if request.method == "POST": - site_name = session['selected_site'] - group_id = request.get_json()['id'] - items = request.get_json()['items'] - name = request.get_json()['name'] - description = request.get_json()['description'] - group_type = request.get_json()['group_type'] - data = (name, description, items, group_type, group_id) - database_config = config() - with psycopg2.connect(**database_config) as conn: - try: - with conn.cursor() as cur: - # Start by updating the group -> included items with the up to date list - sql = f"UPDATE {site_name}_groups SET name = %s, description = %s, included_items = %s, group_type = %s WHERE id=%s;" - cur.execute(sql, data) - - update_item_sql = f"UPDATE {site_name}_item_info SET groups = %s WHERE id = %s;" - select_item_sql = f"SELECT {site_name}_item_info.id, {site_name}_item_info.groups FROM {site_name}_items LEFT JOIN {site_name}_item_info ON {site_name}_items.item_info_id = {site_name}_item_info.id WHERE {site_name}_items.id = %s;" - # Now we will fetch each item row one by one and check if the group id is already inside of its groups array - for item_id in items: - cur.execute(select_item_sql, (item_id, )) - item = cur.fetchone() - print(item) - item_groups: set = set(item[1]) - # Condition check, adds it if it doesnt exist. - if group_id not in item_groups: - item_groups.add(group_id) - cur.execute(update_item_sql, (list(item_groups), item[0])) - - # Now we fetch all items that have the group id in its groups array - fetch_items_with_group = f"SELECT {site_name}_items.id, groups, {site_name}_item_info.id FROM {site_name}_item_info LEFT JOIN {site_name}_items ON {site_name}_items.item_info_id = {site_name}_item_info.id WHERE groups @> ARRAY[%s];" - cur.execute(fetch_items_with_group, (group_id, )) - group_items = cur.fetchall() - print(items) - # We will then check each item id against the groups new included_items list to see if the item should be in there - for item_id, group, info_id in group_items: - # If it is not we remove the group form the items list and update the item - if item_id not in items: - groups: list = list(group) - groups.remove(group_id) - cur.execute(update_item_sql, (list(groups), info_id)) - - conn.commit() - except (Exception, psycopg2.DatabaseError) as error: - print(error) - conn.rollback() - - return jsonify({"state": "SUCCESS"}) - return jsonify({"state": "FAILED"}) - -@database_api.route("/addList") -def addList(): - name = str(request.args.get('name', "")) - description = str(request.args.get('description', "")) - list_type = str(request.args.get('type', "")) - site_name = session['selected_site'] - - print(name, description, list_type) - state = "FAILED" - - #if name or description or group_type == "": - # print("this is empty") - # return jsonify({'state': state}) - timestamp = datetime.datetime.now() - data = (name, description, [], json.dumps({}), [], [], 0, timestamp, list_type) - database_config = config() - with psycopg2.connect(**database_config) as conn: - try: - with conn.cursor() as cur: - sql = f"INSERT INTO {site_name}_shopping_lists (name, description, pantry_items, custom_items, recipes, groups, author, creation_date, type) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s);" - cur.execute(sql, data) - state = "SUCCESS" - conn.commit() - except (Exception, psycopg2.DatabaseError) as error: - print(error) - conn.rollback() - - - return jsonify({'state': state}) - -@database_api.route("/getLists") -def paginate_lists(): - page = int(request.args.get('page', 1)) - limit = int(request.args.get('limit', 10)) - site_name = session['selected_site'] - - offset = (page - 1) * limit - - lists = [] - count = 0 - - database_config = config() - with psycopg2.connect(**database_config) as conn: - try: - with conn.cursor() as cur: - sql = f"SELECT * FROM {site_name}_shopping_lists LIMIT %s OFFSET %s;" - count = f"SELECT COUNT(*) FROM {site_name}_shopping_lists;" - - cur.execute(sql, (limit, offset)) - temp_lists = list(cur.fetchall()) - cur.execute(count) - count = cur.fetchone()[0] - - for shopping_list in temp_lists: - shopping_list: list = list(shopping_list) - pantry_items = shopping_list[3] - custom_items = shopping_list[4] - list_length = len(custom_items) - - sqlfile = open(f"sites/{site_name}/sql/unique/shopping_lists_safetystock_count.sql", "r+") - sql = "\n".join(sqlfile.readlines()) - sqlfile.close() - print(sql) - if shopping_list[10] == 'calculated': - print(shopping_list[0]) - cur.execute(sql, (shopping_list[0], )) - list_length += cur.fetchone()[0] - - else: - list_length += len(pantry_items) - - shopping_list.append(list_length) - lists.append(shopping_list) - - except (Exception, psycopg2.DatabaseError) as error: - print(error) - - return jsonify({'lists': lists, 'end': math.ceil(count/limit)}) - -@database_api.route("/getListView") -def get_list_view(): - id = int(request.args.get('id', 1)) - site_name = session['selected_site'] - shopping_list = [] - database_config = config() - with psycopg2.connect(**database_config) as conn: - try: - with conn.cursor() as cur: - sql = f"SELECT * FROM {site_name}_shopping_lists WHERE id=%s;" - cur.execute(sql, (id, )) - shopping_list = list(cur.fetchone()) - - if shopping_list[10] == "calculated": - sqlfile = open(f"sites/{site_name}/sql/unique/shopping_lists_safetystock.sql", "r+") - sql = "\n".join(sqlfile.readlines()) - sqlfile.close() - else: - sqlfile = open(f"sites/{site_name}/sql/unique/shopping_lists_safetystock_uncalculated.sql", "r+") - sql = "\n".join(sqlfile.readlines()) - sqlfile.close() - - cur.execute(sql, (id, )) - shopping_list[3] = list(cur.fetchall()) - print(shopping_list[4]) - - except (Exception, psycopg2.DatabaseError) as error: - print(error) - - return jsonify(shopping_list=shopping_list) - -@database_api.route("/getList") -def get_list(): - id = int(request.args.get('id', 1)) - database_config = config() - site_name = session['selected_site'] - shopping_list = [] - with psycopg2.connect(**database_config) as conn: - try: - with conn.cursor() as cur: - sql = f"SELECT * FROM {site_name}_shopping_lists WHERE id=%s;" - cur.execute(sql, (id, )) - shopping_list = list(cur.fetchone()) - itemSQL = f"SELECT {site_name}_items.id, {site_name}_items.barcode, {site_name}_items.item_name, {site_name}_items.links, {site_name}_item_info.uom FROM {site_name}_items LEFT JOIN {site_name}_item_info ON {site_name}_items.item_info_id = {site_name}_item_info.id WHERE {site_name}_item_info.shopping_lists @> ARRAY[%s];" - cur.execute(itemSQL, (id, )) - shopping_list[3] = list(cur.fetchall()) - print(shopping_list) - except (Exception, psycopg2.DatabaseError) as error: - print(error) - - return jsonify(shopping_list=shopping_list) - -@database_api.route("/updateList", methods=["POST"]) -def update_list(): - if request.method == "POST": - site_name = session['selected_site'] - list_id = request.get_json()['id'] - items = request.get_json()['items'] - print(items) - custom_items = request.get_json()['custom'] - name = request.get_json()['name'] - description = request.get_json()['description'] - list_type = request.get_json()['list_type'] - quantities = request.get_json()['quantities'] - data = (name, description, items, json.dumps(custom_items), list_type, json.dumps(quantities), list_id) - database_config = config() - with psycopg2.connect(**database_config) as conn: - try: - with conn.cursor() as cur: - # Start by updating the group -> included items with the up to date list - sql = f"UPDATE {site_name}_shopping_lists SET name = %s, description = %s, pantry_items = %s, custom_items = %s, type = %s, quantities = %s WHERE id=%s;" - cur.execute(sql, data) - - update_item_sql = f"UPDATE {site_name}_item_info SET shopping_lists = %s WHERE id = %s;" - select_item_sql = f"SELECT {site_name}_item_info.id, {site_name}_item_info.shopping_lists FROM {site_name}_items LEFT JOIN {site_name}_item_info ON {site_name}_items.item_info_id = {site_name}_item_info.id WHERE {site_name}_items.id = %s;" - # Now we will fetch each item row one by one and check if the group id is already inside of its groups array - for item_id in items: - cur.execute(select_item_sql, (item_id, )) - item = cur.fetchone() - print(item) - shopping_lists: set = set(item[1]) - # Condition check, adds it if it doesnt exist. - if list_id not in shopping_lists: - shopping_lists.add(list_id) - cur.execute(update_item_sql, (list(shopping_lists), item[0])) - - # Now we fetch all items that have the group id in its groups array - fetch_items_with_list = f"SELECT {site_name}_items.id, {site_name}_item_info.shopping_lists, {site_name}_item_info.id FROM {site_name}_item_info LEFT JOIN {site_name}_items ON {site_name}_items.item_info_id = {site_name}_item_info.id WHERE {site_name}_item_info.shopping_lists @> ARRAY[%s];" - cur.execute(fetch_items_with_list, (list_id, )) - list_items = cur.fetchall() - print(items) - # We will then check each item id against the groups new included_items list to see if the item should be in there - for item_id, shopping_list, info_id in list_items: - # If it is not we remove the group form the items list and update the item - if item_id not in items: - shopping_lists: list = list(shopping_list) - shopping_lists.remove(list_id) - cur.execute(update_item_sql, (list(shopping_lists), info_id)) - - conn.commit() - except (Exception, psycopg2.DatabaseError) as error: - print(error) - conn.rollback() - - return jsonify({"state": "SUCCESS"}) - return jsonify({"state": "FAILED"}) \ No newline at end of file + return jsonify(vendors=vendors) \ No newline at end of file diff --git a/application/items/database_items.py b/application/items/database_items.py index afef4eb..6415df2 100644 --- a/application/items/database_items.py +++ b/application/items/database_items.py @@ -208,6 +208,38 @@ def getZone(site:str, payload:tuple, convert:bool=True): except Exception as error: raise postsqldb.DatabaseError(error, payload, sql) +def getItemLocations(site, payload, convert=True, conn=None): + locations = [] + count = 0 + self_conn = False + with open(f"application/items/sql/getItemLocations.sql", "r+") as file: + sql = file.read().replace("%%site_name%%", site) + try: + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = True + self_conn = True + + with conn.cursor() as cur: + cur.execute(sql, payload) + rows = cur.fetchall() + if rows and convert: + locations = [postsqldb.tupleDictionaryFactory(cur.description, row) for row in rows] + if rows and not convert: + locations = rows + + cur.execute(f"SELECT COUNT(*) FROM {site}_item_locations WHERE part_id=%s;", (payload[0],)) + count = cur.fetchone()[0] + + if self_conn: + conn.close() + + return locations, count + + except (Exception, psycopg2.DatabaseError) as error: + raise postsqldb.DatabaseError(error, payload, sql) + def getItemInfoTuple(site:str, payload:tuple, convert=True): """_summary_ diff --git a/application/items/items_API.py b/application/items/items_API.py index d6af383..0223169 100644 --- a/application/items/items_API.py +++ b/application/items/items_API.py @@ -896,4 +896,30 @@ def postNewItemLocation(): item_location = dbPayloads.ItemLocationPayload(item_id, location_id) database_items.insertItemLocationsTuple(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!") \ No newline at end of file + return jsonify(error=True, message="Unable to save this location, ERROR!") + +@items_api.route("/getItemLocations", methods=["GET"]) +def getItemLocations(): + recordset = [] + count = 0 + if request.method == "GET": + item_id = int(request.args.get('id', 1)) + page = int(request.args.get('page', 1)) + limit = int(request.args.get('limit', 10)) + site_name = session['selected_site'] + offset = (page - 1) * limit + recordset, count = database_items.getItemLocations(site_name, (item_id, limit, offset)) + 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"}) + + +@items_api.route('/postTransaction', methods=["POST"]) +def post_transaction(): + if request.method == "POST": + result = items_processes.postAdjustment( + site_name=session['selected_site'], + user_id=session['user_id'], + data=dict(request.json) + ) + return jsonify(result) + return jsonify({"error":True, "message":"There was an error with this POST statement"}) \ No newline at end of file diff --git a/application/items/sql/getItemLocations.sql b/application/items/sql/getItemLocations.sql new file mode 100644 index 0000000..59e72cf --- /dev/null +++ b/application/items/sql/getItemLocations.sql @@ -0,0 +1,5 @@ +SELECT * FROM %%site_name%%_item_locations +LEFT JOIN %%site_name%%_locations ON %%site_name%%_locations.id = %%site_name%%_item_locations.location_id +WHERE part_id = %s +LIMIT %s +OFFSET %s; \ No newline at end of file diff --git a/application/items/static/transactionHandler.js b/application/items/static/transactionHandler.js index 0ecb98b..5a81136 100644 --- a/application/items/static/transactionHandler.js +++ b/application/items/static/transactionHandler.js @@ -166,7 +166,7 @@ async function replenishItemLocationsTable(locations) { let locations_limit = 10; async function getItemLocations() { console.log("getting Locations") - const url = new URL('/external/getItemLocations', window.location.origin); + const url = new URL('/items/getItemLocations', window.location.origin); url.searchParams.append('page', pagination_current); url.searchParams.append('limit', locations_limit); url.searchParams.append('id', item.id); @@ -182,7 +182,7 @@ async function getItemLocations() { let items_limit = 50; async function getItems() { console.log("getting items") - const url = new URL('/external/getModalItems', window.location.origin); + const url = new URL('/items/getModalItems', window.location.origin); url.searchParams.append('page', pagination_current); url.searchParams.append('limit', items_limit); url.searchParams.append('search_string', search_string) @@ -195,7 +195,7 @@ async function getItems() { async function getItem(id) { console.log(`selected item: ${id}`) - const url = new URL('/external/getItem', window.location.origin); + const url = new URL('/items/getItem', window.location.origin); url.searchParams.append('id', id); const response = await fetch(url); data = await response.json(); @@ -267,7 +267,7 @@ async function submitTransaction() { let validated = await validateTransaction() if (validated){ let cost = parseFloat(document.getElementById('transaction_cost').value.replace(/[^0-9.-]+/g, "")); - const response = await fetch(`/external/postTransaction`, { + const response = await fetch(`/items/postTransaction`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -408,278 +408,15 @@ async function updatePaginationElement(elementID) { paginationElement.append(nextElement) } -var scannedItems = Array(); -const queueLimit = 5; // 49 should be default - -async function addToQueue(event) { - if (event.key == "Enter"){ - let data = await getItemBarcode(document.getElementById('barcode-scan').value) - let scannedItem = data.item - if(data.error){ - UIkit.notification({ - message: data.message, - status: "danger", - pos: 'top-right', - timeout: 5000 - }); - } - if(scannedItems.length > queueLimit){ - scannedItems.shift() - } - if(!Array.isArray(scannedItem) && !data.error){ - let status = await submitScanTransaction(scannedItem) - scannedItems.push({'item': scannedItem, 'type': `${document.getElementById('scan_trans_type').value}`, 'error': status}) - document.getElementById('barcode-scan').value = "" - } - } - await replenishScanTable() -} - async function getItemBarcode(barcode) { console.log(`selected item: ${barcode}`) - const url = new URL('/external/getItem/barcode', window.location.origin); + const url = new URL('/items/getItem/barcode', window.location.origin); url.searchParams.append('barcode', barcode); const response = await fetch(url); data = await response.json(); return data; } -async function submitScanTransaction(scannedItem) { - /// I need to find the location that matches the items auto issue location id - - let trans_type = document.getElementById('scan_trans_type').value - let scan_transaction_item_location_id = 0 - let comparator = 0 - - if (trans_type === "Adjust In"){ - comparator = scannedItem.logistics_info.primary_location.id - } else if (trans_type === "Adjust Out"){ - comparator = scannedItem.logistics_info.auto_issue_location.id - } - - for (let i = 0; i < scannedItem.item_locations.length; i++){ - if (scannedItem.item_locations[i].location_id === comparator){ - scan_transaction_item_location_id = scannedItem.item_locations[i].id - } - } - - const response = await fetch(`/external/postTransaction`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - item_id: scannedItem.id, - logistics_info_id: scannedItem.logistics_info_id, - barcode: scannedItem.barcode, - item_name: scannedItem.item_name, - transaction_type: document.getElementById('scan_trans_type').value, - quantity: scannedItem.item_info.uom_quantity, - description: "", - cost: parseFloat(scannedItem.item_info.cost), - vendor: 0, - expires: null, - location_id: scan_transaction_item_location_id - }), - }); - 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 - }); - - return data.error - -} - -async function replenishScanTable() { - let scanTableBody = document.getElementById("scanTableBody") - scanTableBody.innerHTML = "" - - let reversedScannedItems = scannedItems.slice().reverse() - - for(let i = 0; i < reversedScannedItems.length; i++){ - let tableRow = document.createElement('tr') - - let icon = `` - if(reversedScannedItems[i].error){ - icon = `` - } - - let statusCell = document.createElement('td') - statusCell.innerHTML = icon - let barcodeCell = document.createElement('td') - barcodeCell.innerHTML = reversedScannedItems[i].item.barcode - let nameCell = document.createElement('td') - nameCell.innerHTML = reversedScannedItems[i].item.item_name - let typeCell = document.createElement('td') - typeCell.innerHTML = reversedScannedItems[i].type - let locationCell = document.createElement('td') - if (reversedScannedItems[i].type === "Adjust In"){ - locationCell.innerHTML = reversedScannedItems[i].item.logistics_info.primary_location.uuid - } else { - locationCell.innerHTML = reversedScannedItems[i].item.logistics_info.auto_issue_location.uuid - } - - tableRow.append(statusCell, barcodeCell, nameCell, typeCell, locationCell) - scanTableBody.append(tableRow) - } -} - -async function submitScanReceipt(items) { - const response = await fetch(`/external/postReceipt`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - items: items - }), - }); - 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 - }); - - return data.error -} - -var openedReceipt = false -async function startReceipt() { - openedReceipt = true - document.getElementById('barcode-input').classList.remove('uk-disabled') - document.getElementById('barcode-table').classList.remove('uk-disabled') - - document.getElementById('receiptStart').classList.add('uk-disabled') - document.getElementById('receiptComplete').classList.remove('uk-disabled') - document.getElementById('receiptClose').classList.remove('uk-disabled') - -} - -async function completeReceipt() { - openedReceipt = false - document.getElementById('barcode-input').classList.add('uk-disabled') - document.getElementById('barcode-table').classList.add('uk-disabled') - - document.getElementById('receiptStart').classList.remove('uk-disabled') - document.getElementById('receiptComplete').classList.add('uk-disabled') - document.getElementById('receiptClose').classList.add('uk-disabled') - - await submitScanReceipt(scannedReceiptItems) - let scanReceiptTableBody = document.getElementById("scanReceiptTableBody") - scanReceiptTableBody.innerHTML = "" - - scannedReceiptItems = Array() - -} - -async function closeReceipt(){ - openedReceipt = false - document.getElementById('barcode-input').classList.add('uk-disabled') - document.getElementById('barcode-table').classList.add('uk-disabled') - - document.getElementById('receiptStart').classList.remove('uk-disabled') - document.getElementById('receiptComplete').classList.add('uk-disabled') - document.getElementById('receiptClose').classList.add('uk-disabled') - - let scanReceiptTableBody = document.getElementById("scanReceiptTableBody") - scanReceiptTableBody.innerHTML = "" - - scannedReceiptItems = Array() -} - -var scannedReceiptItems = Array(); -async function addToReceipt(event) { - if (event.key == "Enter"){ - let barcode = document.getElementById('barcode-scan-receipt').value - let data = await getItemBarcode(barcode) - let scannedItem = data.item - if(scannedItem){ - let expires = scannedItem.food_info.expires - console.log(expires) - if(scannedItem.food_info.expires){ - let today = new Date(); - today.setDate(today.getDate() + Number(scannedItem.food_info.default_expiration)) - expires = today.toISOString().split('T')[0]; - } - scannedReceiptItems.push({item: { - barcode: scannedItem.barcode, - item_name: scannedItem.item_name, - qty: scannedItem.item_info.uom_quantity, - uom: scannedItem.item_info.uom.id, - data: {cost: scannedItem.item_info.cost, expires: expires} - }, type: 'sku'}) - document.getElementById('barcode-scan-receipt').value = "" - } else { - scannedReceiptItems.push({item: { - barcode: `%${barcode}%`, - item_name: "unknown", - qty: 1, - uom: 1, - data: {'cost': 0.00, 'expires': false} - }, type: 'new sku'}) - document.getElementById('barcode-scan-receipt').value = "" - } - } - await replenishScannedReceiptTable(scannedReceiptItems) -} - -async function replenishScannedReceiptTable(items) { - let scanReceiptTableBody = document.getElementById("scanReceiptTableBody") - scanReceiptTableBody.innerHTML = "" - - for(let i = 0; i < items.length; i++){ - let tableRow = document.createElement('tr') - - let typeCell = document.createElement('td') - typeCell.innerHTML = items[i].type - let barcodeCell = document.createElement('td') - barcodeCell.innerHTML = items[i].item.barcode - let nameCell = document.createElement('td') - nameCell.innerHTML = items[i].item.item_name - - let operationsCell = document.createElement('td') - - let editOp = document.createElement('a') - editOp.style = "margin-right: 5px;" - editOp.setAttribute('class', 'uk-button uk-button-small uk-button-default') - editOp.setAttribute('uk-icon', 'icon: pencil') - editOp.onclick = async function () { - await openLineEditModal(i, items[i]) - } - - let deleteOp = document.createElement('a') - deleteOp.setAttribute('class', 'uk-button uk-button-small uk-button-default') - deleteOp.setAttribute('uk-icon', 'icon: trash') - deleteOp.onclick = async function() { - scannedReceiptItems.splice(i, 1) - await replenishScannedReceiptTable(scannedReceiptItems) - } - - operationsCell.append(editOp, deleteOp) - - operationsCell.classList.add("uk-flex") - operationsCell.classList.add("uk-flex-right") - - tableRow.append(typeCell, barcodeCell, nameCell, operationsCell) - scanReceiptTableBody.append(tableRow) - } -} async function openLineEditModal(ind, line_data) { console.log(line_data) diff --git a/application/items/static/transactionsHandler.js b/application/items/static/transactionsHandler.js index e2cbca7..7047302 100644 --- a/application/items/static/transactionsHandler.js +++ b/application/items/static/transactionsHandler.js @@ -96,7 +96,7 @@ async function replenishTransactionsTable(transactions) { } async function getItem(id) { - const url = new URL('/external/getItem', window.location.origin); + const url = new URL('/items/getItem', window.location.origin); url.searchParams.append('id', id); const response = await fetch(url); data = await response.json(); diff --git a/application/poe/poe_database.py b/application/poe/poe_database.py index fc8df36..bd59bfe 100644 --- a/application/poe/poe_database.py +++ b/application/poe/poe_database.py @@ -175,7 +175,6 @@ def selectItemAllByBarcode(site, payload, convert=True, conn=None): item = () self_conn = False linked_item = selectLinkedItemByBarcode(site, (payload[0],)) - if len(linked_item) > 1: item = selectItemAllByID(site, payload=(linked_item['link'], ), convert=convert) item['item_info']['uom_quantity'] = linked_item['conv_factor'] diff --git a/application/postsqldb.py b/application/postsqldb.py index 57e8288..27b82d2 100644 --- a/application/postsqldb.py +++ b/application/postsqldb.py @@ -61,6 +61,31 @@ def get_sites(sites=[]): return False +def get_units_of_measure(convert=True, conn=None): + records = () + self_conn = False + sql = f"SELECT * FROM units;" + try: + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = True + self_conn = True + + with conn.cursor() as cur: + cur.execute(sql) + rows = cur.fetchall() + if rows and convert: + records = [tupleDictionaryFactory(cur.description, row) for row in rows] + elif rows and not convert: + records = rows + + if self_conn: + conn.close() + return records + except Exception as error: + raise DatabaseError(error, "", sql) + class ConversionsTable: @dataclass class Payload: diff --git a/application/receipts/__init__.py b/application/receipts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/receipts_API.py b/application/receipts/receipts_api.py similarity index 84% rename from receipts_API.py rename to application/receipts/receipts_api.py index a9a6d56..80d9e13 100644 --- a/receipts_API.py +++ b/application/receipts/receipts_api.py @@ -1,44 +1,37 @@ from flask import Blueprint, request, render_template, redirect, session, url_for, send_file, jsonify, Response, current_app, send_from_directory -import psycopg2, math, json, datetime, main, copy, requests, process, database, pprint, MyDataclasses -from config import config, sites_config +import psycopg2, math, datetime, process, database, MyDataclasses +from config import config from user_api import login_required import openfoodfacts import postsqldb import mimetypes, os -import pymupdf, PIL import webpush - -def create_pdf_preview(pdf_path, output_path, size=(600, 400)): - 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) - return file_name + '.jpg' +from application import postsqldb, database_payloads +from application.receipts import receipts_processes, receipts_database -receipt_api = Blueprint('receipt_api', __name__) +receipt_api = Blueprint('receipt_api', __name__, template_folder='templates', static_folder='static') -@receipt_api.route("/receipt/") -@login_required -def receipt(id): - sites = [site[1] for site in main.get_sites(session['user']['sites'])] - database_config = config() - with psycopg2.connect(**database_config) as conn: - units = postsqldb.UnitsTable.getAll(conn) - return render_template("receipts/receipt.html", id=id, current_site=session['selected_site'], sites=sites, units=units) -@receipt_api.route("/receipts") +# ROOT TEMPLATE ROUTES +@receipt_api.route("/") @login_required def receipts(): - sites = [site[1] for site in main.get_sites(session['user']['sites'])] - return render_template("receipts/index.html", current_site=session['selected_site'], sites=sites) + sites = [site[1] for site in postsqldb.get_sites(session['user']['sites'])] + return render_template("receipts_index.html", current_site=session['selected_site'], sites=sites) -@receipt_api.route('/receipts/getItems', methods=["GET"]) +@receipt_api.route("/") +@login_required +def receipt(id): + sites = [site[1] for site in postsqldb.get_sites(session['user']['sites'])] + units = postsqldb.get_units_of_measure() + return render_template("receipt.html", id=id, current_site=session['selected_site'], sites=sites, units=units) + + +# API ROUTES +# Added to Database +@receipt_api.route('/api/getItems', methods=["GET"]) def getItems(): recordset = [] count = {'count': 0} @@ -47,14 +40,13 @@ def getItems(): 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 = database.getItemsWithQOH(conn, site_name, payload, convert=True) + sort_order = "ID ASC" + payload = ("%%", limit, offset, sort_order) + recordset, count = receipts_database.getItemsWithQOH(site_name, payload) return jsonify({"items":recordset, "end":math.ceil(count['count']/limit), "error":False, "message":"items fetched succesfully!"}) return jsonify({"items":recordset, "end":math.ceil(count['count']/limit), "error":True, "message":"There was an error with this GET statement"}) -@receipt_api.route('/receipt/getVendors', methods=["GET"]) +@receipt_api.route('/api/getVendors', methods=["GET"]) def getVendors(): recordset = [] count = 0 @@ -70,7 +62,7 @@ def getVendors(): 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"]) +@receipt_api.route('/api/getLinkedLists', methods=["GET"]) def getLinkedLists(): recordset = [] count = 0 @@ -86,7 +78,7 @@ def getLinkedLists(): 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('/api/getReceipts', methods=["GET"]) def getReceipts(): recordset = [] if request.method == "GET": @@ -100,7 +92,7 @@ def getReceipts(): return jsonify({'receipts':recordset, "end": math.ceil(count/limit), 'error': False, "message": "Get Receipts Successful!"}) return jsonify({'receipts': recordset, "end": math.ceil(count/limit), 'error': True, "message": "Something went wrong while getting receipts!"}) -@receipt_api.route('/receipts/getReceipt', methods=["GET"]) +@receipt_api.route('/api/getReceipt', methods=["GET"]) def getReceipt(): record = [] if request.method == "GET": @@ -112,7 +104,7 @@ def getReceipt(): return jsonify({'receipt': record, 'error': False, "message": "Get Receipts Successful!"}) return jsonify({'receipt': record, 'error': True, "message": "Something went wrong while getting receipts!"}) -@receipt_api.route('/receipts/addReceipt', methods=["POST", "GET"]) +@receipt_api.route('/api/addReceipt', methods=["POST", "GET"]) def addReceipt(): if request.method == "GET": user_id = session['user_id'] @@ -127,34 +119,33 @@ def addReceipt(): return jsonify({'error': False, "message": "Receipt Added Successful!"}) return jsonify({'error': True, "message": "Something went wrong while adding receipt!"}) -@receipt_api.route('/receipts/addSKULine', methods=["POST"]) +# Added to Database +@receipt_api.route('/api/addSKULine', methods=["POST"]) def addSKULine(): if request.method == "POST": item_id = int(request.get_json()['item_id']) receipt_id = int(request.get_json()['receipt_id']) site_name = session['selected_site'] - database_config = config() - with psycopg2.connect(**database_config) as conn: - item = database.getItemAllByID(conn, site_name, (item_id, ), convert=True) - data = { - 'cost': item['item_info']['cost'], - 'expires': item['food_info']['expires'] - } - receipt_item = MyDataclasses.ReceiptItemPayload( - type="sku", - receipt_id=receipt_id, - barcode=item['barcode'], - name=item['item_name'], - qty=item['item_info']['uom_quantity'], - uom=item['item_info']['uom'], - data=data - ) - database.insertReceiptItemsTuple(conn, site_name, receipt_item.payload()) + item = receipts_database.getItemAllByID(site_name, (item_id, )) + data = { + 'cost': item['item_info']['cost'], + 'expires': item['food_info']['expires'] + } + receipt_item = database_payloads.ReceiptItemPayload( + type="sku", + receipt_id=receipt_id, + barcode=item['barcode'], + name=item['item_name'], + qty=item['item_info']['uom_quantity'], + uom=item['item_info']['uom']['id'], + data=data + ) + receipts_database.insertReceiptItemsTuple(site_name, receipt_item.payload()) return jsonify({'error': False, "message": "Line added Succesfully"}) return jsonify({'error': True, "message": "Something went wrong while add SKU line!"}) -@receipt_api.route('/receipts/deleteLine', methods=["POST"]) +@receipt_api.route('/api/deleteLine', methods=["POST"]) def deleteLine(): if request.method == "POST": line_id = int(request.get_json()['line_id']) @@ -166,7 +157,7 @@ def deleteLine(): return jsonify({'error': False, "message": "Line Deleted Succesfully"}) return jsonify({'error': True, "message": "Something went wrong while deleting line!"}) -@receipt_api.route('/receipts/denyLine', methods=["POST"]) +@receipt_api.route('/api/denyLine', methods=["POST"]) def denyLine(): if request.method == "POST": line_id = int(request.get_json()['line_id']) @@ -177,7 +168,7 @@ def denyLine(): return jsonify({'error': False, "message": "Line Denied Succesfully"}) return jsonify({'error': True, "message": "Something went wrong while denying line!"}) -@receipt_api.route('/receipts/saveLine', methods=["POST"]) +@receipt_api.route('/api/saveLine', methods=["POST"]) def saveLine(): if request.method == "POST": line_id = int(request.get_json()['line_id']) @@ -192,7 +183,7 @@ def saveLine(): return jsonify({'error': False, "message": "Line Saved Succesfully"}) return jsonify({'error': True, "message": "Something went wrong while saving line!"}) -@receipt_api.route('/receipt/postLinkedItem', methods=["POST"]) +@receipt_api.route('/api/postLinkedItem', methods=["POST"]) def postLinkedItem(): if request.method == "POST": receipt_item_id = int(request.get_json()['receipt_item_id']) @@ -251,7 +242,7 @@ def postLinkedItem(): 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('/api/resolveLine', methods=["POST"]) def resolveLine(): if request.method == "POST": line_id = int(request.get_json()['line_id']) @@ -339,7 +330,7 @@ def resolveLine(): return jsonify({'error': False, "message": "Line Saved Succesfully"}) return jsonify({'error': True, "message": "Something went wrong while saving line!"}) -@receipt_api.route('/receipt/postVendorUpdate', methods=["POST"]) +@receipt_api.route('/api/postVendorUpdate', methods=["POST"]) def postVendorUpdate(): if request.method == "POST": receipt_id = int(request.get_json()['receipt_id']) @@ -351,7 +342,7 @@ def postVendorUpdate(): 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('/api/resolveReceipt', methods=["POST"]) def resolveReceipt(): if request.method == "POST": receipt_id = int(request.get_json()['receipt_id']) @@ -364,7 +355,7 @@ def resolveReceipt(): return jsonify({'error': False, "message": "Line Saved Succesfully"}) return jsonify({'error': True, "message": "Something went wrong while saving line!"}) -@receipt_api.route('/receipt/uploadfile/', methods=["POST"]) +@receipt_api.route('/api/uploadfile/', methods=["POST"]) def uploadFile(receipt_id): file = request.files['file'] file_path = current_app.config['FILES_FOLDER'] + f"/receipts/{file.filename.replace(" ", "_")}" @@ -373,7 +364,7 @@ def uploadFile(receipt_id): preview_image = "" if file_type == "application/pdf": output_path = "static/files/receipts/previews/" - preview_image = create_pdf_preview(file_path, output_path) + preview_image = receipts_processes.create_pdf_preview(file_path, output_path) file_size = os.path.getsize(file_path) database_config = config() @@ -386,11 +377,11 @@ def uploadFile(receipt_id): return jsonify({}) -@receipt_api.route('/receipt/getFile/') +@receipt_api.route('/api/getFile/') def getFile(file_name): return send_from_directory('static/files/receipts', file_name) -@receipt_api.route('/receipts/checkAPI', methods=["POST"]) +@receipt_api.route('/api/checkAPI', methods=["POST"]) def checkAPI(): if request.method == "POST": line_id = int(request.get_json()['line_id']) diff --git a/application/receipts/receipts_database.py b/application/receipts/receipts_database.py new file mode 100644 index 0000000..2ddaa2b --- /dev/null +++ b/application/receipts/receipts_database.py @@ -0,0 +1,100 @@ +import psycopg2 + + +import config +from application import postsqldb + +def getItemsWithQOH(site, payload, convert=True, conn=None): + recordset = [] + count = 0 + self_conn = False + with open(f"application/receipts/sql/getItemsWithQOH.sql", "r+") as file: + sql = file.read().replace("%%site_name%%", site).replace("%%sort_order%%", payload[3]) + + payload = list(payload) + payload.pop(3) + try: + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = True + self_conn = True + + if convert: + with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur: + cur.execute(sql, payload) + recordset = cur.fetchall() + recordset = [dict(record) for record in recordset] + cur.execute(f"SELECT COUNT(*) FROM {site}_items WHERE search_string LIKE '%%' || %s || '%%';", (payload[0], )) + count = cur.fetchone() + else: + with conn.cursor() as cur: + cur.execute(sql, payload) + recordset = cur.fetchall() + cur.execute(f"SELECT COUNT(*) FROM {site}_items WHERE search_string LIKE '%%' || %s || '%%';", (payload[0], )) + count = cur.fetchone() + + if self_conn: + conn.close() + + return recordset, count + except Exception as error: + raise postsqldb.DatabaseError(error, payload, sql) + +def getItemAllByID(site, payload, convert=True, conn=None): + item = () + self_conn = False + + with open(f"application/receipts/sql/getItemAllByID.sql", "r+") as file: + getItemAllByID_sql = file.read().replace("%%site_name%%", site) + try: + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = True + self_conn = True + + with conn.cursor() as cur: + cur.execute(getItemAllByID_sql, payload) + rows = cur.fetchone() + if rows and convert: + item = postsqldb.tupleDictionaryFactory(cur.description, rows) + if rows and not convert: + item = rows + + if self_conn: + conn.close() + + return item + except (Exception, psycopg2.DatabaseError) as error: + raise postsqldb.DatabaseError(error, payload, getItemAllByID_sql) + + + +def insertReceiptItemsTuple(site, payload, convert=True, conn=None): + receipt_item = () + self_conn = False + with open(f"application/receipts/sql/insertReceiptItemsTuple.sql", "r+") as file: + sql = file.read().replace("%%site_name%%", site) + try: + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = True + self_conn = True + + with conn.cursor() as cur: + cur.execute(sql, payload) + rows = cur.fetchone() + if rows and convert: + receipt_item = postsqldb.tupleDictionaryFactory(cur.description, rows) + elif rows and not convert: + receipt_item = rows + + if self_conn: + conn.commit() + conn.close() + + return receipt_item + except Exception as error: + raise postsqldb.DatabaseError(error, payload, sql) \ No newline at end of file diff --git a/application/receipts/receipts_processes.py b/application/receipts/receipts_processes.py new file mode 100644 index 0000000..1d36b84 --- /dev/null +++ b/application/receipts/receipts_processes.py @@ -0,0 +1,14 @@ +import pymupdf +import os +import PIL + +def create_pdf_preview(pdf_path, output_path, size=(600, 400)): + 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) + return file_name + '.jpg' \ No newline at end of file diff --git a/application/receipts/sql/getItemAllByID.sql b/application/receipts/sql/getItemAllByID.sql new file mode 100644 index 0000000..8b5ca81 --- /dev/null +++ b/application/receipts/sql/getItemAllByID.sql @@ -0,0 +1,86 @@ +WITH passed_id AS (SELECT %s AS passed_id), + logistics_id AS (SELECT logistics_info_id FROM %%site_name%%_items WHERE id=(SELECT passed_id FROM passed_id)), + info_id AS (SELECT item_info_id FROM %%site_name%%_items WHERE id=(SELECT passed_id FROM passed_id)), + cte_conversions AS ( + SELECT + %%site_name%%_conversions.id as conv_id, + %%site_name%%_conversions.conv_factor as conv_factor, + units.* as uom + FROM %%site_name%%_conversions + LEFT JOIN units ON %%site_name%%_conversions.uom_id = units.id + WHERE %%site_name%%_conversions.item_id = (SELECT passed_id FROM passed_id) + ), + cte_item_info AS ( + SELECT + %%site_name%%_item_info.*, + row_to_json(units.*) as uom, + COALESCE((SELECT json_agg(convs) FROM cte_conversions convs), '[]'::json) AS conversions, + COALESCE((SELECT json_agg(p.*) FROM %%site_name%%_sku_prefix as p WHERE p.id = ANY(%%site_name%%_item_info.prefixes)), '[]'::json) as prefixes + FROM %%site_name%%_item_info + LEFT JOIN units ON %%site_name%%_item_info.uom = units.id + WHERE %%site_name%%_item_info.id = (SELECT item_info_id FROM info_id) + ), + cte_groups AS ( + SELECT + %%site_name%%_groups.*, + %%site_name%%_group_items.uuid, + %%site_name%%_group_items.item_type, + %%site_name%%_group_items.qty + FROM %%site_name%%_groups + JOIN %%site_name%%_group_items ON %%site_name%%_groups.id = %%site_name%%_group_items.gr_id + WHERE %%site_name%%_group_items.item_id = (SELECT passed_id FROM passed_id) + ), + cte_shopping_lists AS ( + SELECT + %%site_name%%_shopping_lists.*, + %%site_name%%_shopping_list_items.uuid, + %%site_name%%_shopping_list_items.item_type, + %%site_name%%_shopping_list_items.qty + FROM %%site_name%%_shopping_lists + JOIN %%site_name%%_shopping_list_items ON %%site_name%%_shopping_lists.id = %%site_name%%_shopping_list_items.sl_id + WHERE %%site_name%%_shopping_list_items.item_id = (SELECT passed_id FROM passed_id) + ), + cte_itemlinks AS ( + SELECT * FROM %%site_name%%_itemlinks WHERE link=(SELECT passed_id FROM passed_id) + ), + cte_item_locations AS ( + SELECT * FROM %%site_name%%_item_locations + LEFT JOIN %%site_name%%_locations ON %%site_name%%_locations.id = %%site_name%%_item_locations.location_id + WHERE part_id = (SELECT passed_id FROM passed_id) + ), + cte_logistics_info AS ( + SELECT + li.*, + row_to_json(pl) AS primary_location, + row_to_json(ail) AS auto_issue_location, + row_to_json(pz) AS primary_zone, + row_to_json(aiz) AS auto_issue_zone + FROM %%site_name%%_logistics_info AS li + LEFT JOIN %%site_name%%_locations AS pl ON li.primary_location = pl.id + LEFT JOIN %%site_name%%_locations AS ail ON li.auto_issue_location = ail.id + LEFT JOIN %%site_name%%_zones AS pz ON li.primary_zone = pz.id + LEFT JOIN %%site_name%%_zones AS aiz ON li.auto_issue_zone = aiz.id + WHERE li.id=(SELECT logistics_info_id FROM logistics_id) + ) + +SELECT + (SELECT passed_id FROM passed_id) AS passed_id, + %%site_name%%_items.*, + (SELECT COALESCE(row_to_json(logis), '{}') FROM cte_logistics_info logis) AS logistics_info, + row_to_json(%%site_name%%_food_info.*) as food_info, + row_to_json(%%site_name%%_brands.*) as brand, + (SELECT COALESCE(row_to_json(ii), '{}') FROM cte_item_info ii) AS item_info, + (SELECT COALESCE(array_agg(row_to_json(g)), '{}') FROM cte_groups g) AS item_groups, + (SELECT COALESCE(array_agg(row_to_json(sl)), '{}') FROM cte_shopping_lists sl) AS item_shopping_lists, + (SELECT COALESCE(array_agg(row_to_json(il)), '{}') FROM cte_itemlinks il) AS linked_items, + (SELECT COALESCE(array_agg(row_to_json(ils)), '{}') FROM cte_item_locations ils) AS item_locations +FROM %%site_name%%_items + LEFT JOIN %%site_name%%_item_info ON %%site_name%%_items.item_info_id = %%site_name%%_item_info.id + LEFT JOIN %%site_name%%_food_info ON %%site_name%%_items.food_info_id = %%site_name%%_food_info.id + LEFT JOIN %%site_name%%_brands ON %%site_name%%_items.brand = %%site_name%%_brands.id + LEFT JOIN units ON %%site_name%%_item_info.uom = units.id + LEFT JOIN cte_groups ON %%site_name%%_items.id = cte_groups.id + LEFT JOIN cte_shopping_lists ON %%site_name%%_items.id = cte_shopping_lists.id +WHERE %%site_name%%_items.id=(SELECT passed_id FROM passed_id) +GROUP BY + %%site_name%%_items.id, %%site_name%%_item_info.id, %%site_name%%_food_info.id, %%site_name%%_brands.id; \ No newline at end of file diff --git a/application/receipts/sql/getItemsWithQOH.sql b/application/receipts/sql/getItemsWithQOH.sql new file mode 100644 index 0000000..fb1b171 --- /dev/null +++ b/application/receipts/sql/getItemsWithQOH.sql @@ -0,0 +1,18 @@ +WITH sum_cte AS ( + SELECT mi.id, SUM(mil.quantity_on_hand)::FLOAT8 AS total_sum + FROM %%site_name%%_item_locations mil + JOIN %%site_name%%_items mi ON mil.part_id = mi.id + GROUP BY mi.id + ) + +SELECT %%site_name%%_items.*, + row_to_json(%%site_name%%_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=%%site_name%%_item_info.uom) as uom +FROM %%site_name%%_items +LEFT JOIN sum_cte ON %%site_name%%_items.id = sum_cte.id +LEFT JOIN %%site_name%%_item_info ON %%site_name%%_items.item_info_id = %%site_name%%_item_info.id +WHERE %%site_name%%_items.search_string LIKE '%%' || %s || '%%' +ORDER BY %%sort_order%% +LIMIT %s OFFSET %s; + diff --git a/application/receipts/sql/insertReceiptItemsTuple.sql b/application/receipts/sql/insertReceiptItemsTuple.sql new file mode 100644 index 0000000..6aeb85f --- /dev/null +++ b/application/receipts/sql/insertReceiptItemsTuple.sql @@ -0,0 +1,4 @@ +INSERT INTO %%site_name%%_receipt_items +(type, receipt_id, barcode, name, qty, uom, data, status) +VALUES (%s, %s, %s, %s, %s, %s, %s, %s) +RETURNING *; \ No newline at end of file diff --git a/static/handlers/receiptHandler.js b/application/receipts/static/js/receiptHandler.js similarity index 96% rename from static/handlers/receiptHandler.js rename to application/receipts/static/js/receiptHandler.js index 993fbb3..a20f9b7 100644 --- a/static/handlers/receiptHandler.js +++ b/application/receipts/static/js/receiptHandler.js @@ -38,7 +38,7 @@ async function replenishFields(receipt) { async function checkAPI(line_id, barcode) { console.log(barcode) - const response = await fetch(`/receipts/checkAPI`, { + const response = await fetch(`/receipts/api/checkAPI`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -300,7 +300,7 @@ async function openLineEditModal(line_data) { async function addSKULine(item_id) { console.log(item_id) - const response = await fetch(`/receipts/addSKULine`, { + const response = await fetch(`/receipts/api/addSKULine`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -316,7 +316,7 @@ async function addSKULine(item_id) { } async function resolveLine(line_id) { - const response = await fetch(`/receipts/resolveLine`, { + const response = await fetch(`/receipts/api/resolveLine`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -329,7 +329,7 @@ async function resolveLine(line_id) { } async function resolveReceipt() { - const response = await fetch(`/receipts/resolveReceipt`, { + const response = await fetch(`/receipts/api/resolveReceipt`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -346,7 +346,7 @@ async function uploadFile() { const formData = new FormData(); formData.append('file', fileInput.files[0]); - await fetch(`/receipt/uploadfile/${receipt_id}`, { + await fetch(`/receipts/api/uploadfile/${receipt_id}`, { method: 'POST', body: formData }) @@ -379,7 +379,7 @@ async function saveLine(line_id){ } } - const response = await fetch(`/receipts/saveLine`, { + const response = await fetch(`/receipts/api/saveLine`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -395,7 +395,7 @@ async function saveLine(line_id){ } async function deleteLine(id) { - const response = await fetch(`/receipts/deleteLine`, { + const response = await fetch(`/receipts/api/deleteLine`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -409,7 +409,7 @@ async function deleteLine(id) { async function denyLine(id) { console.log(id) - const response = await fetch(`/receipts/denyLine`, { + const response = await fetch(`/receipts/api/denyLine`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -422,7 +422,7 @@ async function denyLine(id) { } async function getReceipt(id) { - const url = new URL('/receipts/getReceipt', window.location.origin); + const url = new URL('/receipts/api/getReceipt', window.location.origin); url.searchParams.append('id', id); const response = await fetch(url); data = await response.json(); @@ -433,7 +433,7 @@ async function getReceipt(id) { let items_limit = 50; async function getItems() { console.log("getting items") - const url = new URL('/receipts/getItems', window.location.origin); + const url = new URL('/receipts/api/getItems', window.location.origin); url.searchParams.append('page', pagination_current); url.searchParams.append('limit', items_limit); const response = await fetch(url); @@ -531,7 +531,7 @@ 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); + const url = new URL('/receipts/api/getVendors', window.location.origin); url.searchParams.append('page', vendor_current_page); url.searchParams.append('limit', vendor_limit); const response = await fetch(url); @@ -541,7 +541,7 @@ async function getVendors() { } async function postVendorUpdate(vendor_id) { - const response = await fetch(`/receipt/postVendorUpdate`, { + const response = await fetch(`/receipts/api/postVendorUpdate`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -691,7 +691,7 @@ 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); + const url = new URL('/receipts/api/getLinkedLists', window.location.origin); url.searchParams.append('page', vendor_current_page); url.searchParams.append('limit', vendor_limit); const response = await fetch(url); @@ -701,7 +701,7 @@ async function getLinkedLists() { } async function postLinkedItem(receipt_item_id, link_list_id, conv_factor) { - const response = await fetch(`/receipt/postLinkedItem`, { + const response = await fetch(`/receipts/api/postLinkedItem`, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/static/handlers/receiptsHandler.js b/application/receipts/static/js/receiptsHandler.js similarity index 97% rename from static/handlers/receiptsHandler.js rename to application/receipts/static/js/receiptsHandler.js index 54d8a93..d929d2d 100644 --- a/static/handlers/receiptsHandler.js +++ b/application/receipts/static/js/receiptsHandler.js @@ -64,7 +64,7 @@ async function replenishReceiptsTable(receipts) { ) tableRow.onclick = async function() { - let url = `${window.location.origin}/receipt/${receipts[i].id}`; + let url = `${window.location.origin}/receipts/${receipts[i].id}`; window.location.href = url; } @@ -77,7 +77,7 @@ async function replenishReceiptsTable(receipts) { var receipts_limit = 10 async function getReceipts() { - const url = new URL('/receipts/getReceipts', window.location.origin); + const url = new URL('/receipts/api/getReceipts', window.location.origin); url.searchParams.append('page', pagination_current); url.searchParams.append('limit', receipts_limit); const response = await fetch(url); diff --git a/templates/receipts/receipt.html b/application/receipts/templates/receipt.html similarity index 99% rename from templates/receipts/receipt.html rename to application/receipts/templates/receipt.html index 2b1ea1e..d702f47 100644 --- a/templates/receipts/receipt.html +++ b/application/receipts/templates/receipt.html @@ -390,6 +390,6 @@ - + \ No newline at end of file diff --git a/templates/receipts/index.html b/application/receipts/templates/receipts_index.html similarity index 98% rename from templates/receipts/index.html rename to application/receipts/templates/receipts_index.html index 744c85e..da5b01b 100644 --- a/templates/receipts/index.html +++ b/application/receipts/templates/receipts_index.html @@ -124,6 +124,6 @@ - + \ No newline at end of file diff --git a/application/recipes/recipes_api.py b/application/recipes/recipes_api.py index 9719ce9..790b053 100644 --- a/application/recipes/recipes_api.py +++ b/application/recipes/recipes_api.py @@ -23,7 +23,7 @@ def recipes(): description: returns recipes/index.html with sites, current_site. """ sites = [site[1] for site in main.get_sites(session['user']['sites'])] - return render_template("index.html", + return render_template("recipes_index.html", current_site=session['selected_site'], sites=sites) diff --git a/application/recipes/templates/index.html b/application/recipes/templates/recipes_index.html similarity index 100% rename from application/recipes/templates/index.html rename to application/recipes/templates/recipes_index.html diff --git a/database.log b/database.log index 0a169c5..91cf20d 100644 --- a/database.log +++ b/database.log @@ -1961,4 +1961,13 @@ sql='INSERT INTO test_shopping_list_items(uuid, sl_id, item_type, item_name, uom, qty, item_id, links) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) RETURNING *;') 2025-07-12 09:29:10.063362 --- ERROR --- DatabaseError(message='tuple index out of range', payload=(5,), - sql='SELECT *, (SELECT COALESCE(array_agg(row_to_json(g)), '{}') FROM test_shopping_list_items g WHERE sl_id = test_shopping_lists.id) AS sl_items FROM test_shopping_lists LIMIT %s OFFSET %s;') \ No newline at end of file + sql='SELECT *, (SELECT COALESCE(array_agg(row_to_json(g)), '{}') FROM test_shopping_list_items g WHERE sl_id = test_shopping_lists.id) AS sl_items FROM test_shopping_lists LIMIT %s OFFSET %s;') +2025-07-12 10:32:47.237422 --- ERROR --- DatabaseError(message='invalid input syntax for type integer: "each"LINE 3: VALUES ('13kitql4', '13', 'custom', 'test', 'each', 1, NULL,... ^', + payload=('13kitql4', '13', 'custom', 'test', 'each', 1, None, '{"main": "test2"}'), + sql='INSERT INTO test_shopping_list_items(uuid, sl_id, item_type, item_name, uom, qty, item_id, links) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) RETURNING *;') +2025-07-12 15:35:45.537855 --- ERROR --- DatabaseError(message='can't adapt type 'dict'', + payload=('sku', 26, '%041667029362%', 'Microwave popcorn', 1, {'id': 1, 'plural': 'pinches', 'single': ' pinch', 'fullname': ' Pinch', 'description': ' Less than 1/8 teaspoon.'}, '{"cost": 0, "expires": false}', 'Unresolved'), + sql='INSERT INTO test_receipt_items(type, receipt_id, barcode, name, qty, uom, data, status) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) RETURNING *;') +2025-07-12 15:45:15.171378 --- ERROR --- DatabaseError(message='can't adapt type 'dict'', + payload=('sku', 26, '%028400517829%', 'Tostitos', 1, {'id': 1, 'plural': 'pinches', 'single': ' pinch', 'fullname': ' Pinch', 'description': ' Less than 1/8 teaspoon.'}, '{"cost": 0, "expires": false}', 'Unresolved'), + sql='INSERT INTO test_receipt_items(type, receipt_id, barcode, name, qty, uom, data, status) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) RETURNING *;') \ No newline at end of file diff --git a/external_API.py b/external_API.py deleted file mode 100644 index 64ac9a6..0000000 --- a/external_API.py +++ /dev/null @@ -1,120 +0,0 @@ -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 threading import Thread -from queue import Queue -import time, process -from user_api import login_required -import webpush - -external_api = Blueprint('external', __name__) - -@external_api.route('/external/getItemLocations', methods=["GET"]) -def getItemLocations(): - recordset = [] - count = 0 - if request.method == "GET": - item_id = int(request.args.get('id', 1)) - 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: - recordset, count = database.getItemLocations(conn, site_name, (item_id, limit, offset), convert=True) - 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"}) - -@external_api.route('/external/getItem', methods=["GET"]) -def getItem(): - record = {} - if request.method == "GET": - item_id = int(request.args.get('id', 1)) - site_name = session['selected_site'] - database_config = config() - with psycopg2.connect(**database_config) as conn: - record = database.getItemAllByID(conn, site_name, (item_id, ), convert=True) - return jsonify({"item":record, "error":False, "message":"item fetched succesfully!"}) - return jsonify({"item":record, "error":True, "message":"There was an error with this GET statement"}) - -@external_api.route('/external/getItem/barcode', methods=["GET"]) -def getItemBarcode(): - record = {} - if request.method == "GET": - item_barcode = f"%{str(request.args.get('barcode', 1))}%" - site_name = session['selected_site'] - database_config = config() - with psycopg2.connect(**database_config) as conn: - record = database.getItemAllByBarcode(conn, site_name, (item_barcode, ), convert=True) - if record == {}: - return jsonify({"item":None, "error":True, "message":"Item either does not exist or there was a larger problem!"}) - else: - return jsonify({"item":record, "error":False, "message":"item fetched succesfully!"}) - return jsonify({"item":record, "error":True, "message":"There was an error with this GET statement"}) - -@external_api.route('/external/getModalItems', methods=["GET"]) -@login_required -def getModalItems(): - recordset = [] - count = {'count': 0} - if request.method == "GET": - page = int(request.args.get('page', 1)) - limit = int(request.args.get('limit', 10)) - search_string = request.args.get('search_string', '') - site_name = session['selected_site'] - offset = (page - 1) * limit - database_config = config() - with psycopg2.connect(**database_config) as conn: - payload = (search_string, limit, offset) - recordset, count = database.getItemsForModal(conn, site_name, payload, convert=True) - 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"}) - -@external_api.route('/external/postTransaction', methods=["POST"]) -def post_transaction(): - if request.method == "POST": - database_config = config() - with psycopg2.connect(**database_config) as conn: - result = process.postTransaction( - conn=conn, - site_name=session['selected_site'], - user_id=session['user_id'], - data=dict(request.json) - ) - return jsonify(result) - return jsonify({"error":True, "message":"There was an error with this POST statement"}) - - -@external_api.route('/external/postReceipt', methods=["POST"]) -def post_receipt(): - if request.method == "POST": - site_name = session['selected_site'] - user_id = session['user_id'] - database_config = config() - with psycopg2.connect(**database_config) as conn: - items = request.json['items'] - receipt_id = database.request_receipt_id(conn, site_name) - receipt_id = f"SIR-{receipt_id}" - receipt = MyDataclasses.ReceiptPayload( - receipt_id=receipt_id, - submitted_by=user_id - ) - receipt = database.insertReceiptsTuple(conn, site_name, receipt.payload(), convert=True) - - for item in items: - - receipt_item = MyDataclasses.ReceiptItemPayload( - type=item['type'], - receipt_id=receipt['id'], - barcode=item['item']['barcode'], - name=item['item']['item_name'], - qty=item['item']['qty'], - uom=item['item']['uom'], - data=item['item']['data'] - ) - database.insertReceiptItemsTuple(conn, site_name, receipt_item.payload()) - #webpush.push_notifications('New Receipt', f"Receipt {receipt['receipt_id']} was added to Site -> {site_name}!") - webpush.push_ntfy('New Receipt', f"Receipt {receipt['receipt_id']} was added to Site -> {site_name}!") - return jsonify({"error":False, "message":"Transaction Complete!"}) - return jsonify({"error":True, "message":"There was an error with this POST statement"}) \ No newline at end of file diff --git a/group_api.py b/group_api.py deleted file mode 100644 index f0066ee..0000000 --- a/group_api.py +++ /dev/null @@ -1,34 +0,0 @@ -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 - -groups_api = Blueprint('groups_api', __name__) - -@groups_api.route("/groups") -@login_required -def groups(): - sites = [site[1] for site in main.get_sites(session['user']['sites'])] - return render_template("groups/index.html", - current_site=session['selected_site'], - sites=sites) - -@groups_api.route("/group/") -@login_required -def group(id): - sites = [site[1] for site in main.get_sites(session['user']['sites'])] - return render_template("groups/group.html", id=id, current_site=session['selected_site'], sites=sites) - -@groups_api.route('/groups/getGroups', methods=["GET"]) -def getGroups(): - groups = [] - 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: - groups, count = database.getGroups(conn, site_name, (limit, offset), convert=True) - return jsonify({'groups': groups, 'end': math.ceil(count/limit), 'error': False, 'message': 'bleh'}) \ No newline at end of file diff --git a/scratch.py b/scratch.py deleted file mode 100644 index 8ed4f3f..0000000 --- a/scratch.py +++ /dev/null @@ -1,235 +0,0 @@ - -from config import config -import psycopg2, requests, database, MyDataclasses -import main, datetime, json, csv -from main import lst2pgarr -import process - -def importItemFromCSV(test, site_name, uuid, site): - logistics_info = MyDataclasses.LogisticsInfoPayload( - barcode=test['barcode'], - primary_location=site['default_primary_location'], - primary_zone=site['default_zone'], - auto_issue_location=site['default_auto_issue_location'], - auto_issue_zone=site['default_zone']) - - item_info = MyDataclasses.ItemInfoPayload(test['barcode']) - - - # Food Info - t = ['serving', 'serving_unit', 'calories', 'calories_unit', 'proteins', - 'proteins_unit', 'fats', 'fats_unit', 'carbohydrates', 'carbohydrates_unit', 'sugars', 'sugars_unit', 'sodium', 'sodium_unit', - 'fibers', 'fibers_unit'] - - other_tags = [ - 'serving', - 'serving_unit', - 'calories', - 'calories_unit', - 'proteins_serving', - 'proteins_unit', - 'fat_serving', - 'fat_unit', - 'carbohydrates_serving', - 'carbohydrates_unit', - 'sugars_serving', - 'sugars_unit', - 'sodium_serving', - 'sodium_unit', - 'fiber_serving', - 'fiber_unit', - ] - - nutriments = test['nutriments'].replace("'", '"') - nutriments = nutriments.replace("{", "").replace("}", "") - key_values = nutriments.split(", ") - nutriments = {} - - if key_values != ['']: - for s in key_values: - s= s.split(": ") - k = s[0].replace('"', "") - v = s[1].replace('"', "") - nutriments[k] = v - - nutrients = {} - for i in range(len(other_tags)): - if other_tags[i] in nutriments.keys(): - nutrients[t[i]] = nutriments[other_tags[i]] - else: - nutrients[t[i]] = '' - - food_groups = test['food_groups_tags'] - food_groups = food_groups.replace('[', "").replace("]", "") - food_groups = food_groups.replace("'", "") - food_groups = food_groups.split(", ") - - ingrediants = test['ingredients_hierarchy'] - ingrediants = ingrediants.replace('[', "").replace("]", "") - ingrediants = ingrediants.replace("'", "") - ingrediants = ingrediants.split(", ") - - - food_info = MyDataclasses.FoodInfoPayload(food_groups, ingrediants, nutrients) - - if test['brands'] != "": - brand = MyDataclasses.BrandsPayload(test['brands']) - - logistics_info_id = 0 - item_info_id = 0 - food_info_id = 0 - brand_id = 1 - - database_config = config() - try: - with psycopg2.connect(**database_config) as conn: - logistics_info = database.insertLogisticsInfoTuple(conn, site_name, logistics_info.payload()) - item_info = database.insertItemInfoTuple(conn, site_name, item_info.payload()) - food_info = database.insertFoodInfoTuple(conn, site_name, food_info.payload()) - if test['brands'] != "": - brand = database.insertBrandsTuple(conn, site_name, brand.payload()) - brand_id = brand[0] - - print("Logistics:", logistics_info) - print("item_info:", item_info) - print("food_info:", food_info) - print("brand:", brand_id) - - name = test['name'] - name = name.replace("'", "@&apostraphe&") - description = "" - tags = lst2pgarr([]) - links = json.dumps({}) - search_string = f"&&{test['barcode']}&&{name}&&" - - - item = MyDataclasses.ItemsPayload(test['barcode'], test['name'], item_info[0], - logistics_info[0], food_info[0], brand=brand_id, - row_type="single", item_type=test["sub_type"], search_string=search_string) - - item = database.insertItemTuple(conn, site_name, item.payload(), convert=True) - item = database.getItemAllByID(conn, site_name, (item['id'], ), convert=True) - print("Item:", item) - with conn.cursor() as cur: - cur.execute(f"SELECT id FROM {site_name}_locations WHERE uuid=%s;", (uuid, )) - location_id = cur.fetchone()[0] - - - print("Location ID:", location_id) - item_location = MyDataclasses.ItemLocationPayload(item['id'], location_id) - location = database.insertItemLocationsTuple(conn, site_name, item_location.payload(), convert=True) - - print("Item location:", location) - - creation_tuple = MyDataclasses.TransactionPayload( - datetime.datetime.now(), - logistics_info[0], - item['barcode'], - item['item_name'], - "SYSTEM", - 0.0, - "Item added to the System!", - 1, - {'location': uuid} - ) - - - database.insertTransactionsTuple(conn, site_name, creation_tuple.payload()) - - qoh = float(test['qty_on_hand']) - print(qoh, type(qoh)) - trans_type = "Adjust In" - if qoh != 0.0: - if qoh >= 0.0: - trans_type = "Adjust In" - else: - trans_type = "Adjust Out" - - payload = { - 'item_id': item['id'], - 'logistics_info_id': item['logistics_info_id'], - 'barcode': item['barcode'], - 'item_name': item['item_name'], - 'transaction_type': trans_type, - 'quantity': float(qoh), - 'description': f'creation quantity', - 'cost': item['item_info']['cost'], - 'vendor': 1, - 'expires': None, - 'location_id': location_id - } - - process.postTransaction(conn, site_name, 1, payload) - conn.commit() - except Exception as error: - print(error, item_info) - - -def importCSV(path, site_name): - database_config = config() - with psycopg2.connect(**database_config) as conn: - site = database.selectSiteTuple(conn, (site_name,), convert=True) - default_zone = database.__selectTuple(conn, site_name, f"{site_name}_zones", (site['default_zone'], ), convert=True) - default_location = database.__selectTuple(conn, site_name, f"{site_name}_locations", (site['default_primary_location'],), convert=True) - - - uuid = f"{default_zone['name']}@{default_location['name']}" - print(uuid) - with open(path, "r+", encoding="utf-8") as file: - csv_reader = csv.DictReader(file) - for row in csv_reader: - try: - importItemFromCSV(row, site_name, uuid, site) - except Exception as error: - with open("process.log", "a+") as file: - file.write("\n") - file.write(f"{datetime.datetime.now()} --- CAUTION --- {error}\n") - file.write(f"{" "*41}{json.dumps(row)}") - -#importCSV("2025-03-19-Pantry (1).csv", "main") - -def importLinkFromCSV(row, site_name, conn): - barcode = row['barcode'] - link_barcode=row['link_barcode'] - item_data=json.loads(row['data'].replace('\\j*s*o*n\\', "")) - conv_factor=row['conv_factor'] - - link_item = database.getItemAllByBarcode(conn, site_name, (link_barcode, ), convert=True) - - link = MyDataclasses.ItemLinkPayload( - barcode=barcode, - link=link_item['id'], - data=item_data, - conv_factor=conv_factor - ) - - newitem = { - 'barcode': barcode, - 'name': item_data['name'], - 'subtype': '' - } - - try: - process.postNewBlankItem(conn, site_name, 1, newitem) - except Exception as error: - print(error) - pass - - lin = database.insertItemLinksTuple(conn, site_name, link.payload()) - print(lin) - -def importLinksFromCSV(path, site_name): - database_config = config() - with psycopg2.connect(**database_config) as conn: - with open(path, "r+", encoding="utf-8") as file: - csv_reader = csv.DictReader(file) - for row in csv_reader: - try: - importLinkFromCSV(row, site_name, conn) - except Exception as error: - with open("process.log", "a+") as file: - file.write("\n") - file.write(f"{datetime.datetime.now()} --- CAUTION --- {error}\n") - file.write(f"{" "*41}{json.dumps(row)}") - -importLinksFromCSV("test.csv", 'test') \ No newline at end of file diff --git a/static/files/receipts/Order_details_-_Walmart.com_07122025.pdf b/static/files/receipts/Order_details_-_Walmart.com_07122025.pdf new file mode 100644 index 0000000..965d9ce Binary files /dev/null and b/static/files/receipts/Order_details_-_Walmart.com_07122025.pdf differ diff --git a/static/files/receipts/previews/Order_details_-_Walmart.com_07122025.jpg b/static/files/receipts/previews/Order_details_-_Walmart.com_07122025.jpg new file mode 100644 index 0000000..1479ccc Binary files /dev/null and b/static/files/receipts/previews/Order_details_-_Walmart.com_07122025.jpg differ diff --git a/static/pictures/recipes/image.png b/static/pictures/recipes/image.png new file mode 100644 index 0000000..0ab4d34 Binary files /dev/null and b/static/pictures/recipes/image.png differ diff --git a/task_manager.py b/task_manager.py deleted file mode 100644 index 698fad9..0000000 --- a/task_manager.py +++ /dev/null @@ -1,22 +0,0 @@ -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() \ No newline at end of file diff --git a/webserver.py b/webserver.py index 9937fe6..232a214 100644 --- a/webserver.py +++ b/webserver.py @@ -1,16 +1,15 @@ -import celery.schedules from flask import Flask, render_template, session, request, redirect, jsonify from flask_assets import Environment, Bundle -import api, config, user_api, psycopg2, main, api_admin, receipts_API, group_api +import api, config, user_api, psycopg2, main, api_admin from user_api import login_required, update_session_user from workshop_api import workshop_api import database -import postsqldb from webpush import trigger_push_notifications_for_subscriptions from application.recipes import recipes_api from application.items import items_API from application.poe import poe_api from application.shoppinglists import shoplist_api +from application.receipts import receipts_api from flasgger import Swagger @@ -31,9 +30,8 @@ app.register_blueprint(api_admin.admin_api) app.register_blueprint(items_API.items_api, url_prefix='/items') app.register_blueprint(poe_api.point_of_ease, url_prefix='/poe') app.register_blueprint(workshop_api) -app.register_blueprint(receipts_API.receipt_api) +app.register_blueprint(receipts_api.receipt_api, url_prefix='/receipts') app.register_blueprint(shoplist_api.shopping_list_api, url_prefix="/shopping-lists") -app.register_blueprint(group_api.groups_api) app.register_blueprint(recipes_api.recipes_api, url_prefix='/recipes')