From c18c6cec16f693a046ee1e9321182794bafa883e Mon Sep 17 00:00:00 2001 From: Jadowyne Ulve Date: Sat, 11 Jan 2025 12:51:52 -0600 Subject: [PATCH] test --- __pycache__/admin.cpython-312.pyc | Bin 0 -> 8452 bytes __pycache__/api.cpython-312.pyc | Bin 70704 -> 69523 bytes __pycache__/config.cpython-312.pyc | Bin 2672 -> 3534 bytes __pycache__/html_factory.cpython-312.pyc | Bin 0 -> 2563 bytes __pycache__/main.cpython-312.pyc | Bin 30814 -> 42702 bytes __pycache__/manage.cpython-312.pyc | Bin 0 -> 6857 bytes __pycache__/user_api.cpython-312.pyc | Bin 0 -> 6558 bytes admin.py | 122 +++++ api.py | 30 +- config.py | 23 +- database.ini | 4 +- html_factory.py | 56 +++ main.py | 231 ++++++++- manage.py | 16 +- scratch.py | 47 +- sites/Backpack/site.ini | 8 +- sites/Backpack/sql/create/logins.sql | 26 +- sites/Backpack/sql/create/roles.sql | 11 + sites/Backpack/sql/create/sites.sql | 15 + .../sql/unique/shopping_lists_safetystock.sql | 43 ++ .../shopping_lists_safetystock_count.sql | 12 + ...hopping_lists_safetystock_uncalculated.sql | 11 + sites/default/sql/create/logins.sql | 26 +- sites/default/sql/create/roles.sql | 11 + sites/default/sql/create/sites.sql | 15 + .../sql/unique/shopping_lists_safetystock.sql | 43 ++ .../shopping_lists_safetystock_count.sql | 12 + ...hopping_lists_safetystock_uncalculated.sql | 11 + sites/main/site.ini | 10 +- sites/main/sql/create/logins.sql | 26 +- sites/main/sql/create/roles.sql | 11 + sites/main/sql/create/sites.sql | 15 + .../sql/unique/shopping_lists_safetystock.sql | 12 + .../shopping_lists_safetystock_count.sql | 12 + ...hopping_lists_safetystock_uncalculated.sql | 11 + sites/test/site.ini | 9 - sites/test/sql/create/brands.sql | 4 - sites/test/sql/create/food_info.sql | 7 - sites/test/sql/create/groups.sql | 8 - sites/test/sql/create/item.sql | 32 -- sites/test/sql/create/item_info.sql | 15 - sites/test/sql/create/item_locations.sql | 23 - sites/test/sql/create/linked_items.sql | 8 - sites/test/sql/create/locations.sql | 11 - sites/test/sql/create/logins.sql | 16 - sites/test/sql/create/logistics_info.sql | 10 - sites/test/sql/create/receipt_items.sql | 13 - sites/test/sql/create/receipts.sql | 13 - sites/test/sql/create/recipes.sql | 12 - sites/test/sql/create/shopping_lists.sql | 14 - sites/test/sql/create/transactions.sql | 15 - sites/test/sql/create/vendors.sql | 8 - sites/test/sql/create/zones.sql | 5 - sites/test/sql/drop/brands.sql | 1 - sites/test/sql/drop/food_info.sql | 1 - sites/test/sql/drop/groups.sql | 1 - sites/test/sql/drop/item_info.sql | 1 - sites/test/sql/drop/item_locations.sql | 1 - sites/test/sql/drop/items.sql | 1 - sites/test/sql/drop/linked_items.sql | 1 - sites/test/sql/drop/locations.sql | 1 - sites/test/sql/drop/logistics_info.sql | 1 - sites/test/sql/drop/receipt_items.sql | 1 - sites/test/sql/drop/receipts.sql | 1 - sites/test/sql/drop/recipes.sql | 1 - sites/test/sql/drop/shopping_lists.sql | 1 - sites/test/sql/drop/transactions.sql | 1 - sites/test/sql/drop/vendors.sql | 1 - sites/test/sql/drop/zones.sql | 1 - sites/test/sql/unique/Insert_transaction.sql | 3 - .../sql/unique/logistics_transactions.sql | 3 - sites/test/sql/unique/select_item_all.sql | 45 -- .../sql/unique/select_item_all_barcode.sql | 45 -- sites/test/sql/unique/set_location_data.sql | 3 - static/adminHandler.js | 28 ++ static/pictures/logo.jpg | Bin 0 -> 91080 bytes templates/admin.html | 449 ++++++++++++++++++ templates/admin/role.html | 131 +++++ templates/admin/users.html | 17 + templates/items/index.html | 57 ++- templates/login.html | 37 ++ templates/setup.html | 91 ++++ templates/shopping-lists/view.html | 31 +- templates/signup.html | 27 ++ user_api.py | 115 +++++ webserver.py | 116 +++-- 86 files changed, 1793 insertions(+), 514 deletions(-) create mode 100644 __pycache__/admin.cpython-312.pyc create mode 100644 __pycache__/html_factory.cpython-312.pyc create mode 100644 __pycache__/manage.cpython-312.pyc create mode 100644 __pycache__/user_api.cpython-312.pyc create mode 100644 admin.py create mode 100644 html_factory.py create mode 100644 sites/Backpack/sql/create/roles.sql create mode 100644 sites/Backpack/sql/create/sites.sql create mode 100644 sites/Backpack/sql/unique/shopping_lists_safetystock.sql create mode 100644 sites/Backpack/sql/unique/shopping_lists_safetystock_count.sql create mode 100644 sites/Backpack/sql/unique/shopping_lists_safetystock_uncalculated.sql create mode 100644 sites/default/sql/create/roles.sql create mode 100644 sites/default/sql/create/sites.sql create mode 100644 sites/default/sql/unique/shopping_lists_safetystock.sql create mode 100644 sites/default/sql/unique/shopping_lists_safetystock_count.sql create mode 100644 sites/default/sql/unique/shopping_lists_safetystock_uncalculated.sql create mode 100644 sites/main/sql/create/roles.sql create mode 100644 sites/main/sql/create/sites.sql create mode 100644 sites/main/sql/unique/shopping_lists_safetystock.sql create mode 100644 sites/main/sql/unique/shopping_lists_safetystock_count.sql create mode 100644 sites/main/sql/unique/shopping_lists_safetystock_uncalculated.sql delete mode 100644 sites/test/site.ini delete mode 100644 sites/test/sql/create/brands.sql delete mode 100644 sites/test/sql/create/food_info.sql delete mode 100644 sites/test/sql/create/groups.sql delete mode 100644 sites/test/sql/create/item.sql delete mode 100644 sites/test/sql/create/item_info.sql delete mode 100644 sites/test/sql/create/item_locations.sql delete mode 100644 sites/test/sql/create/linked_items.sql delete mode 100644 sites/test/sql/create/locations.sql delete mode 100644 sites/test/sql/create/logins.sql delete mode 100644 sites/test/sql/create/logistics_info.sql delete mode 100644 sites/test/sql/create/receipt_items.sql delete mode 100644 sites/test/sql/create/receipts.sql delete mode 100644 sites/test/sql/create/recipes.sql delete mode 100644 sites/test/sql/create/shopping_lists.sql delete mode 100644 sites/test/sql/create/transactions.sql delete mode 100644 sites/test/sql/create/vendors.sql delete mode 100644 sites/test/sql/create/zones.sql delete mode 100644 sites/test/sql/drop/brands.sql delete mode 100644 sites/test/sql/drop/food_info.sql delete mode 100644 sites/test/sql/drop/groups.sql delete mode 100644 sites/test/sql/drop/item_info.sql delete mode 100644 sites/test/sql/drop/item_locations.sql delete mode 100644 sites/test/sql/drop/items.sql delete mode 100644 sites/test/sql/drop/linked_items.sql delete mode 100644 sites/test/sql/drop/locations.sql delete mode 100644 sites/test/sql/drop/logistics_info.sql delete mode 100644 sites/test/sql/drop/receipt_items.sql delete mode 100644 sites/test/sql/drop/receipts.sql delete mode 100644 sites/test/sql/drop/recipes.sql delete mode 100644 sites/test/sql/drop/shopping_lists.sql delete mode 100644 sites/test/sql/drop/transactions.sql delete mode 100644 sites/test/sql/drop/vendors.sql delete mode 100644 sites/test/sql/drop/zones.sql delete mode 100644 sites/test/sql/unique/Insert_transaction.sql delete mode 100644 sites/test/sql/unique/logistics_transactions.sql delete mode 100644 sites/test/sql/unique/select_item_all.sql delete mode 100644 sites/test/sql/unique/select_item_all_barcode.sql delete mode 100644 sites/test/sql/unique/set_location_data.sql create mode 100644 static/adminHandler.js create mode 100644 static/pictures/logo.jpg create mode 100644 templates/admin.html create mode 100644 templates/admin/role.html create mode 100644 templates/admin/users.html create mode 100644 templates/login.html create mode 100644 templates/setup.html create mode 100644 templates/signup.html create mode 100644 user_api.py diff --git a/__pycache__/admin.cpython-312.pyc b/__pycache__/admin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bb658722ba9405028f99a1d54552c93a350035d3 GIT binary patch literal 8452 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 literal 0 HcmV?d00001 diff --git a/__pycache__/api.cpython-312.pyc b/__pycache__/api.cpython-312.pyc index cc6fb280ae8af963eec9636ad282013bbef0be98..bbf1038eaaf10d56924a15b96de1913f3cce9cb3 100644 GIT binary patch delta 7081 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 B4PF2M delta 8455 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 Al>h($ delta 931 zcmZ9KO-vI(6vt=wtKDw5d=@D!h4K+-Bt{G-YK#FQM8v>>i)o@**|oN|+d5k%>LN8E zO4I{n4njQ0QNl&y)r%e_F)?}|>&1h{gO@@NCd7m9Rn)|n%x~VjdGmJX{pVe{+!)x> zbsqt{y)~YaJj;RR69ce%LJVTiEXmOXrRWP;At4wZLtLN<(eN4)%#tB5kc4a~K;@PY zS2wRb73fbss5CEcoRJ3{A>-jYVk4gy)u7}^r^$r~A#<`L8#DwT$*BCi7U^9|rf2fjm2`@QNDmi^c$ZN1$l}O$v~z7}_3ATg)p|blA=ZN97BN^ghi>d2pv0m4 zlJ{WjZv^tN8CBcZpXUTe;G1HQU(*6%EdQSwqCxjNj>38Q3r(Xn{FN4_b)Z56|E?XK zUsonrGaTIW8_8nwa?&;rrLDB9 zj)Q~4z7GI(Y9laLe7W!sLgm;FV$a5h@cLB!a7cU;l7~B9e)AotDhp!; zFAMQOe^W393)X??M06p#x#Mq>`@lWM*Zj*-c+2oNg}hx%G1I=DjSCDztQ8RV@JYQ* y!|OBrE!M~H>Mi~K;G$Y~8vx{|^egH9M6`-hLj#qL8d|s8rPEj?s?u2XG5iAev(AP9 diff --git a/__pycache__/html_factory.cpython-312.pyc b/__pycache__/html_factory.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fa7ca7c4e68f8deb015a382a6cd4245127e5e4f8 GIT binary patch literal 2563 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 diff --git a/__pycache__/manage.cpython-312.pyc b/__pycache__/manage.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..659efe45357add6aa12e1dc8629e7f2141f7ea13 GIT binary patch literal 6857 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 literal 0 HcmV?d00001 diff --git a/admin.py b/admin.py new file mode 100644 index 0000000..b5d3477 --- /dev/null +++ b/admin.py @@ -0,0 +1,122 @@ +from flask import Blueprint, request, render_template, redirect, session, url_for, send_file, jsonify, Response +import psycopg2, math, json, datetime, main, copy, requests, html_factory +from config import config, sites_config +from main import unfoldCostLayers, get_sites, get_roles, create_site_secondary, getUser +from manage import create + +admin = Blueprint('admin_api', __name__) + +@admin.route("/admin/getSites") +def getSites(): + sites = get_sites(session.get('user')[13]) + return jsonify(sites=sites) + +@admin.route("/getRoles") +def getRoles(): + sites_roles = {} + sites = get_sites(session.get('user')[13]) + for site in sites: + site_roles = get_roles(site_id=site[0]) + sites_roles[site[1]] = site_roles + return jsonify(sites=sites_roles) + +@admin.route("/admin/getUsers", methods=["POST"]) +def getUsers(): + if request.method == "POST": + page = request.get_json()['page'] + limit = request.get_json()['limit'] + offset = (page - 1) * limit + + database_config = config() + with psycopg2.connect(**database_config) as conn: + try: + with conn.cursor() as cur: + sql = f"SELECT * FROM logins LIMIT %s OFFSET %s;" + cur.execute(sql, (limit, offset)) + users = cur.fetchall() + cur.execute("SELECT COUNT(*) FROM main_items;") + count = cur.fetchone()[0] + return jsonify(users=users, endpage=math.ceil(count/limit)) + except (Exception, psycopg2.DatabaseError) as error: + print(error) + conn.rollback() + return jsonify(message="FAILED") + return jsonify(message="FAILED") + + +@admin.route("/admin/editRole/") +def getRole(id): + database_config = config() + with psycopg2.connect(**database_config) as conn: + try: + with conn.cursor() as cur: + sql = f"SELECT * FROM roles LEFT JOIN sites ON sites.id = roles.site_id WHERE roles.id = %s;" + cur.execute(sql, (id, )) + role = cur.fetchone() + return render_template("admin/role.html", role=role, proto={'referrer': request.referrer}) + except (Exception, psycopg2.DatabaseError) as error: + print(error) + conn.rollback() + return jsonify(message="FAILED") + +@admin.route("/addRole", methods=["POST"]) +def addRole(): + if request.method == "POST": + role_name = request.get_json()['role_name'] + role_description = request.get_json()['role_description'] + site_id = request.get_json()['site_id'] + + + sql = f"INSERT INTO roles (role_name, role_description, site_id) VALUES (%s, %s, %s);" + print(role_name, role_description, site_id) + + + database_config = config() + with psycopg2.connect(**database_config) as conn: + try: + with conn.cursor() as cur: + data = (role_name, role_description, site_id) + cur.execute(sql, data) + except (Exception, psycopg2.DatabaseError) as error: + print(error) + conn.rollback() + return jsonify(message="FAILED") + + return jsonify(message="SUCCESS") + + return jsonify(message="FAILED") + +@admin.route("/deleteRole", methods=["POST"]) +def deleteRole(): + if request.method == "POST": + role_id = request.get_json()['role_id'] + database_config = config() + with psycopg2.connect(**database_config) as conn: + try: + with conn.cursor() as cur: + sql = f"DELETE FROM roles WHERE roles.id = %s;" + cur.execute(sql, (role_id, )) + return jsonify(message="Role Deleted!") + except (Exception, psycopg2.DatabaseError) as error: + print(error) + conn.rollback() + return jsonify(message=error) + return jsonify(message="FAILED") + +@admin.route("/addSite", methods=["POST"]) +async def addSite(): + if request.method == "POST": + site_name = request.get_json()['site_name'] + site_description = request.get_json()['site_description'] + default_zone = request.get_json()["default_zone"] + default_location = request.get_json()['default_location'] + username = session.get('user')[1] + user_id = session.get('user')[0] + + create(site_name, username, default_zone, default_location) + result = await create_site_secondary(site_name, user_id, default_zone, default_location, default_location, site_description) + + if result: + return jsonify(message="Success!") + + return jsonify(message="Failed!") \ No newline at end of file diff --git a/api.py b/api.py index 3ae5527..e4cdea9 100644 --- a/api.py +++ b/api.py @@ -116,8 +116,11 @@ def paginate_groups(): print(group[3]) for item_id in group[3]: cur.execute(sql_item, (item_id,)) - item_row = cur.fetchone() - qty += float(item_row[2]) + 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) @@ -1060,10 +1063,15 @@ def paginate_lists(): 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': - item_sql = f"SELECT COUNT(*) FROM {site_name}_items LEFT JOIN {site_name}_logistics_info ON {site_name}_items.logistics_info_id = {site_name}_logistics_info.id 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 WHERE {site_name}_logistics_info.quantity_on_hand < {site_name}_item_info.safety_stock AND shopping_lists @> ARRAY[%s];" - cur.execute(item_sql, (shopping_list[0], )) + print(shopping_list[0]) + cur.execute(sql, (shopping_list[0], )) list_length += cur.fetchone()[0] + else: list_length += len(pantry_items) @@ -1089,13 +1097,17 @@ def get_list_view(): shopping_list = list(cur.fetchone()) if shopping_list[10] == "calculated": - itemSQL = f"SELECT {site_name}_items.id, {site_name}_items.barcode, {site_name}_items.item_name, {site_name}_items.links, {site_name}_logistics_info.quantity_on_hand, {site_name}_item_info.safety_stock, {site_name}_item_info.uom FROM {site_name}_items LEFT JOIN {site_name}_logistics_info ON {site_name}_items.logistics_info_id = {site_name}_logistics_info.id 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 WHERE {site_name}_logistics_info.quantity_on_hand < {site_name}_item_info.safety_stock AND shopping_lists @> ARRAY[%s];" + sqlfile = open(f"sites/{site_name}/sql/unique/shopping_lists_safetystock.sql", "r+") + sql = "\n".join(sqlfile.readlines()) + sqlfile.close() else: - itemSQL = f"SELECT {site_name}_items.id, {site_name}_items.barcode, {site_name}_items.item_name, {site_name}_items.links, {site_name}_logistics_info.quantity_on_hand, {site_name}_item_info.safety_stock, {site_name}_item_info.uom FROM {site_name}_items LEFT JOIN {site_name}_logistics_info ON {site_name}_items.logistics_info_id = {site_name}_logistics_info.id 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 WHERE shopping_lists @> ARRAY[%s];" - - cur.execute(itemSQL, (id, )) + 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) + print(shopping_list[4]) except (Exception, psycopg2.DatabaseError) as error: print(error) diff --git a/config.py b/config.py index 0d83e02..b883f0a 100644 --- a/config.py +++ b/config.py @@ -27,20 +27,33 @@ def sites_config(filename='database.ini', section='manage'): parser.read(filename) # get section, default to postgresql - sites = {} + instance_config = {} + first_setup = False if parser.has_section(section): - params = parser.items(section) + params = parser.items(section) + print(params) for param in params: - sites[param[0]] = param[1].split(',') + instance_config[param[0]] = param[1].split(',') else: raise Exception('Section {0} not found in the {1} file'.format(section, filename)) - return sites + instance_config['first_setup'] = parser.getboolean('manage', 'first_setup') + instance_config['signup_enabled'] = parser.getboolean('manage', 'signup_enabled') + print(instance_config) + return instance_config + +def setFirstSetupDone(): + config = ConfigParser() + config.read('database.ini') + config.set('manage', 'first_setup', 'False') + with open('database.ini', 'w') as configFile: + config.write(configFile) + def write_new_site(site_name): - old_value = sites_config()['sites'] + old_value = [site for site in sites_config()['sites'] if site != ""] print(old_value) old_value.append(site_name) diff --git a/database.ini b/database.ini index 4967f8a..5679f20 100644 --- a/database.ini +++ b/database.ini @@ -6,5 +6,7 @@ password = test port = 5432 [manage] -sites = ,test,main,Backpack +sites = Backpack,main +first_setup = False +signup_enabled = False diff --git a/html_factory.py b/html_factory.py new file mode 100644 index 0000000..9684b03 --- /dev/null +++ b/html_factory.py @@ -0,0 +1,56 @@ +import math + + +def manufactureUsersTable(rows): + table = """ + + + + + + + %%rows%% + +
Username
+ """ + + string_rows = [] + for row in rows: + string_row = f""" + {row[1]} + """ + string_rows.append(string_row) + + table = table.replace("%%rows%%", "".join(string_rows)) + + return table + + +def manufacturePagination(current_page:int , count:int, limit:int): + total_pages = math.ceil(count/limit) + pag = "" + limits = "hx-vals='{" + f'"limit": "{str(limit)}"' + "}'" + if count >= limit: + pag += '
    ' + + if current_page > 1: + pag += f'
  • chevron_left
  • ' + + p = [_ for _ in [current_page-2, current_page-1, current_page] if _ >= 1] + y = [_ for _ in [current_page+1, current_page+2] if _ <= total_pages] + _elems = p + y + print(_elems) + + for _element in _elems: + if _element == current_page: + pag += f'
  • {_element}
  • ' + else: + pag += f'
  • {_element}
  • ' + + if current_page != total_pages: + pag += f'
  • chevron_right
  • ' + + pag += "
" + + return pag + \ No newline at end of file diff --git a/main.py b/main.py index b7012c5..d7ca28a 100644 --- a/main.py +++ b/main.py @@ -189,7 +189,6 @@ def setLogisticsDataTransaction(conn, site_name, location, logistics_info_id, qt return error return "success" - def handleNegativeQuantityOnHand(qty, cost_layers): cost_layers = [ast.literal_eval(item) for item in ast.literal_eval(cost_layers.replace('{', '[').replace('}', ']'))] dummy_quantity = qty @@ -415,11 +414,12 @@ def delete_site(site_name): drop_table(f'sites/{site_name}/sql/drop/shopping_lists.sql') drop_table(f'sites/{site_name}/sql/drop/item_locations.sql') -def create_site(site_name): - - site_config = config(f"sites/{site_name}/site.ini", 'defaults') +def create_site(site_name, admin_user: tuple, default_zone, default_primary, default_auto, description): create_table(f'sites/{site_name}/sql/create/logins.sql') + create_table(f"sites/{site_name}/sql/create/sites.sql") + create_table(f"sites/{site_name}/sql/create/roles.sql") + create_table(f'sites/{site_name}/sql/create/groups.sql') create_table(f'sites/{site_name}/sql/create/linked_items.sql') create_table(f'sites/{site_name}/sql/create/brands.sql') @@ -437,16 +437,69 @@ def create_site(site_name): create_table(f'sites/{site_name}/sql/create/shopping_lists.sql') create_table(f'sites/{site_name}/sql/create/item_locations.sql') - + add_admin_sql = f"INSERT INTO logins(username, password, email) VALUES(%s, %s, %s) RETURNING id;" + add_site_sql = f"INSERT INTO sites(site_name, creation_date, site_owner_id, flags, default_zone, default_auto_issue_location, default_primary_location, site_description) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;" + add_admin_role = f"INSERT INTO roles(role_name, site_id) VALUES(%s, %s) RETURNING id;" + sql = f"INSERT INTO {site_name}_zones(name) VALUES (%s) RETURNING id;" sqltwo = f"INSERT INTO {site_name}_locations(uuid, name, zone_id, items) VALUES (%s, %s, %s, %s);" sqlthree = f"INSERT INTO {site_name}_vendors(vendor_name, creation_date, created_by) VALUES (%s, %s, %s);" + database_config = config() with psycopg2.connect(**database_config) as conn: + try: + with conn.cursor() as cur: + cur.execute(add_admin_sql, admin_user) + rows = cur.fetchone() + if rows: + user_id = rows[0] + except (Exception, psycopg2.DatabaseError) as error: + print(error) + conn.rollback() + return False + print(user_id) + + # set up site in database + try: + with conn.cursor() as cur: + data = (site_name, str(datetime.datetime.now()), user_id, json.dumps({}), default_zone, default_auto, default_primary, description) + cur.execute(add_site_sql, data) + rows = cur.fetchone() + if rows: + site_id = rows[0] + except (Exception, psycopg2.DatabaseError) as error: + print(error) + conn.rollback() + return False + + # add admin role for site + try: + with conn.cursor() as cur: + data = ('Admin', site_id) + cur.execute(add_admin_role, data) + rows = cur.fetchone() + if rows: + role_id = rows[0] + except (Exception, psycopg2.DatabaseError) as error: + print(error) + conn.rollback() + return False + + # update user with site_id and admin role. + try: + with conn.cursor() as cur: + data = (site_id, role_id, user_id) + cur.execute(f"UPDATE logins SET sites = sites || %s, site_roles = site_roles || %s WHERE id=%s;", data) + except (Exception, psycopg2.DatabaseError) as error: + print(error) + conn.rollback() + return False + + # setup the default zone. zone_id = None try: with conn.cursor() as cur: - cur.execute(sql, (site_config["default_zone"], )) + cur.execute(sql, (default_zone, )) rows = cur.fetchone() if rows: zone_id = rows[0] @@ -455,11 +508,11 @@ def create_site(site_name): conn.rollback() return False - uuid = f"{site_config["default_zone"]}@{site_config["default_primary_location"]}" + uuid = f"{default_zone}@{default_primary}" try: with conn.cursor() as cur: - cur.execute(sqltwo, (uuid, site_config["default_primary_location"], zone_id, json.dumps({}))) + cur.execute(sqltwo, (uuid, default_primary, zone_id, json.dumps({}))) except (Exception, psycopg2.DatabaseError) as error: print(error) conn.rollback() @@ -473,9 +526,171 @@ def create_site(site_name): conn.rollback() return False + conn.commit() + +async def create_site_secondary(site_name, user_id, default_zone, default_primary, default_auto, description): + + create_table(f'sites/{site_name}/sql/create/logins.sql') + create_table(f"sites/{site_name}/sql/create/sites.sql") + create_table(f"sites/{site_name}/sql/create/roles.sql") + + create_table(f'sites/{site_name}/sql/create/groups.sql') + create_table(f'sites/{site_name}/sql/create/linked_items.sql') + create_table(f'sites/{site_name}/sql/create/brands.sql') + create_table(f'sites/{site_name}/sql/create/food_info.sql') + create_table(f'sites/{site_name}/sql/create/item_info.sql') + create_table(f'sites/{site_name}/sql/create/logistics_info.sql') + create_table(f'sites/{site_name}/sql/create/transactions.sql') + create_table(f'sites/{site_name}/sql/create/item.sql') + create_table(f'sites/{site_name}/sql/create/zones.sql') + create_table(f'sites/{site_name}/sql/create/locations.sql') + create_table(f'sites/{site_name}/sql/create/vendors.sql') + create_table(f'sites/{site_name}/sql/create/receipts.sql') + create_table(f'sites/{site_name}/sql/create/receipt_items.sql') + create_table(f'sites/{site_name}/sql/create/recipes.sql') + create_table(f'sites/{site_name}/sql/create/shopping_lists.sql') + create_table(f'sites/{site_name}/sql/create/item_locations.sql') + + add_site_sql = f"INSERT INTO sites(site_name, creation_date, site_owner_id, flags, default_zone, default_auto_issue_location, default_primary_location, site_description) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;" + add_admin_role = f"INSERT INTO roles(role_name, site_id, role_description) VALUES(%s, %s, %s) RETURNING id;" + + sql = f"INSERT INTO {site_name}_zones(name) VALUES (%s) RETURNING id;" + sqltwo = f"INSERT INTO {site_name}_locations(uuid, name, zone_id, items) VALUES (%s, %s, %s, %s);" + sqlthree = f"INSERT INTO {site_name}_vendors(vendor_name, creation_date, created_by) VALUES (%s, %s, %s);" + + database_config = config() + with psycopg2.connect(**database_config) as conn: + # set up site in database + try: + with conn.cursor() as cur: + data = (site_name, str(datetime.datetime.now()), user_id, json.dumps({}), default_zone, default_auto, default_primary, description) + cur.execute(add_site_sql, data) + rows = cur.fetchone() + if rows: + site_id = rows[0] + except (Exception, psycopg2.DatabaseError) as error: + print(error) + conn.rollback() + return False + + # add admin role for site + try: + with conn.cursor() as cur: + data = ('Admin', site_id, f"This is the admin role for {site_name}.") + cur.execute(add_admin_role, data) + rows = cur.fetchone() + if rows: + role_id = rows[0] + except (Exception, psycopg2.DatabaseError) as error: + print(error) + conn.rollback() + return False + + # update user with site_id and admin role. + try: + with conn.cursor() as cur: + data = (site_id, role_id, user_id) + cur.execute(f"UPDATE logins SET sites = sites || %s, site_roles = site_roles || %s WHERE id=%s;", data) + except (Exception, psycopg2.DatabaseError) as error: + print(error) + conn.rollback() + return False + + # setup the default zone. + zone_id = None + try: + with conn.cursor() as cur: + cur.execute(sql, (default_zone, )) + rows = cur.fetchone() + if rows: + zone_id = rows[0] + except (Exception, psycopg2.DatabaseError) as error: + print(error) + conn.rollback() + return False + + uuid = f"{default_zone}@{default_primary}" + + try: + with conn.cursor() as cur: + cur.execute(sqltwo, (uuid, default_primary, zone_id, json.dumps({}))) + except (Exception, psycopg2.DatabaseError) as error: + print(error) + conn.rollback() + return False + + try: + with conn.cursor() as cur: + cur.execute(sqlthree, ("None", str(datetime.datetime.now()), 1)) + except (Exception, psycopg2.DatabaseError) as error: + print(error) + conn.rollback() + return False conn.commit() + return True + + +def getUser(username, password): + database_config = config() + with psycopg2.connect(**database_config) as conn: + try: + with conn.cursor() as cur: + sql = f"SELECT * FROM logins WHERE username=%s;" + cur.execute(sql, (username,)) + user = cur.fetchone() + if user and user[2] == password: + return list(user) + except (Exception, psycopg2.DatabaseError) as error: + print(error) + conn.rollback() + return [] + +def setSystemAdmin(user_id: int): + database_config = config() + with psycopg2.connect(**database_config) as conn: + try: + with conn.cursor() as cur: + cur.execute(f"UPDATE logins SET system_admin = TRUE WHERE id=%s;", (user_id, )) + except (Exception, psycopg2.DatabaseError) as error: + print(error) + conn.rollback() + return False + +def get_roles(site_id): + database_config = config() + with psycopg2.connect(**database_config) as conn: + try: + with conn.cursor() as cur: + cur.execute(f"SELECT * FROM roles WHERE site_id=%s;", (site_id, )) + roles = cur.fetchall() + return roles + except (Exception, psycopg2.DatabaseError) as error: + print(error) + conn.rollback() + return False + + +def get_sites(sites=[]): + database_config = config() + with psycopg2.connect(**database_config) as conn: + try: + with conn.cursor() as cur: + site_rows = [] + for each in sites: + cur.execute(f"SELECT * FROM sites WHERE id=%s;", (each, )) + site_rows.append(cur.fetchone()) + print(site_rows) + return site_rows + except (Exception, psycopg2.DatabaseError) as error: + print(error) + conn.rollback() + return False + + + + transaction_payload = { "timestamp": None, "logistics_info_id": 0, diff --git a/manage.py b/manage.py index 05ef3e8..2420b3a 100644 --- a/manage.py +++ b/manage.py @@ -55,19 +55,7 @@ def rename_create_sql(site_name): with open(f"sites/{site_name}/sql/create/{file_name}", "w") as file: file.write(words) -def create(): - site_name = input("Site Name: ") - site_owner = input("Site Owner: ") - email = input("Contact Email: ") - - default_zone_name = input("Set Default Zone Name (default): ").strip() - if default_zone_name == "": - default_zone_name = "default" - - print(f"Now you will set the default location that you wish for things to be received into (primary location) and used from (auto-issue).") - default_location_name = input("Set Default Location (all): ").strip() - if default_location_name == "": - default_location_name = "all" +def create(site_name, owner_name, default_zone_name, default_location_name, email=""): if not os.path.exists(f"sites/{site_name}"): print(f"Creating {site_name} site...") @@ -82,7 +70,7 @@ def create(): with open(f"sites/{site_name}/site.ini", "w+") as config: config.write(f"[site]\n") config.write(f"site_name={site_name}\n") - config.write(f"site_owner={site_owner}\n") + config.write(f"site_owner={owner_name}\n") config.write(f"email={email}\n") config.write(f"\n") diff --git a/scratch.py b/scratch.py index 5b04d32..aaacbca 100644 --- a/scratch.py +++ b/scratch.py @@ -1,38 +1,25 @@ -sql = "SELECT items FROM main_locations WHERE id=1;" from config import config import psycopg2, requests -import main, datetime +import main, datetime, json -"""database_config = config() + +database_config = config() with psycopg2.connect(**database_config) as conn: - result = main.setLocationData(conn, "main", "default@all", 1, 4.0, 0.0) - print(result)""" -url = "http://192.168.1.45:5810/resolveReceiptItem" -"""payload_receipt = { - "receipt_id": 123456, - "receipt_status": "Unresolved", - "date_submitted": str(datetime.datetime.now()), - "submitted_by": 1, - "vendor_id": 0, - "files": {}, - "items": [ - ("FOOD", 0, "%1234%", "test_item", 1.0, {"cost": 1.99, "EXPIRES": False}, "Unresolved"), - ("FOOD", 0, "%1235%", "test_item", 1.0, {"cost": 1.99, "EXPIRES": False}, "Unresolved"), - ("FOOD", 0, "%1236%", "test_item", 1.0, {"cost": 1.99, "EXPIRES": False}, "Unresolved"), - ], - "site_name": "main" -}""" +# update user with site_id and admin role. + sqlfile = open('sites/main/sql/unique/shopping_lists_safetystock_uncalculated.sql', "r+") + sql = sqlfile.readlines() + sql = "\n".join(sql) + sqlfile.close() + try: + with conn.cursor() as cur: + cur.execute(sql, (1, )) + x = cur.fetchall() + for _ in x: + print(_) - - - - -response = requests.post(url) - -receipt_id = response.json()["receipt_id"] - - -print(receipt_id) + except (Exception, psycopg2.DatabaseError) as error: + print(error) + conn.rollback() diff --git a/sites/Backpack/site.ini b/sites/Backpack/site.ini index a63d838..b7537b5 100644 --- a/sites/Backpack/site.ini +++ b/sites/Backpack/site.ini @@ -1,9 +1,9 @@ [site] site_name=Backpack -site_owner=Jadowyne +site_owner=jadowyne email= [defaults] -default_zone=default -default_primary_location=all -default_auto_issue_location=all +default_zone=MAIN +default_primary_location=POUCH A +default_auto_issue_location=POUCH A diff --git a/sites/Backpack/sql/create/logins.sql b/sites/Backpack/sql/create/logins.sql index f69b01b..3630312 100644 --- a/sites/Backpack/sql/create/logins.sql +++ b/sites/Backpack/sql/create/logins.sql @@ -2,15 +2,21 @@ CREATE TABLE IF NOT EXISTS logins( id SERIAL PRIMARY KEY, username VARCHAR(255), password VARCHAR(255), - favorites JSONB, - unseen_pantry_items INTEGER [], - unseen_groups INTEGER [], - unseen_shopping_lists INTEGER [], - unseen_recipes INTEGER [], - seen_pantry_items INTEGER [], - seen_groups INTEGER[], - seen_shopping_lists INTEGER [], - seen_recipes INTEGER [], - flags JSONB + email VARCHAR(255) UNIQUE NOT NULL, + favorites JSONB DEFAULT '{}', + unseen_pantry_items INTEGER [] DEFAULT '{}', + unseen_groups INTEGER [] DEFAULT '{}', + unseen_shopping_lists INTEGER [] DEFAULT '{}', + unseen_recipes INTEGER [] DEFAULT '{}', + seen_pantry_items INTEGER [] DEFAULT '{}', + seen_groups INTEGER[] DEFAULT '{}', + seen_shopping_lists INTEGER [] DEFAULT '{}', + seen_recipes INTEGER [] DEFAULT '{}', + sites INTEGER [] DEFAULT '{}', + site_roles INTEGER [] DEFAULT '{}', + system_admin BOOLEAN DEFAULT FALSE, + flags JSONB DEFAULT '{}', + UNIQUE(username), + CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$') ); diff --git a/sites/Backpack/sql/create/roles.sql b/sites/Backpack/sql/create/roles.sql new file mode 100644 index 0000000..802584f --- /dev/null +++ b/sites/Backpack/sql/create/roles.sql @@ -0,0 +1,11 @@ +CREATE TABLE IF NOT EXISTS roles( + id SERIAL PRIMARY KEY, + role_name VARCHAR(255) NOT NULL, + role_description TEXT, + site_id INTEGER NOT NULL, + flags JSONB DEFAULT '{}', + UNIQUE(role_name, site_id), + CONSTRAINT fk_site + FOREIGN KEY(site_id) + REFERENCES sites(id) +); \ No newline at end of file diff --git a/sites/Backpack/sql/create/sites.sql b/sites/Backpack/sql/create/sites.sql new file mode 100644 index 0000000..c4573b3 --- /dev/null +++ b/sites/Backpack/sql/create/sites.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS sites ( + id SERIAL PRIMARY KEY, + site_name VARCHAR(120), + site_description TEXT, + creation_date TIMESTAMP, + site_owner_id INTEGER NOT NULL, + flags JSONB, + default_zone VARCHAR(32), + default_auto_issue_location VARCHAR(32), + default_primary_location VARCHAR(32), + UNIQUE(site_name), + CONSTRAINT fk_site_owner + FOREIGN KEY(site_owner_id) + REFERENCES logins(id) +); \ No newline at end of file diff --git a/sites/Backpack/sql/unique/shopping_lists_safetystock.sql b/sites/Backpack/sql/unique/shopping_lists_safetystock.sql new file mode 100644 index 0000000..4efc5e5 --- /dev/null +++ b/sites/Backpack/sql/unique/shopping_lists_safetystock.sql @@ -0,0 +1,43 @@ +WITH sum_cte AS ( + SELECT mi.id, SUM(mil.quantity_on_hand) AS total_sum + FROM Backpack_item_locations mil + JOIN Backpack_items mi ON mil.part_id = mi.id + GROUP BY mi.id +) +SELECT * +FROM Backpack_items +LEFT JOIN Backpack_item_info ON Backpack_items.item_info_id = Backpack_item_info.id +LEFT JOIN sum_cte ON Backpack_items.id = sum_cte.id +WHERE Backpack_item_info.safety_stock > COALESCE(sum_cte.total_sum, 0) +AND Backpack_item_info.shopping_lists @> ARRAY[%s]; + +/* +00 - item_id +01 - barcode +02 - item_name +03 - brand (id) +04 - description +05 - tags +06 - links +07 - item_info_id +08 - logistics_info_id +09 - food_info_id +10 - row_type +11 - item_type +12 - search_string +13 - item_info_id +14 - barcode +15 - linked_items +16 - shopping_lists +17 - recipes +18 - groups +19 - packaging +20 - uom +21 - cost +22 - safety_stock +23 - lead_time_days +24 - ai_pick +25 - sum_cte_id +26 - total_sum/QOH +*/ + diff --git a/sites/Backpack/sql/unique/shopping_lists_safetystock_count.sql b/sites/Backpack/sql/unique/shopping_lists_safetystock_count.sql new file mode 100644 index 0000000..756fe85 --- /dev/null +++ b/sites/Backpack/sql/unique/shopping_lists_safetystock_count.sql @@ -0,0 +1,12 @@ +WITH sum_cte AS ( + SELECT mi.id, SUM(mil.quantity_on_hand) AS total_sum + FROM Backpack_item_locations mil + JOIN Backpack_items mi ON mil.part_id = mi.id + GROUP BY mi.id +) +SELECT COUNT(*) +FROM Backpack_items +LEFT JOIN Backpack_item_info ON Backpack_items.item_info_id = Backpack_item_info.id +LEFT JOIN sum_cte ON Backpack_items.id = sum_cte.id +WHERE Backpack_item_info.safety_stock > COALESCE(sum_cte.total_sum, 0) +AND Backpack_item_info.shopping_lists @> ARRAY[%s]; \ No newline at end of file diff --git a/sites/Backpack/sql/unique/shopping_lists_safetystock_uncalculated.sql b/sites/Backpack/sql/unique/shopping_lists_safetystock_uncalculated.sql new file mode 100644 index 0000000..7f52ab9 --- /dev/null +++ b/sites/Backpack/sql/unique/shopping_lists_safetystock_uncalculated.sql @@ -0,0 +1,11 @@ +WITH sum_cte AS ( + SELECT mi.id, SUM(mil.quantity_on_hand) AS total_sum + FROM Backpack_item_locations mil + JOIN Backpack_items mi ON mil.part_id = mi.id + GROUP BY mi.id +) +SELECT * +FROM Backpack_items +LEFT JOIN Backpack_item_info ON Backpack_items.item_info_id = Backpack_item_info.id +LEFT JOIN sum_cte ON Backpack_items.id = sum_cte.id +WHERE Backpack_item_info.shopping_lists @> ARRAY[%s]; \ No newline at end of file diff --git a/sites/default/sql/create/logins.sql b/sites/default/sql/create/logins.sql index f69b01b..3630312 100644 --- a/sites/default/sql/create/logins.sql +++ b/sites/default/sql/create/logins.sql @@ -2,15 +2,21 @@ CREATE TABLE IF NOT EXISTS logins( id SERIAL PRIMARY KEY, username VARCHAR(255), password VARCHAR(255), - favorites JSONB, - unseen_pantry_items INTEGER [], - unseen_groups INTEGER [], - unseen_shopping_lists INTEGER [], - unseen_recipes INTEGER [], - seen_pantry_items INTEGER [], - seen_groups INTEGER[], - seen_shopping_lists INTEGER [], - seen_recipes INTEGER [], - flags JSONB + email VARCHAR(255) UNIQUE NOT NULL, + favorites JSONB DEFAULT '{}', + unseen_pantry_items INTEGER [] DEFAULT '{}', + unseen_groups INTEGER [] DEFAULT '{}', + unseen_shopping_lists INTEGER [] DEFAULT '{}', + unseen_recipes INTEGER [] DEFAULT '{}', + seen_pantry_items INTEGER [] DEFAULT '{}', + seen_groups INTEGER[] DEFAULT '{}', + seen_shopping_lists INTEGER [] DEFAULT '{}', + seen_recipes INTEGER [] DEFAULT '{}', + sites INTEGER [] DEFAULT '{}', + site_roles INTEGER [] DEFAULT '{}', + system_admin BOOLEAN DEFAULT FALSE, + flags JSONB DEFAULT '{}', + UNIQUE(username), + CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$') ); diff --git a/sites/default/sql/create/roles.sql b/sites/default/sql/create/roles.sql new file mode 100644 index 0000000..802584f --- /dev/null +++ b/sites/default/sql/create/roles.sql @@ -0,0 +1,11 @@ +CREATE TABLE IF NOT EXISTS roles( + id SERIAL PRIMARY KEY, + role_name VARCHAR(255) NOT NULL, + role_description TEXT, + site_id INTEGER NOT NULL, + flags JSONB DEFAULT '{}', + UNIQUE(role_name, site_id), + CONSTRAINT fk_site + FOREIGN KEY(site_id) + REFERENCES sites(id) +); \ No newline at end of file diff --git a/sites/default/sql/create/sites.sql b/sites/default/sql/create/sites.sql new file mode 100644 index 0000000..c4573b3 --- /dev/null +++ b/sites/default/sql/create/sites.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS sites ( + id SERIAL PRIMARY KEY, + site_name VARCHAR(120), + site_description TEXT, + creation_date TIMESTAMP, + site_owner_id INTEGER NOT NULL, + flags JSONB, + default_zone VARCHAR(32), + default_auto_issue_location VARCHAR(32), + default_primary_location VARCHAR(32), + UNIQUE(site_name), + CONSTRAINT fk_site_owner + FOREIGN KEY(site_owner_id) + REFERENCES logins(id) +); \ No newline at end of file diff --git a/sites/default/sql/unique/shopping_lists_safetystock.sql b/sites/default/sql/unique/shopping_lists_safetystock.sql new file mode 100644 index 0000000..7ab5d25 --- /dev/null +++ b/sites/default/sql/unique/shopping_lists_safetystock.sql @@ -0,0 +1,43 @@ +WITH sum_cte AS ( + SELECT mi.id, SUM(mil.quantity_on_hand) AS total_sum + FROM %sitename%_item_locations mil + JOIN %sitename%_items mi ON mil.part_id = mi.id + GROUP BY mi.id +) +SELECT * +FROM %sitename%_items +LEFT JOIN %sitename%_item_info ON %sitename%_items.item_info_id = %sitename%_item_info.id +LEFT JOIN sum_cte ON %sitename%_items.id = sum_cte.id +WHERE %sitename%_item_info.safety_stock > COALESCE(sum_cte.total_sum, 0) +AND %sitename%_item_info.shopping_lists @> ARRAY[%s]; + +/* +00 - item_id +01 - barcode +02 - item_name +03 - brand (id) +04 - description +05 - tags +06 - links +07 - item_info_id +08 - logistics_info_id +09 - food_info_id +10 - row_type +11 - item_type +12 - search_string +13 - item_info_id +14 - barcode +15 - linked_items +16 - shopping_lists +17 - recipes +18 - groups +19 - packaging +20 - uom +21 - cost +22 - safety_stock +23 - lead_time_days +24 - ai_pick +25 - sum_cte_id +26 - total_sum/QOH +*/ + diff --git a/sites/default/sql/unique/shopping_lists_safetystock_count.sql b/sites/default/sql/unique/shopping_lists_safetystock_count.sql new file mode 100644 index 0000000..3f9d4c1 --- /dev/null +++ b/sites/default/sql/unique/shopping_lists_safetystock_count.sql @@ -0,0 +1,12 @@ +WITH sum_cte AS ( + SELECT mi.id, SUM(mil.quantity_on_hand) AS total_sum + FROM %sitename%_item_locations mil + JOIN %sitename%_items mi ON mil.part_id = mi.id + GROUP BY mi.id +) +SELECT COUNT(*) +FROM %sitename%_items +LEFT JOIN %sitename%_item_info ON %sitename%_items.item_info_id = %sitename%_item_info.id +LEFT JOIN sum_cte ON %sitename%_items.id = sum_cte.id +WHERE %sitename%_item_info.safety_stock > COALESCE(sum_cte.total_sum, 0) +AND %sitename%_item_info.shopping_lists @> ARRAY[%s]; \ No newline at end of file diff --git a/sites/default/sql/unique/shopping_lists_safetystock_uncalculated.sql b/sites/default/sql/unique/shopping_lists_safetystock_uncalculated.sql new file mode 100644 index 0000000..44f7e66 --- /dev/null +++ b/sites/default/sql/unique/shopping_lists_safetystock_uncalculated.sql @@ -0,0 +1,11 @@ +WITH sum_cte AS ( + SELECT mi.id, SUM(mil.quantity_on_hand) AS total_sum + FROM %sitename%_item_locations mil + JOIN %sitename%_items mi ON mil.part_id = mi.id + GROUP BY mi.id +) +SELECT * +FROM %sitename%_items +LEFT JOIN %sitename%_item_info ON %sitename%_items.item_info_id = %sitename%_item_info.id +LEFT JOIN sum_cte ON %sitename%_items.id = sum_cte.id +WHERE %sitename%_item_info.shopping_lists @> ARRAY[%s]; \ No newline at end of file diff --git a/sites/main/site.ini b/sites/main/site.ini index 2d7e2e6..4c1e583 100644 --- a/sites/main/site.ini +++ b/sites/main/site.ini @@ -1,9 +1,9 @@ [site] site_name=main -site_owner= -email= +site_owner=jadowyne +email=jadowyne.ulve@outlook.com [defaults] -default_zone=default -default_primary_location=all -default_auto_issue_location=all +default_zone=DEFAULT +default_primary_location=ALL +default_auto_issue_location=ALL diff --git a/sites/main/sql/create/logins.sql b/sites/main/sql/create/logins.sql index f69b01b..3630312 100644 --- a/sites/main/sql/create/logins.sql +++ b/sites/main/sql/create/logins.sql @@ -2,15 +2,21 @@ CREATE TABLE IF NOT EXISTS logins( id SERIAL PRIMARY KEY, username VARCHAR(255), password VARCHAR(255), - favorites JSONB, - unseen_pantry_items INTEGER [], - unseen_groups INTEGER [], - unseen_shopping_lists INTEGER [], - unseen_recipes INTEGER [], - seen_pantry_items INTEGER [], - seen_groups INTEGER[], - seen_shopping_lists INTEGER [], - seen_recipes INTEGER [], - flags JSONB + email VARCHAR(255) UNIQUE NOT NULL, + favorites JSONB DEFAULT '{}', + unseen_pantry_items INTEGER [] DEFAULT '{}', + unseen_groups INTEGER [] DEFAULT '{}', + unseen_shopping_lists INTEGER [] DEFAULT '{}', + unseen_recipes INTEGER [] DEFAULT '{}', + seen_pantry_items INTEGER [] DEFAULT '{}', + seen_groups INTEGER[] DEFAULT '{}', + seen_shopping_lists INTEGER [] DEFAULT '{}', + seen_recipes INTEGER [] DEFAULT '{}', + sites INTEGER [] DEFAULT '{}', + site_roles INTEGER [] DEFAULT '{}', + system_admin BOOLEAN DEFAULT FALSE, + flags JSONB DEFAULT '{}', + UNIQUE(username), + CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$') ); diff --git a/sites/main/sql/create/roles.sql b/sites/main/sql/create/roles.sql new file mode 100644 index 0000000..802584f --- /dev/null +++ b/sites/main/sql/create/roles.sql @@ -0,0 +1,11 @@ +CREATE TABLE IF NOT EXISTS roles( + id SERIAL PRIMARY KEY, + role_name VARCHAR(255) NOT NULL, + role_description TEXT, + site_id INTEGER NOT NULL, + flags JSONB DEFAULT '{}', + UNIQUE(role_name, site_id), + CONSTRAINT fk_site + FOREIGN KEY(site_id) + REFERENCES sites(id) +); \ No newline at end of file diff --git a/sites/main/sql/create/sites.sql b/sites/main/sql/create/sites.sql new file mode 100644 index 0000000..c4573b3 --- /dev/null +++ b/sites/main/sql/create/sites.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS sites ( + id SERIAL PRIMARY KEY, + site_name VARCHAR(120), + site_description TEXT, + creation_date TIMESTAMP, + site_owner_id INTEGER NOT NULL, + flags JSONB, + default_zone VARCHAR(32), + default_auto_issue_location VARCHAR(32), + default_primary_location VARCHAR(32), + UNIQUE(site_name), + CONSTRAINT fk_site_owner + FOREIGN KEY(site_owner_id) + REFERENCES logins(id) +); \ No newline at end of file diff --git a/sites/main/sql/unique/shopping_lists_safetystock.sql b/sites/main/sql/unique/shopping_lists_safetystock.sql new file mode 100644 index 0000000..244bd62 --- /dev/null +++ b/sites/main/sql/unique/shopping_lists_safetystock.sql @@ -0,0 +1,12 @@ +WITH sum_cte AS ( + SELECT mi.id, SUM(mil.quantity_on_hand) AS total_sum + FROM main_item_locations mil + JOIN main_items mi ON mil.part_id = mi.id + GROUP BY mi.id +) +SELECT * +FROM main_items +LEFT JOIN main_item_info ON main_items.item_info_id = main_item_info.id +LEFT JOIN sum_cte ON main_items.id = sum_cte.id +WHERE main_item_info.safety_stock > COALESCE(sum_cte.total_sum, 0) +AND main_item_info.shopping_lists @> ARRAY[%s]; \ No newline at end of file diff --git a/sites/main/sql/unique/shopping_lists_safetystock_count.sql b/sites/main/sql/unique/shopping_lists_safetystock_count.sql new file mode 100644 index 0000000..605525f --- /dev/null +++ b/sites/main/sql/unique/shopping_lists_safetystock_count.sql @@ -0,0 +1,12 @@ +WITH sum_cte AS ( + SELECT mi.id, SUM(mil.quantity_on_hand) AS total_sum + FROM main_item_locations mil + JOIN main_items mi ON mil.part_id = mi.id + GROUP BY mi.id +) +SELECT COUNT(*) +FROM main_items +LEFT JOIN main_item_info ON main_items.item_info_id = main_item_info.id +LEFT JOIN sum_cte ON main_items.id = sum_cte.id +WHERE main_item_info.safety_stock > COALESCE(sum_cte.total_sum, 0) +AND main_item_info.shopping_lists @> ARRAY[%s]; \ No newline at end of file diff --git a/sites/main/sql/unique/shopping_lists_safetystock_uncalculated.sql b/sites/main/sql/unique/shopping_lists_safetystock_uncalculated.sql new file mode 100644 index 0000000..19d9baf --- /dev/null +++ b/sites/main/sql/unique/shopping_lists_safetystock_uncalculated.sql @@ -0,0 +1,11 @@ +WITH sum_cte AS ( + SELECT mi.id, SUM(mil.quantity_on_hand) AS total_sum + FROM main_item_locations mil + JOIN main_items mi ON mil.part_id = mi.id + GROUP BY mi.id +) +SELECT * +FROM main_items +LEFT JOIN main_item_info ON main_items.item_info_id = main_item_info.id +LEFT JOIN sum_cte ON main_items.id = sum_cte.id +WHERE main_item_info.shopping_lists @> ARRAY[%s]; \ No newline at end of file diff --git a/sites/test/site.ini b/sites/test/site.ini deleted file mode 100644 index 388ddf8..0000000 --- a/sites/test/site.ini +++ /dev/null @@ -1,9 +0,0 @@ -[site] -site_name=test -site_owner= -email= - -[defaults] -default_zone=default -default_primary_location=all -default_auto_issue_location=all diff --git a/sites/test/sql/create/brands.sql b/sites/test/sql/create/brands.sql deleted file mode 100644 index 6cd9f9c..0000000 --- a/sites/test/sql/create/brands.sql +++ /dev/null @@ -1,4 +0,0 @@ -CREATE TABLE IF NOT EXISTS test_brands ( - id SERIAL PRIMARY KEY, - name VARCHAR(255) -); \ No newline at end of file diff --git a/sites/test/sql/create/food_info.sql b/sites/test/sql/create/food_info.sql deleted file mode 100644 index bedbe47..0000000 --- a/sites/test/sql/create/food_info.sql +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE IF NOT EXISTS test_food_info ( - id SERIAL PRIMARY KEY, - food_groups TEXT [], - ingrediants TEXT [], - nutrients JSONB, - expires BOOLEAN -); \ No newline at end of file diff --git a/sites/test/sql/create/groups.sql b/sites/test/sql/create/groups.sql deleted file mode 100644 index b97fdab..0000000 --- a/sites/test/sql/create/groups.sql +++ /dev/null @@ -1,8 +0,0 @@ -CREATE TABLE IF NOT EXISTS test_groups( - id SERIAL PRIMARY KEY, - name VARCHAR(255) NOT NULL, - description TEXT, - included_items INTEGER [], - group_type VARCHAR(255), - UNIQUE (name) -); \ No newline at end of file diff --git a/sites/test/sql/create/item.sql b/sites/test/sql/create/item.sql deleted file mode 100644 index e01be05..0000000 --- a/sites/test/sql/create/item.sql +++ /dev/null @@ -1,32 +0,0 @@ -CREATE TABLE IF NOT EXISTS test_items( - id SERIAL PRIMARY KEY, - barcode VARCHAR(255) NOT NULL, - item_name VARCHAR(255) NOT NULL, - brand INTEGER, - description TEXT, - tags TEXT [], - links JSONB, - item_info_id INTEGER NOT NULL, - logistics_info_id INTEGER NOT NULL, - food_info_id INTEGER, - row_type VARCHAR(255) NOT NULL, - item_type VARCHAR(255) NOT NULL, - search_string TEXT NOT NULL, - UNIQUE(barcode, item_info_id), - CONSTRAINT fk_item_info - FOREIGN KEY(item_info_id) - REFERENCES test_item_info(id) - ON DELETE CASCADE, - CONSTRAINT fk_food_info - FOREIGN KEY(food_info_id) - REFERENCES test_food_info(id) - ON DELETE CASCADE, - CONSTRAINT fk_brand - FOREIGN KEY(brand) - REFERENCES test_brands(id) - ON DELETE CASCADE, - CONSTRAINT fk_logistics_info - FOREIGN KEY(logistics_info_id) - REFERENCES test_logistics_info(id) - ON DELETE CASCADE -); diff --git a/sites/test/sql/create/item_info.sql b/sites/test/sql/create/item_info.sql deleted file mode 100644 index 317106c..0000000 --- a/sites/test/sql/create/item_info.sql +++ /dev/null @@ -1,15 +0,0 @@ -CREATE TABLE IF NOt EXISTS test_item_info ( - id SERIAL PRIMARY KEY, - barcode VARCHAR(255) NOT NULL, - linked_items INTEGER [], - shopping_lists INTEGER [], - recipes INTEGER [], - groups INTEGER [], - packaging VARCHAR(255), - uom VARCHAR(255), - cost FLOAT8, - safety_stock FLOAT8, - lead_time_days FLOAT8, - ai_pick BOOLEAN, - UNIQUE(barcode) -); \ No newline at end of file diff --git a/sites/test/sql/create/item_locations.sql b/sites/test/sql/create/item_locations.sql deleted file mode 100644 index 30aec57..0000000 --- a/sites/test/sql/create/item_locations.sql +++ /dev/null @@ -1,23 +0,0 @@ -DO $$ -BEGIN - IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'cost_layer') THEN - CREATE TYPE cost_layer AS (qty FLOAT8, cost FLOAT8); - END IF; -END $$; - -CREATE TABLE IF NOT EXISTS test_item_locations( - id SERIAL PRIMARY KEY, - part_id INTEGER NOT NULL, - location_id INTEGER NOT NULL, - quantity_on_hand FLOAT8 NOT NULL, - cost_layers cost_layer[], - UNIQUE(part_id, location_id), - CONSTRAINT fk_part_id - FOREIGN KEY(part_id) - REFERENCES test_items(id) - ON DELETE CASCADE, - CONSTRAINT fk_location_id - FOREIGN KEY(location_id) - REFERENCES test_locations(id) - ON DELETE CASCADE -); \ No newline at end of file diff --git a/sites/test/sql/create/linked_items.sql b/sites/test/sql/create/linked_items.sql deleted file mode 100644 index 16a24e2..0000000 --- a/sites/test/sql/create/linked_items.sql +++ /dev/null @@ -1,8 +0,0 @@ -CREATE TABLE IF NOT EXISTS test_itemlinks ( - id SERIAL PRIMARY KEY, - barcode VARCHAR(255) NOt NULL, - link INTEGER NOT NULL, - data JSONB NOT NULL, - conv_factor FLOAT8 NOt NULL, - UNIQUE(barcode) -); \ No newline at end of file diff --git a/sites/test/sql/create/locations.sql b/sites/test/sql/create/locations.sql deleted file mode 100644 index da7ecdc..0000000 --- a/sites/test/sql/create/locations.sql +++ /dev/null @@ -1,11 +0,0 @@ -CREATE TABLE IF NOT EXISTS test_locations( - id SERIAL PRIMARY KEY, - uuid VARCHAR(255) NOT NULL, - name VARCHAR(32) NOT NULL, - zone_id INTEGER NOT NULL, - items JSONB, - UNIQUE(uuid), - CONSTRAINT fk_zone - FOREIGN KEY(zone_id) - REFERENCES test_zones(id) -); \ No newline at end of file diff --git a/sites/test/sql/create/logins.sql b/sites/test/sql/create/logins.sql deleted file mode 100644 index f69b01b..0000000 --- a/sites/test/sql/create/logins.sql +++ /dev/null @@ -1,16 +0,0 @@ -CREATE TABLE IF NOT EXISTS logins( - id SERIAL PRIMARY KEY, - username VARCHAR(255), - password VARCHAR(255), - favorites JSONB, - unseen_pantry_items INTEGER [], - unseen_groups INTEGER [], - unseen_shopping_lists INTEGER [], - unseen_recipes INTEGER [], - seen_pantry_items INTEGER [], - seen_groups INTEGER[], - seen_shopping_lists INTEGER [], - seen_recipes INTEGER [], - flags JSONB -); - diff --git a/sites/test/sql/create/logistics_info.sql b/sites/test/sql/create/logistics_info.sql deleted file mode 100644 index 7a77d79..0000000 --- a/sites/test/sql/create/logistics_info.sql +++ /dev/null @@ -1,10 +0,0 @@ -CREATE TABLE IF NOT EXISTS test_logistics_info( - id SERIAL PRIMARY KEY, - barcode VARCHAR(255) NOT NULL, - primary_location VARCHAR(64), - auto_issue_location VARCHAR(64), - dynamic_locations JSONB, - location_data JSONB, - quantity_on_hand FLOAT8 NOT NULL, - UNIQUE(barcode) -); \ No newline at end of file diff --git a/sites/test/sql/create/receipt_items.sql b/sites/test/sql/create/receipt_items.sql deleted file mode 100644 index ef34fd6..0000000 --- a/sites/test/sql/create/receipt_items.sql +++ /dev/null @@ -1,13 +0,0 @@ -CREATE TABLE IF NOT EXISTS test_receipt_items ( - id SERIAL PRIMARY KEY, - type VARCHAR(255) NOT NULL, - receipt_id INTEGER NOT NULL, - barcode VARCHAR(255) NOT NULL, - name VARCHAR(255) NOT NULL, - qty FLOAT8 NOT NULL, - data JSONB, - status VARCHAR (64), - CONSTRAINT fk_receipt - FOREIGN KEY(receipt_id) - REFERENCES test_receipts(id) -); \ No newline at end of file diff --git a/sites/test/sql/create/receipts.sql b/sites/test/sql/create/receipts.sql deleted file mode 100644 index fbafa32..0000000 --- a/sites/test/sql/create/receipts.sql +++ /dev/null @@ -1,13 +0,0 @@ -CREATE TABLE IF NOT EXISTS test_receipts ( - id SERIAL PRIMARY KEY, - receipt_id VARCHAR (32) NOT NULL, - receipt_status VARCHAR (64) NOT NULL, - date_submitted TIMESTAMP NOT NULL, - submitted_by INTEGER NOT NULL, - vendor_id INTEGER, - files JSONB, - UNIQUE(receipt_id), - CONSTRAINT fk_vendor - FOREIGN KEY(vendor_id) - REFERENCES test_vendors(id) -); \ No newline at end of file diff --git a/sites/test/sql/create/recipes.sql b/sites/test/sql/create/recipes.sql deleted file mode 100644 index 150b4cc..0000000 --- a/sites/test/sql/create/recipes.sql +++ /dev/null @@ -1,12 +0,0 @@ -CREATE TABLE IF NOT EXISTS test_recipes ( - id SERIAL PRIMARY KEY, - name VARCHAR, - author INTEGER, - description TEXT, - creation_date TIMESTAMP, - custom_items JSONB, - pantry_items JSONB, - group_items JSONB, - instructions TEXT [], - picture_path TEXT -); \ No newline at end of file diff --git a/sites/test/sql/create/shopping_lists.sql b/sites/test/sql/create/shopping_lists.sql deleted file mode 100644 index 9661ef6..0000000 --- a/sites/test/sql/create/shopping_lists.sql +++ /dev/null @@ -1,14 +0,0 @@ -CREATE TABLE IF NOT EXISTS test_shopping_lists ( - id SERIAL PRIMARY KEY, - name VARCHAR(255) NOT NULL, - description TEXT, - pantry_items INTEGER [], - custom_items JSONB, - recipes INTEGER [], - groups INTEGER [], - quantities JSONB, - author INTEGER, - creation_date TIMESTAMP, - type VARCHAR(64), - UNIQUE(name) -); \ No newline at end of file diff --git a/sites/test/sql/create/transactions.sql b/sites/test/sql/create/transactions.sql deleted file mode 100644 index c781b14..0000000 --- a/sites/test/sql/create/transactions.sql +++ /dev/null @@ -1,15 +0,0 @@ -CREATE TABLE IF NOT EXISTS test_Transactions ( - id SERIAL PRIMARY KEY, - timestamp TIMESTAMP, - logistics_info_id INTEGER NOT NULL, - barcode VARCHAR(255) NOT NULL, - name VARCHAR(255), - transaction_type VARCHAR(255) NOT NULL, - quantity FLOAT8 NOT NULL, - description TEXT, - user_id INTEGER NOT NULL, - data JSONB, - CONSTRAINT fk_logistics_info - FOREIGN KEY(logistics_info_id) - REFERENCES test_logistics_info(id) -); \ No newline at end of file diff --git a/sites/test/sql/create/vendors.sql b/sites/test/sql/create/vendors.sql deleted file mode 100644 index eedc869..0000000 --- a/sites/test/sql/create/vendors.sql +++ /dev/null @@ -1,8 +0,0 @@ -CREATE TABLE IF NOT EXISTS test_vendors ( - id SERIAL PRIMARY KEY, - vendor_name VARCHAR(255) NOT NULL, - vendor_address VARCHAR(255), - creation_date TIMESTAMP NOT NULL, - created_by INTEGER NOT NULL, - phone_number VARCHAR(32) -); \ No newline at end of file diff --git a/sites/test/sql/create/zones.sql b/sites/test/sql/create/zones.sql deleted file mode 100644 index 0ed0a9e..0000000 --- a/sites/test/sql/create/zones.sql +++ /dev/null @@ -1,5 +0,0 @@ -CREATE TABLE IF NOT EXISTS test_zones( - id SERIAL PRIMARY KEY, - name VARCHAR(32) NOT NULL, - UNIQUE(name) -); diff --git a/sites/test/sql/drop/brands.sql b/sites/test/sql/drop/brands.sql deleted file mode 100644 index f407a8c..0000000 --- a/sites/test/sql/drop/brands.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE test_brands CASCADE; \ No newline at end of file diff --git a/sites/test/sql/drop/food_info.sql b/sites/test/sql/drop/food_info.sql deleted file mode 100644 index a4c3056..0000000 --- a/sites/test/sql/drop/food_info.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE test_food_info CASCADE; \ No newline at end of file diff --git a/sites/test/sql/drop/groups.sql b/sites/test/sql/drop/groups.sql deleted file mode 100644 index 6b62f3b..0000000 --- a/sites/test/sql/drop/groups.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE test_groups CASCADE; \ No newline at end of file diff --git a/sites/test/sql/drop/item_info.sql b/sites/test/sql/drop/item_info.sql deleted file mode 100644 index c84b0d1..0000000 --- a/sites/test/sql/drop/item_info.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE test_item_info CASCADE; \ No newline at end of file diff --git a/sites/test/sql/drop/item_locations.sql b/sites/test/sql/drop/item_locations.sql deleted file mode 100644 index 005ec66..0000000 --- a/sites/test/sql/drop/item_locations.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE test_item_locations CASCADE; diff --git a/sites/test/sql/drop/items.sql b/sites/test/sql/drop/items.sql deleted file mode 100644 index 097b9a7..0000000 --- a/sites/test/sql/drop/items.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE test_items CASCADE; \ No newline at end of file diff --git a/sites/test/sql/drop/linked_items.sql b/sites/test/sql/drop/linked_items.sql deleted file mode 100644 index bcc9093..0000000 --- a/sites/test/sql/drop/linked_items.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE test_itemlinks CASCADE; \ No newline at end of file diff --git a/sites/test/sql/drop/locations.sql b/sites/test/sql/drop/locations.sql deleted file mode 100644 index 4a1f42e..0000000 --- a/sites/test/sql/drop/locations.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE test_locations CASCADE; \ No newline at end of file diff --git a/sites/test/sql/drop/logistics_info.sql b/sites/test/sql/drop/logistics_info.sql deleted file mode 100644 index cb5abcd..0000000 --- a/sites/test/sql/drop/logistics_info.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE test_logistics_info CASCADE; \ No newline at end of file diff --git a/sites/test/sql/drop/receipt_items.sql b/sites/test/sql/drop/receipt_items.sql deleted file mode 100644 index 0f77f60..0000000 --- a/sites/test/sql/drop/receipt_items.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE test_receipt_items CASCADE; \ No newline at end of file diff --git a/sites/test/sql/drop/receipts.sql b/sites/test/sql/drop/receipts.sql deleted file mode 100644 index 1d9502f..0000000 --- a/sites/test/sql/drop/receipts.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE test_receipts CASCADE; \ No newline at end of file diff --git a/sites/test/sql/drop/recipes.sql b/sites/test/sql/drop/recipes.sql deleted file mode 100644 index 0a626bd..0000000 --- a/sites/test/sql/drop/recipes.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE test_recipes CASCADE; \ No newline at end of file diff --git a/sites/test/sql/drop/shopping_lists.sql b/sites/test/sql/drop/shopping_lists.sql deleted file mode 100644 index a4cec6d..0000000 --- a/sites/test/sql/drop/shopping_lists.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE test_shopping_lists CASCADE; \ No newline at end of file diff --git a/sites/test/sql/drop/transactions.sql b/sites/test/sql/drop/transactions.sql deleted file mode 100644 index 1356e47..0000000 --- a/sites/test/sql/drop/transactions.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE test_transactions CASCADE; \ No newline at end of file diff --git a/sites/test/sql/drop/vendors.sql b/sites/test/sql/drop/vendors.sql deleted file mode 100644 index 28351e8..0000000 --- a/sites/test/sql/drop/vendors.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE test_vendors CASCADE; \ No newline at end of file diff --git a/sites/test/sql/drop/zones.sql b/sites/test/sql/drop/zones.sql deleted file mode 100644 index c84c67b..0000000 --- a/sites/test/sql/drop/zones.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE test_zones CASCADE; \ No newline at end of file diff --git a/sites/test/sql/unique/Insert_transaction.sql b/sites/test/sql/unique/Insert_transaction.sql deleted file mode 100644 index 7449439..0000000 --- a/sites/test/sql/unique/Insert_transaction.sql +++ /dev/null @@ -1,3 +0,0 @@ -INSERT INTO test_transactions -(timestamp, logistics_info_id, barcode, name, transaction_type, quantity, description, user_id, data) -VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s); \ No newline at end of file diff --git a/sites/test/sql/unique/logistics_transactions.sql b/sites/test/sql/unique/logistics_transactions.sql deleted file mode 100644 index 1b3d28d..0000000 --- a/sites/test/sql/unique/logistics_transactions.sql +++ /dev/null @@ -1,3 +0,0 @@ -UPDATE test_logistics_info -SET quantity_on_hand = %s, location_data = %s -WHERE id = %s; diff --git a/sites/test/sql/unique/select_item_all.sql b/sites/test/sql/unique/select_item_all.sql deleted file mode 100644 index c9943d3..0000000 --- a/sites/test/sql/unique/select_item_all.sql +++ /dev/null @@ -1,45 +0,0 @@ -SELECT * FROM test_items - LEFT JOIN test_logistics_info ON test_items.logistics_info_id = test_logistics_info.id - LEFT JOIN test_item_info ON test_items.item_info_id = test_item_info.id - LEFT JOIN test_food_info ON test_items.food_info_id = test_food_info.id -WHERE test_items.id=%s; - -/* -00 - item_id -01 - barcode -02 - item_name -03 - brand (id) -04 - description -05 - tags -06 - links -07 - item_info_id -08 - logistics_info_id -09 - food_info_id -10 - row_type -11 - item_type -12 - search_string -13 - logistics_info_id -14 - barcode -15 - primary_location -16 - auto_issue_location -17 - dynamic_locations -18 - location_data -19 - quantity_on_hand -20 - item_info_id -21 - barcode -22 - linked_items -23 - shopping_lists -24 - recipes -25 - groups -26 - packaging -27 - uom -28 - cost -29 - safety_stock -30 - lead_time_days -31 - ai_pick -32 - food_info_id -33 - food_groups -34 - ingrediants -35 - nutrients -36 - expires -*/ \ No newline at end of file diff --git a/sites/test/sql/unique/select_item_all_barcode.sql b/sites/test/sql/unique/select_item_all_barcode.sql deleted file mode 100644 index 58ded7c..0000000 --- a/sites/test/sql/unique/select_item_all_barcode.sql +++ /dev/null @@ -1,45 +0,0 @@ -SELECT * FROM test_items - LEFT JOIN test_logistics_info ON test_items.logistics_info_id = test_logistics_info.id - LEFT JOIN test_item_info ON test_items.item_info_id = test_item_info.id - LEFT JOIN test_food_info ON test_items.food_info_id = test_food_info.id -WHERE test_items.barcode=%s; - -/* -00 - item_id -01 - barcode -02 - item_name -03 - brand (id) -04 - description -05 - tags -06 - links -07 - item_info_id -08 - logistics_info_id -09 - food_info_id -10 - row_type -11 - item_type -12 - search_string -13 - logistics_info_id -14 - barcode -15 - primary_location -16 - auto_issue_location -17 - dynamic_locations -18 - location_data -19 - quantity_on_hand -20 - item_info_id -21 - barcode -22 - linked_items -23 - shopping_lists -24 - recipes -25 - groups -26 - packaging -27 - uom -28 - cost -29 - safety_stock -30 - lead_time_days -31 - ai_pick -32 - food_info_id -33 - food_groups -34 - ingrediants -35 - nutrients -36 - expires -*/ \ No newline at end of file diff --git a/sites/test/sql/unique/set_location_data.sql b/sites/test/sql/unique/set_location_data.sql deleted file mode 100644 index 4a44e02..0000000 --- a/sites/test/sql/unique/set_location_data.sql +++ /dev/null @@ -1,3 +0,0 @@ -UPDATE test_locations -SET items = %s -WHERE id = %s; \ No newline at end of file diff --git a/static/adminHandler.js b/static/adminHandler.js new file mode 100644 index 0000000..8dca71a --- /dev/null +++ b/static/adminHandler.js @@ -0,0 +1,28 @@ +async function clickRoleRow(role_id){ + const roleurl = new URL(`/admin/editRole/${role_id}`, window.location.origin); + window.location.href = roleurl.toString(); +} + +async function fetchSites() { + const url = new URL('/admin/getSites', window.location.origin); + const response = await fetch(url); + const data = await response.json(); + return data.sites; +} + +async function fetchUsers(limit, page) { + const url = new URL('/admin/getUsers', window.location.origin); + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + limit: limit, + page: page + }), + }); + const data = await response.json(); + return data.users; +} + diff --git a/static/pictures/logo.jpg b/static/pictures/logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..96cad74fbfc02148e886137b3c860441499a3ae9 GIT binary patch literal 91080 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 + + + + Admin + + + + + + + + + +
+
+
+
+ + home + Profile +
+
+
+
+

Your Sites

+
+
+

Listed below are all the sites within your instance of MyPantry. Clicking on one will allow you + edit most of the attributes inherited by the site.

+
+
+ + + + + +
SiteDescription
+
+
+ +
+
+
+
+

Your Roles

+
+
+

Listed below are all the roles within your instance of MyPantry. Clicking on one will allow you + edit most of the attributes inherited by the role.

+
+
+ + + + + + +
SiteRoleRole Description
+
+
+ +
+
+
+
+

Your Users

+
+
+

Listed below is all the users that have access to your instance.

+
+
+ + + + + +
UsernameEmail
+
+
+ +
+
+
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/templates/admin/role.html b/templates/admin/role.html new file mode 100644 index 0000000..eaaa84a --- /dev/null +++ b/templates/admin/role.html @@ -0,0 +1,131 @@ + + + + + Edit Role + + + + + + + +
+
+
+ +
+ + +
+
+ + +
+
+ + +
+
+

Permissions

+
+
+
All
+
+
+

+ +

+

+ +

+
+
+

+ +

+

+ +

+
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/templates/admin/users.html b/templates/admin/users.html new file mode 100644 index 0000000..7d8582c --- /dev/null +++ b/templates/admin/users.html @@ -0,0 +1,17 @@ +
+
+

Your Users

+
+
+

This is where all the users who have access to this instance!

+
+
+ %%userstable%% +
+
+ +
+
+ %%pagination%% +
+
diff --git a/templates/items/index.html b/templates/items/index.html index e01ae49..75dc955 100644 --- a/templates/items/index.html +++ b/templates/items/index.html @@ -19,45 +19,42 @@ cursor: pointer; } [type="radio"]:checked + span:after { - border: 2px solid rgb(0 128 0 / 30%); /* Outline color */ - background-color: rgb(0 128 0 / 30%); /* Fill color */ + border: 2px solid rgb(0 128 0 / 30%); + background-color: rgb(0 128 0 / 30%); } - 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; } - } - .dropdown-disabled { - pointer-events: none; - opacity: 0.5; /* or your desired degree of transparency */ - } + -
    -
  • - -
  • -
  • Current Site > {{current_site}}arrow_drop_down
  • -
  • -
  • Site Items
  • -
  • Site Groups
  • -
  • Site Shopping Lists
  • -
  • Site Receipts
  • +
    diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..c06eb3c --- /dev/null +++ b/templates/login.html @@ -0,0 +1,37 @@ + + + + + My Pantry + + + + + +
    +
    +
    +
    +
    + Description +
    +
    +
    +
    + + +
    +
    + + +
    +
    + +
    +
    +
    +
    +
    + \ No newline at end of file diff --git a/templates/setup.html b/templates/setup.html new file mode 100644 index 0000000..92220c3 --- /dev/null +++ b/templates/setup.html @@ -0,0 +1,91 @@ + + + + + My Pantry - Setup + + + + + +
    +
    +
    +
    +
    Database Setup
    +

    Setup your Postgresql database settings here!

    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    Admin Account
    +

    Setup your first admin account! This will be your main account to edit sites + and other instance wide settings! Other users will later be able to be made admins + as well

    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    First Site
    +

    Setup your first Site! This will be your main site in this instance to start out. + later you will be able to add more sites beyond this one or even delete this site! NOTE: + you must have atleast one site. +

    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + +
    +
    + +
    + \ No newline at end of file diff --git a/templates/shopping-lists/view.html b/templates/shopping-lists/view.html index 53b2978..41dd3c0 100644 --- a/templates/shopping-lists/view.html +++ b/templates/shopping-lists/view.html @@ -92,7 +92,6 @@ await populateInfo() await populateReferences() - console.log(quantities) }) async function fetchList(){ @@ -129,7 +128,6 @@ var tbl_body = document.createElement('tbody') let inventory_items = shoppingList[3]; - console.log(inventory_items) // if it is plain then we want to grab the qty from the quantities dictionary @@ -138,17 +136,16 @@ var row = document.createElement('tr') let name_cell = document.createElement('td') - if (inventory_items[i][3]){ - name_cell.innerHTML = `${inventory_items[i][2]}` + if (inventory_items[i][6]){ + name_cell.innerHTML = `${inventory_items[i][2]}` } else { name_cell.innerHTML = `${inventory_items[i][2]}` } - console.log(inventory_items[i]) let qty_uom_cell = document.createElement('td') if(shoppingList[10] == 'calculated'){ - qty = Number(inventory_items[i][5]) - Number(inventory_items[i][4]) - uom = inventory_items[i][6] + qty = Number(inventory_items[i][22]) - Number(inventory_items[i][26]) + uom = inventory_items[i][20] } else { qty = quantities[`${inventory_items[i][0]}@item`]['qty'] uom = quantities[`${inventory_items[i][0]}@item`]['uom'] @@ -162,6 +159,26 @@ }; // all custom items will pull quantities form the quantities dictionary. + console.log(quantities) + + for(let key in shoppingList[4]){ + var row = document.createElement('tr') + + let name = key + let tag = `${key}@custom` + qty = quantities[tag]['qty'] + uom = quantities[tag]['uom'] + + let name_cell = document.createElement('td') + name_cell.innerHTML = `${shoppingList[4][key][2]}` + let qty_uom_cell = document.createElement('td') + qty_uom_cell.innerHTML = `${qty} ${uom}` + + row.appendChild(name_cell) + row.appendChild(qty_uom_cell) + + tbl_body.appendChild(row) + } references_table.appendChild(tbl_header) references_table.appendChild(tbl_body) diff --git a/templates/signup.html b/templates/signup.html new file mode 100644 index 0000000..0bf2202 --- /dev/null +++ b/templates/signup.html @@ -0,0 +1,27 @@ + + + + + My Pantry + + + + + +
    +
    +
    + + + Supporting text for additional information +
    +
    + + +
    + +
    +
    + \ No newline at end of file diff --git a/user_api.py b/user_api.py new file mode 100644 index 0000000..0a72972 --- /dev/null +++ b/user_api.py @@ -0,0 +1,115 @@ +from flask import Blueprint, request, render_template, redirect, session, url_for +import hashlib, psycopg2 +from config import config, sites_config, setFirstSetupDone +from functools import wraps +from manage import create +from main import create_site, getUser, setSystemAdmin + +login_app = Blueprint('login', __name__) + +def login_required(func): + @wraps(func) + def wrapper(*args, **kwargs): + if 'user' not in session or session['user'] == None: + return redirect(url_for('login.login')) + return func(*args, **kwargs) + return wrapper + + +@login_app.route('/setup', methods=['GET', 'POST']) +def first_time_setup(): + if request.method == "POST": + database_address = request.form['database_address'] + database_port = request.form['database_port'] + database_name = request.form['database_name'] + database_user = request.form['database_user'] + database_password = request.form['database_address'] + + username = request.form['username'] + email = request.form['email'] + password = hashlib.sha256(request.form['password'].encode()).hexdigest() + + site_name = request.form['site_name'] + site_description = request.form['site_description'] + site_default_zone = request.form['site_default_zone'] + site_default_location = request.form['site_default_location'] + + print(email, site_description) + + create(site_name, username, site_default_zone, site_default_location, email=email) + create_site(site_name, (username, password, email), site_default_zone, site_default_location, site_default_location, site_description) + setFirstSetupDone() + user = getUser(username, password) + setSystemAdmin(user_id=user[0]) + + + return redirect("/login") + + return render_template("setup.html") + + + +@login_app.route('/logout', methods=['GET']) +def logout(): + if 'user' in session.keys(): + session['user'] = None + return redirect('/login') + +@login_app.route('/login', methods=['POST', 'GET']) +def login(): + session.clear() + instance_config = sites_config() + print(instance_config["first_setup"]) + + if instance_config['first_setup']: + return redirect('/setup') + + if request.method == "POST": + username = request.form['username'] + password = hashlib.sha256(request.form['password'].encode()).hexdigest() + database_config = config() + with psycopg2.connect(**database_config) as conn: + try: + with conn.cursor() as cur: + sql = f"SELECT * FROM logins WHERE username=%s;" + cur.execute(sql, (username,)) + user = cur.fetchone() + except (Exception, psycopg2.DatabaseError) as error: + print(error) + conn.rollback() + + if user and user[2] == password: + session['user_id'] = user[0] + session['user'] = user + return redirect('/') + + if 'user' not in session.keys(): + session['user'] = None + + return render_template("login.html") + +@login_app.route('/signup', methods=['POST', 'GET']) +def signup(): + + instance_config = sites_config() + print(instance_config["signup_enabled"]) + + if not instance_config['signup_enabled']: + return redirect('/login') + + if request.method == "POST": + username = request.form['username'] + password = hashlib.sha256(request.form['password'].encode()).hexdigest() + database_config = config() + with psycopg2.connect(**database_config) as conn: + try: + with conn.cursor() as cur: + sql = f"INSERT INTO logins(username, password) VALUES(%s, %s);" + cur.execute(sql, (username, password)) + except (Exception, psycopg2.DatabaseError) as error: + print(error) + conn.rollback() + + return redirect("/login") + + return render_template("signup.html") \ No newline at end of file diff --git a/webserver.py b/webserver.py index 3cc1e87..2acee22 100644 --- a/webserver.py +++ b/webserver.py @@ -1,84 +1,134 @@ -from flask import Flask, render_template, session, request -import api, config, external_devices +from flask import Flask, render_template, session, request, redirect +import api, config, external_devices, user_api, psycopg2, main, admin +from user_api import login_required + app = Flask(__name__) app.secret_key = '11gs22h2h1a4h6ah8e413a45' app.register_blueprint(api.database_api) app.register_blueprint(external_devices.external_api) +app.register_blueprint(user_api.login_app) +app.register_blueprint(admin.admin) + + +@app.context_processor +def inject_user(): + if 'user_id' in session.keys() and session['user_id'] is not None: + database_config = config.config() + with psycopg2.connect(**database_config) as conn: + try: + with conn.cursor() as cur: + sql = f"SELECT * FROM logins WHERE id=%s;" + cur.execute(sql, (session['user_id'],)) + user = cur.fetchone() + session['user'] = user + except (Exception, psycopg2.DatabaseError) as error: + print(error) + conn.rollback() + return dict(username="") + + return dict( + user_id=session.get('user')[0], + username=session.get('user')[1], + system_admin=session.get('user')[15] + ) + + return dict(username="") @app.route("/group/") +@login_required def group(id): - sites = config.sites_config() - return render_template("groups/group.html", id=id, current_site=session['selected_site'], sites=sites['sites']) + sites = [site[1] for site in main.get_sites(session['user'][13])] + return render_template("groups/group.html", id=id, current_site=session['selected_site'], sites=sites) @app.route("/transactions/") +@login_required def transactions(id): - sites = config.sites_config() - return render_template("items/transactions.html", id=id, current_site=session['selected_site'], sites=sites['sites']) + sites = [site[1] for site in main.get_sites(session['user'][13])] + return render_template("items/transactions.html", id=id, current_site=session['selected_site'], sites=sites) @app.route("/item/") +@login_required def item(id): - sites = config.sites_config() - return render_template("items/item.html", id=id, current_site=session['selected_site'], sites=sites['sites']) + sites = [site[1] for site in main.get_sites(session['user'][13])] + return render_template("items/item.html", id=id, current_site=session['selected_site'], sites=sites) @app.route("/itemlink/") +@login_required def itemLink(id): - sites = config.sites_config() - return render_template("items/itemlink.html", current_site=session['selected_site'], sites=sites['sites'], proto={'referrer': request.referrer}, id=id) + sites = [site[1] for site in main.get_sites(session['user'][13])] + return render_template("items/itemlink.html", current_site=session['selected_site'], sites=sites, proto={'referrer': request.referrer}, id=id) @app.route("/transaction") +@login_required def transaction(): - print(request.referrer) - sites = config.sites_config() - return render_template("transaction.html", current_site=session['selected_site'], sites=sites['sites'], proto={'referrer': request.referrer}) + sites = [site[1] for site in main.get_sites(session['user'][13])] + return render_template("transaction.html", current_site=session['selected_site'], sites=sites, proto={'referrer': request.referrer}) -@app.route("/workshop") +@app.route("/admin") +@login_required def workshop(): - sites = config.sites_config() - return render_template("workshop.html", current_site=session['selected_site'], sites=sites['sites']) + sites = [site[1] for site in main.get_sites(session['user'][13])] + if not session.get('user')[15]: + return redirect('/logout') + return render_template("admin.html", current_site=session['selected_site'], sites=sites) @app.route("/shopping-list/view/") +@login_required def shopping_lists_view(id): - sites = config.sites_config() - return render_template("shopping-lists/view.html", id=id, current_site=session['selected_site'], sites=sites['sites']) + sites = [site[1] for site in main.get_sites(session['user'][13])] + return render_template("shopping-lists/view.html", id=id, current_site=session['selected_site'], sites=sites) @app.route("/shopping-list/edit/") +@login_required def shopping_lists_edit(id): - sites = config.sites_config() - return render_template("shopping-lists/edit.html", id=id, current_site=session['selected_site'], sites=sites['sites']) + sites = [site[1] for site in main.get_sites(session['user'][13])] + return render_template("shopping-lists/edit.html", id=id, current_site=session['selected_site'], sites=sites) @app.route("/shopping-lists") +@login_required def shopping_lists(): - sites = config.sites_config() - return render_template("shopping-lists/index.html", current_site=session['selected_site'], sites=sites['sites']) + sites = [site[1] for site in main.get_sites(session['user'][13])] + return render_template("shopping-lists/index.html", current_site=session['selected_site'], sites=sites) @app.route("/receipt/") +@login_required def receipt(id): - sites = config.sites_config() - return render_template("receipts/receipt.html", id=id, current_site=session['selected_site'], sites=sites['sites']) + sites = [site[1] for site in main.get_sites(session['user'][13])] + return render_template("receipts/receipt.html", id=id, current_site=session['selected_site'], sites=sites) @app.route("/receipts") +@login_required def receipts(): - sites = config.sites_config() - return render_template("receipts/index.html", current_site=session['selected_site'], sites=sites['sites']) + sites = [site[1] for site in main.get_sites(session['user'][13])] + return render_template("receipts/index.html", current_site=session['selected_site'], sites=sites) @app.route("/groups") +@login_required def groups(): - sites = config.sites_config() - return render_template("groups/index.html", current_site=session['selected_site'], sites=sites['sites']) + sites = [site[1] for site in main.get_sites(session['user'][13])] + return render_template("groups/index.html", + current_site=session['selected_site'], + sites=sites) @app.route("/items") +@login_required def items(): - sites = config.sites_config() - return render_template("items/index.html", current_site=session['selected_site'], sites=sites['sites']) + sites = [site[1] for site in main.get_sites(session['user'][13])] + return render_template("items/index.html", + current_site=session['selected_site'], + sites=sites) @app.route("/") +@login_required def home(): - session['selected_site'] = 'main' - sites = config.sites_config() - return render_template("items/index.html", current_site=session['selected_site'], sites=sites['sites']) + print(session['user'][12]) + sites = [site[1] for site in main.get_sites(session['user'][13])] + session['selected_site'] = sites[0] + return redirect("/items") + return render_template("items/index.html", current_site=session['selected_site'], sites=sites) app.run(host="0.0.0.0", port=5810, debug=True) \ No newline at end of file