This commit is contained in:
Jadowyne Ulve 2025-01-11 12:51:52 -06:00
parent f1cc51f378
commit c18c6cec16
86 changed files with 1793 additions and 514 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

122
admin.py Normal file
View File

@ -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/<id>")
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!")

30
api.py
View File

@ -116,8 +116,11 @@ def paginate_groups():
print(group[3]) print(group[3])
for item_id in group[3]: for item_id in group[3]:
cur.execute(sql_item, (item_id,)) cur.execute(sql_item, (item_id,))
item_row = cur.fetchone() item_row = list(cur.fetchone())
qty += float(item_row[2]) 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) items.append(item_row)
group[3] = items group[3] = items
group.append(qty) group.append(qty)
@ -1060,10 +1063,15 @@ def paginate_lists():
custom_items = shopping_list[4] custom_items = shopping_list[4]
list_length = len(custom_items) 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': 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];" print(shopping_list[0])
cur.execute(item_sql, (shopping_list[0], )) cur.execute(sql, (shopping_list[0], ))
list_length += cur.fetchone()[0] list_length += cur.fetchone()[0]
else: else:
list_length += len(pantry_items) list_length += len(pantry_items)
@ -1089,13 +1097,17 @@ def get_list_view():
shopping_list = list(cur.fetchone()) shopping_list = list(cur.fetchone())
if shopping_list[10] == "calculated": 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: 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];" sqlfile = open(f"sites/{site_name}/sql/unique/shopping_lists_safetystock_uncalculated.sql", "r+")
sql = "\n".join(sqlfile.readlines())
cur.execute(itemSQL, (id, )) sqlfile.close()
cur.execute(sql, (id, ))
shopping_list[3] = list(cur.fetchall()) shopping_list[3] = list(cur.fetchall())
print(shopping_list) print(shopping_list[4])
except (Exception, psycopg2.DatabaseError) as error: except (Exception, psycopg2.DatabaseError) as error:
print(error) print(error)

View File

@ -27,20 +27,33 @@ def sites_config(filename='database.ini', section='manage'):
parser.read(filename) parser.read(filename)
# get section, default to postgresql # get section, default to postgresql
sites = {} instance_config = {}
first_setup = False
if parser.has_section(section): if parser.has_section(section):
params = parser.items(section) params = parser.items(section)
print(params)
for param in params: for param in params:
sites[param[0]] = param[1].split(',') instance_config[param[0]] = param[1].split(',')
else: else:
raise Exception('Section {0} not found in the {1} file'.format(section, filename)) 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): 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) print(old_value)
old_value.append(site_name) old_value.append(site_name)

View File

@ -6,5 +6,7 @@ password = test
port = 5432 port = 5432
[manage] [manage]
sites = ,test,main,Backpack sites = Backpack,main
first_setup = False
signup_enabled = False

56
html_factory.py Normal file
View File

@ -0,0 +1,56 @@
import math
def manufactureUsersTable(rows):
table = """<table>
<thead>
<tr>
<th>Username</th>
</tr>
</thead>
<tbody>
%%rows%%
</tbody>
</table>
"""
string_rows = []
for row in rows:
string_row = f"""<tr>
<td>{row[1]}</td>
</tr>"""
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 += '<ul class="pagination">'
if current_page > 1:
pag += f'<li class="waves-effect my_btn"><a hx-post="/admin/users/{current_page - 1}" hx-target="#main_body" {limits}><i class="material-icons">chevron_left</i></a></li>'
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'<li class="active"><a hx-post="/admin/users/{_element}" hx-target="#main_body" {limits}>{_element}</a></li>'
else:
pag += f'<li class="my_btn waves-effect"><a hx-post="/admin/users/{_element}" hx-target="#main_body" {limits}>{_element}</a></li>'
if current_page != total_pages:
pag += f'<li class="waves-effect my_btn"><a hx-post="/admin/users/{current_page + 1}" hx-target="#main_body" {limits}><i class="material-icons">chevron_right</i></a></li>'
pag += "</ul>"
return pag

231
main.py
View File

@ -189,7 +189,6 @@ def setLogisticsDataTransaction(conn, site_name, location, logistics_info_id, qt
return error return error
return "success" return "success"
def handleNegativeQuantityOnHand(qty, cost_layers): def handleNegativeQuantityOnHand(qty, cost_layers):
cost_layers = [ast.literal_eval(item) for item in ast.literal_eval(cost_layers.replace('{', '[').replace('}', ']'))] cost_layers = [ast.literal_eval(item) for item in ast.literal_eval(cost_layers.replace('{', '[').replace('}', ']'))]
dummy_quantity = qty 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/shopping_lists.sql')
drop_table(f'sites/{site_name}/sql/drop/item_locations.sql') drop_table(f'sites/{site_name}/sql/drop/item_locations.sql')
def create_site(site_name): def create_site(site_name, admin_user: tuple, default_zone, default_primary, default_auto, description):
site_config = config(f"sites/{site_name}/site.ini", 'defaults')
create_table(f'sites/{site_name}/sql/create/logins.sql') 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/groups.sql')
create_table(f'sites/{site_name}/sql/create/linked_items.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/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/shopping_lists.sql')
create_table(f'sites/{site_name}/sql/create/item_locations.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;" 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);" 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);" sqlthree = f"INSERT INTO {site_name}_vendors(vendor_name, creation_date, created_by) VALUES (%s, %s, %s);"
database_config = config() database_config = config()
with psycopg2.connect(**database_config) as conn: 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 zone_id = None
try: try:
with conn.cursor() as cur: with conn.cursor() as cur:
cur.execute(sql, (site_config["default_zone"], )) cur.execute(sql, (default_zone, ))
rows = cur.fetchone() rows = cur.fetchone()
if rows: if rows:
zone_id = rows[0] zone_id = rows[0]
@ -455,11 +508,11 @@ def create_site(site_name):
conn.rollback() conn.rollback()
return False return False
uuid = f"{site_config["default_zone"]}@{site_config["default_primary_location"]}" uuid = f"{default_zone}@{default_primary}"
try: try:
with conn.cursor() as cur: 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: except (Exception, psycopg2.DatabaseError) as error:
print(error) print(error)
conn.rollback() conn.rollback()
@ -473,9 +526,171 @@ def create_site(site_name):
conn.rollback() conn.rollback()
return False 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() 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 = { transaction_payload = {
"timestamp": None, "timestamp": None,
"logistics_info_id": 0, "logistics_info_id": 0,

View File

@ -55,19 +55,7 @@ def rename_create_sql(site_name):
with open(f"sites/{site_name}/sql/create/{file_name}", "w") as file: with open(f"sites/{site_name}/sql/create/{file_name}", "w") as file:
file.write(words) file.write(words)
def create(): def create(site_name, owner_name, default_zone_name, default_location_name, email=""):
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"
if not os.path.exists(f"sites/{site_name}"): if not os.path.exists(f"sites/{site_name}"):
print(f"Creating {site_name} site...") print(f"Creating {site_name} site...")
@ -82,7 +70,7 @@ def create():
with open(f"sites/{site_name}/site.ini", "w+") as config: with open(f"sites/{site_name}/site.ini", "w+") as config:
config.write(f"[site]\n") config.write(f"[site]\n")
config.write(f"site_name={site_name}\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"email={email}\n")
config.write(f"\n") config.write(f"\n")

View File

@ -1,38 +1,25 @@
sql = "SELECT items FROM main_locations WHERE id=1;"
from config import config from config import config
import psycopg2, requests import psycopg2, requests
import main, datetime import main, datetime, json
"""database_config = config()
database_config = config()
with psycopg2.connect(**database_config) as conn: 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" # update user with site_id and admin role.
"""payload_receipt = { sqlfile = open('sites/main/sql/unique/shopping_lists_safetystock_uncalculated.sql', "r+")
"receipt_id": 123456, sql = sqlfile.readlines()
"receipt_status": "Unresolved", sql = "\n".join(sql)
"date_submitted": str(datetime.datetime.now()), sqlfile.close()
"submitted_by": 1, try:
"vendor_id": 0, with conn.cursor() as cur:
"files": {}, cur.execute(sql, (1, ))
"items": [ x = cur.fetchall()
("FOOD", 0, "%1234%", "test_item", 1.0, {"cost": 1.99, "EXPIRES": False}, "Unresolved"), for _ in x:
("FOOD", 0, "%1235%", "test_item", 1.0, {"cost": 1.99, "EXPIRES": False}, "Unresolved"), print(_)
("FOOD", 0, "%1236%", "test_item", 1.0, {"cost": 1.99, "EXPIRES": False}, "Unresolved"),
],
"site_name": "main"
}"""
except (Exception, psycopg2.DatabaseError) as error:
print(error)
conn.rollback()
response = requests.post(url)
receipt_id = response.json()["receipt_id"]
print(receipt_id)

View File

@ -1,9 +1,9 @@
[site] [site]
site_name=Backpack site_name=Backpack
site_owner=Jadowyne site_owner=jadowyne
email= email=
[defaults] [defaults]
default_zone=default default_zone=MAIN
default_primary_location=all default_primary_location=POUCH A
default_auto_issue_location=all default_auto_issue_location=POUCH A

View File

@ -2,15 +2,21 @@ CREATE TABLE IF NOT EXISTS logins(
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
username VARCHAR(255), username VARCHAR(255),
password VARCHAR(255), password VARCHAR(255),
favorites JSONB, email VARCHAR(255) UNIQUE NOT NULL,
unseen_pantry_items INTEGER [], favorites JSONB DEFAULT '{}',
unseen_groups INTEGER [], unseen_pantry_items INTEGER [] DEFAULT '{}',
unseen_shopping_lists INTEGER [], unseen_groups INTEGER [] DEFAULT '{}',
unseen_recipes INTEGER [], unseen_shopping_lists INTEGER [] DEFAULT '{}',
seen_pantry_items INTEGER [], unseen_recipes INTEGER [] DEFAULT '{}',
seen_groups INTEGER[], seen_pantry_items INTEGER [] DEFAULT '{}',
seen_shopping_lists INTEGER [], seen_groups INTEGER[] DEFAULT '{}',
seen_recipes INTEGER [], seen_shopping_lists INTEGER [] DEFAULT '{}',
flags JSONB 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,}$')
); );

View File

@ -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)
);

View File

@ -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)
);

View File

@ -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
*/

View File

@ -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];

View File

@ -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];

View File

@ -2,15 +2,21 @@ CREATE TABLE IF NOT EXISTS logins(
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
username VARCHAR(255), username VARCHAR(255),
password VARCHAR(255), password VARCHAR(255),
favorites JSONB, email VARCHAR(255) UNIQUE NOT NULL,
unseen_pantry_items INTEGER [], favorites JSONB DEFAULT '{}',
unseen_groups INTEGER [], unseen_pantry_items INTEGER [] DEFAULT '{}',
unseen_shopping_lists INTEGER [], unseen_groups INTEGER [] DEFAULT '{}',
unseen_recipes INTEGER [], unseen_shopping_lists INTEGER [] DEFAULT '{}',
seen_pantry_items INTEGER [], unseen_recipes INTEGER [] DEFAULT '{}',
seen_groups INTEGER[], seen_pantry_items INTEGER [] DEFAULT '{}',
seen_shopping_lists INTEGER [], seen_groups INTEGER[] DEFAULT '{}',
seen_recipes INTEGER [], seen_shopping_lists INTEGER [] DEFAULT '{}',
flags JSONB 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,}$')
); );

View File

@ -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)
);

View File

@ -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)
);

View File

@ -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
*/

View File

@ -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];

View File

@ -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];

View File

@ -1,9 +1,9 @@
[site] [site]
site_name=main site_name=main
site_owner= site_owner=jadowyne
email= email=jadowyne.ulve@outlook.com
[defaults] [defaults]
default_zone=default default_zone=DEFAULT
default_primary_location=all default_primary_location=ALL
default_auto_issue_location=all default_auto_issue_location=ALL

View File

@ -2,15 +2,21 @@ CREATE TABLE IF NOT EXISTS logins(
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
username VARCHAR(255), username VARCHAR(255),
password VARCHAR(255), password VARCHAR(255),
favorites JSONB, email VARCHAR(255) UNIQUE NOT NULL,
unseen_pantry_items INTEGER [], favorites JSONB DEFAULT '{}',
unseen_groups INTEGER [], unseen_pantry_items INTEGER [] DEFAULT '{}',
unseen_shopping_lists INTEGER [], unseen_groups INTEGER [] DEFAULT '{}',
unseen_recipes INTEGER [], unseen_shopping_lists INTEGER [] DEFAULT '{}',
seen_pantry_items INTEGER [], unseen_recipes INTEGER [] DEFAULT '{}',
seen_groups INTEGER[], seen_pantry_items INTEGER [] DEFAULT '{}',
seen_shopping_lists INTEGER [], seen_groups INTEGER[] DEFAULT '{}',
seen_recipes INTEGER [], seen_shopping_lists INTEGER [] DEFAULT '{}',
flags JSONB 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,}$')
); );

View File

@ -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)
);

View File

@ -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)
);

View File

@ -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];

View File

@ -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];

View File

@ -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];

View File

@ -1,9 +0,0 @@
[site]
site_name=test
site_owner=
email=
[defaults]
default_zone=default
default_primary_location=all
default_auto_issue_location=all

View File

@ -1,4 +0,0 @@
CREATE TABLE IF NOT EXISTS test_brands (
id SERIAL PRIMARY KEY,
name VARCHAR(255)
);

View File

@ -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
);

View File

@ -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)
);

View File

@ -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
);

View File

@ -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)
);

View File

@ -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
);

View File

@ -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)
);

View File

@ -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)
);

View File

@ -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
);

View File

@ -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)
);

View File

@ -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)
);

View File

@ -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)
);

View File

@ -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
);

View File

@ -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)
);

View File

@ -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)
);

View File

@ -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)
);

View File

@ -1,5 +0,0 @@
CREATE TABLE IF NOT EXISTS test_zones(
id SERIAL PRIMARY KEY,
name VARCHAR(32) NOT NULL,
UNIQUE(name)
);

View File

@ -1 +0,0 @@
DROP TABLE test_brands CASCADE;

View File

@ -1 +0,0 @@
DROP TABLE test_food_info CASCADE;

View File

@ -1 +0,0 @@
DROP TABLE test_groups CASCADE;

View File

@ -1 +0,0 @@
DROP TABLE test_item_info CASCADE;

View File

@ -1 +0,0 @@
DROP TABLE test_item_locations CASCADE;

View File

@ -1 +0,0 @@
DROP TABLE test_items CASCADE;

View File

@ -1 +0,0 @@
DROP TABLE test_itemlinks CASCADE;

View File

@ -1 +0,0 @@
DROP TABLE test_locations CASCADE;

View File

@ -1 +0,0 @@
DROP TABLE test_logistics_info CASCADE;

View File

@ -1 +0,0 @@
DROP TABLE test_receipt_items CASCADE;

View File

@ -1 +0,0 @@
DROP TABLE test_receipts CASCADE;

View File

@ -1 +0,0 @@
DROP TABLE test_recipes CASCADE;

View File

@ -1 +0,0 @@
DROP TABLE test_shopping_lists CASCADE;

View File

@ -1 +0,0 @@
DROP TABLE test_transactions CASCADE;

View File

@ -1 +0,0 @@
DROP TABLE test_vendors CASCADE;

View File

@ -1 +0,0 @@
DROP TABLE test_zones CASCADE;

View File

@ -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);

View File

@ -1,3 +0,0 @@
UPDATE test_logistics_info
SET quantity_on_hand = %s, location_data = %s
WHERE id = %s;

View File

@ -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
*/

View File

@ -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
*/

View File

@ -1,3 +0,0 @@
UPDATE test_locations
SET items = %s
WHERE id = %s;

28
static/adminHandler.js Normal file
View File

@ -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;
}

BIN
static/pictures/logo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

449
templates/admin.html Normal file
View File

@ -0,0 +1,449 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title>Admin</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/css/materialize.min.css" />
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" />
<script src="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/js/materialize.min.js"></script>
<script src="https://unpkg.com/htmx.org@2.0.4" integrity="sha384-HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+" crossorigin="anonymous"></script>
</head>
<style>
header, main, footer, body {
padding-left: 300px;
}
@media only screen and (max-width : 992px) {
header, main, footer, body {
padding-left: 0;
}
}
.dropdown-disabled {
pointer-events: none;
}
.item :hover{
cursor: pointer;
background-color: whitesmoke;
}
.custom_row:hover{
background-color: rgb(230, 230, 230) !important;
cursor: pointer;
}
.my_btn:hover{
background-color: rgb(230, 230, 230) !important;
cursor: pointer;
}
</style>
<ul id="slide-out" class="sidenav sidenav-fixed z-depth-0" style="width: 250px; border-right: 2px;">
<div class="center-align" style="padding-top: 10px; padding-bottom: 10px;">
<img src="{{ url_for('static', filename='pictures/logo.jpg') }}" alt="Description" class="responsive-img circle center-align" style="width: 30%; height: auto;">
</div>
<li><a onclick="openSection('sites')">Sites</a></li>
<li><a onclick="openSection('roles')">Roles</a></li>
<li><a onclick="openSection('users')">Users</a></li>
<li><a href="#!">Instance Settings</a></li>
</ul>
<body>
<div class="container">
<div class="section">
<div class="row">
<div class="col s12">
<button class="btn btn-flat sidenav-trigger hide-on-large-only" data-target="slide-out"><i class="material-symbols-outlined">side_navigation</i></button>
<a href="/items" class="btn btn-flat right">home</a>
<a href="/profile" class="btn btn-flat right">Profile</a>
</div>
<div class="col s12" id="main_body">
<div id="sites" class="row hide">
<div class="col s12">
<h1>Your Sites</h1>
</div>
<div class="col s12">
<p class="flow-text">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.</p>
</div>
<div class="col s12" id="sites_div">
<table id="sites_table">
<tr>
<th>Site</th>
<th>Description</th>
</tr>
</table>
</div>
<div class="col s12 center-align" style="padding-top: 10px;">
<span class="center-align"><button data-target="add_site"class="btn btn-flat center-align modal-trigger" style="width: 100%; border-radius: 10px;"><i class="large material-symbols-outlined" style="font-size: 2rem;">add_circle</i></button></span>
</div>
</div>
<div id="roles" class="row hide">
<div class="col s12">
<h1>Your Roles</h1>
</div>
<div class="col s12">
<p class="flow-text">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.</p>
</div>
<div class="col s12" id="roles_div">
<table id="roles_table">
<tr>
<th>Site</th>
<th>Role</th>
<th>Role Description</th>
</tr>
</table>
</div>
<div class="col s12 center-align" style="padding-top: 10px;">
<span class="center-align"><button data-target="add_role"class="btn btn-flat center-align modal-trigger" style="width: 100%; border-radius: 10px;"><i class="large material-symbols-outlined" style="font-size: 2rem;">add_circle</i></button></span>
</div>
</div>
<div id="users" class="row hide">
<div class="col s12">
<h1>Your Users</h1>
</div>
<div class="col s12">
<p class="flow-text">Listed below is all the users that have access to your instance.</p>
</div>
<div class="col s12" id="users_div">
<table id="users_table">
<tr>
<th>Username</th>
<th>Email</th>
</tr>
</table>
</div>
<div class="col s12 center-align" style="padding-top: 10px;">
<span class="center-align"><button data-target="add_role"class="btn btn-flat center-align modal-trigger" style="width: 100%; border-radius: 10px;"><i class="large material-symbols-outlined" style="font-size: 2rem;">add_circle</i></button></span>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="add_site" class="modal">
<div class="modal-content">
<h4>Create Site</h4>
<div class="card-panel green lighten-4 z-depth-0">
<span class="black-text">A site is a main component to your instance. Each site is a boudry property meaning that all parts
made within the site is not accessible within other sites beyond cross-site features. Think of them like <b>House A</b>,
<b>Garage</b>, or <b>Warehouse</b>.
</span>
</div>
<div class="row" style="gap: 10px;">
<div class="s12 m6 input-field">
<input id="site_name" type="text" placeholder="main" maxlength="20">
<label for="site_name">Site Name</label>
<span class="supporting-text">Supporting Text</span>
</div>
<div class="s12 m6 input-field">
<i class="material-icons prefix">account_circle</i>
<input id="site_owner" type="text" placeholder=" " value="{{username}}" disabled>
<label for="site_owner">Site Ownser</label>
</div>
<div class="input-field col s12">
<textarea id="site_description" class="materialize-textarea" placeholder=" "></textarea>
<label for="site_description">Site Description</label>
</div>
<div class="s12 m6 input-field">
<input id="default_zone" type="text" placeholder="DEFAULT" value="DEFAULT" maxlength="20">
<label for="default_zone">Default Zone</label>
</div>
<div class="s12 m6 input-field">
<input id="default_location" type="text" placeholder="ALL" value="ALL" maxlength="20">
<label for="default_location">Default Location</label>
</div>
</div>
</div>
<div class="modal-footer">
<button onclick="addSite()" class="modal-close waves-effect btn-flat green lighten-4">Submit</button>
</div>
</div>
<div id="add_role" class="modal">
<div class="modal-content">
<h4>Create Role</h4>
<div class="card-panel green lighten-4 z-depth-0">
<span class="black-text">A Site Role is used to define and assign general permissions to users for that specific site. This could be important
as a user's permission to access certain sites is determined by their roles.
</span>
</div>
<div class="row" style="gap: 10px;">
<div class="s12 m6 input-field">
<input id="role_name" type="text" placeholder="main" maxlength="20">
<label for="role_name">Role Name</label>
<span class="supporting-text">Supporting Text</span>
</div>
<div class="s12 m6 input-field">
<select id="selected_site">
<option value="" disabled selected>Choose your option</option>
<option value="1">Option 1</option>
<option value="2">Option 2</option>
<option value="3">Option 3</option>
</select>
<label for="selected_site">Role's Site</label>
</div>
<div class="input-field col s12">
<textarea id="role_description" class="materialize-textarea" placeholder=" "></textarea>
<label for="role_description">Role Description</label>
</div>
</div>
</div>
<div class="modal-footer">
<button onclick="addRole()" class="modal-close waves-effect btn-flat green lighten-4">Submit</button>
</div>
</div>
<div id="edit_role" class="modal">
<div class="modal-content">
<h4>Edit Role</h4>
<div class="card-panel green lighten-4 z-depth-0">
<span class="black-text">A Site Role is used to define and assign general permissions to users for that specific site. This could be important
as a user's permission to access certain sites is determined by their roles.
</span>
</div>
<div class="row" style="gap: 10px;">
</div>
</div>
<div class="modal-footer">
<button onclick="" class="modal-close waves-effect btn-flat red lighten-4">Delete</button>
<button onclick="" class="modal-close waves-effect btn-flat green lighten-4">Update</button>
</div>
</div>
</body>
<script src="{{ url_for('static', filename='adminHandler.js') }}"></script>
<script>
document.addEventListener('DOMContentLoaded', async function() {
var elems = document.querySelectorAll('.sidenav');
var instances = M.Sidenav.init(elems);
var elems = document.querySelectorAll('.dropdown-trigger');
var instances = M.Dropdown.init(elems, {});
var elems = document.querySelectorAll('.collapsible');
var instances = M.Collapsible.init(elems, {});
var elems = document.querySelectorAll('.modal');
var instances = M.Modal.init(elems, {});
var elems = document.querySelectorAll('select');
var instances = M.FormSelect.init(elems, {})
M.AutoInit();
await openSection('sites')
});
async function openSection(section){
let sections = ['sites', 'roles', 'users']
for (i=0; i < sections.length; i++){
document.getElementById(sections[i]).classList.add("hide");
}
document.getElementById(section).classList.remove("hide");
if (section == "sites"){
var sites = await fetchSites()
await populateSites(sites)
} else if (section == "roles") {
await fetchPopulateRoles()
} else if (section == "users"){
var users = await fetchUsers(50, 1)
await populateUsers(users)
}
}
async function fetchPopulateSites(){
const url = new URL('/admin/getSites', window.location.origin);
await fetch(url)
.then(response => response.json())
.then(data => {
const collection = document.getElementById('sites_collection')
collection.innerHTML = ""
data.sites.forEach(site => {
console.log(site)
let list_item = document.createElement('li')
list_item.classList.add('collection-item')
list_item.classList.add('avatar')
list_item.onclick = function(){
selectSite(site[0])
};
list_item.id = site[0]
list_item.style = "border-radius: 10px;"
list_item.innerHTML = `
<i class="material-icons circle green">insert_chart</i>
<span class="title" style="font-size:16pt;">${site[1]}</span>
<p>${site[3]}<br>${site[2]}</p>`
collection.append(list_item)
})
})
}
async function populateSites(sites){
const table = document.getElementById("sites_table")
while (table.rows.length > 1) {
table.deleteRow(1);
};
let reference_state = 1
for(let i = 0; i < sites.length; i++){
var row = table.insertRow();
var row_name = row.insertCell();
var row_desc = row.insertCell();
row_name.style = "display: inline-flex; align-items: center;"
row_name.innerHTML = `<i class="material-symbols-outlined" style="padding-right: 5px;">wysiwyg</i>${sites[i][1]}`
row_desc.innerHTML = `${sites[i][2]}`
if ((reference_state % 2) == 0){
row.classList.add('green')
row.classList.add('lighten-5')
}
row.classList.add("custom_row")
row.addEventListener('click', function(){
clickRoleRow(sites[i][0])
})
reference_state++
}
}
async function populateUsers(users){
const table = document.getElementById("users_table")
while (table.rows.length > 1) {
table.deleteRow(1);
};
let reference_state = 1
for(let i = 0; i < users.length; i++){
var row = table.insertRow();
var row_username = row.insertCell();
var row_email = row.insertCell();
row_username.style = "display: inline-flex; align-items: center;"
row_username.innerHTML = `<i class="material-symbols-outlined" style="padding-right: 5px;">person</i>${users[i][1]}`
row_email.innerHTML = `${users[i][3]}`
if ((reference_state % 2) == 0){
row.classList.add('green')
row.classList.add('lighten-5')
}
row.classList.add("custom_row")
row.addEventListener('click', function(){
clickRoleRow(users[i][0])
})
reference_state++
}
}
async function fetchPopulateRoles(){
const Rolesurl = new URL('/getRoles', window.location.origin);
await fetch(Rolesurl)
.then(response => response.json())
.then(data => {
console.log(data.sites)
const table = document.getElementById("roles_table")
while (table.rows.length > 1) {
table.deleteRow(1);
};
let reference_state = 1
for (let key in data.sites){
for (let i = 0; i < data.sites[key].length; i++){
var row = table.insertRow();
var row_site = row.insertCell();
var row_name = row.insertCell();
var row_description = row.insertCell();
row_site.style = "display: inline-flex; align-items: center;"
row_site.innerHTML = `<i class="material-symbols-outlined" style="padding-right: 5px;">group</i>${key}`
row_name.innerHTML = `${data.sites[key][i][1]}`
row_description.innerHTML = `${data.sites[key][i][2]}`
if ((reference_state % 2) == 0){
row.classList.add('green')
row.classList.add('lighten-5')
}
row.classList.add("custom_row")
row.addEventListener('click', function(){
clickRoleRow(data.sites[key][i][0])
})
reference_state++
}
}
})
var selectElement = document.getElementById('selected_site');
selectElement.innerHTML = '';
const SitesURL = new URL('/admin/getSites', window.location.origin);
await fetch(SitesURL)
.then(response => response.json())
.then(data => {
console.log(data)
data.sites.forEach(site => {
var newOption = document.createElement('option')
newOption.value = site[0];
newOption.text = site[1];
selectElement.appendChild(newOption);
})
})
M.FormSelect.init(selectElement);
}
function selectSite(id){
console.log(id)
}
async function addSite(){
var site_name = document.getElementById('site_name').value
var site_description = document.getElementById('site_description').value
var default_zone = document.getElementById('default_zone').value
var default_location = document.getElementById('default_location').value
await fetch(`/addSite`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
site_name: site_name,
site_description: site_description,
default_zone: default_zone,
default_location: default_location,
}),
});
// var sites = await fetchSites()
// await populateSites(sites)
location.reload()
M.toast({text: "Site has been added Successfully!", classes: "rounded green lighten-4 black-text"});
}
async function addRole(){
var role_name = document.getElementById('role_name').value
var role_description = document.getElementById('role_description').value
var selected_site_id = Number(document.getElementById('selected_site').value)
await fetch(`/addRole`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
role_name: role_name,
role_description: role_description,
site_id: selected_site_id
}),
});
await fetchPopulateRoles()
M.toast({text: "Role has been added Successfully!", classes: "rounded green lighten-4 black-text"});
}
</script>
</html>

131
templates/admin/role.html Normal file
View File

@ -0,0 +1,131 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title>Edit Role</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/css/materialize.min.css" />
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" />
<script src="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/js/materialize.min.js"></script>
</head>
<style>
.dropdown-disabled {
pointer-events: none;
}
.item :hover{
cursor: pointer;
background-color: whitesmoke;
}
.custom_row:hover{
background-color: rgb(230, 230, 230) !important;
cursor: pointer;
}
</style>
<body>
<div class="container">
<div class="section">
<div class="row" style="gap: 1em;">
<div class="col s12" style="padding-bottom: 10px;">
<a href='{{ proto['referrer'] }}' class="left btn green lighten-4 black-text btn-flat"><i class="material-icons">arrow_back</i></a>
<a href="/items" class="btn btn-flat right">Home</a>
<a href="/profile" class="btn btn-flat right">Profile</a>
</div>
<div class="s12 m6 input-field">
<input id="role_name" type="text" placeholder=" " maxlength="20">
<label for="role_name">Name</label>
</div>
<div class="input-field col s12">
<select id="role_site">
<option value="" disabled selected>Choose your option</option>
</select>
<label for="role_site">Site</label>
</div>
<div class="input-field col s12">
<textarea id="role_description" class="materialize-textarea" placeholder=" "></textarea>
<label for="role_description">Description</label>
</div>
<div class="col s12">
<h4>Permissions</h4>
</div>
<div class="col s12">
<h5>All</h5>
</div>
<div class="col s12 m6">
<p>
<label>
<input type="checkbox" />
<span>Edit</span>
</label>
</p>
<p>
<label>
<input type="checkbox" />
<span>Delete</span>
</label>
</p>
</div>
<div class="col s12 m6">
<p>
<label>
<input type="checkbox" />
<span>Create</span>
</label>
</p>
<p>
<label>
<input type="checkbox" />
<span>View</span>
</label>
</p>
</div>
<div class="col s12">
<button class="btn btn-flat green lighten-4 right" onclick="updateRole()">Update</button>
<button class="btn btn-flat red lighten-4 right" style="margin-right: 10px;" onclick="deleteRole()">Delete</button>
</div>
</div>
</div>
</div>
</body>
<script src="{{ url_for('static', filename='adminHandler.js') }}"></script>
<script>
let role = {{role|tojson}}
console.log(role)
document.addEventListener('DOMContentLoaded', async function() {
var elems = document.querySelectorAll('select');
var instances = M.FormSelect.init(elems, {})
await populateRoleForm()
});
async function populateRoleForm() {
document.getElementById("role_name").value = role[1];
document.getElementById("role_description").value = role[2];
const selectElement = document.getElementById("role_site");
selectElement.innerHTML = "";
var sites = await fetchSites()
for (let i = 0; i < sites.length; i++){
let newOption = document.createElement("option");
newOption.value = sites[i][0];
newOption.text = sites[i][1];
selectElement.appendChild(newOption);
};
selectElement.value = role[3];
M.FormSelect.init(selectElement);
}
async function deleteRole(){
const url = new URL('/deleteRole', window.location.origin);
await fetch(url, {
method: 'POST',
headers: {'Content-Type': 'application/json',},
body: JSON.stringify({role_id: role[0],}),
});
const adminurl = new URL(`/admin`, window.location.origin);
window.location.href = adminurl.toString();
}
</script>
</html>

View File

@ -0,0 +1,17 @@
<div class="row" id="sites">
<div class="col s12">
<h1>Your Users</h1>
</div>
<div class="col s12">
<p class="flow-text">This is where all the users who have access to this instance!</p>
</div>
<div class="col s12">
%%userstable%%
</div>
<div class="col s12 center-align">
<span class="center-align"><button data-target="add_site"class="btn btn-flat center-align modal-trigger" style="width: 100%; border-radius: 10px;"><i class="large material-symbols-outlined" style="font-size: 2rem;">add_circle</i></button></span>
</div>
<div class="col s12">
%%pagination%%
</div>
</div>

View File

@ -19,45 +19,42 @@
cursor: pointer; cursor: pointer;
} }
[type="radio"]:checked + span:after { [type="radio"]:checked + span:after {
border: 2px solid rgb(0 128 0 / 30%); /* Outline color */ border: 2px solid rgb(0 128 0 / 30%);
background-color: rgb(0 128 0 / 30%); /* Fill color */ background-color: rgb(0 128 0 / 30%);
} }
header, main, footer, body {
padding-left: 300px;
}
@media only screen and (max-width : 992px) { .dropdown-disabled {
header, main, footer, body { pointer-events: none;
padding-left: 0; opacity: 0.5;
} }
}
.dropdown-disabled {
pointer-events: none;
opacity: 0.5; /* or your desired degree of transparency */
}
</style> </style>
<div class="navbar-fixed">
<nav class="green lighten-4 text-black z-depth-0">
<div class="nav-wrapper">
<ul id="nav-mobile" class="left hide-on-med-and-down black-text">
<li><a class="dropdown-trigger black-text" data-target="dropdown1">Current Site > {{current_site}}<i class="material-icons right">arrow_drop_down</i></a></li>
<li class="active"><a href="/items" class="black-text">Site Items</a></li>
<li><a href="/groups" class="black-text">Site Groups</a></li>
<li><a href="/shopping-lists" class="black-text">Site Shopping Lists</a></li>
<li><a href="/receipts" class="black-text">Site Receipts</a></li>
</ul>
<ul class="right">
<li><a class="dropdown-trigger hide-on-med-and-down black-text" data-target="username_dropdown">{{username}}<i class="material-icons right">arrow_drop_down</i></a></li>
</ul>
</div>
</nav>
</div>
<ul id='dropdown1' class='dropdown-content'> <ul id='dropdown1' class='dropdown-content'>
{% for site in sites %} {% for site in sites %}
<li><button class="btn transparent black-text z-depth-0" onclick="changeSite('{{ site }}')">{{site}}</button></li> <li><button class="btn transparent black-text z-depth-0" onclick="changeSite('{{ site }}')">{{site}}</button></li>
{% endfor %} {% endfor %}
</ul> </ul>
<ul id="slide-out" class="sidenav sidenav-fixed green lighten-4" style="width: 250px;"> <ul id='username_dropdown' class='dropdown-content'>
<li> <li><a href="/profile" class="hide-on-med-and-down black-text">Profile</a></li>
<div class="user-view"> {% if system_admin == True %}
<!-- <div class="background"> <li><a href="/admin" class="hide-on-med-and-down black-text">Administration</a></li>
<img src="images/office.jpg"> {% endif %}
</div> --> <li><a href="/logout" class="hide-on-med-and-down black-text">Logout</a></li>
<!-- <a href="#user"><img class="circle" src="images/yuna.jpg"></a> -->
<a href="#name"><span class="black-text name">John Doe</span></a>
<a href="#email"><span class="black-text email">jdoe@example.com</span></a>
</div>
</li>
<li><a class="dropdown-trigger" data-target="dropdown1">Current Site > {{current_site}}<i class="material-icons right">arrow_drop_down</i></a></li>
<li><div class="divider grey darken-1" style="margin-left: 5px; margin-right: 10px;"></div></li>
<li class="active"><a href="/items">Site Items</a></li>
<li><a href="/groups">Site Groups</a></li>
<li><a href="/shopping-lists">Site Shopping Lists</a></li>
<li><a href="/receipts">Site Receipts</a></li>
</ul> </ul>
<body> <body>
<div class="container section" style="padding-bottom: 72px;"> <div class="container section" style="padding-bottom: 72px;">

37
templates/login.html Normal file
View File

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title>My Pantry</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/css/materialize.min.css" />
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/js/materialize.min.js"></script>
</head>
<body>
<div class="valign-wrapper" style="height: 90vh;">
<div class="container center-align">
<div class="section">
<div class="row" style="gap: 1em; margin-bottom: 10px;">
<div class="s12 m6 offset-m3">
<img src="{{ url_for('static', filename='pictures/logo.jpg') }}" alt="Description" class="responsive-img circle" style="width: 30%; height: auto;">
</div>
</div>
<form class="row" style="gap: 1em;" action="/login" method="post" enctype="multipart/form-data">
<div class="s12 m6 input-field offset-m3" style="margin: 10px;">
<input id="username" type="text" name="username" class="validate" placeholder=" ">
<label for="username">Username</label>
</div>
<div class="s12 m6 input-field offset-m3" style="margin: 10px;">
<input id="password" type="password" name="password" class="validate" placeholder=" " maxlength="20">
<label for="password">Password</label>
</div>
<div class="s12 m6 offset-m3" style="margin: 10px;">
<button class="btn icon-right waves-effect waves-light right" type="submit" name="action">
Login
</button>
</div>
</form>
</div>
</div>
</div>
</body>

91
templates/setup.html Normal file
View File

@ -0,0 +1,91 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title>My Pantry - Setup</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/css/materialize.min.css" />
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/js/materialize.min.js"></script>
</head>
<body>
<div class="container section">
<div class="s12 row">
<form class="s12 row" style="gap: 1em;" action="/setup" method="post" enctype="multipart/form-data">
<div class="s12">
<h5>Database Setup</h5>
<p> Setup your Postgresql database settings here! </p>
</div>
<div class="s12 input-field">
<input id="database_address" name="database_address" type="text" placeholder=" ">
<label for="database_address">Database Host</label>
</div>
<div class="s12 input-field ">
<input id="database_port" name="database_port" type="text" placeholder=" ">
<label for="database_port">Database Port</label>
</div>
<div class="s12 input-field">
<input id="database_name" name="database_name" type="text" placeholder=" ">
<label for="database_name">Database Name</label>
</div>
<div class="s12 input-field">
<input id="database_user" name="database_user" type="text" placeholder=" ">
<label for="database_user">Database User</label>
</div>
<div class="s12 input-field">
<input id="database_password" type="password" name="database_password" placeholder=" ">
<label for="database_password">Database Password</label>
</div>
<div class="s12">
<h5>Admin Account</h5>
<p> 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 </p>
</div>
<div class="s12 input-field">
<input id="email" name="email" type="email" placeholder=" ">
<label for="email">Email</label>
</div>
<div class="s12 input-field">
<input id="username" name="username" type="text" placeholder=" ">
<label for="username">Username</label>
</div>
<div class="s12 input-field">
<input id="password" type="password" name="password" placeholder=" ">
<label for="password">Password</label>
</div>
<div class="s12 input-field">
<input id="password_verification" type="password" name="password_verification" placeholder=" ">
<label for="password_verification">Retype Password</label>
</div>
<div class="s12">
<h5>First Site</h5>
<p> 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.
</p>
</div>
<div class="s12 input-field">
<input id="site_name" name="site_name" type="text" placeholder=" ">
<label for="site_name">Site Name</label>
</div>
<div class="input-field col s12">
<textarea id="site_description" name="site_description" class="materialize-textarea" placeholder=" " value="This is your first site!"></textarea>
<label for="site_description">Site Description</label>
</div>
<div class="s12 input-field">
<input id="site_default_zone" name="site_default_zone" type="text" placeholder=" ">
<label for="site_default_zone">Default Zone</label>
</div>
<div class="s12 input-field">
<input id="site_default_location" name="site_default_location" type="text" placeholder=" ">
<label for="site_default_location">Default Location</label>
</div>
<div class="s12 input-field">
<button class="btn btn-flat green lighten-4 icon-right waves-effect waves-light right" type="submit" name="action">
Submit<i class="material-icons right">send</i>
</button>
</div>
</div>
</form>
</div>
</body>

View File

@ -92,7 +92,6 @@
await populateInfo() await populateInfo()
await populateReferences() await populateReferences()
console.log(quantities)
}) })
async function fetchList(){ async function fetchList(){
@ -129,7 +128,6 @@
var tbl_body = document.createElement('tbody') var tbl_body = document.createElement('tbody')
let inventory_items = shoppingList[3]; let inventory_items = shoppingList[3];
console.log(inventory_items)
// if it is plain then we want to grab the qty from the quantities dictionary // if it is plain then we want to grab the qty from the quantities dictionary
@ -138,17 +136,16 @@
var row = document.createElement('tr') var row = document.createElement('tr')
let name_cell = document.createElement('td') let name_cell = document.createElement('td')
if (inventory_items[i][3]){ if (inventory_items[i][6]){
name_cell.innerHTML = `<a href=${inventory_items[i][3].main} target="_blank">${inventory_items[i][2]}</a>` name_cell.innerHTML = `<a href=${inventory_items[i][6].main} target="_blank">${inventory_items[i][2]}</a>`
} else { } else {
name_cell.innerHTML = `${inventory_items[i][2]}` name_cell.innerHTML = `${inventory_items[i][2]}`
} }
console.log(inventory_items[i])
let qty_uom_cell = document.createElement('td') let qty_uom_cell = document.createElement('td')
if(shoppingList[10] == 'calculated'){ if(shoppingList[10] == 'calculated'){
qty = Number(inventory_items[i][5]) - Number(inventory_items[i][4]) qty = Number(inventory_items[i][22]) - Number(inventory_items[i][26])
uom = inventory_items[i][6] uom = inventory_items[i][20]
} else { } else {
qty = quantities[`${inventory_items[i][0]}@item`]['qty'] qty = quantities[`${inventory_items[i][0]}@item`]['qty']
uom = quantities[`${inventory_items[i][0]}@item`]['uom'] uom = quantities[`${inventory_items[i][0]}@item`]['uom']
@ -162,6 +159,26 @@
}; };
// all custom items will pull quantities form the quantities dictionary. // 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 = `<a href=${shoppingList[4][key][3].main} target="_blank">${shoppingList[4][key][2]}</a>`
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_header)
references_table.appendChild(tbl_body) references_table.appendChild(tbl_body)

27
templates/signup.html Normal file
View File

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<title>My Pantry</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/css/materialize.min.css" />
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/@materializecss/materialize@2.0.3-alpha/dist/js/materialize.min.js"></script>
</head>
<body>
<div class="container">
<form class="row" style="gap: 1em;" action="/signup" method="post" enctype="multipart/form-data">
<div class="s12 m6 input-field">
<input id="username" name="username" type="text" class="validate" placeholder=" ">
<label for="username">Username</label>
<span class="supporting-text" data-error="wrong" data-success="right">Supporting text for additional information</span>
</div>
<div class="s12 m6 input-field">
<input id="password" type="password" name="password" class="validate" placeholder=" " maxlength="20">
<label for="password">Password</label>
</div>
<button class="btn icon-right waves-effect waves-light" type="submit" name="action">
Submit<i class="material-icons right">send</i>
</button>
</form>
</div>
</body>

115
user_api.py Normal file
View File

@ -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")

View File

@ -1,84 +1,134 @@
from flask import Flask, render_template, session, request from flask import Flask, render_template, session, request, redirect
import api, config, external_devices import api, config, external_devices, user_api, psycopg2, main, admin
from user_api import login_required
app = Flask(__name__) app = Flask(__name__)
app.secret_key = '11gs22h2h1a4h6ah8e413a45' app.secret_key = '11gs22h2h1a4h6ah8e413a45'
app.register_blueprint(api.database_api) app.register_blueprint(api.database_api)
app.register_blueprint(external_devices.external_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/<id>") @app.route("/group/<id>")
@login_required
def group(id): def group(id):
sites = config.sites_config() 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['sites']) return render_template("groups/group.html", id=id, current_site=session['selected_site'], sites=sites)
@app.route("/transactions/<id>") @app.route("/transactions/<id>")
@login_required
def transactions(id): def transactions(id):
sites = config.sites_config() 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['sites']) return render_template("items/transactions.html", id=id, current_site=session['selected_site'], sites=sites)
@app.route("/item/<id>") @app.route("/item/<id>")
@login_required
def item(id): def item(id):
sites = config.sites_config() 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['sites']) return render_template("items/item.html", id=id, current_site=session['selected_site'], sites=sites)
@app.route("/itemlink/<id>") @app.route("/itemlink/<id>")
@login_required
def itemLink(id): def itemLink(id):
sites = config.sites_config() 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['sites'], proto={'referrer': request.referrer}, id=id) return render_template("items/itemlink.html", current_site=session['selected_site'], sites=sites, proto={'referrer': request.referrer}, id=id)
@app.route("/transaction") @app.route("/transaction")
@login_required
def transaction(): def transaction():
print(request.referrer) sites = [site[1] for site in main.get_sites(session['user'][13])]
sites = config.sites_config() return render_template("transaction.html", current_site=session['selected_site'], sites=sites, proto={'referrer': request.referrer})
return render_template("transaction.html", current_site=session['selected_site'], sites=sites['sites'], proto={'referrer': request.referrer})
@app.route("/workshop") @app.route("/admin")
@login_required
def workshop(): def workshop():
sites = config.sites_config() sites = [site[1] for site in main.get_sites(session['user'][13])]
return render_template("workshop.html", current_site=session['selected_site'], sites=sites['sites']) 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/<id>") @app.route("/shopping-list/view/<id>")
@login_required
def shopping_lists_view(id): def shopping_lists_view(id):
sites = config.sites_config() 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['sites']) return render_template("shopping-lists/view.html", id=id, current_site=session['selected_site'], sites=sites)
@app.route("/shopping-list/edit/<id>") @app.route("/shopping-list/edit/<id>")
@login_required
def shopping_lists_edit(id): def shopping_lists_edit(id):
sites = config.sites_config() 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['sites']) return render_template("shopping-lists/edit.html", id=id, current_site=session['selected_site'], sites=sites)
@app.route("/shopping-lists") @app.route("/shopping-lists")
@login_required
def shopping_lists(): def shopping_lists():
sites = config.sites_config() 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['sites']) return render_template("shopping-lists/index.html", current_site=session['selected_site'], sites=sites)
@app.route("/receipt/<id>") @app.route("/receipt/<id>")
@login_required
def receipt(id): def receipt(id):
sites = config.sites_config() 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['sites']) return render_template("receipts/receipt.html", id=id, current_site=session['selected_site'], sites=sites)
@app.route("/receipts") @app.route("/receipts")
@login_required
def receipts(): def receipts():
sites = config.sites_config() 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['sites']) return render_template("receipts/index.html", current_site=session['selected_site'], sites=sites)
@app.route("/groups") @app.route("/groups")
@login_required
def groups(): def groups():
sites = config.sites_config() 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['sites']) return render_template("groups/index.html",
current_site=session['selected_site'],
sites=sites)
@app.route("/items") @app.route("/items")
@login_required
def items(): def items():
sites = config.sites_config() 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['sites']) return render_template("items/index.html",
current_site=session['selected_site'],
sites=sites)
@app.route("/") @app.route("/")
@login_required
def home(): def home():
session['selected_site'] = 'main' print(session['user'][12])
sites = config.sites_config() 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['sites']) 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) app.run(host="0.0.0.0", port=5810, debug=True)