test
This commit is contained in:
parent
f1cc51f378
commit
c18c6cec16
BIN
__pycache__/admin.cpython-312.pyc
Normal file
BIN
__pycache__/admin.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
__pycache__/html_factory.cpython-312.pyc
Normal file
BIN
__pycache__/html_factory.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
__pycache__/manage.cpython-312.pyc
Normal file
BIN
__pycache__/manage.cpython-312.pyc
Normal file
Binary file not shown.
BIN
__pycache__/user_api.cpython-312.pyc
Normal file
BIN
__pycache__/user_api.cpython-312.pyc
Normal file
Binary file not shown.
122
admin.py
Normal file
122
admin.py
Normal 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
30
api.py
@ -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)
|
||||||
|
|||||||
23
config.py
23
config.py
@ -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)
|
||||||
|
|||||||
@ -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
56
html_factory.py
Normal 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
231
main.py
@ -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,
|
||||||
|
|||||||
16
manage.py
16
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:
|
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")
|
||||||
|
|
||||||
|
|||||||
47
scratch.py
47
scratch.py
@ -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)
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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,}$')
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
11
sites/Backpack/sql/create/roles.sql
Normal file
11
sites/Backpack/sql/create/roles.sql
Normal 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)
|
||||||
|
);
|
||||||
15
sites/Backpack/sql/create/sites.sql
Normal file
15
sites/Backpack/sql/create/sites.sql
Normal 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)
|
||||||
|
);
|
||||||
43
sites/Backpack/sql/unique/shopping_lists_safetystock.sql
Normal file
43
sites/Backpack/sql/unique/shopping_lists_safetystock.sql
Normal 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
|
||||||
|
*/
|
||||||
|
|
||||||
@ -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];
|
||||||
@ -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];
|
||||||
@ -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,}$')
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
11
sites/default/sql/create/roles.sql
Normal file
11
sites/default/sql/create/roles.sql
Normal 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)
|
||||||
|
);
|
||||||
15
sites/default/sql/create/sites.sql
Normal file
15
sites/default/sql/create/sites.sql
Normal 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)
|
||||||
|
);
|
||||||
43
sites/default/sql/unique/shopping_lists_safetystock.sql
Normal file
43
sites/default/sql/unique/shopping_lists_safetystock.sql
Normal 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
|
||||||
|
*/
|
||||||
|
|
||||||
@ -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];
|
||||||
@ -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];
|
||||||
@ -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
|
||||||
|
|||||||
@ -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,}$')
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
11
sites/main/sql/create/roles.sql
Normal file
11
sites/main/sql/create/roles.sql
Normal 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)
|
||||||
|
);
|
||||||
15
sites/main/sql/create/sites.sql
Normal file
15
sites/main/sql/create/sites.sql
Normal 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)
|
||||||
|
);
|
||||||
12
sites/main/sql/unique/shopping_lists_safetystock.sql
Normal file
12
sites/main/sql/unique/shopping_lists_safetystock.sql
Normal 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];
|
||||||
12
sites/main/sql/unique/shopping_lists_safetystock_count.sql
Normal file
12
sites/main/sql/unique/shopping_lists_safetystock_count.sql
Normal 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];
|
||||||
@ -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];
|
||||||
@ -1,9 +0,0 @@
|
|||||||
[site]
|
|
||||||
site_name=test
|
|
||||||
site_owner=
|
|
||||||
email=
|
|
||||||
|
|
||||||
[defaults]
|
|
||||||
default_zone=default
|
|
||||||
default_primary_location=all
|
|
||||||
default_auto_issue_location=all
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS test_brands (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
name VARCHAR(255)
|
|
||||||
);
|
|
||||||
@ -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
|
|
||||||
);
|
|
||||||
@ -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)
|
|
||||||
);
|
|
||||||
@ -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
|
|
||||||
);
|
|
||||||
@ -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)
|
|
||||||
);
|
|
||||||
@ -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
|
|
||||||
);
|
|
||||||
@ -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)
|
|
||||||
);
|
|
||||||
@ -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)
|
|
||||||
);
|
|
||||||
@ -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
|
|
||||||
);
|
|
||||||
|
|
||||||
@ -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)
|
|
||||||
);
|
|
||||||
@ -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)
|
|
||||||
);
|
|
||||||
@ -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)
|
|
||||||
);
|
|
||||||
@ -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
|
|
||||||
);
|
|
||||||
@ -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)
|
|
||||||
);
|
|
||||||
@ -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)
|
|
||||||
);
|
|
||||||
@ -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)
|
|
||||||
);
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS test_zones(
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
name VARCHAR(32) NOT NULL,
|
|
||||||
UNIQUE(name)
|
|
||||||
);
|
|
||||||
@ -1 +0,0 @@
|
|||||||
DROP TABLE test_brands CASCADE;
|
|
||||||
@ -1 +0,0 @@
|
|||||||
DROP TABLE test_food_info CASCADE;
|
|
||||||
@ -1 +0,0 @@
|
|||||||
DROP TABLE test_groups CASCADE;
|
|
||||||
@ -1 +0,0 @@
|
|||||||
DROP TABLE test_item_info CASCADE;
|
|
||||||
@ -1 +0,0 @@
|
|||||||
DROP TABLE test_item_locations CASCADE;
|
|
||||||
@ -1 +0,0 @@
|
|||||||
DROP TABLE test_items CASCADE;
|
|
||||||
@ -1 +0,0 @@
|
|||||||
DROP TABLE test_itemlinks CASCADE;
|
|
||||||
@ -1 +0,0 @@
|
|||||||
DROP TABLE test_locations CASCADE;
|
|
||||||
@ -1 +0,0 @@
|
|||||||
DROP TABLE test_logistics_info CASCADE;
|
|
||||||
@ -1 +0,0 @@
|
|||||||
DROP TABLE test_receipt_items CASCADE;
|
|
||||||
@ -1 +0,0 @@
|
|||||||
DROP TABLE test_receipts CASCADE;
|
|
||||||
@ -1 +0,0 @@
|
|||||||
DROP TABLE test_recipes CASCADE;
|
|
||||||
@ -1 +0,0 @@
|
|||||||
DROP TABLE test_shopping_lists CASCADE;
|
|
||||||
@ -1 +0,0 @@
|
|||||||
DROP TABLE test_transactions CASCADE;
|
|
||||||
@ -1 +0,0 @@
|
|||||||
DROP TABLE test_vendors CASCADE;
|
|
||||||
@ -1 +0,0 @@
|
|||||||
DROP TABLE test_zones CASCADE;
|
|
||||||
@ -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);
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
UPDATE test_logistics_info
|
|
||||||
SET quantity_on_hand = %s, location_data = %s
|
|
||||||
WHERE id = %s;
|
|
||||||
@ -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
|
|
||||||
*/
|
|
||||||
@ -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
|
|
||||||
*/
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
UPDATE test_locations
|
|
||||||
SET items = %s
|
|
||||||
WHERE id = %s;
|
|
||||||
28
static/adminHandler.js
Normal file
28
static/adminHandler.js
Normal 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
BIN
static/pictures/logo.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 89 KiB |
449
templates/admin.html
Normal file
449
templates/admin.html
Normal 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
131
templates/admin/role.html
Normal 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>
|
||||||
17
templates/admin/users.html
Normal file
17
templates/admin/users.html
Normal 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>
|
||||||
@ -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
37
templates/login.html
Normal 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
91
templates/setup.html
Normal 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>
|
||||||
@ -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
27
templates/signup.html
Normal 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
115
user_api.py
Normal 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")
|
||||||
116
webserver.py
116
webserver.py
@ -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)
|
||||||
Loading…
x
Reference in New Issue
Block a user