From 78d79f9a5765f6de5f1d6b7521ecbcbf0a461870 Mon Sep 17 00:00:00 2001 From: Jadowyne Ulve Date: Tue, 12 Aug 2025 15:42:47 -0500 Subject: [PATCH] Implemeted SKU creation in recipe module - edit --- .../database_payloads.cpython-313.pyc | Bin 28783 -> 28454 bytes .../administration/sql/CREATE/food_info.sql | 3 +- .../administration/sql/CREATE/item_info.sql | 4 +- .../sql/CREATE/logistics_info.sql | 2 +- .../sql/CREATE/transactions.sql | 2 +- application/database_payloads.py | 4 - .../database_recipes.cpython-313.pyc | Bin 28461 -> 38648 bytes .../recipe_processes.cpython-313.pyc | Bin 6308 -> 9926 bytes .../__pycache__/recipes_api.cpython-313.pyc | Bin 15202 -> 16797 bytes application/recipes/database_recipes.py | 205 ++++++++++++++++++ application/recipes/recipe_processes.py | 94 ++++++++ application/recipes/recipes_api.py | 28 +++ .../recipes/sql/insertFoodInfoTuple.sql | 4 + .../recipes/sql/insertItemInfoTuple.sql | 4 + .../recipes/sql/insertItemLocationsTuple.sql | 4 + application/recipes/sql/insertItemTuple.sql | 5 + .../recipes/sql/insertLogisticsInfoTuple.sql | 4 + .../recipes/static/js/recipeEditHandler.js | 53 +++++ .../recipes/templates/recipe_edit.html | 65 ++++++ 19 files changed, 472 insertions(+), 9 deletions(-) create mode 100644 application/recipes/sql/insertFoodInfoTuple.sql create mode 100644 application/recipes/sql/insertItemInfoTuple.sql create mode 100644 application/recipes/sql/insertItemLocationsTuple.sql create mode 100644 application/recipes/sql/insertItemTuple.sql create mode 100644 application/recipes/sql/insertLogisticsInfoTuple.sql diff --git a/application/__pycache__/database_payloads.cpython-313.pyc b/application/__pycache__/database_payloads.cpython-313.pyc index 0d264997383dd24c4aedf50a32ec744d4926e7c8..82e08d3302b2037289a947a4d1fcc90854e42d2d 100644 GIT binary patch delta 4100 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 diff --git a/application/administration/sql/CREATE/food_info.sql b/application/administration/sql/CREATE/food_info.sql index 0ec79a9..49d1ec7 100644 --- a/application/administration/sql/CREATE/food_info.sql +++ b/application/administration/sql/CREATE/food_info.sql @@ -5,5 +5,6 @@ CREATE TABLE IF NOT EXISTS %%site_name%%_food_info ( ingrediants TEXT [], nutrients JSONB, expires BOOLEAN, - default_expiration FLOAT8 + default_expiration FLOAT8, + UNIQUE(food_info_uuid) ); \ No newline at end of file diff --git a/application/administration/sql/CREATE/item_info.sql b/application/administration/sql/CREATE/item_info.sql index 54a985b..3cedeb0 100644 --- a/application/administration/sql/CREATE/item_info.sql +++ b/application/administration/sql/CREATE/item_info.sql @@ -9,6 +9,6 @@ CREATE TABLE IF NOt EXISTS %%site_name%%_item_info ( safety_stock FLOAT8, lead_time_days FLOAT8, ai_pick BOOLEAN, - prefixes INTEGER [], - UNIQUE(barcode) + prefixes INTEGER [] + UNIQUE(item_info_uuid) ); \ No newline at end of file diff --git a/application/administration/sql/CREATE/logistics_info.sql b/application/administration/sql/CREATE/logistics_info.sql index 22a52fa..bc53c31 100644 --- a/application/administration/sql/CREATE/logistics_info.sql +++ b/application/administration/sql/CREATE/logistics_info.sql @@ -6,7 +6,7 @@ CREATE TABLE IF NOT EXISTS %%site_name%%_logistics_info( primary_zone INTEGER NOT NULL, auto_issue_location INTEGER NOT NULL, auto_issue_zone INTEGER NOT NULL, - UNIQUE(barcode), + UNIQUE(logistics_info_uuid), CONSTRAINT fk_primary_location FOREIGN KEY(primary_location) REFERENCES %%site_name%%_locations(id), diff --git a/application/administration/sql/CREATE/transactions.sql b/application/administration/sql/CREATE/transactions.sql index 0ddefd6..6fef0e2 100644 --- a/application/administration/sql/CREATE/transactions.sql +++ b/application/administration/sql/CREATE/transactions.sql @@ -2,7 +2,7 @@ CREATE TABLE IF NOT EXISTS %%site_name%%_Transactions ( id SERIAL PRIMARY KEY, timestamp TIMESTAMP, logistics_info_id INTEGER NOT NULL, - barcode VARCHAR(255) NOT NULL, + barcode VARCHAR(255), name VARCHAR(255), transaction_type VARCHAR(255) NOT NULL, quantity FLOAT8 NOT NULL, diff --git a/application/database_payloads.py b/application/database_payloads.py index bea0256..e4254f9 100644 --- a/application/database_payloads.py +++ b/application/database_payloads.py @@ -45,10 +45,6 @@ class ItemInfoPayload: 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 ( diff --git a/application/recipes/__pycache__/database_recipes.cpython-313.pyc b/application/recipes/__pycache__/database_recipes.cpython-313.pyc index f51235da8ddc229fa5df225f2ad80305cb1e3e42..72db4bb534e7779a38e3cb42c158e6d7189fafdb 100644 GIT binary patch delta 5114 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 delta 1078 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 diff --git a/application/recipes/__pycache__/recipes_api.cpython-313.pyc b/application/recipes/__pycache__/recipes_api.cpython-313.pyc index 2dff7dba3a47fc28ef468d1279ef85486fa93c1b..caa1dd554432b19dc225ef694c05ddd89b085cce 100644 GIT binary patch delta 1015 zcmYjPUr19?9KPpncW&yoo7&kww>g)abDPU@W-3MhvCz!xFpyYgmm0dw&a^10`mP!OZUv(#?NRQ1PHeo|kH21=+!+4yNBaA+CO&{%KPAOR6Pc<-*KM zM|mZlQ@pQLaR~D+GzXtOOK3)!_qtJ_HZ(dJ3r!|dQ~mNe=}@G1N{)|}yd|uri;B)w zjS6P!1L^~4^dLklTi}Va3YO|cFt*k}gMABfgV@##zuNU^AM9*$h>hIQrgjwOOUEQM zkg+(dgQgN)j}ak)B!Rkpyheagvx^;FQ6bQt5evMiZ!>9G9Y!TnL?h`KmnxaXQpM`c zNUGkhO;n5+NPC*#rBNixoqvka_E*2PC-iReTuF;30{^43+53sm-PfNF=e zg9TcwDXWkgj8pAJR@9J~L{#o@hHs4F8lX={>fZGb# zS*WoUux%0B^VpNco*Zs|h8OVWMbVTO8?s_UPHfC{E{FlR>Bja#o%y~ceYj9-d9*EW z^Ji`ToGtile8JZBv8G!|G>g1IF4H%{rIvf>Dp>p>;Wnl7N}pdw=o%=2km&{$h>n6! zy)KsP@GIaJ?oj!?;tIC&nzi_KG=3*s2t|blbmxQeCG?s{cj4}qSHfc|6qO@ed6kf% zZ_o)tCYEA2|8OOO5n^(grc delta 307 zcmbQ+%=oB`?=vqi7XuLdSUV#lU1K7j1k)#$i5hT$09#jkwup&Sa|XW zM%l@^ENfZPnKX4aXR+>4WYnCjuKs|rW;2I|GUMhudh;2XZ!yMCZZMe6ST|Y1P@lCE zXzqf|K8C4`jP;W@8R?6+00oLTfrKVYkrIe)10tFx|1eT!?FBMdZB{TgVPdNUsoB$5VRT7gI8i-Gxp~N%!kVPl!G@#JF%>kB7 z%&aqk%rle2ZTcD2CO@}%Y*z!)0&-0e$d)3IVMQS2MIePM8H%((Y*`?2i^C>2KczG$ Z)vjm}kjn_f#eti7?ZTNh&vsy91OPo9R8jx{ diff --git a/application/recipes/database_recipes.py b/application/recipes/database_recipes.py index 6d29857..56e374d 100644 --- a/application/recipes/database_recipes.py +++ b/application/recipes/database_recipes.py @@ -126,6 +126,59 @@ def getPicturePath(site:str, payload:tuple): rows = cur.fetchone()[0] return rows +def selectSiteTuple(payload, convert=True): + """ payload (tuple): (site_name,) """ + 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 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 getLocation(site:str, payload:tuple, convert:bool=True): + selected = () + database_config = config.config() + sql = f"SELECT * FROM {site}_locations 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 selectItemLocationsTuple(site_name, payload, convert=True, conn=None): item_locations = () self_conn = False @@ -312,6 +365,158 @@ def insertTransactionsTuple(site, payload, convert=True, conn=None): raise postsqldb.DatabaseError(error, payload, sql) return transaction +def insertLogisticsInfoTuple(site, payload, convert=True, conn=None): + """ payload (tuple): (barcode[str], primary_location[str], auto_issue_location[str], dynamic_locations[jsonb], + location_data[jsonb], quantity_on_hand[float]) """ + logistics_info = () + self_conn = False + + with open(f"application/recipes/sql/insertLogisticsInfoTuple.sql", "r+") as file: + sql = file.read().replace("%%site_name%%", site) + 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: + logistics_info = postsqldb.tupleDictionaryFactory(cur.description, rows) + elif rows and not convert: + logistics_info = rows + + if self_conn: + conn.commit() + conn.close() + + return logistics_info + + except Exception as error: + raise postsqldb.DatabaseError(error, payload, sql) + +def insertItemInfoTuple(site, payload, convert=True, conn=None): + """ 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]) """ + item_info = () + self_conn = False + with open(f"application/recipes/sql/insertItemInfoTuple.sql", "r+") as file: + sql = file.read().replace("%%site_name%%", site) + 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: + item_info = postsqldb.tupleDictionaryFactory(cur.description, rows) + elif rows and not convert: + item_info = rows + if self_conn: + conn.commit() + conn.close() + + return item_info + except Exception as error: + raise postsqldb.DatabaseError(error, payload, sql) + +def insertFoodInfoTuple(site, payload, convert=True, conn=None): + """ payload (_type_): (ingrediants[lst2pgarr], food_groups[lst2pgarr], nutrients[jsonstr], expires[bool]) """ + food_info = () + self_conn = False + with open(f"application/recipes/sql/insertFoodInfoTuple.sql", "r+") as file: + sql = file.read().replace("%%site_name%%", site) + 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: + food_info = postsqldb.tupleDictionaryFactory(cur.description, rows) + elif rows and not convert: + food_info = rows + + if self_conn: + conn.commit() + conn.close() + + return food_info + except Exception as error: + raise postsqldb.DatabaseError(error, payload, sql) + +def insertItemTuple(site, payload, convert=True, conn=None): + """ 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]) """ + item = () + self_conn = False + with open(f"application/recipes/sql/insertItemTuple.sql", "r+") as file: + sql = file.read().replace("%%site_name%%", site) + 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: + item = postsqldb.tupleDictionaryFactory(cur.description, rows) + elif rows and not convert: + item = rows + + if self_conn: + conn.commit() + conn.close() + + return item + except Exception as error: + raise postsqldb.DatabaseError(error, payload, sql) + + +def insertItemLocationsTuple(site, payload, convert=True, conn=None): + """ payload (tuple): (part_id[int], location_id[int], quantity_on_hand[float], cost_layers[lst2pgarr]) """ + location = () + self_conn = False + database_config = config.config() + with open(f"application/recipes/sql/insertItemLocationsTuple.sql", "r+") as file: + sql = file.read().replace("%%site_name%%", site) + 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: + location = postsqldb.tupleDictionaryFactory(cur.description, rows) + elif rows and not convert: + location = rows + + if self_conn: + conn.commit() + conn.close() + + return location + except Exception as error: + raise postsqldb.DatabaseError(error, payload, sql) + def postAddRecipe(site:str, payload:tuple, convert:bool=True): database_config = config.config() record = () diff --git a/application/recipes/recipe_processes.py b/application/recipes/recipe_processes.py index 5bd19a1..1b52075 100644 --- a/application/recipes/recipe_processes.py +++ b/application/recipes/recipe_processes.py @@ -1,8 +1,10 @@ import psycopg2 import datetime +import json from application import database_payloads, postsqldb from application.recipes import database_recipes +from application.items import database_items import config def postTransaction(site_name, user_id, data: dict, conn=None): @@ -137,3 +139,95 @@ def process_recipe_receipt(site_name, user_id, data:dict, conn=None): conn.close() return True, "" + +def postNewSkuFromRecipe(site_name: str, user_id: int, data: dict, conn=None): + """ data = {'name', 'subtype', 'qty', 'uom_id', 'main_link', 'cost'}""" + self_conn = False + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = False + self_conn = True + + site = database_recipes.selectSiteTuple((site_name,)) + default_zone = database_recipes.getZone(site_name,(site['default_zone'], )) + default_location = database_recipes.getLocation(site_name, (site['default_primary_location'],)) + uuid = f"{default_zone['name']}@{default_location['name']}" + + # create logistics info + logistics_info = database_payloads.LogisticsInfoPayload( + barcode=None, + 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 = database_payloads.ItemInfoPayload(barcode=None) + + # create Food Info + food_info = database_payloads.FoodInfoPayload() + + logistics_info_id = 0 + item_info_id = 0 + food_info_id = 0 + brand_id = 1 + + + logistics_info = database_recipes.insertLogisticsInfoTuple(site_name, logistics_info.payload(), conn=conn) + item_info = database_recipes.insertItemInfoTuple(site_name, item_info.payload(), conn=conn) + food_info = database_recipes.insertFoodInfoTuple(site_name, food_info.payload(), conn=conn) + + name = data['name'] + name = name.replace("'", "@&apostraphe&") + links = {'main': data['main_link']} + search_string = f"&&{name}&&" + + + item = database_payloads.ItemsPayload( + barcode=None, + item_name=data['name'], + item_info_id=item_info['id'], + logistics_info_id=logistics_info['id'], + food_info_id=food_info['id'], + links=links, + brand=brand_id, + row_type="single", + item_type=data['subtype'], + search_string=search_string + ) + + item = database_recipes.insertItemTuple(site_name, item.payload(), conn=conn) + + with conn.cursor() as cur: + cur.execute(f"SELECT id FROM {site_name}_locations WHERE uuid=%s;", (uuid, )) + location_id = cur.fetchone()[0] + + database_payloads.ItemLocationPayload + item_location = database_payloads.ItemLocationPayload(item['id'], location_id) + database_recipes.insertItemLocationsTuple(site_name, item_location.payload(), conn=conn) + + + creation_tuple = database_payloads.TransactionPayload( + datetime.datetime.now(), + logistics_info['id'], + None, + item['item_name'], + "SYSTEM", + 0.0, + "Item added to the System!", + user_id, + {'location': uuid} + ) + + database_recipes.insertTransactionsTuple(site_name, creation_tuple.payload(), conn=conn) + + item_uuid = item['item_uuid'] + + if self_conn: + conn.commit() + conn.close() + return False, item_uuid + + return conn, item_uuid diff --git a/application/recipes/recipes_api.py b/application/recipes/recipes_api.py index 0a79bc6..bb08bc8 100644 --- a/application/recipes/recipes_api.py +++ b/application/recipes/recipes_api.py @@ -157,6 +157,34 @@ def postSKUItem(): return jsonify({'recipe': recipe, 'error': False, 'message': 'Recipe Item was added successful!'}) return jsonify({'recipe': recipe, 'error': True, 'message': f'method {request.method} is not allowed!'}) +@recipes_api.route('/api/postNewSKUItem', methods=["POST"]) +@access_api.login_required +def postNewSKUItem(): + recipe = {} + if request.method == "POST": + recipe_id = int(request.get_json()['recipe_id']) + site_name = session['selected_site'] + user_id = session['user_id'] + + _, item_uuid= recipe_processes.postNewSkuFromRecipe(site_name, user_id, request.get_json()) + + item = database_recipes.selectItemTupleByUUID(site_name, (item_uuid,)) + + recipe_item = db.RecipesTable.ItemPayload( + item_uuid=item_uuid, + rp_id=recipe_id, + item_type='sku', + item_name=request.get_json()['name'], + uom=request.get_json()['uom_id'], + qty=float(request.get_json()['qty']), + item_id=item['item_id'], + links={'main': request.get_json()['main_link']} + ) + database_recipes.postAddRecipeItem(site_name, recipe_item.payload()) + recipe = database_recipes.getRecipe(site_name, (recipe_id, )) + return jsonify({'recipe': recipe, 'error': False, 'message': 'Recipe Item was added successful!'}) + return jsonify({'recipe': recipe, 'error': True, 'message': f'method {request.method} is not allowed!'}) + @recipes_api.route('/postImage/', methods=["POST"]) @access_api.login_required def uploadImage(recipe_id): diff --git a/application/recipes/sql/insertFoodInfoTuple.sql b/application/recipes/sql/insertFoodInfoTuple.sql new file mode 100644 index 0000000..08afdf2 --- /dev/null +++ b/application/recipes/sql/insertFoodInfoTuple.sql @@ -0,0 +1,4 @@ +INSERT INTO %%site_name%%_food_info +(ingrediants, food_groups, nutrients, expires, default_expiration) +VALUES (%s, %s, %s, %s, %s) +RETURNING *; \ No newline at end of file diff --git a/application/recipes/sql/insertItemInfoTuple.sql b/application/recipes/sql/insertItemInfoTuple.sql new file mode 100644 index 0000000..154e9d3 --- /dev/null +++ b/application/recipes/sql/insertItemInfoTuple.sql @@ -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 *; \ No newline at end of file diff --git a/application/recipes/sql/insertItemLocationsTuple.sql b/application/recipes/sql/insertItemLocationsTuple.sql new file mode 100644 index 0000000..67abbd4 --- /dev/null +++ b/application/recipes/sql/insertItemLocationsTuple.sql @@ -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 *; \ No newline at end of file diff --git a/application/recipes/sql/insertItemTuple.sql b/application/recipes/sql/insertItemTuple.sql new file mode 100644 index 0000000..4c9b940 --- /dev/null +++ b/application/recipes/sql/insertItemTuple.sql @@ -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 *; \ No newline at end of file diff --git a/application/recipes/sql/insertLogisticsInfoTuple.sql b/application/recipes/sql/insertLogisticsInfoTuple.sql new file mode 100644 index 0000000..312ee1c --- /dev/null +++ b/application/recipes/sql/insertLogisticsInfoTuple.sql @@ -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 *; \ No newline at end of file diff --git a/application/recipes/static/js/recipeEditHandler.js b/application/recipes/static/js/recipeEditHandler.js index 0213a97..f02b709 100644 --- a/application/recipes/static/js/recipeEditHandler.js +++ b/application/recipes/static/js/recipeEditHandler.js @@ -254,6 +254,47 @@ async function addSKUItem(item_id) { UIkit.modal(document.getElementById('itemsModal')).hide(); } +async function addNewSKUItem() { + let newSKUName = document.getElementById('newSKUName').value + let newSKUSubtype = document.getElementById('newSKUSubtype').value + let newSKUQty = parseFloat(document.getElementById('newSKUQty').value) + let newSKUUOM = parseInt(document.getElementById('newSKUUOM').value) + let newWeblink = document.getElementById('newWeblink').value + let newSKUCost = parseFloat(document.getElementById('newSKUCost').value) + + + const response = await fetch(`/recipes/api/postNewSKUItem`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + recipe_id: recipe.id, + name: newSKUName, + subtype: newSKUSubtype, + qty: newSKUQty, + uom_id: newSKUUOM, + main_link: newWeblink, + cost: newSKUCost + }), + }); + data = await response.json() + message_type = "primary" + if(data.error){ + message_type = "danger" + } + UIkit.notification({ + message: data.message, + status: message_type, + pos: 'top-right', + timeout: 5000 + }); + recipe = data.recipe + await replenishRecipe() + UIkit.modal(document.getElementById('addNewSKUItem')).hide(); +} + + let updated = {} async function postUpdate() { let description = document.getElementById('recipeDescription').value @@ -326,6 +367,18 @@ async function deleteInstruction(index){ await replenishInstructions() } + +async function openNewSKUModal() { + let itemsModal = document.getElementById('addNewSKUItem') + document.getElementById('newSKUName').value = "" + document.getElementById('newSKUSubtype').value = "FOOD" + document.getElementById('newSKUQty').value = 1 + document.getElementById('newSKUUOM').value = "1" + document.getElementById('newWeblink').value = "" + document.getElementById('newSKUCost').value = 0.00 + UIkit.modal(itemsModal).show(); +} + let pagination_current = 1; let pagination_end = 10; let search_string = ""; diff --git a/application/recipes/templates/recipe_edit.html b/application/recipes/templates/recipe_edit.html index 769a4aa..36dacd5 100644 --- a/application/recipes/templates/recipe_edit.html +++ b/application/recipes/templates/recipe_edit.html @@ -174,6 +174,8 @@
+ +
@@ -318,6 +320,69 @@
+ +
+
+ +
+

Add New Item...

+
+
+
+

Adding a new sku with both create a new item in the system with the information you provide and also add it to the recipe. +

+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ + +
+
+ +
+ +
+
+
+ + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+