2nd Database oop migration

This commit is contained in:
Jadowyne Ulve 2025-08-28 17:58:05 -05:00
parent 5b5b914b35
commit a3cad9622e
163 changed files with 1918 additions and 525 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,27 +1,20 @@
from flask import Blueprint, request, render_template, redirect, session, url_for, jsonify from flask import Blueprint, request, render_template, redirect, session, url_for, jsonify
from authlib.integrations.flask_client import OAuth import hashlib
import hashlib, psycopg2
from config import config, sites_config from config import config, sites_config
from functools import wraps from functools import wraps
import postsqldb
import requests import requests
from application.access_module import access_database from application.access_module import access_database
from application.database_postgres.UsersModel import UsersModel
from outh import oauth from outh import oauth
access_api = Blueprint('access_api', __name__, template_folder="templates", static_folder="static") access_api = Blueprint('access_api', __name__, template_folder="templates", static_folder="static")
def update_session_user(): def update_session_user():
user = access_database.selectLoginsTupleByID((session['user_id'],)) user = UsersModel.select_tuple(session['selected_site'], {'key': session['user_uuid']})
user = access_database.washUserDictionary(user) user = UsersModel.washUserDictionary(user)
session['user'] = user session['user'] = user
print(user)
def login_required(func): def login_required(func):
@wraps(func) @wraps(func)
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
@ -89,20 +82,13 @@ def login():
password = hashlib.sha256(password.encode()).hexdigest() password = hashlib.sha256(password.encode()).hexdigest()
database_config = config() 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:
conn.rollback()
return jsonify({'error': True, 'message': str(error)})
if user and user[2] == password: user = UsersModel.select_tuple_by_username({'key': username})
session['user_id'] = user[0]
session['user'] = {'id': user[0], 'username': user[1], 'sites': user[13], 'site_roles': user[14], 'system_admin': user[15], 'flags': user[16]} if user and user['user_password'] == password:
session['login_type'] = 'Internal' session['user_uuid'] = user['user_uuid']
session['user'] = UsersModel.washUserDictionary(user)
session['user_login_type'] = 'Internal'
return jsonify({'error': False, 'message': 'Logged In Sucessfully!'}) return jsonify({'error': False, 'message': 'Logged In Sucessfully!'})
else: else:
return jsonify({'error': True, 'message': 'Username or Password was incorrect!'}) return jsonify({'error': True, 'message': 'Username or Password was incorrect!'})
@ -111,16 +97,8 @@ def login():
if 'user' not in session.keys(): if 'user' not in session.keys():
session['user'] = None session['user'] = None
print(instance_config)
return render_template("login.html", instance_settings=instance_config) return render_template("login.html", instance_settings=instance_config)
@access_api.route('/dashboard')
def dashboard():
if 'user' not in session:
return redirect('/')
return f"Hello, {session['user']['name']}! <a href='/logout'>Logout</a>"
@access_api.route('/signup', methods=['POST', 'GET']) @access_api.route('/signup', methods=['POST', 'GET'])
def signup(): def signup():
instance_config = sites_config() instance_config = sites_config()
@ -132,14 +110,14 @@ def signup():
password = request.get_json()['password'] password = request.get_json()['password']
email = request.get_json()['email'] email = request.get_json()['email']
password = hashlib.sha256(password.encode()).hexdigest() password = hashlib.sha256(password.encode()).hexdigest()
database_config = config()
with psycopg2.connect(**database_config) as conn: new_user = UsersModel.Payload(
try: user_name=username,
with conn.cursor() as cur: user_password=password,
sql = f"INSERT INTO logins(username, password, email, row_type) VALUES(%s, %s, %s, %s);" user_email=email
cur.execute(sql, (username, password, email, 'user')) )
except (Exception, psycopg2.DatabaseError) as error:
conn.rollback() new_user = UsersModel.insert_tuple('', new_user.payload_dictionary())
return jsonify({'error': True, 'message': str(error)})
return jsonify({'error': False, 'message': 'You have been signed up successfully, you will have to wait until the server admin finishes your onboarding!'}) return jsonify({'error': False, 'message': 'You have been signed up successfully, you will have to wait until the server admin finishes your onboarding!'})
return jsonify({'error': True, 'message': 'There was a problem with this POST request!'}) return jsonify({'error': True, 'message': 'There was a problem with this POST request!'})

View File

@ -7,7 +7,7 @@ import hashlib
# APPLICATION IMPORTS # APPLICATION IMPORTS
from application.access_module import access_api from application.access_module import access_api
from application.administration import administration_database, administration_processes from application.administration import administration_database, administration_services
from application import database_payloads, postsqldb from application import database_payloads, postsqldb
@ -69,7 +69,7 @@ def first_time_setup():
"site_description": request.form['site_description'] "site_description": request.form['site_description']
} }
administration_processes.addSite(payload) administration_services.addSite(payload)
return redirect("/login") return redirect("/login")
@ -127,7 +127,7 @@ def postDeleteSite():
return jsonify({'error': True, 'message': f"You must be the owner of this site to delete."}) return jsonify({'error': True, 'message': f"You must be the owner of this site to delete."})
try: try:
administration_processes.deleteSite(site, user) administration_services.deleteSite(site, user)
except Exception as err: except Exception as err:
print(err) print(err)
@ -144,7 +144,7 @@ def postAddSite():
user = administration_database.selectLoginsTuple((user_id,)) user = administration_database.selectLoginsTuple((user_id,))
payload['admin_user'] = (user['username'], user['password'], user['email'], user['row_type']) payload['admin_user'] = (user['username'], user['password'], user['email'], user['row_type'])
administration_processes.addSite(payload) administration_services.addSite(payload)
return jsonify({'error': False, 'message': f"Zone added to {site_name}."}) return jsonify({'error': False, 'message': f"Zone added to {site_name}."})

View File

@ -0,0 +1,148 @@
from application.database_postgres.UsersModel import UsersModel
from application.database_postgres.RolesModel import RolesModel
from application.database_postgres.BaseModel import DatabaseError, tupleDictionaryFactory
import config
import psycopg2
class ExtendedRolesModel(RolesModel):
@classmethod
def select_by_site_uuid(self, payload, convert=True, conn=None):
roles = ()
self_conn = False
select_roles_sql = f"SELECT * FROM roles WHERE role_site_uuid = %(site_uuid)s::uuid;"
try:
if not conn:
database_config = config.config()
conn = psycopg2.connect(**database_config)
conn.autocommit = True
self_conn = True
with conn.cursor() as cur:
cur.execute(select_roles_sql, payload)
rows = cur.fetchall()
if rows and convert:
roles = [tupleDictionaryFactory(cur.description, role) for role in rows]
elif rows and not convert:
roles = rows
if self_conn:
conn.close()
return roles
except Exception as error:
raise DatabaseError(error, payload, select_roles_sql)
class ExtendedUsersModel(UsersModel):
@classmethod
def add_admin_user(self, payload:dict, convert=True, conn=None):
admin_user = ()
self_conn = False
sql = f"""INSERT INTO users (user_name, user_password, user_email, user_row_type)
VALUES (%(user_name)s, %(user_password)s, %(user_email)s, %(user_row_type)s) ON CONFLICT (user_name) DO UPDATE SET user_name = excluded.user_name RETURNING *;"""
try:
if not conn:
database_config = config.config()
conn = psycopg2.connect(**database_config)
conn.autocommit = True
self_conn = True
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchone()
if rows and convert:
admin_user = tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
admin_user = rows
if self_conn:
conn.commit()
conn.close()
return admin_user
except Exception as e:
DatabaseError(str(e), payload, sql)
@classmethod
def update_roles(self, payload, convert=True, conn=None):
""" payload: {'role_uuid': x,} """
self_conn = False
try:
if not conn:
database_config = config.config()
conn = psycopg2.connect(**database_config)
conn.autocommit = True
self_conn = True
select_sql = f"SELECT users.user_uuid FROM users WHERE user_roles @> ARRAY[%(role_uuid)s::uuid];"
with conn.cursor() as cur:
cur.execute(select_sql, payload)
users = tuple([row[0] for row in cur.fetchall()])
update_sql = f"UPDATE users SET user_roles = array_remove(user_roles, %(role_uuid)s::uuid) WHERE user_uuid = %(user_uuid)s::uuid;"
with conn.cursor() as cur:
for user_uuid in users:
cur.execute(update_sql, {'role_uuid': payload['role_uuid'], 'user_uuid': user_uuid})
if self_conn:
conn.commit()
conn.close()
except Exception as error:
raise error
@classmethod
def update_sites(self, payload, convert=True, conn=None):
""" payload: {'site_uuid',} """
self_conn = False
try:
if not conn:
database_config = config.config()
conn = psycopg2.connect(**database_config)
conn.autocommit = True
self_conn = True
select_sql = f"SELECT users.user_uuid FROM users WHERE user_sites @> ARRAY[%(site_uuid)s::uuid];"
with conn.cursor() as cur:
cur.execute(select_sql, payload)
user = tuple([row[0] for row in cur.fetchall()])
update_sql = f"UPDATE users SET user_sites = array_remove(user_sites, %(site_uuid)s::uuid) WHERE user_uuid = %(user_uuid)s::uuid;"
with conn.cursor() as cur:
for user_uuid in user:
cur.execute(update_sql, {'site_uuid': payload['site_uuid'], 'user_uuid': user_uuid})
if self_conn:
conn.commit()
conn.close()
except Exception as error:
raise error
@classmethod
def update_user_site_roles(self, payload, convert=True, conn=None):
""" payload (tuple): (site_uuid, role_uuid, user_uuid) """
sql = f"UPDATE users SET user_sites = user_sites || %(site_uuid)s::uuid, user_roles = user_roles || %(role_uuid)s::uuid WHERE user_uuid=%(user_uuid)s RETURNING *;"
user = ()
self_conn = False
try:
if not conn:
database_config = config.config()
conn = psycopg2.connect(**database_config)
conn.autocommit = True
self_conn = True
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchone()
if rows and convert:
user = tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
user = rows
if self_conn:
conn.commit()
conn.close()
return user
except Exception as error:
raise DatabaseError(error, payload, sql)

View File

@ -1,167 +0,0 @@
# 3RD PARTY IMPORTS
import psycopg2
import datetime
# APPLICATION IMPORTS
import config
from application import postsqldb, database_payloads
from application.administration import administration_database
def dropSiteTables(conn, site_manager):
try:
for table in site_manager.drop_order:
administration_database.dropTable(site_manager.site_name, table, conn=conn)
with open("logs/process.log", "a+") as file:
file.write(f"{datetime.datetime.now()} --- INFO --- {table} DROPPED!\n")
except Exception as error:
raise error
def deleteSite(site, user, conn=None):
"""Uses a Site Manager to delete a site from the system.
Args:
site_manager (MyDataclasses.SiteManager):
Raises:
Exception:
"""
self_conn = False
if not conn:
database_config = config.config()
conn = psycopg2.connect(**database_config)
conn.autocommit = False
self_conn = True
try:
admin_user = (user['username'], user['password'], user['email'], user['row_type'])
site_manager = database_payloads.SiteManager(
site['site_name'],
admin_user,
site['default_zone'],
site['default_primary_location'],
site['site_description']
)
roles = administration_database.selectRolesTupleBySite((site['id'],), conn=conn)
administration_database.deleteRolesTuple([role['id'] for role in roles], conn=conn)
dropSiteTables(conn, site_manager)
for role in roles:
administration_database.updateUsersRoles({'role_id': role['id']}, conn=conn)
administration_database.updateUsersSites({'site_id': site['id']}, conn=conn)
site = administration_database.deleteSitesTuple((site['id'], ), conn=conn)
if self_conn:
conn.commit()
conn.close()
except Exception as error:
with open("logs/process.log", "a+") as file:
file.write(f"{datetime.datetime.now()} --- ERROR --- {error}\n")
conn.rollback()
conn.close()
def addAdminUser(conn, site_manager, convert=True):
admin_user = ()
try:
sql = f"INSERT INTO logins (username, password, email, row_type) VALUES (%s, %s, %s, %s) ON CONFLICT (username) DO UPDATE SET username = excluded.username RETURNING *;"
with conn.cursor() as cur:
cur.execute(sql, site_manager.admin_user)
rows = cur.fetchone()
if rows and convert:
admin_user = postsqldb.tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
admin_user = rows
with open("logs/process.log", "a+") as file:
file.write(f"{datetime.datetime.now()} --- INFO --- Admin User Created!\n")
except Exception as error:
raise error
return admin_user
def setupSiteTables(conn, site_manager):
try:
for table in site_manager.create_order:
administration_database.createTable(site_manager.site_name, table, conn=conn)
with open("logs/process.log", "a+") as file:
file.write(f"{datetime.datetime.now()} --- INFO --- {table} Created!\n")
except Exception as error:
raise error
def addSite(payload, conn=None):
"""uses a Site Manager to add a site to the system
Args:
site_manager (MyDataclasses.SiteManager):
"""
self_conn = False
site_manager = database_payloads.SiteManager(
payload['site_name'],
payload['admin_user'],
payload['default_zone'],
payload['default_primary_location'],
payload['site_description']
)
try:
if not conn:
database_config = config.config()
conn = psycopg2.connect(**database_config)
conn.autocommit = False
self_conn = True
setupSiteTables(conn, site_manager)
admin_user = addAdminUser(conn, site_manager)
site = database_payloads.SitePayload(
site_name=site_manager.site_name,
site_description=site_manager.description,
site_owner_id=admin_user['id']
)
site = administration_database.insertSitesTuple(site.payload(), conn=conn)
role = database_payloads.RolePayload("Admin", f"Admin for {site['site_name']}", site['id'])
role = administration_database.insertRolesTuple(role.payload(), conn=conn)
admin_user = administration_database.updateAddLoginSitesRoles((site["id"], role["id"], admin_user["id"]), conn=conn)
default_zone = database_payloads.ZonesPayload(site_manager.default_zone)
default_zone = administration_database.insertZonesTuple(site["site_name"], default_zone.payload(), conn=conn)
uuid = f"{site_manager.default_zone}@{site_manager.default_location}"
default_location = database_payloads.LocationsPayload(uuid, site_manager.default_location, default_zone['id'])
default_location = administration_database.insertLocationsTuple(site['site_name'], default_location.payload(), conn=conn)
payload = {
'id': site['id'],
'update': {
'default_zone': default_zone['id'],
'default_auto_issue_location': default_location['id'],
'default_primary_location': default_location['id']
}
}
administration_database.updateSitesTuple(payload, conn=conn)
blank_vendor = database_payloads.VendorsPayload("None", admin_user['id'])
blank_brand = database_payloads.BrandsPayload("None")
blank_vendor = administration_database.insertVendorsTuple(site['site_name'], blank_vendor.payload(), conn=conn)
blank_brand = administration_database.insertBrandsTuple(site['site_name'], blank_brand.payload(), conn=conn)
if self_conn:
conn.commit()
conn.close()
except Exception as error:
with open("logs/process.log", "a+") as file:
file.write(f"{datetime.datetime.now()} --- ERROR --- {error}\n")
conn.rollback()
raise error

View File

@ -0,0 +1,261 @@
# 3RD PARTY IMPORTS
import psycopg2
import datetime
# APPLICATION IMPORTS
import config
from application import postsqldb, database_payloads
from application.administration import administration_database, administration_models
from dataclasses import dataclass, field
from application.database_postgres import (
CostLayersModel,
BrandsModel,
FoodInfoModel,
ItemInfoModel,
ZonesModel,
LocationsModel,
LogisticsInfoModel,
TransactionsModel,
ItemsModel,
ItemLocationsModel,
ConversionsModel,
SKUPrefixModel,
BarcodesModel,
VendorsModel,
ReceiptsModel,
ReceiptItemsModel,
RecipesModel,
RecipeItemsModel,
ShoppingListsModel,
ShoppingListItemsModel,
PlansModel,
PlanEventsModel,
SitesModel,
UsersModel,
RolesModel,
UnitsModel
)
from application.database_postgres import BaseModel
@dataclass
class SiteManager:
site_name: str
admin_user: tuple
default_zone: int
default_location: int
description: str
create_order: list = field(init=False)
drop_order: list = field(init=False)
def create_tables(self, conn):
UsersModel.UsersModel.create_table(self.site_name, conn=conn)
SitesModel.SitesModel.create_table(self.site_name, conn=conn)
RolesModel.RolesModel.create_table(self.site_name, conn=conn)
UnitsModel.UnitsModel.create_table(self.site_name, conn=conn)
# Needed for Items and Logistics
BrandsModel.BrandsModel.create_table(self.site_name, conn=conn)
ZonesModel.ZonesModel.create_table(self.site_name, conn=conn)
LocationsModel.LocationsModel.create_table(self.site_name, conn=conn)
ItemsModel.ItemsModel.create_table(self.site_name, conn=conn)
FoodInfoModel.FoodInfoModel.create_table(self.site_name, conn=conn)
ItemInfoModel.ItemInfoModel.create_table(self.site_name, conn=conn)
LogisticsInfoModel.LogisticsInfoModel.create_table(self.site_name, conn=conn)
ItemLocationsModel.ItemLocationsModel.create_table(self.site_name, conn=conn)
CostLayersModel.CostLayersModel.create_table(self.site_name, conn=conn)
ConversionsModel.ConversionsModel.create_table(self.site_name, conn=conn)
TransactionsModel.TransactionsModel.create_table(self.site_name, conn=conn)
SKUPrefixModel.SKUPrefixModel.create_table(self.site_name, conn=conn)
BarcodesModel.BarcodesModel.create_table(self.site_name, conn=conn)
# Vendors is used losely in Planner and in receipts.
VendorsModel.VendorsModel.create_table(self.site_name, conn=conn)
ReceiptsModel.ReceiptsModel.create_table(self.site_name, conn=conn)
ReceiptItemsModel.ReceiptItemsModel.create_table(self.site_name, conn=conn)
# This is the Recipe Module
RecipesModel.RecipesModel.create_table(self.site_name, conn=conn)
RecipeItemsModel.RecipeItemsModel.create_table(self.site_name, conn=conn)
# this is the Shopping List Module
ShoppingListsModel.ShoppingListsModel.create_table(self.site_name, conn=conn)
ShoppingListItemsModel.ShoppingListItemsModel.create_table(self.site_name, conn=conn)
# Planner Module
PlansModel.PlansModel.create_table(self.site_name, conn=conn)
PlanEventsModel.PlanEventsModel.create_table(self.site_name, conn=conn)
def drop_tables(self, conn):
# Needed for Items and Logistics
BrandsModel.BrandsModel.drop_table(self.site_name,conn=conn)
CostLayersModel.CostLayersModel.drop_table(self.site_name, conn=conn)
FoodInfoModel.FoodInfoModel.drop_table(self.site_name, conn=conn)
ItemInfoModel.ItemInfoModel.drop_table(self.site_name, conn=conn)
ZonesModel.ZonesModel.drop_table(self.site_name, conn=conn)
LocationsModel.LocationsModel.drop_table(self.site_name, conn=conn)
LogisticsInfoModel.LogisticsInfoModel.drop_table(self.site_name, conn=conn)
TransactionsModel.TransactionsModel.drop_table(self.site_name, conn=conn)
ItemsModel.ItemsModel.drop_table(self.site_name, conn=conn)
ItemLocationsModel.ItemLocationsModel.drop_table(self.site_name, conn=conn)
ConversionsModel.ConversionsModel.drop_table(self.site_name, conn=conn)
SKUPrefixModel.SKUPrefixModel.drop_table(self.site_name, conn=conn)
BarcodesModel.BarcodesModel.drop_table(self.site_name, conn=conn)
# Vendors is used losely in Planner and in receipts.
VendorsModel.VendorsModel.drop_table(self.site_name, conn=conn)
ReceiptsModel.ReceiptsModel.drop_table(self.site_name, conn=conn)
ReceiptItemsModel.ReceiptItemsModel.drop_table(self.site_name, conn=conn)
# This is the Recipe Module
RecipesModel.RecipesModel.drop_table(self.site_name, conn=conn)
RecipeItemsModel.RecipeItemsModel.drop_table(self.site_name, conn=conn)
# this is the Shopping List Module
ShoppingListsModel.ShoppingListsModel.drop_table(self.site_name, conn=conn)
ShoppingListItemsModel.ShoppingListItemsModel.drop_table(self.site_name, conn=conn)
# Planner Module
PlansModel.PlansModel.drop_table(self.site_name, conn=conn)
PlanEventsModel.PlanEventsModel.drop_table(self.site_name, conn=conn)
def deleteSite(payload, conn=None):
"""Uses a Site Manager to delete a site from the system.
Args:
site_manager (MyDataclasses.SiteManager):
Raises:
Exception:
"""
self_conn = False
if not conn:
database_config = config.config()
conn = psycopg2.connect(**database_config)
conn.autocommit = False
self_conn = True
site_manager = SiteManager(
payload['site_name'],
payload['admin_user'],
payload['default_zone'],
payload['default_primary_location'],
payload['site_description']
)
roles = administration_models.ExtendedRolesModel.select_by_site_uuid({'site_uuid': payload['site_uuid']}, conn=conn)
roles = RolesModel.RolesModel.delete_tuples([role['role_uuid'] for role in roles], conn=conn)
site_manager.drop_tables(conn=conn)
for role in roles:
administration_models.ExtendedUsersModel.update_roles({'role_uuid': role['role_uuid']}, conn=conn)
administration_models.ExtendedUsersModel.update_sites({'site_uuid': payload['site_uuid']}, conn=conn)
SitesModel.SitesModel.delete_tuples((payload['site_uuid'],), conn=conn)
if self_conn:
conn.commit()
conn.close()
def addSite(payload, conn=None):
"""uses a Site Manager to add a site to the system
Args:
site_manager (MyDataclasses.SiteManager):
"""
self_conn = False
site_manager = SiteManager(
payload['site_name'],
payload['admin_user'],
payload['default_zone'],
payload['default_primary_location'],
payload['site_description']
)
try:
if not conn:
database_config = config.config()
conn = psycopg2.connect(**database_config)
conn.autocommit = False
self_conn = True
sql = 'CREATE EXTENSION IF NOT EXISTS "uuid-ossp";'
with conn.cursor() as cur:
cur.execute(sql)
site_manager.create_tables(conn=conn)
admin_user = administration_models.ExtendedUsersModel.add_admin_user(site_manager.admin_user, conn=conn)
site = SitesModel.SitesModel.Payload(
site_name=site_manager.site_name,
site_description=site_manager.description,
site_created_by=admin_user['user_uuid']
)
# have to build site table
site = SitesModel.SitesModel.insert_tuple(site.site_name, site.payload_dictionary(), conn=conn)
# have to build roles table
role = RolesModel.RolesModel.Payload(
role_name="Admin",
role_description=f"Admin for {site['site_name']}",
role_site_uuid=site['site_uuid']
)
role = RolesModel.RolesModel.insert_tuple(site['site_name'], role.payload_dictionary(), conn=conn)
# have to build logins table
payload = {
'user_uuid': admin_user['user_uuid'],
'site_uuid': site['site_uuid'],
'role_uuid': role['role_uuid']
}
admin_user = administration_models.ExtendedUsersModel.update_user_site_roles(payload, conn=conn)
default_zone = ZonesModel.ZonesModel.Payload(zone_name=site_manager.default_zone)
default_zone = ZonesModel.ZonesModel.insert_tuple(site["site_name"], default_zone.payload_dictionary(), conn=conn)
uuid = f"{site_manager.default_zone}@{site_manager.default_location}"
default_location = LocationsModel.LocationsModel.Payload(
location_shortname=uuid,
location_name=site_manager.default_location,
zone_uuid=default_zone['zone_uuid']
)
default_location = LocationsModel.LocationsModel.insert_tuple(site['site_name'], default_location.payload_dictionary(), conn=conn)
payload = {
'key': site['site_uuid'],
'update': {
'site_default_zone_uuid': default_zone['zone_uuid'],
'site_default_auto_issue_location_uuid': default_location['location_uuid'],
'site_default_primary_location_uuid': default_location['location_uuid']
}
}
SitesModel.SitesModel.update_tuple(payload, conn=conn)
blank_vendor = VendorsModel.VendorsModel.Payload("None", admin_user['user_uuid'])
blank_brand = BrandsModel.BrandsModel.Payload("None")
VendorsModel.VendorsModel.insert_tuple(site['site_name'], blank_vendor.payload_dictionary(), conn=conn)
BrandsModel.BrandsModel.insert_tuple(site['site_name'], blank_brand.payload_dictionary(), conn=conn)
if self_conn:
conn.commit()
conn.close()
except Exception as error:
with open("logs/process.log", "a+") as file:
file.write(f"{datetime.datetime.now()} --- ERROR --- {error}\n")
conn.rollback()
raise error

View File

@ -204,7 +204,7 @@
payload: payload payload: payload
}), }),
}); });
location.href = '/administration' //location.href = '/administration'
} }
async function postEditSite(){ async function postEditSite(){

View File

@ -1,5 +1,6 @@
from abc import ABC from abc import ABC
import psycopg2 import psycopg2
import psycopg2.extras
import datetime import datetime
import uuid import uuid
import json import json
@ -9,7 +10,6 @@ from copy import deepcopy
import config import config
def validateUUID(uuid_string, version): def validateUUID(uuid_string, version):
try: try:
u = uuid.UUID(uuid_string, version=version) u = uuid.UUID(uuid_string, version=version)
@ -58,6 +58,10 @@ def getUUID(n):
class BasePayload(ABC): class BasePayload(ABC):
"""BasePayloads holds the bare minimum methods required of a Payload. """ """BasePayloads holds the bare minimum methods required of a Payload. """
def __repr__(self):
return self.__dict__
def payload_dictionary(self): def payload_dictionary(self):
return deepcopy(self.__dict__) return deepcopy(self.__dict__)
@ -76,6 +80,8 @@ class BaseModel(ABC):
""" """
table_name: str = None # All extended class must assign a table name that CRUD uses to call upon table_name: str = None # All extended class must assign a table name that CRUD uses to call upon
primary_key: str = 'id' # All extended class can assign a different primary key/cloumn which is used to call delete and update queries on. primary_key: str = 'id' # All extended class can assign a different primary key/cloumn which is used to call delete and update queries on.
primary_key_type: str = 'int'
site_agnostic: bool = False #all extended class can set this to true to avoid site injection
def __init_subclass__(cls, **kwargs): def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs) super().__init_subclass__(**kwargs)
@ -225,3 +231,69 @@ class BaseModel(ABC):
except Exception as error: except Exception as error:
raise DatabaseError(error, payload, sql) raise DatabaseError(error, payload, sql)
@classmethod
def select_tuple(self, site: str, payload: dict, convert: bool = True, conn=None):
record = ()
self_conn = False
if self.site_agnostic:
sql = f"SELECT * FROM {self.table_name} WHERE {self.primary_key} = %(key)s::{self.primary_key_type};"
else:
sql = f"SELECT * FROM {site}_{self.table_name} WHERE {self.primary_key} = %(key)s::{self.primary_key_type};"
try:
if not conn:
database_config = config.config()
conn = psycopg2.connect(**database_config)
conn.autocommit = True
self_conn = True
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchone()
if rows and convert:
record = tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
record = rows
if self_conn:
conn.commit()
conn.close()
return record
except Exception as error:
raise DatabaseError(error, payload, sql)
@classmethod
def select_tuples(self, site: str, convert: bool = True, conn=None):
records = ()
self_conn = False
if self.site_agnostic:
sql = f"SELECT * FROM {self.table_name};"
else:
sql = f"SELECT * FROM {site}_{self.table_name};"
try:
if not conn:
database_config = config.config()
conn = psycopg2.connect(**database_config)
conn.autocommit = True
self_conn = True
with conn.cursor() as cur:
cur.execute(sql)
rows = cur.fetchall()
if rows and convert:
records = [tupleDictionaryFactory(cur.description, row) for row in rows]
elif rows and not convert:
records = rows
if self_conn:
conn.commit()
conn.close()
return records
except Exception as error:
raise DatabaseError(error, {}, sql)

View File

@ -7,5 +7,5 @@ class BrandsModel(BaseModel):
@dataclass @dataclass
class Payload(BasePayload): class Payload(BasePayload):
name: str brand_name: str

View File

@ -5,20 +5,23 @@ from application.database_postgres.BaseModel import BasePayload, BaseModel, lst2
class FoodInfoModel(BaseModel): class FoodInfoModel(BaseModel):
table_name = "food_info" table_name = "food_info"
primary_key = "item_uuid"
primary_key_type = "uuid"
@dataclass @dataclass
class Payload(BasePayload): class Payload(BasePayload):
food_groups: list = field(default_factory=list) item_uuid: str
ingrediants: list = field(default_factory=list) item_food_groups: list = field(default_factory=list)
nutrients: dict = field(default_factory=dict) item_ingredients: list = field(default_factory=list)
expires: bool = False item_nutrients: dict = field(default_factory=dict)
default_expiration: float = 0.0 item_expires: bool = False
item_default_expiration: float = 0.0
def payload_dictionary(self): def payload_dictionary(self):
return { payload = super().payload_dictionary()
'food_groups': lst2pgarr(self.food_groups), payload['item_food_groups'] = lst2pgarr(self.item_food_groups)
'ingrediants': lst2pgarr(self.ingrediants), payload['item_ingredients'] = lst2pgarr(self.item_ingredients)
'nutrients': json.dumps(self.nutrients), payload['item_nutrients'] = json.dumps(self.item_nutrients)
'expires': self.expires, return payload
'default_expiration': self.default_expiration
}

View File

@ -5,20 +5,22 @@ from application.database_postgres.BaseModel import BasePayload, BaseModel, lst2
class ItemInfoModel(BaseModel): class ItemInfoModel(BaseModel):
table_name = "item_info" table_name = "item_info"
primary_key = "item_uuid"
primary_key_type = "uuid"
@dataclass @dataclass
class Payload(BasePayload): class Payload(BasePayload):
barcode: str item_uuid: str
packaging: str = "" item_uom: str = None
uom_quantity: float = 1.0 item_packaging: str = ""
uom: int = 1 item_uom_quantity: float = 1.0
cost: float = 0.0 item_cost: float = 0.0
safety_stock: float = 0.0 item_safety_stock: float = 0.0
lead_time_days: float = 0.0 item_lead_time_days: float = 0.0
ai_pick: bool = False item_ai_pick: bool = False
prefixes: list = field(default_factory=list) item_prefixes: list = field(default_factory=list)
def payload_dictionary(self): def payload_dictionary(self):
payload = super().payload_dictionary() payload = super().payload_dictionary()
payload['prefixes'] = lst2pgarr(self.prefixes) payload['item_prefixes'] = lst2pgarr(self.item_prefixes)
return payload return payload

View File

@ -3,16 +3,12 @@ from application.database_postgres.BaseModel import BasePayload, BaseModel, lst2
class ItemLocationsModel(BaseModel): class ItemLocationsModel(BaseModel):
table_name = "item_locations" table_name = "item_locations"
primary_key = "item_location_uuid"
primary_key_type = 'uuid'
@dataclass @dataclass
class Payload(BasePayload): class Payload(BasePayload):
part_id: int item_uuid: str
location_id: int location_uuid: str
quantity_on_hand: float = 0.0 item_quantity_on_hand: float = 0.0
cost_layers: list = field(default_factory=list)
def payload_dictionary(self):
payload = super().payload_dictionary()
payload['cost_layers'] = lst2pgarr(self.cost_layers)
return payload

View File

@ -1,31 +1,106 @@
from dataclasses import dataclass, field from dataclasses import dataclass, field
import json import json
import psycopg2
import datetime
from application.database_postgres.BaseModel import BasePayload, BaseModel, lst2pgarr from application.database_postgres.BaseModel import BasePayload, BaseModel, lst2pgarr, DatabaseError, tupleDictionaryFactory
import config
class ItemsModel(BaseModel): class ItemsModel(BaseModel):
table_name = "items" table_name = "items"
primary_key = "item_uuid"
primary_key_type = "uuid"
@dataclass @dataclass
class Payload(BasePayload): class Payload(BasePayload):
item_info_id: int item_category: str
item_info_uuid: str item_name: str
logistics_info_id: int item_created_at: datetime.datetime = field(init=False)
logistics_info_uuid: str item_updated_at: datetime.datetime = field(init=False)
food_info_id: int item_description: str = ""
food_info_uuid: str item_tags: list = field(default_factory=list)
barcode: str = "" item_links: dict = field(default_factory=dict)
item_name: str = "" item_brand_uuid: str = None
brand: int = 0 item_search_string: str = ""
description: str = "" item_inactive: bool = False
tags: list = field(default_factory=list)
links: dict = field(default_factory=dict) def __post_init__(self):
row_type: str = "" self.item_created_at = datetime.datetime.now()
item_type: str = "" self.item_updated_at = datetime.datetime.now()
search_string: str =""
def payload_dictionary(self): def payload_dictionary(self):
payload = super().payload_dictionary() payload = super().payload_dictionary()
payload['tags'] = lst2pgarr(self.tags) payload['item_tags'] = lst2pgarr(self.item_tags)
payload['links'] = json.dumps(self.links) payload['item_links'] = json.dumps(self.item_links)
return payload return payload
@classmethod
def paginate_items_with_qoh(self, site:str, payload: dict, convert: bool=True, conn = None):
recordset = ()
count = 0
self_conn = False
with open('application/database_postgres/sql/ItemsModel/paginateItemsWithQOH.sql', 'r+') as file:
sql = file.read().replace("%%site_name%%", site).replace("%%sort_order%%", payload['sort_order'])
sql_count = f"SELECT COUNT(*) FROM {site}_{self.table_name} items WHERE items.item_search_string LIKE '%%' || %(search_string)s || '%%';"
recordset = ()
count = 0
try:
if not conn:
database_config = config.config()
conn = psycopg2.connect(**database_config)
conn.autocommit = True
self_conn = True
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchall()
if rows and convert:
recordset = [tupleDictionaryFactory(cur.description, row) for row in rows]
if rows and not convert:
recordset = rows
cur.execute(sql_count, payload)
count = cur.fetchone()[0]
if self_conn:
conn.close()
return recordset, count
except Exception as error:
raise DatabaseError(error, payload, sql)
@classmethod
def paginate_items_for_modal(self, site:str, payload: dict, convert: bool=True, conn = None):
recordset = ()
count = 0
self_conn = False
with open('application/database_postgres/sql/ItemsModel/paginateItemsForModal.sql', 'r+') as file:
sql = file.read().replace("%%site_name%%", site).replace("%%sort_order%%", payload['sort_order'])
sql_count = f"SELECT COUNT(*) FROM {site}_{self.table_name} items WHERE items.item_search_string LIKE '%%' || %(search_string)s || '%%';"
recordset = ()
count = 0
try:
if not conn:
database_config = config.config()
conn = psycopg2.connect(**database_config)
conn.autocommit = True
self_conn = True
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchall()
if rows and convert:
recordset = [tupleDictionaryFactory(cur.description, row) for row in rows]
if rows and not convert:
recordset = rows
cur.execute(sql_count, payload)
count = cur.fetchone()[0]
if self_conn:
conn.close()
return recordset, count
except Exception as error:
raise DatabaseError(error, payload, sql)

View File

@ -7,7 +7,8 @@ class LocationsModel(BaseModel):
@dataclass @dataclass
class Payload(BasePayload): class Payload(BasePayload):
uuid: str location_shortname: str
name: str location_name: str
zone_id: int zone_uuid: str

View File

@ -4,12 +4,14 @@ from application.database_postgres.BaseModel import BasePayload, BaseModel
class LogisticsInfoModel(BaseModel): class LogisticsInfoModel(BaseModel):
table_name = "logistics_info" table_name = "logistics_info"
primary_key_type = "uuid"
primary_key = "item_uuid"
@dataclass @dataclass
class Payload(BasePayload): class Payload(BasePayload):
barcode: str item_uuid: str
primary_location: int item_primary_location: str = None
primary_zone: int item_primary_zone: str = None
auto_issue_location: int item_auto_issue_location: str = None
auto_issue_zone: int item_auto_issue_zone: str = None

View File

@ -0,0 +1,50 @@
from dataclasses import dataclass, field
import json
import config
import psycopg2
from application.database_postgres.BaseModel import BasePayload, BaseModel, DatabaseError, tupleDictionaryFactory
class RolesModel(BaseModel):
table_name = "roles"
primary_key = "role_uuid"
@dataclass
class Payload(BasePayload):
role_name: str
role_description: str
role_site_uuid: str
role_flags: dict = field(default_factory=dict)
def payload_dictionary(self):
payload = super().payload_dictionary()
payload['role_flags'] = json.dumps(self.role_flags)
return payload
@classmethod
def delete_tuples(self, payload: tuple, convert: bool = True, conn=None):
deleted = ()
self_conn = False
sql = f"WITH deleted_rows AS (DELETE FROM {self.table_name} WHERE {self.primary_key} IN ({','.join(['%s'] * len(payload))}) RETURNING *) SELECT * FROM deleted_rows;"
try:
if not conn:
database_config = config.config()
conn = psycopg2.connect(**database_config)
conn.autocommit = True
self_conn = True
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchall()
if rows and convert:
deleted = [tupleDictionaryFactory(cur.description, r) for r in rows]
elif rows and not convert:
deleted = rows
if self_conn:
conn.commit()
conn.close()
return deleted
except Exception as error:
raise DatabaseError(error, payload, sql)

View File

@ -0,0 +1,121 @@
from dataclasses import dataclass, field
import json
import datetime
import psycopg2
from application.database_postgres.BaseModel import (
BasePayload, BaseModel, tupleDictionaryFactory, DatabaseError, updateStringFactory
)
import config
class SitesModel(BaseModel):
table_name = "sites"
primary_key = "site_uuid"
primary_key_type = "uuid"
site_agnostic = True
@dataclass
class Payload(BasePayload):
site_name: str
site_description: str
site_created_by: str
site_default_zone_uuid: str = None
site_default_auto_issue_location_uuid: str = None
site_default_primary_location_uuid: str = None
site_created_on: datetime.datetime = field(init=False)
site_flags: dict = field(default_factory=dict)
def __post_init__(self):
self.site_created_on = datetime.datetime.now()
def payload_dictionary(self):
payload = super().payload_dictionary()
payload['site_flags'] = json.dumps(self.site_flags)
return payload
@classmethod
def delete_tuples(self, payload: tuple, convert: bool = True, conn=None):
deleted = ()
self_conn = False
sql = f"WITH deleted_rows AS (DELETE FROM {self.table_name} WHERE {self.primary_key} IN ({','.join(['%s'] * len(payload))}) RETURNING *) SELECT * FROM deleted_rows;"
try:
if not conn:
database_config = config.config()
conn = psycopg2.connect(**database_config)
conn.autocommit = True
self_conn = True
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchall()
if rows and convert:
deleted = [tupleDictionaryFactory(cur.description, r) for r in rows]
elif rows and not convert:
deleted = rows
if self_conn:
conn.commit()
conn.close()
return deleted
except Exception as error:
raise DatabaseError(error, payload, sql)
@classmethod
def update_tuple(self, payload: dict, convert=True, conn=None):
""" payload (dict): {'key': row_id, 'update': {... column_to_update: value_to_update_to...}} """
updated = ()
self_conn = False
set_clause, values = updateStringFactory(payload['update'])
values.append(payload['key'])
sql = f"UPDATE {self.table_name} SET {set_clause} WHERE {self.primary_key}=%s RETURNING *;"
try:
if not conn:
database_config = config.config()
conn = psycopg2.connect(**database_config)
conn.autocommit = False
self_conn = True
with conn.cursor() as cur:
cur.execute(sql, values)
rows = cur.fetchone()
if rows and convert:
updated = tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
updated = rows
if self_conn:
conn.commit()
conn.close()
return updated
except Exception as error:
raise DatabaseError(error, payload, sql)
@classmethod
def select_all(self, payload: dict, convert=True, conn=None):
record = ()
self_conn = False
sql = f"SELECT * FROM {self.table_name} WHERE {self.primary_key}=%(key)s;"
try:
if not conn:
database_config = config.config()
conn = psycopg2.connect(**database_config)
conn.autocommit = False
self_conn = True
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchone()
if rows and convert:
record = tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
record = rows
if self_conn:
conn.commit()
conn.close()
return record
except Exception as error:
raise DatabaseError(error, payload, sql)

View File

@ -6,20 +6,25 @@ from application.database_postgres.BaseModel import BasePayload, BaseModel
class TransactionsModel(BaseModel): class TransactionsModel(BaseModel):
table_name = "transactions" table_name = "transactions"
primary_key = "item_uuid"
primary_key_type = "uuid"
@dataclass @dataclass
class Payload(BasePayload): class Payload(BasePayload):
timestamp: datetime.datetime item_uuid: str
logistics_info_id: int transaction_created_by: str
barcode: str transaction_name: str
name: str
transaction_type: str transaction_type: str
quantity: float transaction_created_at: datetime.datetime = field(init=False)
description: str transaction_quantity: float = 0.00
user_id: int transaction_description: str = ''
data: dict = field(default_factory=dict) transaction_cost: float = 0.00
transaction_data: dict = field(default_factory=dict)
def __post_init__(self):
self.transaction_created_at = datetime.datetime.now()
def payload_dictionary(self): def payload_dictionary(self):
payload = super().payload_dictionary() payload = super().payload_dictionary()
payload['data'] = json.dumps(self.data) payload['transaction_data'] = json.dumps(self.transaction_data)
return payload return payload

View File

@ -0,0 +1,17 @@
from dataclasses import dataclass, field
from application.database_postgres.BaseModel import BasePayload, BaseModel, lst2pgarr, tupleDictionaryFactory, DatabaseError
class UnitsModel(BaseModel):
table_name = "units"
primary_key = "units_uuid"
primary_key_type = "uuid"
site_agnostic = True
@dataclass
class Payload(BasePayload):
unit_plural:str
unit_single:str
unit_fullname: str
unit_description: str

View File

@ -0,0 +1,82 @@
from dataclasses import dataclass, field
import json
import datetime
import psycopg2
from application.database_postgres.BaseModel import BasePayload, BaseModel, lst2pgarr, tupleDictionaryFactory, DatabaseError
import config
class UsersModel(BaseModel):
table_name = "users"
primary_key = "user_uuid"
primary_key_type = "uuid"
site_agnostic = True
@dataclass
class Payload(BasePayload):
user_name:str
user_password:str
user_email: str
user_flags: dict = field(default_factory=dict)
user_favorites: dict = field(default_factory=dict)
user_sites: list = field(default_factory=list)
user_roles: list = field(default_factory=list)
user_is_system_admin: bool = False
user_row_type: str = "user"
user_profile_pic_url: str = ""
user_login_type: str = "Internal"
user_joined_on: datetime.datetime = field(init=False)
def __post_init__(self):
self.creation_date = datetime.datetime.now()
def payload_dictionary(self):
payload = super().payload_dictionary()
payload['user_flags'] = json.dumps(self.user_flags)
payload['user_favorites'] = json.dumps(self.user_favorites)
payload['user_sites'] = lst2pgarr(self.user_sites)
payload['user_roles'] = lst2pgarr(self.user_roles)
return payload
@staticmethod
def washUserDictionary(user):
return {
'user_uuid': user['user_uuid'],
'user_name': user['user_name'],
'user_sites': user['user_sites'],
'user_roles': user['user_roles'],
'user_is_system_admin': user['user_is_system_admin'],
'user_flags': user['user_flags'],
'user_profile_pic_url': user['user_profile_pic_url'],
'user_login_type': user['user_login_type']
}
@classmethod
def select_tuple_by_username(self, payload: dict, convert: bool = True, conn=None):
record = ()
self_conn = False
sql = f"SELECT * FROM {self.table_name} WHERE user_name = %(key)s"
try:
if not conn:
database_config = config.config()
conn = psycopg2.connect(**database_config)
conn.autocommit = True
self_conn = True
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchone()
if rows and convert:
record = tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
record = rows
if self_conn:
conn.commit()
conn.close()
return record
except Exception as error:
raise DatabaseError(error, payload, sql)

View File

@ -9,10 +9,10 @@ class VendorsModel(BaseModel):
@dataclass @dataclass
class Payload(BasePayload): class Payload(BasePayload):
vendor_name: str vendor_name: str
created_by: int vendor_created_by: str
vendor_address: str = "" vendor_address: str = ""
creation_date: datetime.datetime = field(init=False) vendor_creation_date: datetime.datetime = field(init=False)
phone_number: str = "" vendor_phone_number: str = ""
def __post_init__(self): def __post_init__(self):
self.creation_date = datetime.datetime.now() self.vendor_creation_date = datetime.datetime.now()

View File

@ -7,6 +7,6 @@ class ZonesModel(BaseModel):
@dataclass @dataclass
class Payload(BasePayload): class Payload(BasePayload):
name: str zone_name: str
description: str = "" zone_description: str = ""

View File

@ -5,5 +5,5 @@ CREATE TABLE IF NOT EXISTS %%site_name%%_cost_layers (
layer_cost FLOAT8 DEFAULT 0.00 NOT NULL, layer_cost FLOAT8 DEFAULT 0.00 NOT NULL,
layer_currency_type VARCHAR(16) DEFAULT 'USD' NOT NULL, layer_currency_type VARCHAR(16) DEFAULT 'USD' NOT NULL,
layer_expires TIMESTAMP DEFAULT NULL, layer_expires TIMESTAMP DEFAULT NULL,
layer_vendor INTEGER DEFAULT 0 NOT NULL layer_vendor UUID DEFAULT NULL
); );

View File

@ -1,6 +1,6 @@
CREATE TABLE IF NOT EXISTS %%site_name%%_item_info ( CREATE TABLE IF NOT EXISTS %%site_name%%_item_info (
item_uuid UUID PRIMARY KEY REFERENCES %%site_name%%_items(item_uuid) ON DELETE CASCADE, item_uuid UUID PRIMARY KEY REFERENCES %%site_name%%_items(item_uuid) ON DELETE CASCADE,
item_uom INTEGER NOT NULL, item_uom UUID DEFAULT NULL REFERENCES units(unit_uuid) ON DELETE SET NULL,
item_packaging VARCHAR(255) DEFAULT '' NOT NULL, item_packaging VARCHAR(255) DEFAULT '' NOT NULL,
item_uom_quantity FLOAT8 DEFAULT 0.00 NOT NULL, item_uom_quantity FLOAT8 DEFAULT 0.00 NOT NULL,
item_cost FLOAT8 DEFAULT 0.00 NOT NULL, item_cost FLOAT8 DEFAULT 0.00 NOT NULL,

View File

@ -10,8 +10,5 @@ CREATE TABLE IF NOT EXISTS %%site_name%%_items(
item_category VARCHAR(255) NOT NULL, item_category VARCHAR(255) NOT NULL,
item_search_string TEXT DEFAULT '' NOT NULL, item_search_string TEXT DEFAULT '' NOT NULL,
item_inactive BOOLEAN DEFAULT false NOT NULL, item_inactive BOOLEAN DEFAULT false NOT NULL,
CONSTRAINT fk_brand UNIQUE(item_name)
FOREIGN KEY(item_brand_uuid)
REFERENCES %%site_name%%_brands(brand_uuid)
ON DELETE SET NULL
); );

View File

@ -0,0 +1,8 @@
CREATE TABLE IF NOT EXISTS roles(
role_uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4() NOT NULL,
role_name VARCHAR(255) NOT NULL,
role_description TEXT DEFAULT '' NOT NULL,
role_site_uuid UUID REFERENCES sites(site_uuid) ON DELETE CASCADE NOT NULL,
role_flags JSONB DEFAULT '{}' NOT NULL,
UNIQUE(role_name, role_site_uuid)
);

View File

@ -0,0 +1,12 @@
CREATE TABLE IF NOT EXISTS sites (
site_uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4() NOT NULL,
site_name VARCHAR(120) NOT NULL,
site_description TEXT DEFAULT '' NOT NULL,
site_created_on TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL,
site_created_by UUID REFERENCES users(user_uuid) ON DELETE SET NULL,
site_flags JSONB DEFAULT '{}' NOT NULL,
site_default_zone_uuid UUID DEFAULT NULL,
site_default_auto_issue_location_uuid UUID DEFAULT NULL,
site_default_primary_location_uuid UUID DEFAULT NULL,
UNIQUE(site_name)
);

View File

@ -6,6 +6,6 @@ CREATE TABLE IF NOT EXISTS %%site_name%%_transactions (
transaction_quantity FLOAT8 DEFAULT 0.00 NOT NULL, transaction_quantity FLOAT8 DEFAULT 0.00 NOT NULL,
transaction_description TEXT DEFAULT '' NOT NULL, transaction_description TEXT DEFAULT '' NOT NULL,
transaction_cost FLOAT8 DEFAULT 0.00 NOT NULL, transaction_cost FLOAT8 DEFAULT 0.00 NOT NULL,
transaction_created_by INTEGER NOT NULL, transaction_created_by UUID DEFAULT NULL REFERENCES users(user_uuid) ON DELETE SET NULL,
transaction_data JSONB DEFAULT '{}' NOT NULL transaction_data JSONB DEFAULT '{}' NOT NULL
); );

View File

@ -0,0 +1,10 @@
CREATE TABLE IF NOT EXISTS units (
unit_uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
unit_plural VARCHAR(32) NOT NULL,
unit_single VARCHAR(32) NOT NULL,
unit_fullname VARCHAR(255) NOT NULL,
unit_description TEXT DEFAULT '' NOT NULL,
unique(unit_plural),
unique(unit_single),
unique(unit_fullname)
);

View File

@ -0,0 +1,17 @@
CREATE TABLE IF NOT EXISTS users(
user_uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4() NOT NULL,
user_name VARCHAR(255) NOT NULL,
user_password VARCHAR(255) DEFAULT NULL,
user_email VARCHAR(255) UNIQUE NOT NULL,
user_favorites JSONB DEFAULT '{}' NOT NULL,
user_sites UUID [] DEFAULT '{}' NOT NULL,
user_roles UUID [] DEFAULT '{}' NOT NULL,
user_is_system_admin BOOLEAN DEFAULT FALSE NOT NULL,
user_flags JSONB DEFAULT '{}' NOT NULL,
user_row_type VARCHAR(50) DEFAULT 'user' NOT NULL,
user_profile_pic_url VARCHAR(255) DEFAULT '' NOT NULL,
user_login_type VARCHAR(32) DEFAULT 'Internal' NOT NULL,
UNIQUE(user_name),
CHECK (user_email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$')
);

View File

@ -4,6 +4,6 @@ CREATE TABLE IF NOT EXISTS %%site_name%%_vendors (
vendor_address VARCHAR(255) DEFAULT '' NOT NULL, vendor_address VARCHAR(255) DEFAULT '' NOT NULL,
vendor_phone_number VARCHAR(32) DEFAULT '' NOT NULL, vendor_phone_number VARCHAR(32) DEFAULT '' NOT NULL,
vendor_creation_date TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL, vendor_creation_date TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL,
vendor_created_by INTEGER NOT NULL, vendor_created_by UUID NOT NULL,
UNIQUE(vendor_name, vendor_address, vendor_phone_number) UNIQUE(vendor_name, vendor_address, vendor_phone_number)
); );

View File

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

View File

@ -1,4 +1,4 @@
INSERT INTO %%site_name%%_brands INSERT INTO %%site_name%%_brands
(name) (brand_name)
VALUES (%(name)s) VALUES (%(brand_name)s)
RETURNING *; RETURNING *;

View File

@ -1,4 +1,18 @@
INSERT INTO %%site_name%%_food_info INSERT INTO %%site_name%%_food_info
(ingrediants, food_groups, nutrients, expires, default_expiration) (
VALUES (%(ingrediants)s, %(food_groups)s, %(nutrients)s, %(expires)s, %(default_expiration)s) item_uuid,
item_food_groups,
item_ingredients,
item_nutrients,
item_expires,
item_default_expiration
)
VALUES (
%(item_uuid)s,
%(item_food_groups)s,
%(item_ingredients)s,
%(item_nutrients)s,
%(item_expires)s,
%(item_default_expiration)s
)
RETURNING *; RETURNING *;

View File

@ -1,4 +1,24 @@
INSERT INTO %%site_name%%_item_info INSERT INTO %%site_name%%_item_info
(barcode, packaging, uom_quantity, uom, cost, safety_stock, lead_time_days, ai_pick, prefixes) (
VALUES (%(barcode)s, %(packaging)s, %(uom_quantity)s, %(uom)s, %(cost)s, %(safety_stock)s, %(lead_time_days)s, %(ai_pick)s, %(prefixes)s) item_uuid,
item_uom,
item_packaging,
item_uom_quantity,
item_cost,
item_safety_stock,
item_lead_time_days,
item_ai_pick,
item_prefixes
)
VALUES(
%(item_uuid)s,
%(item_uom)s,
%(item_packaging)s,
%(item_uom_quantity)s,
%(item_cost)s,
%(item_safety_stock)s,
%(item_lead_time_days)s,
%(item_ai_pick)s,
%(item_prefixes)s
)
RETURNING *; RETURNING *;

View File

@ -1,4 +1,4 @@
INSERT INTO %%site_name%%_item_locations INSERT INTO %%site_name%%_item_locations
(part_id, location_id, quantity_on_hand, cost_layers) (item_uuid, location_uuid, item_quantity_on_hand)
VALUES (%(part_id)s, %(location_id)s, %(quantity_on_hand)s, %(cost_layers)s) VALUES (%(item_uuid)s, %(location_uuid)s, %(item_quantity_on_hand)s)
RETURNING *; RETURNING *;

View File

@ -1,7 +1,27 @@
INSERT INTO %%site_name%%_items INSERT INTO %%site_name%%_items
(barcode, item_name, brand, description, tags, links, item_info_id, item_info_uuid, (
logistics_info_id, logistics_info_uuid, food_info_id, food_info_uuid, row_type, item_type, search_string) item_category,
VALUES(%(barcode)s, %(item_name)s, %(brand)s, %(description)s, %(tags)s, %(links)s, %(item_info_id)s, %(item_info_uuid)s, item_name,
%(logistics_info_id)s, %(logistics_info_uuid)s, %(food_info_id)s, %(food_info_uuid)s, item_created_at,
%(row_type)s, %(item_type)s, %(search_string)s) item_updated_at,
item_description,
item_tags,
item_links,
item_brand_uuid,
item_search_string,
item_inactive
)
VALUES(
%(item_category)s,
%(item_name)s,
%(item_created_at)s,
%(item_updated_at)s,
%(item_description)s,
%(item_tags)s,
%(item_links)s,
%(item_brand_uuid)s,
%(item_search_string)s,
%(item_inactive)s
)
RETURNING *; RETURNING *;

View File

@ -1,4 +1,4 @@
INSERT INTO %%site_name%%_locations INSERT INTO %%site_name%%_locations
(uuid, name, zone_id) (location_shortname, location_name, zone_uuid)
VALUES (%s, %s, %s) VALUES (%(location_shortname)s, %(location_name)s, %(zone_uuid)s)
RETURNING *; RETURNING *;

View File

@ -1,4 +1,16 @@
INSERT INTO %%site_name%%_logistics_info INSERT INTO %%site_name%%_logistics_info
(barcode, primary_location, primary_zone, auto_issue_location, auto_issue_zone) (
VALUES (%(barcode)s, %(primary_location)s, %(primary_zone)s, %(auto_issue_location)s, %(auto_issue_zone)s) item_uuid,
item_primary_location,
item_primary_zone,
item_auto_issue_location,
item_auto_issue_zone
)
VALUES (
%(item_uuid)s,
%(item_primary_location)s,
%(item_primary_zone)s,
%(item_auto_issue_location)s,
%(item_auto_issue_zone)s
)
RETURNING *; RETURNING *;

View File

@ -0,0 +1,4 @@
INSERT INTO roles
(role_name, role_description, role_site_uuid, role_flags)
VALUES (%(role_name)s, %(role_description)s, %(role_site_uuid)s, %(role_flags)s)
RETURNING *;

View File

@ -0,0 +1,7 @@
INSERT INTO sites
(site_name, site_description, site_created_by, site_default_zone_uuid, site_default_auto_issue_location_uuid,
site_default_primary_location_uuid, site_created_on, site_flags)
VALUES (%(site_name)s, %(site_description)s, %(site_created_by)s, %(site_default_zone_uuid)s,
%(site_default_auto_issue_location_uuid)s, %(site_default_primary_location_uuid)s,
%(site_created_on)s, %(site_flags)s)
RETURNING *;

View File

@ -1,6 +1,24 @@
INSERT INTO %%site_name%%_transactions INSERT INTO %%site_name%%_transactions
(timestamp, logistics_info_id, barcode, name, transaction_type, (
quantity, description, user_id, data) item_uuid,
VALUES (%(timestamp)s, %(logistics_info_id)s, %(barcode)s, %(name)s, %(transaction_type)s, transaction_created_by,
%(quantity)s, %(description)s, %(user_id)s, %(data)s) transaction_name,
transaction_type,
transaction_created_at,
transaction_quantity,
transaction_description,
transaction_cost,
transaction_data
)
VALUES (
%(item_uuid)s,
%(transaction_created_by)s,
%(transaction_name)s,
%(transaction_type)s,
%(transaction_created_at)s,
%(transaction_quantity)s,
%(transaction_description)s,
%(transaction_cost)s,
%(transaction_data)s
)
RETURNING *; RETURNING *;

View File

@ -0,0 +1,3 @@
INSERT INTO units (unit_plural, unit_single, unit_fullname, unit_description)
VALUES (%(unit_plural)s, %(unit_single)s,%(unit_fullname)s,%(unit_description)s)
RETURNING *;

View File

@ -0,0 +1,3 @@
INSERT INTO users (user_name, user_password, user_email)
VALUES (%(user_name)s, %(user_password)s, %(user_email)s)
RETURNING *;

View File

@ -1,4 +1,4 @@
INSERT INTO %%site_name%%_vendors INSERT INTO %%site_name%%_vendors
(vendor_name, vendor_address, creation_date, created_by, phone_number) (vendor_name, vendor_address, vendor_creation_date, vendor_created_by, vendor_phone_number)
VALUES (%(vendor_name)s, %(vendor_address)s, %(creation_date)s, %(created_by)s, %(phone_number)s) VALUES (%(vendor_name)s, %(vendor_address)s, %(vendor_creation_date)s, %(vendor_created_by)s, %(vendor_phone_number)s)
RETURNING *; RETURNING *;

View File

@ -1,4 +1,4 @@
INSERT INTO %%site_name%%_zones INSERT INTO %%site_name%%_zones
(name, description) (zone_name, zone_description)
VALUES (%(name)s, %(description)s) VALUES (%(zone_name)s, %(zone_description)s)
RETURNING *; RETURNING *;

View File

@ -0,0 +1,17 @@
WITH sum_cte AS (
SELECT items.item_uuid, SUM(items_locations.item_quantity_on_hand)::FLOAT8 AS total_sum
FROM %%site_name%%_item_locations items_locations
JOIN %%site_name%%_items items ON items_locations.item_uuid = items.item_uuid
GROUP BY items.item_uuid
)
SELECT items.item_uuid, items.item_description, items.item_name, sum_cte.total_sum as quantity_on_hand, units.unit_fullname
FROM %%site_name%%_items items
LEFT JOIN sum_cte ON items.item_uuid = sum_cte.item_uuid
LEFT JOIN %%site_name%%_item_info item_info ON items.item_uuid = item_info.item_uuid
LEFT JOIN units ON item_info.item_uom = units.unit_uuid
WHERE items.item_search_string LIKE '%%' || %(search_string)s || '%%'
AND items.item_inactive IS false
ORDER BY %%sort_order%%
LIMIT %(limit)s OFFSET %(offset)s;

View File

@ -0,0 +1,7 @@
SELECT items.item_uuid, items.item_name, units.unit_uuid as uom FROM %%site_name%%_items items
LEFT JOIN %%site_name%%_item_info item_info ON item_info.item_uuid = items.item_uuid
LEFT JOIN units ON units.unit_uuid = item_info.item_uom
WHERE items.item_search_string LIKE '%%' || %(search_string)s || '%%'
ANd items.item_inactive IS false
ORDER BY %%sort_order%%
LIMIT %(limit)s OFFSET %(offset)s;

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More