From 160c21427d989affcb4f4cd44560208b931dabe3 Mon Sep 17 00:00:00 2001 From: Jadowyne Ulve Date: Wed, 20 Aug 2025 17:30:25 -0500 Subject: [PATCH] Added site planners to list generation --- .../recipe_processes.cpython-313.pyc | Bin 10234 -> 10234 bytes .../__pycache__/shoplist_api.cpython-313.pyc | Bin 18374 -> 18408 bytes .../shoplist_database.cpython-313.pyc | Bin 22016 -> 23331 bytes .../shoplist_processess.cpython-313.pyc | Bin 6599 -> 7354 bytes application/shoppinglists/shoplist_api.py | 1 + .../shoppinglists/shoplist_database.py | 30 ++++ .../shoppinglists/shoplist_processess.py | 25 +++- .../shoppinglists/sql/getEventsRecipes.sql | 7 + .../static/js/shoppingListGeneratorHandler.js | 128 +++++++++++++++++- .../static/js/shoppingListViewHandler.js | 65 ++++++--- .../shoppinglists/templates/generate.html | 76 ++++++++++- logs/database.log | 26 +++- 12 files changed, 333 insertions(+), 25 deletions(-) create mode 100644 application/shoppinglists/sql/getEventsRecipes.sql diff --git a/application/recipes/__pycache__/recipe_processes.cpython-313.pyc b/application/recipes/__pycache__/recipe_processes.cpython-313.pyc index 2bc4861191122c8dcfc9485047f70933c432439c..5789dd4f9529c2cbd89b5bc90a7ccfd53af6bee7 100644 GIT binary patch delta 20 acmez6|I45IGcPX}0}%LsTe6Y+y*dC<8wYRz delta 20 acmez6|I45IGcPX}0}$+6GJhlYdvyR(9|wp4 diff --git a/application/shoppinglists/__pycache__/shoplist_api.cpython-313.pyc b/application/shoppinglists/__pycache__/shoplist_api.cpython-313.pyc index 3a6bdadc4d95db952a2bbd4495b7412dbe41497c..4851f7694731ad1a5d8e6cd2c2df315d83255893 100644 GIT binary patch delta 154 zcmX@s&-kLBk@qt%FBbz4m>Mk0c(9RI&Vh-IadM!zCS&mA3np5V*BGdYMRS4_fIujN zDIk#k@qt%FBbz4ta`B|CU7NAu05PBU4h*cmezKQl8hNrMPi N#*Zu@fg)R=6#z9OAprmY diff --git a/application/shoppinglists/__pycache__/shoplist_database.cpython-313.pyc b/application/shoppinglists/__pycache__/shoplist_database.cpython-313.pyc index a6bb1b373a94c38a1f693e284600aed338e52cd2..62740f5dabddd5ffddc2f87c33dcccc2e82bd283 100644 GIT binary patch delta 877 zcmYMyOH30%7zgkU?QUPRv^)xZ2o(;sfQUkP2sOf5X`!W&QiMPQ3%e}^EHGQ}K?rz| zs4)hd#FLzS9E>57O^lr6qTz`6K-2??CL|t=5e>$8@Y_}6Ci~Ct`(`FHv)S1P==LLI z+%*_<3V7noy}>B6V9Y~n3$DVM?6A_N7(!ST&JL?&Q6Ned4WebKL26kVkVcjkq?M%u z>163adRgh9^d3WHTG#-!3U)9;$0(0wfHGv6N`WSsW{_D{CMZ)@7AQ+rb{x5LmOnkS zqg`>?;iC~F{w%ytBcFKJT|&`bxEMZ3uIe3DL!3_q5<*G{j&ke>Zh?`H@Cwyr$(<(_ z9V$X9Kh&hW#3J^U&nc0I>{LvnpvYI6DHIaV*5)JPDbbS8xe2mZ^@D`0H;KNchcwtG z19URIAw7m-3Xift5kqdxh3YZ({KzQF9d&Yvft1tZoN|qgu*u$ui3H~=b-IKUi&MQE zo8qO4PjY`ejn!na!sYNz^2wCY#t$UMc%e)fAHheYtI!aw#B6tH-l{P_Fp4=+W-cb( z_T1_WxSzc0?;_j=bDI#{&S{y1r`a+X+ zOV@#|DEq?JzY%5EJGySwZrbZM>~)WsC!v+lTKKj7)bl7iuU@zH&y!@k)zN&V`C4E* z!*bcUcTl18qgPtrudn$E`vBV0k~6u*6t0CMh(pJ>(nQwNi>O{GdGYD785L?LUZ%wo zfd=l7;xt4%?v&U=3j8**%H&i1P+k;2FoOv7i2?7F5_OZq`YiI!caDm|*rM3!zpg?Y z*$G~vhM=(`j<;G7ij#oXD&7eV|65QLo}&a9-xQa^VT5u>TBNFmfd=+TA^i(DC`F?b zAt|JTM7;{{wp8>+PrxPr$t2 DyzK9X delta 514 zcmYMv&r1S96bEo$&TMM_Y(*-qgB?PG3X&)kBr9C3q+B5}iAb=hgEXGYIuuk8*c>}_ zs8d8g7ziS&Tm2V7x2{p$JoIKFT9_}NdHZ(X%;_y`U4Z-3<#Jf?vAv(IloEA!8$8x~ z!ZqErc34({5mPsb5=BuZ4W%hHqned$D4UWUWmj^b97-*y79}UjS#(94Oc(A6PH808@UWZNa?MR3> z`aF;p&i-)#L(C4GSRu|o1~*_zRH8l#8L>Um1>!Q%OXxg)l@hOGH3Bny!I%up%HL#V zm>4;hlg#rcW0)==OQK<{04XB5PY;OA^pX|k_}laWUBX&TtY;1hEFPIVw2b>>v2V5m zDgCuS}(hAn^#7o`;@bh3{XfTNdmXeW{F(qSEMn;A_ER&~Tau$;VScUHu fIvorNxT)7%GU_5x{ diff --git a/application/shoppinglists/__pycache__/shoplist_processess.cpython-313.pyc b/application/shoppinglists/__pycache__/shoplist_processess.cpython-313.pyc index 99f14f3fa17dea3fd4fc10bae8e3863f29474c8b..31bd45a544938a271d051d99e9504f84580a62db 100644 GIT binary patch delta 2074 zcmd5+O-vg{6rQy)ti9OlAJ%_s!x}FJ2MADNkQ3xY3294%!_Q_*6=)2qP3n|x&>B*y zp-NS$5{IZl8Y%J-4y`y;m0NN^J@oI=oT9aKnH7M3eOLcGDisxykPPSsL zzRAc&^@_C%DK@N}0nQbU)?9NjeyJ$)bmU<-$bcK-Xl5-abOgS=Nw(Cd)e z@DbQ@3=RNTKi-NpXA>31drm*=yACN6AZ3bg7KPh3M$NqxQ!-`J3#r8X+>C-hvgCaw zBbg=^7t=|#7SgGbw^((82y^%<`)PBDy`EaQcq^qWjHlj8&!;k~pI9^zArP}hY_*=J zdhuoJy>Q8R?7d`25m2eV>|d>5GmU@{;KRRJ`xqG(gny>ZfKS+FO+^=<9nQPLAHIfH zY)RdBZ*Kfyi|FszcRa!6p(5+f@_Dvp-MPhfJrHxA%`^F)S9W6~+p$C;me8h?+U!m3 z*87Fn?Ja%@WKParTZS}Y7}*x%1u_2w?+Cqn0np_9eXiS;x2P=8SnSLHQkS7=#Hu1D71 z*%Dql-f9l5zPHx7&(ba4tZ~ndJYC!FNZuXErGIvxJK#{D^T2?_?tLTjiiaWG%AL1A ze$Ht+BL-yy$Y3p)Mg@}0g-wc=Uwflt1$fr{>%@!*8Pd|mpbn*?b)XL3Ups?L7SK? z&{K!Cc+KFoJ$}R@UaC;~Ua(wXu;!LdLMI`WbfrpeO(pKiRiYAxY6wJ$9(ea=a&ayd vQxka1b9&&4j^eVJJw(QJu`+Ht{w%Ne=V}^YSK31`tk;x>T;`Ch${zm$k$!@G delta 1266 zcmcgrO=uHQ5Pq-OH0f@#n{2X4c9S$ow_1Z(YtvdIwfIxCrL_LMF0Ck{(5i>JmQ4k3 zf;T;gbS{E7sZcLc=|u`!#Dh1fMNo=}2l3F0)v5>S!Fk)HSjFN&e6TZb=6(CkeDii^ ziq8u2axf?oT$dLo&ex_N$g^H4fT0JUv?gkK81L?XcX6a9PgQv5&vjSy=s)Rkm8{cma3!r24K9{s zDMY7vu8Fes`o90hT>FohC&WCB!E@2nVl9=q#jQBrV>iD?uBNA=CiC|-5xLQQtpp9Y zBgJU~W+XG2s8nWIuHM!y9qS*i)E_9_)v5qLuk)N$*iYB!Yfy z)mx!0$jjH6ZCEvgJvF#Jy$qOm&-f2y@GC=nzKjp9;d6-h9NPqs^$cx*w|d%}JeSPZ zPrsK76wJ_^&xs*zJ_<_sZrDlV-pO#2EVPz;i`T;*o(P?{f>mr=>Xkmp@*Q)IH~cx zgFOdZs;hj31%9ZK1=4{4r!QX!2;?Sj$;ST3Gbf$K= %(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