Transferred posting new item to new api

This commit is contained in:
Jadowyne Ulve 2025-05-31 17:37:17 -05:00
parent 5933cc7338
commit aec8f85a4d
16 changed files with 927 additions and 81 deletions

View File

@ -0,0 +1,461 @@
from dataclasses import dataclass, field
import json, datetime
from database import lst2pgarr
@dataclass
class LogisticsInfoPayload:
barcode: str
primary_location: int
primary_zone: int
auto_issue_location: int
auto_issue_zone: int
def payload(self):
return (self.barcode,
self.primary_location,
self.primary_zone,
self.auto_issue_location,
self.auto_issue_zone)
@dataclass
class ItemInfoPayload:
barcode: str
packaging: str = ""
uom_quantity: float = 1.0
uom: int = 1
cost: float = 0.0
safety_stock: float = 0.0
lead_time_days: float = 0.0
ai_pick: bool = False
prefixes: list = field(default_factory=list)
def __post_init__(self):
if not isinstance(self.barcode, str):
raise TypeError(f"barcode must be of type str; not {type(self.barcode)}")
def payload(self):
return (
self.barcode,
self.packaging,
self.uom_quantity,
self.uom,
self.cost,
self.safety_stock,
self.lead_time_days,
self.ai_pick,
lst2pgarr(self.prefixes)
)
@dataclass
class FoodInfoPayload:
food_groups: list = field(default_factory=list)
ingrediants: list = field(default_factory=list)
nutrients: dict = field(default_factory=dict)
expires: bool = False
default_expiration: float = 0.0
def payload(self):
return (
lst2pgarr(self.food_groups),
lst2pgarr(self.ingrediants),
json.dumps(self.nutrients),
self.expires,
self.default_expiration
)
@dataclass
class ItemsPayload:
barcode: str
item_name: str
item_info_id: int
logistics_info_id: int
food_info_id: int
brand: int = 0
description: str = ""
tags: list = field(default_factory=list)
links: dict = field(default_factory=dict)
row_type: str = ""
item_type: str = ""
search_string: str =""
def payload(self):
return (
self.barcode,
self.item_name,
self.brand,
self.description,
lst2pgarr(self.tags),
json.dumps(self.links),
self.item_info_id,
self.logistics_info_id,
self.food_info_id,
self.row_type,
self.item_type,
self.search_string
)
# done
@dataclass
class TransactionPayload:
timestamp: datetime.datetime
logistics_info_id: int
barcode: str
name: str
transaction_type: str
quantity: float
description: str
user_id: int
data: dict = field(default_factory=dict)
def payload(self):
return (
self.timestamp,
self.logistics_info_id,
self.barcode,
self.name,
self.transaction_type,
self.quantity,
self.description,
self.user_id,
json.dumps(self.data)
)
@dataclass
class CostLayerPayload:
aquisition_date: datetime.datetime
quantity: float
cost: float
currency_type: str
vendor: int = 0
expires: datetime.datetime = None
def payload(self):
return (
self.aquisition_date,
self.quantity,
self.cost,
self.currency_type,
self.expires,
self.vendor
)
@dataclass
class ItemLinkPayload:
barcode: str
link: int
data: dict = field(default_factory=dict)
conv_factor: float = 1
def __post_init__(self):
if not isinstance(self.barcode, str):
raise TypeError(f"barcode must be of type str; not {type(self.barocde)}")
if not isinstance(self.link, int):
raise TypeError(f"link must be of type str; not {type(self.link)}")
def payload(self):
return (
self.barcode,
self.link,
json.dumps(self.data),
self.conv_factor
)
@dataclass
class GroupPayload:
name: str
description: str
group_type: str = "plain"
def payload(self):
return (
self.name,
self.description,
self.group_type
)
@dataclass
class GroupItemPayload:
uuid: str
gr_id: int
item_type: str
item_name:str
uom: str
qty: float = 0.0
item_id: int = None
links: dict = field(default_factory=dict)
def payload(self):
return (
self.uuid,
self.gr_id,
self.item_type,
self.item_name,
self.uom,
self.qty,
self.item_id,
json.dumps(self.links)
)
@dataclass
class RecipeItemPayload:
uuid: str
rp_id: int
item_type: str
item_name:str
uom: str
qty: float = 0.0
item_id: int = None
links: dict = field(default_factory=dict)
def payload(self):
return (
self.uuid,
self.rp_id,
self.item_type,
self.item_name,
self.uom,
self.qty,
self.item_id,
json.dumps(self.links)
)
@dataclass
class RecipePayload:
name: str
author: int
description: str
creation_date: datetime.datetime = field(init=False)
instructions: list = field(default_factory=list)
picture_path: str = ""
def __post_init__(self):
self.creation_date = datetime.datetime.now()
def payload(self):
return (
self.name,
self.author,
self.description,
self.creation_date,
lst2pgarr(self.instructions),
self.picture_path
)
@dataclass
class ReceiptItemPayload:
type: str
receipt_id: int
barcode: str
name: str
qty: float = 1.0
uom: str = "each"
data: dict = field(default_factory=dict)
status: str = "Unresolved"
def payload(self):
return (
self.type,
self.receipt_id,
self.barcode,
self.name,
self.qty,
self.uom,
json.dumps(self.data),
self.status
)
@dataclass
class ReceiptPayload:
receipt_id: str
receipt_status: str = "Unresolved"
date_submitted: datetime.datetime = field(init=False)
submitted_by: int = 0
vendor_id: int = 1
files: dict = field(default_factory=dict)
def __post_init__(self):
self.date_submitted = datetime.datetime.now()
def payload(self):
return (
self.receipt_id,
self.receipt_status,
self.date_submitted,
self.submitted_by,
self.vendor_id,
json.dumps(self.files)
)
@dataclass
class ShoppingListItemPayload:
uuid: str
sl_id: int
item_type: str
item_name: str
uom: str
qty: float
item_id: int = None
links: dict = field(default_factory=dict)
def payload(self):
return (
self.uuid,
self.sl_id,
self.item_type,
self.item_name,
self.uom,
self.qty,
self.item_id,
json.dumps(self.links)
)
@dataclass
class ShoppingListPayload:
name: str
description: str
author: int
type: str = "plain"
creation_date: datetime.datetime = field(init=False)
def __post_init__(self):
self.creation_date = datetime.datetime.now()
def payload(self):
return (
self.name,
self.description,
self.author,
self.creation_date,
self.type
)
# DONE
@dataclass
class SitePayload:
site_name: str
site_description: str
site_owner_id: int
default_zone: str = None
default_auto_issue_location: str = None
default_primary_location: str = None
creation_date: datetime.datetime = field(init=False)
flags: dict = field(default_factory=dict)
def __post_init__(self):
self.creation_date = datetime.datetime.now()
def payload(self):
return (
self.site_name,
self.site_description,
self.creation_date,
self.site_owner_id,
json.dumps(self.flags),
self.default_zone,
self.default_auto_issue_location,
self.default_primary_location
)
#DONE
@dataclass
class RolePayload:
role_name:str
role_description:str
site_id: int
flags: dict = field(default_factory=dict)
def payload(self):
return (
self.role_name,
self.role_description,
self.site_id,
json.dumps(self.flags)
)
@dataclass
class ItemLocationPayload:
part_id: int
location_id: int
quantity_on_hand: float = 0.0
cost_layers: list = field(default_factory=list)
def __post_init__(self):
if not isinstance(self.part_id, int):
raise TypeError(f"part_id must be of type int; not {type(self.part_id)}")
if not isinstance(self.location_id, int):
raise TypeError(f"part_id must be of type int; not {type(self.part_id)}")
def payload(self):
return (
self.part_id,
self.location_id,
self.quantity_on_hand,
lst2pgarr(self.cost_layers)
)
@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 __post_init__(self):
self.create_order = [
"logins",
"sites",
"roles",
"units",
"cost_layers",
"linked_items",
"brands",
"food_info",
"item_info",
"zones",
"locations",
"logistics_info",
"transactions",
"item",
"vendors",
"groups",
"group_items",
"receipts",
"receipt_items",
"recipes",
"recipe_items",
"shopping_lists",
"shopping_list_items",
"item_locations",
"conversions",
"sku_prefix"
]
self.drop_order = [
"item_info",
"items",
"cost_layers",
"linked_items",
"transactions",
"brands",
"food_info",
"logistics_info",
"zones",
"locations",
"vendors",
"group_items",
"groups",
"receipt_items",
"receipts",
"recipe_items",
"recipes",
"shopping_list_items",
"shopping_lists",
"item_locations",
"conversions",
"sku_prefix"
]

View File

@ -3,7 +3,6 @@ import config
import psycopg2
import datetime
def getTransactions(site:str, payload: tuple, convert:bool=True):
database_config = config.config()
sql = f"SELECT * FROM {site}_transactions WHERE logistics_info_id=%s LIMIT %s OFFSET %s;"
@ -181,6 +180,23 @@ def getLocation(site:str, payload:tuple, convert:bool=True):
return selected
except Exception as error:
raise postsqldb.DatabaseError(error, payload, sql)
def getZone(site:str, payload:tuple, convert:bool=True):
selected = ()
database_config = config.config()
sql = f"SELECT * FROM {site}_zones WHERE id=%s;"
try:
with psycopg2.connect(**database_config) as conn:
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchone()
if rows and convert:
selected = postsqldb.tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
selected = rows
return selected
except Exception as error:
raise postsqldb.DatabaseError(error, payload, sql)
def paginateZonesBySku(site: str, payload: tuple, convert=True):
database_config = config.config()
@ -513,6 +529,162 @@ def insertItemLocationsTuple(conn, site, payload, convert=True):
except Exception as error:
raise postsqldb.DatabaseError(error, payload, sql)
def insertLogisticsInfoTuple(conn, site, payload, convert=False):
"""insert payload into logistics_info table for site
Args:
conn (_T_connector@connect): Postgresql Connector
site (str):
payload (tuple): (barcode[str], primary_location[str], auto_issue_location[str], dynamic_locations[jsonb],
location_data[jsonb], quantity_on_hand[float])
convert (bool, optional): Determines if to return tuple as dictionary. Defaults to False.
Raises:
DatabaseError:
Returns:
tuple or dict: inserted tuple
"""
logistics_info = ()
with open(f"application/items/sql/insertLogisticsInfoTuple.sql", "r+") as file:
sql = file.read().replace("%%site_name%%", site)
try:
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchone()
if rows and convert:
logistics_info = postsqldb.tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
logistics_info = rows
except Exception as error:
raise postsqldb.DatabaseError(error, payload, sql)
return logistics_info
def insertItemInfoTuple(conn, site, payload, convert=False):
"""inserts payload into the item_info table of site
Args:
conn (_T_connector@connect): Postgresql Connector
site_name (str):
payload (tuple): (barcode[str], linked_items[lst2pgarr], shopping_lists[lst2pgarr], recipes[lst2pgarr], groups[lst2pgarr],
packaging[str], uom[str], cost[float], safety_stock[float], lead_time_days[float], ai_pick[bool])
convert (bool optional): Determines if to return tuple as dictionary. DEFAULTS to False.
Raises:
DatabaseError:
Returns:
tuple or dict: inserted tuple
"""
item_info = ()
with open(f"application/items/sql/insertItemInfoTuple.sql", "r+") as file:
sql = file.read().replace("%%site_name%%", site)
try:
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchone()
if rows and convert:
item_info = postsqldb.tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
item_info = rows
except Exception as error:
raise postsqldb.DatabaseError(error, payload, sql)
return item_info
def insertFoodInfoTuple(conn, site, payload, convert=False):
"""insert payload into food_info table for site
Args:
conn (_T_connector@connect): Postgresql Connector
site (str):
payload (_type_): (ingrediants[lst2pgarr], food_groups[lst2pgarr], nutrients[jsonstr], expires[bool])
convert (bool, optional): Determines if to return tuple as dictionary. Defaults to False.
Raises:
DatabaseError:
Returns:
tuple or dict: inserted tuple
"""
food_info = ()
with open(f"application/items/sql/insertFoodInfoTuple.sql", "r+") as file:
sql = file.read().replace("%%site_name%%", site)
try:
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchone()
if rows and convert:
food_info = postsqldb.tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
food_info = rows
except Exception as error:
raise postsqldb.DatabaseError(error, payload, sql)
return food_info
def insertItemTuple(conn, site, payload, convert=False):
"""insert payload into items table for site
Args:
conn (_T_connector@connect): Postgresql Connector
site (str):
payload (tuple): (barcode[str], item_name[str], brand[int], description[str],
tags[lst2pgarr], links[jsonb], item_info_id[int], logistics_info_id[int],
food_info_id[int], row_type[str], item_type[str], search_string[str])
convert (bool, optional): Determines if to return tuple as a dictionary. Defaults to False.
Raises:
DatabaseError:
Returns:
tuple or dict: inserted tuple
"""
item = ()
with open(f"application/items/sql/insertItemTuple.sql", "r+") as file:
sql = file.read().replace("%%site_name%%", site)
try:
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchone()
if rows and convert:
item = postsqldb.tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
item = rows
except Exception as error:
raise postsqldb.DatabaseError(error, payload, sql)
return item
def insertItemLocationsTuple(conn, site, payload, convert=False):
"""insert payload into item_locations table for site
Args:
conn (_T_connector@connect): Postgresql Connector
site (str):
payload (tuple): (part_id[int], location_id[int], quantity_on_hand[float], cost_layers[lst2pgarr])
convert (bool, optional): Determines if to return tuple as dictionary. Defaults to False.
Raises:
DatabaseError:
Returns:
tuple or dict: inserted tuple
"""
location = ()
with open(f"application/items/sql/insertItemLocationsTuple.sql", "r+") as file:
sql = file.read().replace("%%site_name%%", site)
try:
with conn.cursor() as cur:
cur.execute(sql, payload)
rows = cur.fetchone()
if rows and convert:
location = postsqldb.tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
location = rows
except Exception as error:
raise postsqldb.DatabaseError(error, payload, sql)
return location
def selectItemLocationsTuple(site_name, payload, convert=True):
"""select a single tuple from ItemLocations table for site_name
@ -569,7 +741,37 @@ def selectCostLayersTuple(site_name, payload, convert=True):
return cost_layers
except Exception as error:
return error
def selectSiteTuple(payload, convert=True):
"""Select a single Site from sites using site_name
Args:
conn (_T_connector@connect): Postgresql Connector
payload (tuple): (site_name,)
convert (bool, optional): determines if to return tuple as dictionary. Defaults to False.
Raises:
DatabaseError:
Returns:
tuple or dict: selected tuples
"""
site = ()
database_config = config.config()
select_site_sql = f"SELECT * FROM sites WHERE site_name = %s;"
try:
with psycopg2.connect(**database_config) as conn:
with conn.cursor() as cur:
cur.execute(select_site_sql, payload)
rows = cur.fetchone()
if rows and convert:
site = postsqldb.tupleDictionaryFactory(cur.description, rows)
elif rows and not convert:
site = rows
except Exception as error:
raise postsqldb.DatabaseError(error, payload, select_site_sql)
return site
def postDeleteCostLayer(site_name, payload, convert=True, conn=None):
"""
payload (tuple): (table_to_delete_from, tuple_id)
@ -660,7 +862,6 @@ def postAddTransaction(site, payload, convert=False, conn=None):
except Exception as error:
raise postsqldb.DatabaseError(error, payload, sql)
def postInsertItemLink(site, payload, convert=True, conn=None):
"""insert payload into itemlinks table of site

View File

@ -1,7 +1,22 @@
from flask import Blueprint, request, render_template, redirect, session, url_for, send_file, jsonify, Response
import psycopg2, math, json, datetime, main, copy, requests, process, database, pprint, MyDataclasses
# 3rd Party imports
from flask import (
Blueprint, request, render_template, redirect, session, url_for, send_file, jsonify, Response
)
import psycopg2
import math
import json
import datetime
import copy
import requests
import pprint
# applications imports
from config import config, sites_config
from main import unfoldCostLayers
import process
import database
import main
import MyDataclasses
from user_api import login_required
import application.postsqldb as db
from application.items import database_items
@ -25,14 +40,14 @@ def items():
current_site=session['selected_site'],
sites=sites)
@items_api.route("/item/<id>")
@items_api.route("/<id>")
@login_required
def item(id):
sites = [site[1] for site in main.get_sites(session['user']['sites'])]
database_config = config()
with psycopg2.connect(**database_config) as conn:
units = db.UnitsTable.getAll(conn)
return render_template("items/item_new.html", id=id, units=units, current_site=session['selected_site'], sites=sites)
return render_template("item_new.html", id=id, units=units, current_site=session['selected_site'], sites=sites)
@items_api.route("/transaction")
@login_required
@ -43,14 +58,31 @@ def transaction():
units = db.UnitsTable.getAll(conn)
return render_template("transaction.html", units=units, current_site=session['selected_site'], sites=sites, proto={'referrer': request.referrer})
@items_api.route("/transactions/<id>")
@login_required
def transactions(id):
"""This is the main endpoint to reach the webpage for an items transaction history
---
parameters:
- name: id
in: path
type: integer
required: true
default: all
responses:
200:
description: Returns the transactions.html webpage for the item with passed ID
"""
sites = [site[1] for site in main.get_sites(session['user']['sites'])]
return render_template("transactions.html", id=id, current_site=session['selected_site'], sites=sites)
@items_api.route("/item/<parent_id>/itemLink/<id>")
@items_api.route("/<parent_id>/itemLink/<id>")
@login_required
def itemLink(parent_id, id):
sites = [site[1] for site in main.get_sites(session['user']['sites'])]
return render_template("itemlink.html", current_site=session['selected_site'], sites=sites, proto={'referrer': request.referrer}, id=id)
@items_api.route("/item/getTransactions", methods=["GET"])
@items_api.route("/getTransactions", methods=["GET"])
@login_required
def getTransactions():
""" GET a subquery of transactions by passing a logistics_info_id, limit, and page
@ -71,7 +103,7 @@ def getTransactions():
return jsonify({"transactions": recordset, "end": math.ceil(count/limit), "error": False, "message": ""})
return jsonify({"transactions": recordset, "end": math.ceil(count/limit), "error": True, "message": f"method {request.method} is not allowed."})
@items_api.route("/item/getTransaction", methods=["GET"])
@items_api.route("/getTransaction", methods=["GET"])
@login_required
def getTransaction():
""" GET a transaction from the system by passing an ID
@ -97,7 +129,7 @@ def getTransaction():
return jsonify({"transaction": transaction, "error": False, "message": ""})
return jsonify({"transaction": transaction, "error": True, "message": f"method {request.method} is not allowed."})
@items_api.route("/item/getItem", methods=["GET"])
@items_api.route("/getItem", methods=["GET"])
@login_required
def get_item():
""" GET item from system by passing its ID
@ -183,7 +215,7 @@ def pagninate_items():
return jsonify({'items': items, "end": math.ceil(count/limit), 'error':False, 'message': 'Items Loaded Successfully!'})
return jsonify({'items': items, "end": math.ceil(count/limit), 'error':True, 'message': 'There was a problem loading the items!'})
@items_api.route('/item/getModalItems', methods=["GET"])
@items_api.route('/getModalItems', methods=["GET"])
@login_required
def getModalItems():
""" GET items from the system by passing a page, limit, search_string. For select modals
@ -223,7 +255,7 @@ def getModalItems():
return jsonify({"items":recordset, "end":math.ceil(count/limit), "error":False, "message":"items fetched succesfully!"})
return jsonify({"items":recordset, "end":math.ceil(count/limit), "error":True, "message": f"method {request.method} is not allowed."})
@items_api.route('/item/getPrefixes', methods=["GET"])
@items_api.route('/getPrefixes', methods=["GET"])
@login_required
def getModalPrefixes():
""" GET prefixes from the system by passing page and limit.
@ -259,7 +291,7 @@ def getModalPrefixes():
return jsonify({"prefixes":recordset, "end":math.ceil(count/limit), "error":False, "message":"items fetched succesfully!"})
return jsonify({"prefixes":recordset, "end":math.ceil(count/limit), "error":True, "message":f"method {request.method} is not allowed!"})
@items_api.route('/item/getZonesBySku', methods=["GET"])
@items_api.route('/getZonesBySku', methods=["GET"])
@login_required
def getZonesbySku():
""" GET zones by sku by passing page, limit, item_id
@ -301,7 +333,7 @@ def getZonesbySku():
return jsonify({'zones': zones, 'endpage': math.ceil(count/limit), 'error':False, 'message': f''})
return jsonify({'zones': zones, 'endpage': math.ceil(count/limit), 'error':False, 'message': f'method {request.method} not allowed.'})
@items_api.route('/item/getLocationsBySkuZone', methods=['GET'])
@items_api.route('/getLocationsBySkuZone', methods=['GET'])
@login_required
def getLocationsBySkuZone():
""" GET locations by sku by passing page, limit, item_id, zone_id
@ -351,7 +383,7 @@ def getLocationsBySkuZone():
return jsonify({'locations': locations, 'endpage': math.ceil(count/limit), 'error': False, 'message': f''})
return jsonify({'locations': locations, 'endpage': math.ceil(count/limit), 'error': True, 'message': f'method {request.method} is not allowed.'})
@items_api.route('/item/getBrands', methods=['GET'])
@items_api.route('/getBrands', methods=['GET'])
@login_required
def getBrands():
""" GET brands from the system by passing page, limit
@ -386,7 +418,7 @@ def getBrands():
return jsonify({'brands': brands, 'endpage': math.ceil(count/limit), 'error': True, 'message': f'method {request.method} is not allowed.'})
@items_api.route('/item/updateItem', methods=['POST'])
@items_api.route('/updateItem', methods=['POST'])
@login_required
def updateItem():
""" POST update to item in the system by passing item_id, data
@ -414,7 +446,7 @@ def updateItem():
return jsonify({'error': False, 'message': f'Item was updated successfully!'})
return jsonify({'error': True, 'message': f'method {request.method} is not allowed!'})
@items_api.route('/item/updateItemLink', methods=['POST'])
@items_api.route('/updateItemLink', methods=['POST'])
@login_required
def updateItemLink():
""" UPDATE item link by passing id, conv_factor, barcode, old_conv
@ -462,7 +494,7 @@ def updateItemLink():
return jsonify({'error': True, 'message': f"method {request.method} not allowed."})
@items_api.route('/item/getPossibleLocations', methods=["GET"])
@items_api.route('/getPossibleLocations', methods=["GET"])
@login_required
def getPossibleLocations():
""" GET locations with zones by passing a page and limit
@ -496,7 +528,7 @@ def getPossibleLocations():
return jsonify({'locations': locations, 'end':math.ceil(count/limit), 'error':False, 'message': f'Locations received successfully!'})
return jsonify({'locations': locations, 'end':math.ceil(count/limit), 'error':True, 'message': f'method {request.method} not allowed.'})
@items_api.route('/item/getLinkedItem', methods=["GET"])
@items_api.route('/getLinkedItem', methods=["GET"])
@login_required
def getLinkedItem():
""" GET itemlink from system by passing an ID
@ -521,7 +553,7 @@ def getLinkedItem():
return jsonify({'linked_item': linked_item, 'error': False, 'message': 'Linked Item added!!'})
return jsonify({'linked_item': linked_item, 'error': True, 'message': f'method {request.method} not allowed'})
@items_api.route('/item/addLinkedItem', methods=["POST"])
@items_api.route('/addLinkedItem', methods=["POST"])
@login_required
def addLinkedItem():
""" POST a link between items by passing a parent_id, a child_id, conv_factor
@ -569,7 +601,7 @@ def addLinkedItem():
return jsonify({'error': False, 'message': 'Linked Item added!!'})
return jsonify({'error': True, 'message': 'These was an error with adding to the linked list!'})
@items_api.route('/items/addBlankItem', methods=["POST"])
@items_api.route('/addBlankItem', methods=["POST"])
def addBlankItem():
if request.method == "POST":
data = {
@ -577,20 +609,16 @@ def addBlankItem():
'name': request.get_json()['name'],
'subtype': request.get_json()['subtype']
}
pprint.pprint(data)
database_config = config()
site_name = session['selected_site']
user_id = session['user_id']
try:
with psycopg2.connect(**database_config) as conn:
process.postNewBlankItem(conn, site_name, user_id, data)
except Exception as error:
conn.rollback()
return jsonify({'error': True, 'message': error})
items_processes.postNewBlankItem(site_name, user_id, data)
return jsonify({'error': False, 'message': 'Item added!!'})
return jsonify({'error': True, 'message': 'These was an error with adding Item!'})
@items_api.route('/items/addSKUPrefix', methods=["POST"])
@items_api.route('/addSKUPrefix', methods=["POST"])
def addSKUPrefix():
if request.method == "POST":
database_config = config()
@ -609,7 +637,7 @@ def addSKUPrefix():
return jsonify({'error': False, 'message': 'Prefix added!!'})
return jsonify({'error': True, 'message': 'These was an error with adding this Prefix!'})
@items_api.route('/item/addConversion', methods=['POST'])
@items_api.route('/addConversion', methods=['POST'])
def addConversion():
if request.method == "POST":
item_id = request.get_json()['parent_id']
@ -627,7 +655,7 @@ def addConversion():
return jsonify(error=False, message="Conversion was added successfully")
return jsonify(error=True, message="Unable to save this conversion, ERROR!")
@items_api.route('/item/deleteConversion', methods=['POST'])
@items_api.route('/deleteConversion', methods=['POST'])
def deleteConversion():
if request.method == "POST":
conversion_id = request.get_json()['conversion_id']
@ -640,7 +668,7 @@ def deleteConversion():
return jsonify(error=False, message="Conversion was deleted successfully")
return jsonify(error=True, message="Unable to delete this conversion, ERROR!")
@items_api.route('/item/updateConversion', methods=['POST'])
@items_api.route('/updateConversion', methods=['POST'])
def updateConversion():
if request.method == "POST":
conversion_id = request.get_json()['conversion_id']
@ -653,7 +681,7 @@ def updateConversion():
return jsonify(error=False, message="Conversion was updated successfully")
return jsonify(error=True, message="Unable to save this conversion, ERROR!")
@items_api.route('/item/addPrefix', methods=['POST'])
@items_api.route('/addPrefix', methods=['POST'])
def addPrefix():
if request.method == "POST":
item_info_id = request.get_json()['parent_id']
@ -670,7 +698,7 @@ def addPrefix():
return jsonify(error=False, message="Prefix was added successfully")
return jsonify(error=True, message="Unable to save this prefix, ERROR!")
@items_api.route('/item/deletePrefix', methods=['POST'])
@items_api.route('/deletePrefix', methods=['POST'])
def deletePrefix():
if request.method == "POST":
item_info_id = request.get_json()['item_info_id']
@ -685,7 +713,7 @@ def deletePrefix():
return jsonify(error=False, message="Prefix was deleted successfully")
return jsonify(error=True, message="Unable to delete this prefix, ERROR!")
@items_api.route('/item/refreshSearchString', methods=['POST'])
@items_api.route('/refreshSearchString', methods=['POST'])
def refreshSearchString():
if request.method == "POST":
item_id = request.get_json()['item_id']
@ -706,7 +734,7 @@ def refreshSearchString():
return jsonify(error=False, message="Search String was updated successfully")
return jsonify(error=True, message="Unable to update this search string, ERROR!")
@items_api.route('/item/postNewItemLocation', methods=['POST'])
@items_api.route('/postNewItemLocation', methods=['POST'])
def postNewItemLocation():
if request.method == "POST":
item_id = request.get_json()['item_id']

View File

@ -1,9 +1,102 @@
from application.items import database_items
import application.postsqldb as db
import application.database_payloads as dbPayloads
import config
import datetime
import psycopg2
import json
def postNewBlankItem(site_name: str, user_id: int, data: dict, conn=None):
""" data = {'barcode', 'name', 'subtype'}"""
self_conn = False
if not conn:
database_config = config.config()
conn = psycopg2.connect(**database_config)
conn.autocommit = False
self_conn = True
site = database_items.selectSiteTuple((site_name,))
default_zone = database_items.getZone(site_name,(site['default_zone'], ))
default_location = database_items.getLocation(site_name, (site['default_primary_location'],))
uuid = f"{default_zone['name']}@{default_location['name']}"
# create logistics info
logistics_info = dbPayloads.LogisticsInfoPayload(
barcode=data['barcode'],
primary_location=site['default_primary_location'],
primary_zone=site['default_zone'],
auto_issue_location=site['default_auto_issue_location'],
auto_issue_zone=site['default_zone']
)
# create item info
item_info = dbPayloads.ItemInfoPayload(data['barcode'])
# create Food Info
food_info = dbPayloads.FoodInfoPayload()
logistics_info_id = 0
item_info_id = 0
food_info_id = 0
brand_id = 1
logistics_info = database_items.insertLogisticsInfoTuple(conn, site_name, logistics_info.payload(), convert=True)
item_info = database_items.insertItemInfoTuple(conn, site_name, item_info.payload(), convert=True)
food_info = database_items.insertFoodInfoTuple(conn, site_name, food_info.payload(), convert=True)
name = data['name']
name = name.replace("'", "@&apostraphe&")
description = ""
tags = db.lst2pgarr([])
links = json.dumps({})
search_string = f"&&{data['barcode']}&&{name}&&"
item = dbPayloads.ItemsPayload(
data['barcode'],
data['name'],
item_info['id'],
logistics_info['id'],
food_info['id'],
brand=brand_id,
row_type="single",
item_type=data['subtype'],
search_string=search_string
)
item = database_items.insertItemTuple(conn, site_name, item.payload(), convert=True)
with conn.cursor() as cur:
cur.execute(f"SELECT id FROM {site_name}_locations WHERE uuid=%s;", (uuid, ))
location_id = cur.fetchone()[0]
dbPayloads.ItemLocationPayload
item_location = dbPayloads.ItemLocationPayload(item['id'], location_id)
database_items.insertItemLocationsTuple(conn, site_name, item_location.payload())
creation_tuple = dbPayloads.TransactionPayload(
datetime.datetime.now(),
logistics_info['id'],
item['barcode'],
item['item_name'],
"SYSTEM",
0.0,
"Item added to the System!",
user_id,
{'location': uuid}
)
database_items.postAddTransaction(site_name, creation_tuple.payload(), conn=conn)
if self_conn:
conn.commit()
conn.close()
return False
return conn
def postLinkedItem(site, payload):
"""

View File

@ -0,0 +1,4 @@
INSERT INTO %%site_name%%_food_info
(ingrediants, food_groups, nutrients, expires, default_expiration)
VALUES (%s, %s, %s, %s, %s)
RETURNING *;

View File

@ -0,0 +1,4 @@
INSERT INTO %%site_name%%_item_info
(barcode, packaging, uom_quantity, uom, cost, safety_stock, lead_time_days, ai_pick, prefixes)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
RETURNING *;

View File

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

View File

@ -0,0 +1,5 @@
INSERT INTO %%site_name%%_items
(barcode, item_name, brand, description, tags, links, item_info_id, logistics_info_id,
food_info_id, row_type, item_type, search_string)
VALUES(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
RETURNING *;

View File

@ -0,0 +1,4 @@
INSERT INTO %%site_name%%_logistics_info
(barcode, primary_location, primary_zone, auto_issue_location, auto_issue_zone)
VALUES (%s, %s, %s, %s, %s)
RETURNING *;

View File

@ -214,12 +214,12 @@ async function updateTableElements(){
let viewOp = document.createElement('a')
viewOp.innerHTML = `edit <span uk-icon="icon: pencil"></span>`
viewOp.setAttribute('class', 'uk-button uk-button-default uk-button-small')
viewOp.href = `/item/${items[i].id}`
viewOp.href = `/items/${items[i].id}`
let historyOp = document.createElement('a')
historyOp.innerHTML = `history <span uk-icon="icon: history"></span>`
historyOp.setAttribute('class', 'uk-button uk-button-default uk-button-small')
historyOp.href = `/transactions/${items[i].id}`
historyOp.href = `/items/transactions/${items[i].id}`
buttonGroup.append(viewOp, historyOp)
opsCell.append(buttonGroup)
@ -263,8 +263,8 @@ async function updateListElements(){
let footer = document.createElement('div')
footer.classList.add('uk-card-footer')
footer.innerHTML = `<a style="margin-right: 5px; border-radius: 10px;" class="uk-button uk-button-default uk-button-small" uk-icon="icon: pencil" href="/item/${items[i].id}">edit</a>
<a style="border-radius: 10px;" class="uk-button uk-button-default uk-button-small" uk-icon="icon: history" href="/transactions/${items[i].id}">History</a>`
footer.innerHTML = `<a style="margin-right: 5px; border-radius: 10px;" class="uk-button uk-button-default uk-button-small" uk-icon="icon: pencil" href="/items/${items[i].id}">edit</a>
<a style="border-radius: 10px;" class="uk-button uk-button-default uk-button-small" uk-icon="icon: history" href="/items/transactions/${items[i].id}">History</a>`
listItem.append(header)
if(!items[i].description == ""){

View File

@ -581,7 +581,7 @@ async function updateLinkedItemsTable() {
let editOp = document.createElement('a')
editOp.setAttribute('class', 'uk-button uk-button-default')
editOp.setAttribute('uk-icon', 'icon: pencil')
editOp.setAttribute('href', `/item/${item['id']}/itemLink/${linked_items[i].id}`)
editOp.setAttribute('href', `/items/${item['id']}/itemLink/${linked_items[i].id}`)
opCell.append(editOp)
@ -755,7 +755,7 @@ async function openEditConversionsModal(conversion) {
async function postConversion() {
const response = await fetch(`/item/addConversion`, {
const response = await fetch(`/items/addConversion`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@ -786,7 +786,7 @@ async function postConversion() {
}
async function postConversionUpdate(id, update) {
const response = await fetch(`/item/updateConversion`, {
const response = await fetch(`/items/updateConversion`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@ -816,7 +816,7 @@ async function postConversionUpdate(id, update) {
}
async function deleteConversion(conversion_id) {
const response = await fetch(`/item/deleteConversion`, {
const response = await fetch(`/items/deleteConversion`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@ -858,7 +858,7 @@ async function openAddPrefixesModal() {
}
async function postPrefix(id) {
const response = await fetch(`/item/addPrefix`, {
const response = await fetch(`/items/addPrefix`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@ -887,7 +887,7 @@ async function postPrefix(id) {
}
async function deletePrefix(prefix_id) {
const response = await fetch(`/item/deletePrefix`, {
const response = await fetch(`/items/deletePrefix`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@ -920,7 +920,7 @@ async function deletePrefix(prefix_id) {
let prefix_limit = 2;
async function fetchPrefixes() {
const url = new URL('/item/getPrefixes', window.location.origin);
const url = new URL('/items/getPrefixes', window.location.origin);
url.searchParams.append('page', current_page);
url.searchParams.append('limit', prefix_limit);
const response = await fetch(url);
@ -930,7 +930,7 @@ async function fetchPrefixes() {
let brands_limit = 25;
async function fetchBrands() {
const url = new URL('/item/getBrands', window.location.origin);
const url = new URL('/items/getBrands', window.location.origin);
url.searchParams.append('page', current_page);
url.searchParams.append('limit', brands_limit);
const response = await fetch(url);
@ -940,7 +940,7 @@ async function fetchBrands() {
let items_limit = 25;
async function fetchItems() {
const url = new URL('/item/getModalItems', window.location.origin);
const url = new URL('/items/getModalItems', window.location.origin);
url.searchParams.append('page', current_page);
url.searchParams.append('limit', items_limit);
url.searchParams.append('search_string', search_string);
@ -951,7 +951,7 @@ async function fetchItems() {
let zones_limit = 20;
async function fetchZones(){
const url = new URL('/item/getZonesBySku', window.location.origin);
const url = new URL('/items/getZonesBySku', window.location.origin);
url.searchParams.append('page', current_page);
url.searchParams.append('limit', zones_limit);
url.searchParams.append('item_id', item.id);
@ -962,7 +962,7 @@ async function fetchZones(){
let locations_limit = 10;
async function fetchLocations(logis) {
const url = new URL('/item/getLocationsBySkuZone', window.location.origin);
const url = new URL('/items/getLocationsBySkuZone', window.location.origin);
url.searchParams.append('page', current_page);
url.searchParams.append('limit', locations_limit);
url.searchParams.append('part_id', item.id);
@ -977,7 +977,7 @@ async function fetchLocations(logis) {
}
async function fetchItem() {
const url = new URL('/item/getItem', window.location.origin);
const url = new URL('/items/getItem', window.location.origin);
url.searchParams.append('id', item_id);
const response = await fetch(url);
data = await response.json();
@ -1106,7 +1106,7 @@ async function addLinkedItem(parent_id, child_id) {
if(Number.isInteger(conversion_factor)){
document.getElementById('conversion_factor').classList.remove('uk-form-danger')
const response = await fetch(`/item/addLinkedItem`, {
const response = await fetch(`/items/addLinkedItem`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@ -1145,7 +1145,7 @@ async function addLinkedItem(parent_id, child_id) {
}
async function saveUpdated() {
const response = await fetch(`/item/updateItem`, {
const response = await fetch(`/items/updateItem`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@ -1175,7 +1175,7 @@ async function saveUpdated() {
};
async function refreshSearchString() {
const response = await fetch(`/item/refreshSearchString`, {
const response = await fetch(`/items/refreshSearchString`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@ -1370,7 +1370,7 @@ var new_locations_current_page = 1
var new_locations_end_page = 1
var new_locations_limit = 25
async function fetch_new_locations() {
const url = new URL('/item/getPossibleLocations', window.location.origin);
const url = new URL('/items/getPossibleLocations', window.location.origin);
url.searchParams.append('page', new_locations_current_page);
url.searchParams.append('limit', new_locations_limit);
const response = await fetch(url);
@ -1380,7 +1380,7 @@ async function fetch_new_locations() {
};
async function postNewItemLocation(location_id) {
const response = await fetch(`/item/postNewItemLocation`, {
const response = await fetch(`/items/postNewItemLocation`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',

View File

@ -105,7 +105,7 @@ async function getItem(id) {
}
async function getTransaction(id) {
const url = new URL('/item/getTransaction', window.location.origin);
const url = new URL('/items/getTransaction', window.location.origin);
url.searchParams.append('id', id);
const response = await fetch(url);
data = await response.json();
@ -114,7 +114,7 @@ async function getTransaction(id) {
}
async function getTransactions(){
const url = new URL('/item/getTransactions', window.location.origin);
const url = new URL('/items/getTransactions', window.location.origin);
url.searchParams.append('page', pagination_current);
url.searchParams.append('limit', limit);
url.searchParams.append('logistics_info_id', item.logistics_info_id)

View File

@ -691,6 +691,6 @@
</body>
<script src="{{ url_for('static', filename='handlers/itemEditHandler.js') }}"></script>
<script src="{{ url_for('items_api.static', filename='itemEditHandler.js') }}"></script>
<script>const item_id = {{id|tojson}}</script>
</html>

View File

@ -115,6 +115,6 @@
</div>
</div>
</body>
<script src="{{ url_for('static', filename='handlers/transactionsHandler.js') }}"></script>
<script src="{{ url_for('items_api.static', filename='transactionsHandler.js') }}"></script>
<script>const item_id = {{id|tojson}}</script>
</html>

View File

@ -2409,3 +2409,64 @@ class ItemLinkPayload:
json.dumps(self.data),
self.conv_factor
)
@dataclass
class LogisticsInfoPayload:
barcode: str
primary_location: int
primary_zone: int
auto_issue_location: int
auto_issue_zone: int
def payload(self):
return (self.barcode,
self.primary_location,
self.primary_zone,
self.auto_issue_location,
self.auto_issue_zone)
@dataclass
class ItemInfoPayload:
barcode: str
packaging: str = ""
uom_quantity: float = 1.0
uom: int = 1
cost: float = 0.0
safety_stock: float = 0.0
lead_time_days: float = 0.0
ai_pick: bool = False
prefixes: list = field(default_factory=list)
def __post_init__(self):
if not isinstance(self.barcode, str):
raise TypeError(f"barcode must be of type str; not {type(self.barcode)}")
def payload(self):
return (
self.barcode,
self.packaging,
self.uom_quantity,
self.uom,
self.cost,
self.safety_stock,
self.lead_time_days,
self.ai_pick,
lst2pgarr(self.prefixes)
)
@dataclass
class FoodInfoPayload:
food_groups: list = field(default_factory=list)
ingrediants: list = field(default_factory=list)
nutrients: dict = field(default_factory=dict)
expires: bool = False
default_expiration: float = 0.0
def payload(self):
return (
lst2pgarr(self.food_groups),
lst2pgarr(self.ingrediants),
json.dumps(self.nutrients),
self.expires,
self.default_expiration
)

View File

@ -67,25 +67,6 @@ def inject_user():
return dict(username="")
@app.route("/transactions/<id>")
@login_required
def transactions(id):
"""This is the main endpoint to reach the webpage for an items transaction history
---
parameters:
- name: id
in: path
type: integer
required: true
default: all
responses:
200:
description: Returns the transactions.html webpage for the item with passed ID
"""
sites = [site[1] for site in main.get_sites(session['user']['sites'])]
return render_template("items/transactions.html", id=id, current_site=session['selected_site'], sites=sites)
@app.route("/api/push-subscriptions", methods=["POST"])
def create_push_subscription():
json_data = request.get_json()