diff --git a/application/recipes/__pycache__/recipe_processes.cpython-313.pyc b/application/recipes/__pycache__/recipe_processes.cpython-313.pyc index 2bc4861..5789dd4 100644 Binary files a/application/recipes/__pycache__/recipe_processes.cpython-313.pyc and b/application/recipes/__pycache__/recipe_processes.cpython-313.pyc differ diff --git a/application/shoppinglists/__pycache__/shoplist_api.cpython-313.pyc b/application/shoppinglists/__pycache__/shoplist_api.cpython-313.pyc index 3a6bdad..4851f76 100644 Binary files a/application/shoppinglists/__pycache__/shoplist_api.cpython-313.pyc and b/application/shoppinglists/__pycache__/shoplist_api.cpython-313.pyc differ diff --git a/application/shoppinglists/__pycache__/shoplist_database.cpython-313.pyc b/application/shoppinglists/__pycache__/shoplist_database.cpython-313.pyc index a6bb1b3..62740f5 100644 Binary files a/application/shoppinglists/__pycache__/shoplist_database.cpython-313.pyc and b/application/shoppinglists/__pycache__/shoplist_database.cpython-313.pyc differ diff --git a/application/shoppinglists/__pycache__/shoplist_processess.cpython-313.pyc b/application/shoppinglists/__pycache__/shoplist_processess.cpython-313.pyc index 99f14f3..31bd45a 100644 Binary files a/application/shoppinglists/__pycache__/shoplist_processess.cpython-313.pyc and b/application/shoppinglists/__pycache__/shoplist_processess.cpython-313.pyc differ diff --git a/application/shoppinglists/shoplist_api.py b/application/shoppinglists/shoplist_api.py index f3cb556..9fdbdac 100644 --- a/application/shoppinglists/shoplist_api.py +++ b/application/shoppinglists/shoplist_api.py @@ -314,6 +314,7 @@ def postGeneratedList(): payload: dict = request.get_json() site_name: str = session['selected_site'] user_id: int = session['user_id'] + print(payload) shoplist_processess.postNewGeneratedList(site_name, payload, user_id) return jsonify(status=201, message=f"List Generated successfully!") return jsonify(status=405, message=f"{request.method} is not an accepted method on this endpoint!") \ No newline at end of file diff --git a/application/shoppinglists/shoplist_database.py b/application/shoppinglists/shoplist_database.py index 6665739..ed226ce 100644 --- a/application/shoppinglists/shoplist_database.py +++ b/application/shoppinglists/shoplist_database.py @@ -324,6 +324,36 @@ def getItemByUUID(site, payload:dict, convert=True, conn=None): except Exception as error: raise postsqldb.DatabaseError(error, payload, sql) +def getEventRecipes(site, payload, convert=True, conn=None): + """ payload: dict = {'plan_uuid', 'start_date', 'end_date'}""" + records = () + self_conn = False + with open('application/shoppinglists/sql/getEventsRecipes.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.fetchall() + if rows and convert: + records = [postsqldb.tupleDictionaryFactory(cur.description, row) for row in rows] + elif rows and not convert: + records = rows + + if self_conn: + conn.close() + + return records + except Exception as error: + raise postsqldb.DatabaseError(error, payload, sql) + def deleteShoppingListsTuple(site_name, payload, convert=True, conn=None): deleted = () self_conn = False diff --git a/application/shoppinglists/shoplist_processess.py b/application/shoppinglists/shoplist_processess.py index dd8cc4d..e0ff281 100644 --- a/application/shoppinglists/shoplist_processess.py +++ b/application/shoppinglists/shoplist_processess.py @@ -48,6 +48,7 @@ def postNewGeneratedList(site: str, data: dict, user_id: int, conn=None): recipes: list = data['recipes'] full_system_calculated: list = data['full_system_calculated'] shopping_lists: list = data['shopping_lists'] + site_plans: list = data['site_plans'] self_conn=False @@ -157,6 +158,26 @@ def postNewGeneratedList(site: str, data: dict, user_id: int, conn=None): ) items_to_add_to_system.append(temp_item) + + if site_plans: + for site_plan in site_plans: + if site_plan['plan_uuid'] == 'site': site_plan['plan_uuid'] = None + plan_recipes = [event['recipe_uuid'] for event in shoplist_database.getEventRecipes(site, site_plan, conn=conn)] + if plan_recipes: + for recipe_uuid in plan_recipes: + recipe_items = shoplist_database.getRecipeItemsByUUID(site, (recipe_uuid,), conn=conn) + for item in recipe_items: + temp_item = database_payloads.ShoppingListItemPayload( + list_uuid=shopping_list['list_uuid'], + item_type='recipe', + item_name=item['item_name'], + uom=item['uom'], + qty=float(item['qty']), + item_uuid=item['item_uuid'], + links=item['links'] + ) + items_to_add_to_system.append(temp_item) + if items_to_add_to_system: for item in items_to_add_to_system: @@ -180,7 +201,9 @@ def deleteShoppingList(site: str, data: dict, user_id: int, conn=None): shopping_list_items = [item['list_item_uuid'] for item in shopping_list_items] shoplist_database.deleteShoppingListsTuple(site, (shopping_list_uuid,), conn=conn) - shoplist_database.deleteShoppingListItemsTuple(site, shopping_list_items, conn=conn) + if shopping_list_items: + shoplist_database.deleteShoppingListItemsTuple(site, shopping_list_items, conn=conn) + if self_conn: conn.commit() diff --git a/application/shoppinglists/sql/getEventsRecipes.sql b/application/shoppinglists/sql/getEventsRecipes.sql new file mode 100644 index 0000000..d318bc0 --- /dev/null +++ b/application/shoppinglists/sql/getEventsRecipes.sql @@ -0,0 +1,7 @@ +SELECT events.recipe_uuid +FROM %%site_name%%_plan_events events +WHERE events.plan_uuid IS NULL + AND events.event_type = 'recipe' + AND events.recipe_uuid IS NOT NULL + AND events.event_date_start <= %(end_date)s + AND events.event_date_end >= %(start_date)s; \ No newline at end of file diff --git a/application/shoppinglists/static/js/shoppingListGeneratorHandler.js b/application/shoppinglists/static/js/shoppingListGeneratorHandler.js index e1258e7..a68e8ad 100644 --- a/application/shoppinglists/static/js/shoppingListGeneratorHandler.js +++ b/application/shoppinglists/static/js/shoppingListGeneratorHandler.js @@ -1076,6 +1076,130 @@ async function generateListsTable() { } +// Site Planner Functions +var site_planners = {} +var site_planner_card_active = false; +async function addPlannerCard(){ + if(!site_planner_card_active){ + document.getElementById('plannerCard').hidden = false + site_planner_card_active = true; + } +} + +async function removePlannerCard(){ + document.getElementById('plannerCard').hidden = true + site_planner_card_active = false; + site_planners = [] +} + +var PlannerZoneState = true +async function changePlannerZoneState() { + PlannerZoneState = !PlannerZoneState + document.getElementById('plannerZone').hidden = !PlannerZoneState +} + +async function openPlannerModal(){ + document.getElementById('planUUID').setAttribute('class', 'uk-input uk-disabled') + document.getElementById('planUUID').value = 'site' + document.getElementById('planStartDate').value = '' + document.getElementById('planEndDate').value = '' + document.getElementById('plannerModalButton').innerHTML = "Save" + document.getElementById('plannerModalButton').onclick = async function () { await addPlanner()} + UIkit.modal(document.getElementById('plannerModal')).show() +} + +async function addPlanner() { + var planner_select = document.getElementById('planUUID') + planner_uuid = planner_select.value + plan_name = planner_select.options[planner_select.selectedIndex].text + startDate = document.getElementById('planStartDate').value + endDate = document.getElementById('planEndDate').value + site_planners[planner_uuid] = { + start_date: startDate, + end_date: endDate, + plan_uuid: planner_uuid, + plan_name: plan_name + } + UIkit.modal(document.getElementById('plannerModal')).hide() + console.log(site_planners) + await generatePlannerTable() +} + +async function editPlanner(planUUID) { + let data = site_planners[planUUID] + document.getElementById('planUUID').setAttribute('class', 'uk-input uk-disabled') + document.getElementById('planUUID').value = data['plan_uuid'] + document.getElementById('planStartDate').value = data['start_date'] + document.getElementById('planEndDate').value = data['end_date'] + document.getElementById('plannerModalButton').innerHTML = "Save" + document.getElementById('plannerModalButton').onclick = async function () { + var planner_select = document.getElementById('planUUID') + planner_uuid = planner_select.value + plan_name = planner_select.options[planner_select.selectedIndex].text + startDate = document.getElementById('planStartDate').value + endDate = document.getElementById('planEndDate').value + site_planners[planner_uuid] = { + start_date: startDate, + end_date: endDate, + plan_uuid: planner_uuid, + plan_name: plan_name + } + + await generatePlannerTable() + UIkit.modal(document.getElementById('plannerModal')).hide() + } + + UIkit.modal(document.getElementById('plannerModal')).show() +} + + +async function deletePlan(plannerUUID) { + delete site_planners[plannerUUID] + await generatePlannerTable() +} + +async function generatePlannerTable() { + let plannerTableBody = document.getElementById('plannerTableBody') + plannerTableBody.innerHTML = "" + + for(const key in site_planners){ + if(site_planners.hasOwnProperty(key)){ + let tableRow = document.createElement('tr') + + + let nameCell = document.createElement('td') + nameCell.innerHTML = `${site_planners[key].plan_name}` + + let startCell = document.createElement('td') + startCell.innerHTML = `${site_planners[key].start_date}` + + let endCell = document.createElement('td') + endCell.innerHTML = `${site_planners[key].end_date}` + + let opCell = document.createElement('td') + + let editButton = document.createElement('button') + editButton.setAttribute('class', 'uk-button uk-button-default uk-button-small') + editButton.setAttribute('uk-tooltip', 'Edits this rows plan dates.') + editButton.innerHTML = "Edit" + editButton.onclick = async function() {await editPlanner(site_planners[key].plan_uuid)} + + + let removeButton = document.createElement('button') + removeButton.setAttribute('class', 'uk-button uk-button-default uk-button-small') + removeButton.setAttribute('uk-tooltip', 'Removes Shopping List from the saved shopping lists') + removeButton.innerHTML = "Remove" + removeButton.onclick = async function() {await deletePlan(site_planners[key].plan_uuid)} + + opCell.append(editButton, removeButton) + + tableRow.append(nameCell, startCell, endCell, opCell) + plannerTableBody.append(tableRow) + + } + } + +} // Generate Functions async function postGenerateList() { @@ -1088,7 +1212,8 @@ async function postGenerateList() { calculated_items: Object.keys(calculated_items), recipes: Object.keys(recipes), full_system_calculated: full_sku_enabled, - shopping_lists: Object.keys(shopping_lists) + shopping_lists: Object.keys(shopping_lists), + site_plans: Object.values(site_planners) } const response = await fetch(`/shopping-lists/api/postGeneratedList`, { @@ -1098,4 +1223,5 @@ async function postGenerateList() { }, body: JSON.stringify(data), }); + location.href = "/shopping-lists" } \ No newline at end of file diff --git a/application/shoppinglists/static/js/shoppingListViewHandler.js b/application/shoppinglists/static/js/shoppingListViewHandler.js index bfae935..5351385 100644 --- a/application/shoppinglists/static/js/shoppingListViewHandler.js +++ b/application/shoppinglists/static/js/shoppingListViewHandler.js @@ -23,29 +23,54 @@ async function replenishLineTable(sl_items){ listItemsTableBody.innerHTML = "" console.log(sl_items) - for(let i = 0; i < sl_items.length; i++){ - let tableRow = document.createElement('tr') + let grouped = sl_items.reduce((accumen, item) => { + if (!accumen[item.item_type]) { + accumen[item.item_type] = []; + } + accumen[item.item_type].push(item); + return accumen; + }, {}); + + console.log(grouped) + for(let key in grouped){ + console.log(key) + let items = grouped[key] + let headerRow = document.createElement('tr') + let headerCell = document.createElement('td') + headerCell.colSpan = 3; + headerCell.textContent = key.toUpperCase(); + headerCell.className = 'type-header'; + headerCell.style = `font-weight: bold;background: #eee; text-align: left;` + headerRow.appendChild(headerCell); + listItemsTableBody.appendChild(headerRow); - let checkboxCell = document.createElement('td') - checkboxCell.innerHTML = `` - checkboxCell.onclick = async function (event) { - await updateListItemState(sl_items[i].list_item_uuid, event.target.checked) + for(let i = 0; i < items.length; i++){ + console.log(items) + let tableRow = document.createElement('tr') + let item = items[i] + let checkboxCell = document.createElement('td') + checkboxCell.innerHTML = `` + checkboxCell.onclick = async function (event) { + console.log(item) + await updateListItemState(item.list_item_uuid, event.target.checked) + } + + namefield = items[i].item_name + if(items[i].links.hasOwnProperty('main')){ + namefield = `${item.item_name}` + } + + let nameCell = document.createElement('td') + nameCell.innerHTML = namefield + + let qtyuomCell = document.createElement('td') + qtyuomCell.innerHTML = `${item.qty} ${item.uom.fullname}` + + checkboxCell.checked = item.list_item_state + tableRow.append(checkboxCell, nameCell, qtyuomCell) + listItemsTableBody.append(tableRow) } - namefield = sl_items[i].item_name - if(sl_items[i].links.hasOwnProperty('main')){ - namefield = `${sl_items[i].item_name}` - } - - let nameCell = document.createElement('td') - nameCell.innerHTML = namefield - - let qtyuomCell = document.createElement('td') - qtyuomCell.innerHTML = `${sl_items[i].qty} ${sl_items[i].uom.fullname}` - - checkboxCell.checked = sl_items[i].list_item_state - tableRow.append(checkboxCell, nameCell, qtyuomCell) - listItemsTableBody.append(tableRow) } } diff --git a/application/shoppinglists/templates/generate.html b/application/shoppinglists/templates/generate.html index 3d8e855..a73cada 100644 --- a/application/shoppinglists/templates/generate.html +++ b/application/shoppinglists/templates/generate.html @@ -176,11 +176,11 @@
  • Active Operators
  • Custom Items
  • -
  • Non-Calculated System Items
  • +
  • Un-Calculated System Items
  • Calculated System Items
  • System Recipes
  • Full System Calculated
  • -
  • Site Planners
  • +
  • Site Planners
  • Shopping Lists
  • @@ -353,6 +353,39 @@ + + @@ -655,6 +688,45 @@ + +
    +
    +

    Add Planner Date Range...

    +

    Site Planner Operators allow you to select specific plans and a date range on that planner to insert any planned recipes into the list. This is best + utilized without the Recipe Operator, but it has been allowed to have both. +

    + + + + + + + + + + + + + + + + + + + + + +
    Plan + +
    Start Date
    End Date
    +

    + + +

    +
    +
    diff --git a/logs/database.log b/logs/database.log index aa6c42c..154d858 100644 --- a/logs/database.log +++ b/logs/database.log @@ -577,4 +577,28 @@ sql='WITH sum_cte AS ( SELECT mi.id, SUM(mil.quantity_on_hand) AS total_sum FROM main_item_locations mil JOIN main_items mi ON mil.part_id = mi.id GROUP BY mi.id)SELECT main_items.*, COALESCE(row_to_json(main_item_info.*), '{}') AS item_info, COALESCE(sum_cte.total_sum, 0) AS total_sum,FROM main_items itemsLEFT JOIN main_item_info item_info ON items.item_info_id = item_info.idLEFT JOIN units ON units.id = item_info.uomLEFT JOIN sum_cte ON items.id = sum_cte.idWHERE items.item_uuid = %(item_uuid)s') 2025-08-19 15:45:32.184317 --- ERROR --- DatabaseError(message='syntax error at or near "FROM"LINE 11: FROM main_items items ^', payload={'item_uuid': '392f05b5-4ccd-41da-875d-0e593f51b610'}, - sql='WITH sum_cte AS ( SELECT mi.id, SUM(mil.quantity_on_hand) AS total_sum FROM main_item_locations mil JOIN main_items mi ON mil.part_id = mi.id GROUP BY mi.id)SELECT items.*, COALESCE(row_to_json(item_info.*), '{}') AS item_info, COALESCE(sum_cte.total_sum, 0) AS total_sum,FROM main_items itemsLEFT JOIN main_item_info item_info ON items.item_info_id = item_info.idLEFT JOIN units ON units.id = item_info.uomLEFT JOIN sum_cte ON items.id = sum_cte.idWHERE items.item_uuid = %(item_uuid)s') \ No newline at end of file + sql='WITH sum_cte AS ( SELECT mi.id, SUM(mil.quantity_on_hand) AS total_sum FROM main_item_locations mil JOIN main_items mi ON mil.part_id = mi.id GROUP BY mi.id)SELECT items.*, COALESCE(row_to_json(item_info.*), '{}') AS item_info, COALESCE(sum_cte.total_sum, 0) AS total_sum,FROM main_items itemsLEFT JOIN main_item_info item_info ON items.item_info_id = item_info.idLEFT JOIN units ON units.id = item_info.uomLEFT JOIN sum_cte ON items.id = sum_cte.idWHERE items.item_uuid = %(item_uuid)s') +2025-08-19 17:38:27.622014 --- ERROR --- DatabaseError(message='syntax error at or near ")"LINE 1: ...WHERE main_shopping_list_items.list_item_uuid IN () RETURNIN... ^', + payload=[], + sql='WITH deleted_rows AS (DELETE FROM main_shopping_list_items WHERE main_shopping_list_items.list_item_uuid IN () RETURNING *) SELECT * FROM deleted_rows;') +2025-08-19 17:39:39.750553 --- ERROR --- DatabaseError(message='syntax error at or near ")"LINE 1: ...WHERE main_shopping_list_items.list_item_uuid IN () RETURNIN... ^', + payload=[], + sql='WITH deleted_rows AS (DELETE FROM main_shopping_list_items WHERE main_shopping_list_items.list_item_uuid IN () RETURNING *) SELECT * FROM deleted_rows;') +2025-08-19 17:55:16.825854 --- ERROR --- DatabaseError(message='duplicate key value violates unique constraint "main_shopping_lists_name_key"DETAIL: Key (name)=() already exists.', + payload=('', '', 1, datetime.datetime(2025, 8, 19, 17, 55, 16, 816288), 'plain', 'temporary'), + sql='INSERT INTO main_shopping_lists(name, description, author, creation_date, sub_type, list_type) VALUES (%s, %s, %s, %s, %s, %s) RETURNING *;') +2025-08-20 05:17:43.764499 --- ERROR --- DatabaseError(message='syntax error at or near ")"LINE 1: ...WHERE main_shopping_list_items.list_item_uuid IN () RETURNIN... ^', + payload=[], + sql='WITH deleted_rows AS (DELETE FROM main_shopping_list_items WHERE main_shopping_list_items.list_item_uuid IN () RETURNING *) SELECT * FROM deleted_rows;') +2025-08-20 05:17:54.498269 --- ERROR --- DatabaseError(message='syntax error at or near ")"LINE 1: ...WHERE main_shopping_list_items.list_item_uuid IN () RETURNIN... ^', + payload=[], + sql='WITH deleted_rows AS (DELETE FROM main_shopping_list_items WHERE main_shopping_list_items.list_item_uuid IN () RETURNING *) SELECT * FROM deleted_rows;') +2025-08-20 05:17:57.313016 --- ERROR --- DatabaseError(message='syntax error at or near ")"LINE 1: ...WHERE main_shopping_list_items.list_item_uuid IN () RETURNIN... ^', + payload=[], + sql='WITH deleted_rows AS (DELETE FROM main_shopping_list_items WHERE main_shopping_list_items.list_item_uuid IN () RETURNING *) SELECT * FROM deleted_rows;') +2025-08-20 15:32:51.822870 --- ERROR --- DatabaseError(message='duplicate key value violates unique constraint "main_shopping_lists_name_key"DETAIL: Key (name)=(test) already exists.', + payload=('test', 'test', 1, datetime.datetime(2025, 8, 20, 15, 32, 51, 814595), 'plain', 'temporary'), + sql='INSERT INTO main_shopping_lists(name, description, author, creation_date, sub_type, list_type) VALUES (%s, %s, %s, %s, %s, %s) RETURNING *;') +2025-08-20 15:38:30.303845 --- ERROR --- DatabaseError(message='column "start_date" does not existLINE 1: ... FROM main_plan_events WHERE plan_uuid = NULL AND start_date... ^', + payload={'start_date': '2025-08-18', 'end_date': '2025-08-24', 'plan_uuid': None, 'plan_name': 'Site Planner'}, + sql='SELECT * FROM main_plan_events WHERE plan_uuid = %(plan_uuid)s AND start_date <= %(end_date)s AND end_date >= %(start_date)s;') \ No newline at end of file