From a3cad9622e20c0b5eeb2e40bea0e97b994c14141 Mon Sep 17 00:00:00 2001 From: Jadowyne Ulve Date: Thu, 28 Aug 2025 17:58:05 -0500 Subject: [PATCH] 2nd Database oop migration --- __pycache__/config.cpython-313.pyc | Bin 3569 -> 3567 bytes __pycache__/database.cpython-313.pyc | Bin 86358 -> 86357 bytes __pycache__/main.cpython-313.pyc | Bin 44656 -> 44655 bytes __pycache__/outh.cpython-313.pyc | Bin 239 -> 238 bytes __pycache__/postsqldb.cpython-313.pyc | Bin 94068 -> 94067 bytes __pycache__/webpush.cpython-313.pyc | Bin 2971 -> 2970 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 159 -> 158 bytes .../database_payloads.cpython-313.pyc | Bin 28897 -> 28896 bytes .../__pycache__/postsqldb.cpython-313.pyc | Bin 102735 -> 102734 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 173 -> 172 bytes .../__pycache__/access_api.cpython-313.pyc | Bin 8651 -> 6802 bytes .../access_database.cpython-313.pyc | Bin 4244 -> 4243 bytes application/access_module/access_api.py | 60 ++-- .../__pycache__/__init__.cpython-313.pyc | Bin 174 -> 173 bytes .../administration_api.cpython-313.pyc | Bin 14760 -> 14758 bytes .../administration_database.cpython-313.pyc | Bin 33653 -> 33652 bytes .../administration_models.cpython-313.pyc | Bin 0 -> 7838 bytes .../administration_processes.cpython-313.pyc | Bin 8571 -> 8570 bytes .../administration_services.cpython-313.pyc | Bin 0 -> 13472 bytes .../administration/administration_api.py | 8 +- .../administration/administration_models.py | 148 +++++++++ .../administration_processes.py | 167 ---------- .../administration/administration_services.py | 261 +++++++++++++++ .../administration/templates/site.html | 2 +- application/database_postgres/BaseModel.py | 74 ++++- application/database_postgres/BrandsModel.py | 2 +- .../database_postgres/FoodInfoModel.py | 27 +- .../database_postgres/ItemInfoModel.py | 22 +- .../database_postgres/ItemLocationsModel.py | 14 +- application/database_postgres/ItemsModel.py | 115 +++++-- .../database_postgres/LocationsModel.py | 7 +- .../database_postgres/LogisticsInfoModel.py | 12 +- application/database_postgres/RolesModel.py | 50 +++ application/database_postgres/SitesModel.py | 121 +++++++ .../database_postgres/TransactionsModel.py | 23 +- application/database_postgres/UnitsModel.py | 17 + application/database_postgres/UsersModel.py | 82 +++++ application/database_postgres/VendorsModel.py | 8 +- application/database_postgres/ZonesModel.py | 4 +- .../__pycache__/BarcodesModel.cpython-313.pyc | Bin 1019 -> 1018 bytes .../__pycache__/BaseModel.cpython-313.pyc | Bin 13675 -> 16800 bytes .../__pycache__/BrandsModel.cpython-313.pyc | Bin 862 -> 867 bytes .../ConversionsModel.cpython-313.pyc | Bin 944 -> 943 bytes .../CostLayersModel.cpython-313.pyc | Bin 1137 -> 1136 bytes .../__pycache__/FoodInfoModel.cpython-313.pyc | Bin 1688 -> 1882 bytes .../__pycache__/ItemInfoModel.cpython-313.pyc | Bin 1685 -> 1784 bytes .../ItemLocationsModel.cpython-313.pyc | Bin 1470 -> 1110 bytes .../__pycache__/ItemsModel.cpython-313.pyc | Bin 1975 -> 6074 bytes .../LocationsModel.cpython-313.pyc | Bin 924 -> 945 bytes .../LogisticsInfoModel.cpython-313.pyc | Bin 1033 -> 1166 bytes .../PlanEventsModel.cpython-313.pyc | Bin 1214 -> 1213 bytes .../__pycache__/PlansModel.cpython-313.pyc | Bin 935 -> 934 bytes .../ReceiptItemsModel.cpython-313.pyc | Bin 1628 -> 1627 bytes .../__pycache__/ReceiptsModel.cpython-313.pyc | Bin 1894 -> 1893 bytes .../RecipeItemsModel.cpython-313.pyc | Bin 1571 -> 1570 bytes .../__pycache__/RecipesModel.cpython-313.pyc | Bin 1810 -> 1809 bytes .../__pycache__/RolesModel.cpython-313.pyc | Bin 0 -> 3180 bytes .../SKUPrefixModel.cpython-313.pyc | Bin 925 -> 924 bytes .../ShoppingListItemsModel.cpython-313.pyc | Bin 1564 -> 1563 bytes .../ShoppingListsModel.cpython-313.pyc | Bin 1437 -> 1436 bytes .../__pycache__/SitesModel.cpython-313.pyc | Bin 0 -> 6515 bytes .../TransactionsModel.cpython-313.pyc | Bin 1647 -> 2128 bytes .../__pycache__/UnitsModel.cpython-313.pyc | Bin 0 -> 1146 bytes .../__pycache__/UsersModel.cpython-313.pyc | Bin 0 -> 4381 bytes .../__pycache__/VendorsModel.cpython-313.pyc | Bin 1382 -> 1406 bytes .../__pycache__/ZonesModel.cpython-313.pyc | Bin 902 -> 912 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 177 -> 176 bytes .../sql/CREATE/cost_layers.sql | 2 +- .../sql/CREATE/item_info.sql | 2 +- .../database_postgres/sql/CREATE/items.sql | 5 +- .../database_postgres/sql/CREATE/roles.sql | 8 + .../database_postgres/sql/CREATE/sites.sql | 12 + .../sql/CREATE/transactions.sql | 2 +- .../database_postgres/sql/CREATE/units.sql | 10 + .../database_postgres/sql/CREATE/users.sql | 17 + .../database_postgres/sql/CREATE/vendors.sql | 2 +- .../database_postgres/sql/DROP/units.sql | 1 + .../database_postgres/sql/INSERT/brands.sql | 4 +- .../sql/INSERT/food_info.sql | 18 +- .../sql/INSERT/item_info.sql | 24 +- .../sql/INSERT/item_locations.sql | 4 +- .../database_postgres/sql/INSERT/items.sql | 30 +- .../sql/INSERT/locations.sql | 4 +- .../sql/INSERT/logistics_info.sql | 16 +- .../database_postgres/sql/INSERT/roles.sql | 4 + .../database_postgres/sql/INSERT/sites.sql | 7 + .../sql/INSERT/transactions.sql | 26 +- .../database_postgres/sql/INSERT/units.sql | 3 + .../database_postgres/sql/INSERT/users.sql | 3 + .../database_postgres/sql/INSERT/vendors.sql | 4 +- .../database_postgres/sql/INSERT/zones.sql | 4 +- .../sql/ItemsModel/paginateItemsWithQOH.sql | 17 + .../sql/ItemsModel/paginateitemsForModal.sql | 7 + .../__pycache__/__init__.cpython-313.pyc | Bin 165 -> 164 bytes .../database_items.cpython-313.pyc | Bin 61946 -> 61945 bytes .../__pycache__/items_API.cpython-313.pyc | Bin 35818 -> 36469 bytes .../__pycache__/items_models.cpython-313.pyc | Bin 0 -> 2391 bytes .../items_processes.cpython-313.pyc | Bin 11063 -> 11062 bytes .../items/__pycache__/models.cpython-313.pyc | Bin 0 -> 4067 bytes .../__pycache__/services.cpython-313.pyc | Bin 0 -> 3162 bytes application/items/items_API.py | 80 +++-- application/items/models.py | 72 ++++ application/items/services.py | 61 ++++ .../items/sql/getItemForTransaction.sql | 28 ++ .../{sql => sql_old}/getItemAllByBarcode.sql | 0 .../items/{sql => sql_old}/getItemAllByID.sql | 0 .../{sql => sql_old}/getItemLocations.sql | 0 .../items/{sql => sql_old}/getItemsAll.sql | 0 .../{sql => sql_old}/getItemsWithQOH.sql | 0 .../{sql => sql_old}/getLocationsWithZone.sql | 0 .../items/{sql => sql_old}/getSkuPrefixes.sql | 0 .../insertCostLayersTuple.sql | 0 .../{sql => sql_old}/insertFoodInfoTuple.sql | 0 .../{sql => sql_old}/insertItemInfoTuple.sql | 0 .../{sql => sql_old}/insertItemLinksTuple.sql | 0 .../insertItemLocationsTuple copy.sql | 0 .../insertItemLocationsTuple.sql | 0 .../{sql => sql_old}/insertItemTuple.sql | 0 .../insertLogisticsInfoTuple.sql | 0 .../{sql => sql_old}/insertSKUPrefixTuple.sql | 0 .../insertTransactionsTuple.sql | 0 .../items/{sql => sql_old}/itemsModal.sql | 0 .../{sql => sql_old}/itemsModalCount.sql | 0 .../paginateLocationsBySkuZone.sql | 0 .../paginateLocationsBySkuZoneCount.sql | 0 .../{sql => sql_old}/paginateZonesBySku.sql | 0 .../paginateZonesBySkuCount.sql | 0 .../{sql => sql_old}/selectItemByBarcode.sql | 0 application/items/static/ItemListHandler.js | 22 +- .../items/static/transactionHandler.js | 84 +++-- .../items/static/transactionsHandler.js | 40 +-- application/items/templates/index.html | 46 +-- application/items/templates/item_new.html | 10 +- application/items/templates/transaction.html | 31 +- application/items/templates/transactions.html | 10 +- .../__pycache__/__init__.cpython-313.pyc | Bin 172 -> 171 bytes .../meal_planner_api.cpython-313.pyc | Bin 9142 -> 9141 bytes .../meal_planner_database.cpython-313.pyc | Bin 15375 -> 15374 bytes .../meal_planner_processes.cpython-313.pyc | Bin 3646 -> 3645 bytes .../poe/__pycache__/__init__.cpython-313.pyc | Bin 163 -> 162 bytes .../poe/__pycache__/poe_api.cpython-313.pyc | Bin 5814 -> 5813 bytes .../__pycache__/poe_database.cpython-313.pyc | Bin 20947 -> 20946 bytes .../__pycache__/poe_processes.cpython-313.pyc | Bin 5894 -> 5893 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 168 -> 167 bytes .../__pycache__/receipts_api.cpython-313.pyc | Bin 21945 -> 21944 bytes .../receipts_database.cpython-313.pyc | Bin 34717 -> 34716 bytes .../receipts_processes.cpython-313.pyc | Bin 10156 -> 10155 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 167 -> 166 bytes .../database_recipes.cpython-313.pyc | Bin 39959 -> 39958 bytes .../recipe_processes.cpython-313.pyc | Bin 10199 -> 10198 bytes .../__pycache__/recipes_api.cpython-313.pyc | Bin 17505 -> 17504 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 173 -> 172 bytes .../__pycache__/shoplist_api.cpython-313.pyc | Bin 19531 -> 19530 bytes .../shoplist_database.cpython-313.pyc | Bin 25051 -> 25050 bytes .../shoplist_processess.cpython-313.pyc | Bin 7354 -> 7353 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 175 -> 174 bytes .../site_management_api.cpython-313.pyc | Bin 15017 -> 15016 bytes .../site_management_database.cpython-313.pyc | Bin 20386 -> 20385 bytes config.py | 2 +- database.ini | 9 +- logs/database.log | 314 +++++++++++++++++- logs/process.log | 136 ++++++++ webserver.py | 38 +-- 163 files changed, 1918 insertions(+), 525 deletions(-) create mode 100644 application/administration/__pycache__/administration_models.cpython-313.pyc create mode 100644 application/administration/__pycache__/administration_services.cpython-313.pyc create mode 100644 application/administration/administration_models.py delete mode 100644 application/administration/administration_processes.py create mode 100644 application/administration/administration_services.py create mode 100644 application/database_postgres/RolesModel.py create mode 100644 application/database_postgres/SitesModel.py create mode 100644 application/database_postgres/UnitsModel.py create mode 100644 application/database_postgres/UsersModel.py create mode 100644 application/database_postgres/__pycache__/RolesModel.cpython-313.pyc create mode 100644 application/database_postgres/__pycache__/SitesModel.cpython-313.pyc create mode 100644 application/database_postgres/__pycache__/UnitsModel.cpython-313.pyc create mode 100644 application/database_postgres/__pycache__/UsersModel.cpython-313.pyc create mode 100644 application/database_postgres/sql/CREATE/roles.sql create mode 100644 application/database_postgres/sql/CREATE/sites.sql create mode 100644 application/database_postgres/sql/CREATE/units.sql create mode 100644 application/database_postgres/sql/CREATE/users.sql create mode 100644 application/database_postgres/sql/DROP/units.sql create mode 100644 application/database_postgres/sql/INSERT/roles.sql create mode 100644 application/database_postgres/sql/INSERT/sites.sql create mode 100644 application/database_postgres/sql/INSERT/units.sql create mode 100644 application/database_postgres/sql/INSERT/users.sql create mode 100644 application/database_postgres/sql/ItemsModel/paginateItemsWithQOH.sql create mode 100644 application/database_postgres/sql/ItemsModel/paginateitemsForModal.sql create mode 100644 application/items/__pycache__/items_models.cpython-313.pyc create mode 100644 application/items/__pycache__/models.cpython-313.pyc create mode 100644 application/items/__pycache__/services.cpython-313.pyc create mode 100644 application/items/models.py create mode 100644 application/items/services.py create mode 100644 application/items/sql/getItemForTransaction.sql rename application/items/{sql => sql_old}/getItemAllByBarcode.sql (100%) rename application/items/{sql => sql_old}/getItemAllByID.sql (100%) rename application/items/{sql => sql_old}/getItemLocations.sql (100%) rename application/items/{sql => sql_old}/getItemsAll.sql (100%) rename application/items/{sql => sql_old}/getItemsWithQOH.sql (100%) rename application/items/{sql => sql_old}/getLocationsWithZone.sql (100%) rename application/items/{sql => sql_old}/getSkuPrefixes.sql (100%) rename application/items/{sql => sql_old}/insertCostLayersTuple.sql (100%) rename application/items/{sql => sql_old}/insertFoodInfoTuple.sql (100%) rename application/items/{sql => sql_old}/insertItemInfoTuple.sql (100%) rename application/items/{sql => sql_old}/insertItemLinksTuple.sql (100%) rename application/items/{sql => sql_old}/insertItemLocationsTuple copy.sql (100%) rename application/items/{sql => sql_old}/insertItemLocationsTuple.sql (100%) rename application/items/{sql => sql_old}/insertItemTuple.sql (100%) rename application/items/{sql => sql_old}/insertLogisticsInfoTuple.sql (100%) rename application/items/{sql => sql_old}/insertSKUPrefixTuple.sql (100%) rename application/items/{sql => sql_old}/insertTransactionsTuple.sql (100%) rename application/items/{sql => sql_old}/itemsModal.sql (100%) rename application/items/{sql => sql_old}/itemsModalCount.sql (100%) rename application/items/{sql => sql_old}/paginateLocationsBySkuZone.sql (100%) rename application/items/{sql => sql_old}/paginateLocationsBySkuZoneCount.sql (100%) rename application/items/{sql => sql_old}/paginateZonesBySku.sql (100%) rename application/items/{sql => sql_old}/paginateZonesBySkuCount.sql (100%) rename application/items/{sql => sql_old}/selectItemByBarcode.sql (100%) diff --git a/__pycache__/config.cpython-313.pyc b/__pycache__/config.cpython-313.pyc index fa2a8523d0ce5bfe0e23c8a27cf978b67a55b2a6..b17bb2532383795cd8ee11702fec89d2a1b2afdc 100644 GIT binary patch delta 68 zcmew;{a%{)GcPX}0}#B`UX|gwkynyQQs31oCOJPPHKrgjucW9_x1=aBIXfmLwQREk RlO#76Cqx*?pPa(m3jp-v7PbHY delta 70 zcmaDa{ZX3tGcPX}0}%XvG(E#>Bd;Wrl!2>NOmcooYD__5UP)13u`)J$0{}lD6o>!- diff --git a/__pycache__/main.cpython-313.pyc b/__pycache__/main.cpython-313.pyc index db96cfc9ca4e8a76518f36ed43ce21a8fc64e6a0..d6128f4b2b06cefd618baf3c1a7420b6c735ee85 100644 GIT binary patch delta 37 scmexxhw1$tChpI?yj%=G(5bv?Bllz`M%~HtnM9dWQp+~)VA`=70QY(f?f?J) delta 38 tcmaEVhv~x|ChpI?yj%=GV1D0lBllz`M!m`NnM7G~6EpKRZ)e)E8UOPhLGcPX}0}ym7ubRl+E~)Ek6_cEwk{VNxm{(F%sasN%n4BGxl3F%#jWYmn Cst}F< delta 49 zcmaFI_@0sbGcPX}0}!bG=%2{lE~V#c6_cEwk{VNxm{(F%sasN%n4BGxo0yq5akVo5 DjRp}* diff --git a/__pycache__/postsqldb.cpython-313.pyc b/__pycache__/postsqldb.cpython-313.pyc index 7a7b51c20a38b7109dac9509243404f60895f594..ab4c94f9b78588afe520b9ad956570e5c9f3f89a 100644 GIT binary patch delta 57 zcmexzkM;9CR_@Qdyj%=G(5bv?BlkUKNh4RQnB@GF)R=A MCYJ3?ER2QO0Ajoo*8l(j diff --git a/__pycache__/webpush.cpython-313.pyc b/__pycache__/webpush.cpython-313.pyc index 90a348e20fa9e751b63bce97b8169a7d138a05b1..ea72ac0ade32893a4fcd54b89bec1309331844da 100644 GIT binary patch delta 51 zcmbO&K1-bYGcPX}0}ym7uiD5xol(-j)hZ@AKP5G$ATh6`s8YA2C^0!ZCMC6O^B%_g FoB)Ti5gq^l delta 52 zcmbOwK3kmoGcPX}0}xo;H{8fQol(lr)hZ@AKP5G$ATh6`s8YA2C^0!ZCO0uNZ}V=( G`UGGcPX}0}z diff --git a/application/__pycache__/database_payloads.cpython-313.pyc b/application/__pycache__/database_payloads.cpython-313.pyc index 6d92e3df1bae93eca22fdafb3b87baebd4fa6655..b3fc156a7573decafc6f446ecb94a8bb02075ec5 100644 GIT binary patch delta 53 zcmaF(knzDoM()qNyj%=G(5bv?Bexl=q?@Z%OmcooYD__5UP)1eCXX delta 54 zcmaFxkn!O|M()qNyj%=GaQfxSjofCeQtqx+G0FKUsWAnKc_l@ax+O)4$=NZviJ5tu IqgdOr0VQ%26951J diff --git a/application/__pycache__/postsqldb.cpython-313.pyc b/application/__pycache__/postsqldb.cpython-313.pyc index 6664be4aa18da1dd2413dec5cd59e925f2a02bbd..e8e03e39c553703346f27b18933d7b152a0aa2fb 100644 GIT binary patch delta 57 zcmX@Vi0#}WHtx^7yj%=G(5bv?Blme`Njq1onB@GF)R=(Ge-9U05RYcsQ>@~ delta 58 zcmX@Ni0%9$Htx^7yj%=Gu<6zGjojy%rR-g;Vv_SyQez4d^Gb>;bxVp8le1%T6EpLg NKQnLt%*+_w3jk{`73=^2 diff --git a/application/access_module/__pycache__/__init__.cpython-313.pyc b/application/access_module/__pycache__/__init__.cpython-313.pyc index f88ef8c1be34ef070c7acc43134ac746b4c13c9a..0a227d62e01c215c6fd7c8aa1a595e167f665ba3 100644 GIT binary patch delta 31 lcmZ3>xQ3DYGcPX}0}ym7ubRkh%;-JQUX(c{wQOQUC;)v62vh(7 delta 32 mcmZ3(xR#OoGcPX}0}!bG=%2`K%;+=GUX&#_F*9#scqjmik_iz2 diff --git a/application/access_module/__pycache__/access_api.cpython-313.pyc b/application/access_module/__pycache__/access_api.cpython-313.pyc index 0fea0f3554668387f2e8a1612d8b68099cb56094..2b363bf34b1ba1ed4947b0d4514e1526280c1a60 100644 GIT binary patch delta 2188 zcmZ`)TTC2P7(R2`+wANDyDaPi%N9yuT7ecNEiFo`K-1k)E#sz@MYq%40mm*g>zP>< zHPU!VQ2Ri8qG{qK#u$TIO%q>uw+}{RWWfY7wW*hCqAz0G#0QOf&RLLZ;z{P4fBy5G z|6Kn6eDnD|=XwJ-B*_PG`{L;P5C2kqHc-#Jbhf>F26_~ra4B!n3%yAn^d$iP=2K$Q z5B=0;QxcT0Pe=ulK^UYySE?#m4Xdf`PSqqsFq8n}Obbvv<6*_C_!O~^hqa1724F;y zFxHLNj_xAU|GOz-KnZp;N>v{_UUzn_R)`WF@-eayqt+g5HBf7U+0onhKT)De9S*~i zFiI>2deYg?$mf*dko#+QitN0 zBN1{xhST6{v35mlSrS{`**!l{Zr!(_T{`w@rabh(;+o{^nb_oPD z0KQ1%oHO+sj?qT6i>saCH)7tx^b)4xyTv2Yu_Pt5DSky#0$uFJ*sUP5*%wr*{@tf| zHzSQE)<;j(|y)lJ{< ze$0h?F*u&0YFUq)Fb~NN9f{V#KfWm8kt^bO4jGliIYCOP)S^9I)P_+pR3Cbebp)JH z6FTlG&9c-H8m^LSXrVYluAr$9is<{$;87xfl{;B~>z&M=oTg=As8brGHRdJ6 z+X#@P$bu8p4a-)IoR-a*#)LjeHvx~Ki{bFL%3UVJ1ZkB^BUI`|$KHeZu_B18-E}T5 zqX%o-My~~($2(Ty`XCE1)I@B z_0j5o6s`*2=vP37L7%x}=6em&(xqlqRoOY z&5uTr8f@?qO~B3I(fQaORt}&`4I{frscfbd9wVQ3V34_WJ*0<?hNc|B+A zrlG=VS%8yd7^%0-g*hHWdz_}b)%s7)T$Vm*l#0;8119ie{S44j(`?_hP}54NZ7I~Y z8i}vQT2|{Ce|7U=->m1RAB5v8q3uhd?ejhJPpot&%iYQ4?!(KWky+2RU<6%=?{GtM z)#Nxj)4Y94Wm~ii$<7_`M9Ik6rdhBc(Rg?O1ryC(0n*i>7K;Tvr;^QdVXah8s+N{5 znwC9@Rjn(H_rCro>r5nO$X|jAvm-#Py&4r?s%liSAgM#TFW5<}O|ka$Pnr2rLAwh+ zg*%8|S$|3W$6wMdqG<>-fS`i(@DK oWhr;scb(^$hU-4`P2%P1M&>Ycg9B{u8cWQze)Q%RnJ4r94G7O9I{*Lx delta 4051 zcmb_fYit|G5#BrU_&z=)QV&WNPmyGqie8Q_KV`YDDe7q|(MgsgDo{`4QDRIT={+*B zl>~Hwrf}i@uyRi9B1NnGY14Or6orcfMF2aFg8~I*yRmR_kfcaa_)nq4K?Ec}+S#QT zDMDSKK!@P$?Ck7&=9|42y8e9Bd7tNP2uS+drMaK`7M)ehjm74U{Gb(E1tcQTFl56v zTH3K4O5>1Y(19I;JmzUTH{=|2Vb{8?8@s8k2YaA24V4X+<8s<&9`X+Quuni!%tJ`D zOjU?h(I(pW8*!!R@FQF$^1!R7Do49uH8o^On@Dtut`0_Y?`Nl~7eDfGHN^|)nJ7zP&fCl*Azo`9+?K19Wx)|*X_Q)}|8I(~gFiPX7v=RZ$ zqM#w7n{%p+r;~BrNQ5AVU0|sDR5>Nbvcu`wq@s@HGAa4nX9hyrGluF8oy*{)k_|Gr z3M^H!F+q>?9+--yPlT-6jQqkTp4E1dTBM-4Y0PHNfYxm!=ciyyj z{=WH2aQTr?VQ2W3z3*&@>f3JG+g^>|vhO+@TB-1VukT#=yCa_%t>+q_AJRsQI#Z>! zaJ@{emf_kMzxEUETYKwZP|O1hvbcfBjlfhE*mne63x)1Xk&R4a`BW~6Tz;#IE%>Tt#ydF2d%Q7umyG<(J zLuN4LGI1#@<4UODD(z>s;VMjANjGNY(^+hy;*)Ax!7j~VZyEFxJ2SALMdz~Vld^(4 zh>eHbLQ+Yjr)4FcNy9ece8=m* zv^OxYxxYAm!yM9{<=ZQ$g)Na+z?{+Ff3Axb!gl;x=s7Y>+ggrCz=$~I5!%A z@wU(j3H*}W3y^@ork-@lD`tpT_-!<8txuSD6nh~lg3!2Gv~o}%XAhhJ_~@k?8_`79 ztsvy=#PPwB>Y;Sd9%hkWFP-b9i^~@c-9#aJ+D)4VDISWRha1S9qkoJkSNl;e5gr1 zIQ9!x=OAX%SU1nhsw&OO_lP@U%1G$mgR?~U_({T@gb_5n=LQxuVG`)YsW~;7l~vtF zifNoqf$UJvs9AY_T8hsn6`f0@q*;|Bq;45diUEsv!vI>Vr&>GV3HvZ{fM7KR0iBDb zWC>%3_FGRVZ-)lhoq8sg&dffdn`3E30Wj80u^d*(+U3)7ESHsaOG3`Z=F*C+TYFE( z?A1yQ5FUb|6pXLu^oirWkvn#He8Y4Xju;Q z-l`1Wc8AwIuqil~uT~wz_RBRM>hJNLU#i46uR551pW&+09qwVT)-{FOxcoJP1uECu zY~h2}>w67EK0u7FAEa`RzrT!or)qmY&%M*b5c#kXT5s?i$T!LiME2SG8_f&0j<}@G z&7>t9kHJ)56UhxUKMaHLA`+PmEIY`cJd(g|qr`xcxCs(P!-lEIP?$9m?nEND%g_j+ z6)8GVHz*g({c&wQ-4c-MU8pr%xM>M6#kSBv-p z9w+2Y<9goYAa8P7##j3&WKEN3Rm|h2(V9}%jq=2)6AhwmxADR0aVzF(FZuRO{+AT- zNC}9eMcp5BFfymg%dP3MyN(HS#DRZ<6GQ3e!tpZLzJ)J$1k%PkV zBg5mp;*KV@RoM6k4^jsWFQgt%=Y%=wDOs42Wkt{u73cFsATPrXvKHbI*hrX6Sc0%w z!sZA|680ov1mp2Z!cv6I6Q&TBA?y@km@pMs(2BD}CRuM|i#SKBPoaIlpC;mI!XAXf zrlR*^^4KA9?A3LXwQn;KSN-RX__=2lC`=h<<|T9EPt9nSN%_cRPKQW0EW>VA0#!3Tl?_`E)#& zl6&wuup{pkwGsHLfnk^r(c~R8xr`=1K)XIfRUe|}4^Ykf$o)QY{T+3EfGR%bD=v7S zT{>~)nZo4EG9No@`^?5M?$6xXyVd7+H8I1?eFI{(7yrsa*e3~ diff --git a/application/access_module/__pycache__/access_database.cpython-313.pyc b/application/access_module/__pycache__/access_database.cpython-313.pyc index bf35a238dfca71ad5a25282f3e6bb3d9e19f97b7..01221a277d36b368653511e7cc5791b61eb69f13 100644 GIT binary patch delta 51 zcmbQDI9ZYVGcPX}0}ym7uiD6+%_te*H~ FKLCWf5E%df delta 52 zcmbQNI7N~BGcPX}0}!bG=-Logout" - @access_api.route('/signup', methods=['POST', 'GET']) def signup(): instance_config = sites_config() @@ -132,14 +110,14 @@ def signup(): password = request.get_json()['password'] email = request.get_json()['email'] password = hashlib.sha256(password.encode()).hexdigest() - database_config = config() - with psycopg2.connect(**database_config) as conn: - try: - with conn.cursor() as cur: - sql = f"INSERT INTO logins(username, password, email, row_type) VALUES(%s, %s, %s, %s);" - cur.execute(sql, (username, password, email, 'user')) - except (Exception, psycopg2.DatabaseError) as error: - conn.rollback() - return jsonify({'error': True, 'message': str(error)}) + + new_user = UsersModel.Payload( + user_name=username, + user_password=password, + user_email=email + ) + + new_user = UsersModel.insert_tuple('', new_user.payload_dictionary()) + return jsonify({'error': False, 'message': 'You have been signed up successfully, you will have to wait until the server admin finishes your onboarding!'}) return jsonify({'error': True, 'message': 'There was a problem with this POST request!'}) diff --git a/application/administration/__pycache__/__init__.cpython-313.pyc b/application/administration/__pycache__/__init__.cpython-313.pyc index 0d169b412c3b31a23ff9ef5ad1f57956b5d43487..562367caae16de9405daae58ffb5d67cd96f827c 100644 GIT binary patch delta 31 lcmZ3-xR#OoGcPX}0}ym7ubRkh%;+=GUX(c{wQOQU7yx}!2v`6B delta 32 mcmZ3>xQ>zgGcPX}0}!bG=%2`K%;-DOUX&#_F*9#sco+bS-3b){ diff --git a/application/administration/__pycache__/administration_api.cpython-313.pyc b/application/administration/__pycache__/administration_api.cpython-313.pyc index ea0a32492bacbd9cfee5da804fbac235a0c411bb..2b9880f2a0c36f551ad2ae865530b0bd2f10d431 100644 GIT binary patch delta 142 zcmZ2cysVh_GcPX}0}xcIuFBxt$h(47Ogu3qH#0A@xTGktBr`uRzBsk0EHioY8PSIkmWW^J&(8 zMyYUDtC-~cl+>7l#JrNCN?o9$-W;!J%*>;JtY)6dJwe8Z&9!El%m83xG5i1k diff --git a/application/administration/__pycache__/administration_database.cpython-313.pyc b/application/administration/__pycache__/administration_database.cpython-313.pyc index 9f4a8bc53c4222cef80f05a81a1e211ada9475d5..f012cad7672d481c3b3bae1bbd51877df1f0b6b2 100644 GIT binary patch delta 53 zcmey`#`L9)iTg7zFBbz4bSkgf$bE)IGTPNDCOJPPHKrgjucW9_x1=aBIXfmLwQTc8 HmXi?g)>~b;KiP}Ci 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 literal 0 HcmV?d00001 diff --git a/application/administration/__pycache__/administration_processes.cpython-313.pyc b/application/administration/__pycache__/administration_processes.cpython-313.pyc index e5706096514e68a9c6dbc2f6708fb267675b5768..e10e7263a6bc25280e0c59e3fb26f7c846456ed4 100644 GIT binary patch delta 35 pcmezE^vj9+GcPX}0}ym7uiD7{l!-BB@<%37=9JX3&0NfdasbrR3myOf delta 36 qcmez6^xKL1GcPX}0}!bG=-}nGsIt$wX diff --git a/application/administration/__pycache__/administration_services.cpython-313.pyc b/application/administration/__pycache__/administration_services.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ff6bdcd7aa2ad1621c7ebaa41fabf832539f3882 GIT binary patch literal 13472 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 ARRAY[%(role_uuid)s::uuid];" + with conn.cursor() as cur: + cur.execute(select_sql, payload) + users = tuple([row[0] for row in cur.fetchall()]) + + update_sql = f"UPDATE users SET user_roles = array_remove(user_roles, %(role_uuid)s::uuid) WHERE user_uuid = %(user_uuid)s::uuid;" + with conn.cursor() as cur: + for user_uuid in users: + cur.execute(update_sql, {'role_uuid': payload['role_uuid'], 'user_uuid': user_uuid}) + + if self_conn: + conn.commit() + conn.close() + + except Exception as error: + raise error + + @classmethod + def update_sites(self, payload, convert=True, conn=None): + """ payload: {'site_uuid',} """ + self_conn = False + try: + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = True + self_conn = True + + select_sql = f"SELECT users.user_uuid FROM users WHERE user_sites @> ARRAY[%(site_uuid)s::uuid];" + with conn.cursor() as cur: + cur.execute(select_sql, payload) + user = tuple([row[0] for row in cur.fetchall()]) + + update_sql = f"UPDATE users SET user_sites = array_remove(user_sites, %(site_uuid)s::uuid) WHERE user_uuid = %(user_uuid)s::uuid;" + with conn.cursor() as cur: + for user_uuid in user: + cur.execute(update_sql, {'site_uuid': payload['site_uuid'], 'user_uuid': user_uuid}) + + if self_conn: + conn.commit() + conn.close() + + except Exception as error: + raise error + + @classmethod + def update_user_site_roles(self, payload, convert=True, conn=None): + """ payload (tuple): (site_uuid, role_uuid, user_uuid) """ + sql = f"UPDATE users SET user_sites = user_sites || %(site_uuid)s::uuid, user_roles = user_roles || %(role_uuid)s::uuid WHERE user_uuid=%(user_uuid)s RETURNING *;" + user = () + self_conn = False + try: + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = True + self_conn = True + + with conn.cursor() as cur: + cur.execute(sql, payload) + rows = cur.fetchone() + if rows and convert: + user = tupleDictionaryFactory(cur.description, rows) + elif rows and not convert: + user = rows + + if self_conn: + conn.commit() + conn.close() + + return user + except Exception as error: + raise DatabaseError(error, payload, sql) \ No newline at end of file diff --git a/application/administration/administration_processes.py b/application/administration/administration_processes.py deleted file mode 100644 index 11e6376..0000000 --- a/application/administration/administration_processes.py +++ /dev/null @@ -1,167 +0,0 @@ -# 3RD PARTY IMPORTS -import psycopg2 -import datetime - -# APPLICATION IMPORTS -import config -from application import postsqldb, database_payloads -from application.administration import administration_database - -def dropSiteTables(conn, site_manager): - try: - for table in site_manager.drop_order: - administration_database.dropTable(site_manager.site_name, table, conn=conn) - with open("logs/process.log", "a+") as file: - file.write(f"{datetime.datetime.now()} --- INFO --- {table} DROPPED!\n") - except Exception as error: - raise error - -def deleteSite(site, user, conn=None): - """Uses a Site Manager to delete a site from the system. - - Args: - site_manager (MyDataclasses.SiteManager): - - Raises: - Exception: - """ - self_conn = False - if not conn: - database_config = config.config() - conn = psycopg2.connect(**database_config) - conn.autocommit = False - self_conn = True - - try: - admin_user = (user['username'], user['password'], user['email'], user['row_type']) - site_manager = database_payloads.SiteManager( - site['site_name'], - admin_user, - site['default_zone'], - site['default_primary_location'], - site['site_description'] - ) - - roles = administration_database.selectRolesTupleBySite((site['id'],), conn=conn) - administration_database.deleteRolesTuple([role['id'] for role in roles], conn=conn) - - dropSiteTables(conn, site_manager) - - for role in roles: - administration_database.updateUsersRoles({'role_id': role['id']}, conn=conn) - - administration_database.updateUsersSites({'site_id': site['id']}, conn=conn) - - site = administration_database.deleteSitesTuple((site['id'], ), conn=conn) - - if self_conn: - conn.commit() - conn.close() - - except Exception as error: - with open("logs/process.log", "a+") as file: - file.write(f"{datetime.datetime.now()} --- ERROR --- {error}\n") - conn.rollback() - conn.close() - -def addAdminUser(conn, site_manager, convert=True): - admin_user = () - try: - sql = f"INSERT INTO logins (username, password, email, row_type) VALUES (%s, %s, %s, %s) ON CONFLICT (username) DO UPDATE SET username = excluded.username RETURNING *;" - with conn.cursor() as cur: - cur.execute(sql, site_manager.admin_user) - rows = cur.fetchone() - if rows and convert: - admin_user = postsqldb.tupleDictionaryFactory(cur.description, rows) - elif rows and not convert: - admin_user = rows - with open("logs/process.log", "a+") as file: - file.write(f"{datetime.datetime.now()} --- INFO --- Admin User Created!\n") - except Exception as error: - raise error - return admin_user - -def setupSiteTables(conn, site_manager): - try: - for table in site_manager.create_order: - administration_database.createTable(site_manager.site_name, table, conn=conn) - with open("logs/process.log", "a+") as file: - file.write(f"{datetime.datetime.now()} --- INFO --- {table} Created!\n") - except Exception as error: - raise error - -def addSite(payload, conn=None): - """uses a Site Manager to add a site to the system - - Args: - site_manager (MyDataclasses.SiteManager): - """ - self_conn = False - site_manager = database_payloads.SiteManager( - payload['site_name'], - payload['admin_user'], - payload['default_zone'], - payload['default_primary_location'], - payload['site_description'] - ) - - try: - - if not conn: - database_config = config.config() - conn = psycopg2.connect(**database_config) - conn.autocommit = False - self_conn = True - - setupSiteTables(conn, site_manager) - - admin_user = addAdminUser(conn, site_manager) - - site = database_payloads.SitePayload( - site_name=site_manager.site_name, - site_description=site_manager.description, - site_owner_id=admin_user['id'] - ) - - site = administration_database.insertSitesTuple(site.payload(), conn=conn) - - role = database_payloads.RolePayload("Admin", f"Admin for {site['site_name']}", site['id']) - role = administration_database.insertRolesTuple(role.payload(), conn=conn) - - admin_user = administration_database.updateAddLoginSitesRoles((site["id"], role["id"], admin_user["id"]), conn=conn) - - default_zone = database_payloads.ZonesPayload(site_manager.default_zone) - default_zone = administration_database.insertZonesTuple(site["site_name"], default_zone.payload(), conn=conn) - uuid = f"{site_manager.default_zone}@{site_manager.default_location}" - - default_location = database_payloads.LocationsPayload(uuid, site_manager.default_location, default_zone['id']) - default_location = administration_database.insertLocationsTuple(site['site_name'], default_location.payload(), conn=conn) - - payload = { - 'id': site['id'], - 'update': { - 'default_zone': default_zone['id'], - 'default_auto_issue_location': default_location['id'], - 'default_primary_location': default_location['id'] - } - } - - administration_database.updateSitesTuple(payload, conn=conn) - - - blank_vendor = database_payloads.VendorsPayload("None", admin_user['id']) - blank_brand = database_payloads.BrandsPayload("None") - - blank_vendor = administration_database.insertVendorsTuple(site['site_name'], blank_vendor.payload(), conn=conn) - blank_brand = administration_database.insertBrandsTuple(site['site_name'], blank_brand.payload(), conn=conn) - - - if self_conn: - conn.commit() - conn.close() - - except Exception as error: - with open("logs/process.log", "a+") as file: - file.write(f"{datetime.datetime.now()} --- ERROR --- {error}\n") - conn.rollback() - raise error \ No newline at end of file diff --git a/application/administration/administration_services.py b/application/administration/administration_services.py new file mode 100644 index 0000000..a176a2a --- /dev/null +++ b/application/administration/administration_services.py @@ -0,0 +1,261 @@ +# 3RD PARTY IMPORTS +import psycopg2 +import datetime + +# APPLICATION IMPORTS +import config +from application import postsqldb, database_payloads +from application.administration import administration_database, administration_models +from dataclasses import dataclass, field + +from application.database_postgres import ( + CostLayersModel, + BrandsModel, + FoodInfoModel, + ItemInfoModel, + ZonesModel, + LocationsModel, + LogisticsInfoModel, + TransactionsModel, + ItemsModel, + ItemLocationsModel, + ConversionsModel, + SKUPrefixModel, + BarcodesModel, + VendorsModel, + ReceiptsModel, + ReceiptItemsModel, + RecipesModel, + RecipeItemsModel, + ShoppingListsModel, + ShoppingListItemsModel, + PlansModel, + PlanEventsModel, + SitesModel, + UsersModel, + RolesModel, + UnitsModel +) + +from application.database_postgres import BaseModel + +@dataclass +class SiteManager: + site_name: str + admin_user: tuple + default_zone: int + default_location: int + description: str + create_order: list = field(init=False) + drop_order: list = field(init=False) + + def create_tables(self, conn): + UsersModel.UsersModel.create_table(self.site_name, conn=conn) + SitesModel.SitesModel.create_table(self.site_name, conn=conn) + RolesModel.RolesModel.create_table(self.site_name, conn=conn) + UnitsModel.UnitsModel.create_table(self.site_name, conn=conn) + + # Needed for Items and Logistics + BrandsModel.BrandsModel.create_table(self.site_name, conn=conn) + ZonesModel.ZonesModel.create_table(self.site_name, conn=conn) + LocationsModel.LocationsModel.create_table(self.site_name, conn=conn) + ItemsModel.ItemsModel.create_table(self.site_name, conn=conn) + FoodInfoModel.FoodInfoModel.create_table(self.site_name, conn=conn) + ItemInfoModel.ItemInfoModel.create_table(self.site_name, conn=conn) + LogisticsInfoModel.LogisticsInfoModel.create_table(self.site_name, conn=conn) + ItemLocationsModel.ItemLocationsModel.create_table(self.site_name, conn=conn) + CostLayersModel.CostLayersModel.create_table(self.site_name, conn=conn) + ConversionsModel.ConversionsModel.create_table(self.site_name, conn=conn) + TransactionsModel.TransactionsModel.create_table(self.site_name, conn=conn) + SKUPrefixModel.SKUPrefixModel.create_table(self.site_name, conn=conn) + BarcodesModel.BarcodesModel.create_table(self.site_name, conn=conn) + + + # Vendors is used losely in Planner and in receipts. + VendorsModel.VendorsModel.create_table(self.site_name, conn=conn) + ReceiptsModel.ReceiptsModel.create_table(self.site_name, conn=conn) + ReceiptItemsModel.ReceiptItemsModel.create_table(self.site_name, conn=conn) + + # This is the Recipe Module + RecipesModel.RecipesModel.create_table(self.site_name, conn=conn) + RecipeItemsModel.RecipeItemsModel.create_table(self.site_name, conn=conn) + + # this is the Shopping List Module + ShoppingListsModel.ShoppingListsModel.create_table(self.site_name, conn=conn) + ShoppingListItemsModel.ShoppingListItemsModel.create_table(self.site_name, conn=conn) + + # Planner Module + PlansModel.PlansModel.create_table(self.site_name, conn=conn) + PlanEventsModel.PlanEventsModel.create_table(self.site_name, conn=conn) + + def drop_tables(self, conn): + # Needed for Items and Logistics + BrandsModel.BrandsModel.drop_table(self.site_name,conn=conn) + CostLayersModel.CostLayersModel.drop_table(self.site_name, conn=conn) + FoodInfoModel.FoodInfoModel.drop_table(self.site_name, conn=conn) + ItemInfoModel.ItemInfoModel.drop_table(self.site_name, conn=conn) + ZonesModel.ZonesModel.drop_table(self.site_name, conn=conn) + LocationsModel.LocationsModel.drop_table(self.site_name, conn=conn) + LogisticsInfoModel.LogisticsInfoModel.drop_table(self.site_name, conn=conn) + TransactionsModel.TransactionsModel.drop_table(self.site_name, conn=conn) + ItemsModel.ItemsModel.drop_table(self.site_name, conn=conn) + ItemLocationsModel.ItemLocationsModel.drop_table(self.site_name, conn=conn) + ConversionsModel.ConversionsModel.drop_table(self.site_name, conn=conn) + SKUPrefixModel.SKUPrefixModel.drop_table(self.site_name, conn=conn) + BarcodesModel.BarcodesModel.drop_table(self.site_name, conn=conn) + + + # Vendors is used losely in Planner and in receipts. + VendorsModel.VendorsModel.drop_table(self.site_name, conn=conn) + ReceiptsModel.ReceiptsModel.drop_table(self.site_name, conn=conn) + ReceiptItemsModel.ReceiptItemsModel.drop_table(self.site_name, conn=conn) + + # This is the Recipe Module + RecipesModel.RecipesModel.drop_table(self.site_name, conn=conn) + RecipeItemsModel.RecipeItemsModel.drop_table(self.site_name, conn=conn) + + # this is the Shopping List Module + ShoppingListsModel.ShoppingListsModel.drop_table(self.site_name, conn=conn) + ShoppingListItemsModel.ShoppingListItemsModel.drop_table(self.site_name, conn=conn) + + # Planner Module + PlansModel.PlansModel.drop_table(self.site_name, conn=conn) + PlanEventsModel.PlanEventsModel.drop_table(self.site_name, conn=conn) + +def deleteSite(payload, conn=None): + """Uses a Site Manager to delete a site from the system. + + Args: + site_manager (MyDataclasses.SiteManager): + + Raises: + Exception: + """ + self_conn = False + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = False + self_conn = True + + + site_manager = SiteManager( + payload['site_name'], + payload['admin_user'], + payload['default_zone'], + payload['default_primary_location'], + payload['site_description'] + ) + + roles = administration_models.ExtendedRolesModel.select_by_site_uuid({'site_uuid': payload['site_uuid']}, conn=conn) + roles = RolesModel.RolesModel.delete_tuples([role['role_uuid'] for role in roles], conn=conn) + + site_manager.drop_tables(conn=conn) + + for role in roles: + administration_models.ExtendedUsersModel.update_roles({'role_uuid': role['role_uuid']}, conn=conn) + + + administration_models.ExtendedUsersModel.update_sites({'site_uuid': payload['site_uuid']}, conn=conn) + + SitesModel.SitesModel.delete_tuples((payload['site_uuid'],), conn=conn) + + if self_conn: + conn.commit() + conn.close() + +def addSite(payload, conn=None): + """uses a Site Manager to add a site to the system + + Args: + site_manager (MyDataclasses.SiteManager): + """ + self_conn = False + site_manager = SiteManager( + payload['site_name'], + payload['admin_user'], + payload['default_zone'], + payload['default_primary_location'], + payload['site_description'] + ) + + try: + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = False + self_conn = True + + sql = 'CREATE EXTENSION IF NOT EXISTS "uuid-ossp";' + with conn.cursor() as cur: + cur.execute(sql) + + site_manager.create_tables(conn=conn) + + + admin_user = administration_models.ExtendedUsersModel.add_admin_user(site_manager.admin_user, conn=conn) + + site = SitesModel.SitesModel.Payload( + site_name=site_manager.site_name, + site_description=site_manager.description, + site_created_by=admin_user['user_uuid'] + ) + + # have to build site table + site = SitesModel.SitesModel.insert_tuple(site.site_name, site.payload_dictionary(), conn=conn) + + # have to build roles table + role = RolesModel.RolesModel.Payload( + role_name="Admin", + role_description=f"Admin for {site['site_name']}", + role_site_uuid=site['site_uuid'] + ) + role = RolesModel.RolesModel.insert_tuple(site['site_name'], role.payload_dictionary(), conn=conn) + + # have to build logins table + payload = { + 'user_uuid': admin_user['user_uuid'], + 'site_uuid': site['site_uuid'], + 'role_uuid': role['role_uuid'] + } + admin_user = administration_models.ExtendedUsersModel.update_user_site_roles(payload, conn=conn) + + default_zone = ZonesModel.ZonesModel.Payload(zone_name=site_manager.default_zone) + default_zone = ZonesModel.ZonesModel.insert_tuple(site["site_name"], default_zone.payload_dictionary(), conn=conn) + uuid = f"{site_manager.default_zone}@{site_manager.default_location}" + + default_location = LocationsModel.LocationsModel.Payload( + location_shortname=uuid, + location_name=site_manager.default_location, + zone_uuid=default_zone['zone_uuid'] + ) + + default_location = LocationsModel.LocationsModel.insert_tuple(site['site_name'], default_location.payload_dictionary(), conn=conn) + + payload = { + 'key': site['site_uuid'], + 'update': { + 'site_default_zone_uuid': default_zone['zone_uuid'], + 'site_default_auto_issue_location_uuid': default_location['location_uuid'], + 'site_default_primary_location_uuid': default_location['location_uuid'] + } + } + + SitesModel.SitesModel.update_tuple(payload, conn=conn) + + + blank_vendor = VendorsModel.VendorsModel.Payload("None", admin_user['user_uuid']) + blank_brand = BrandsModel.BrandsModel.Payload("None") + + VendorsModel.VendorsModel.insert_tuple(site['site_name'], blank_vendor.payload_dictionary(), conn=conn) + BrandsModel.BrandsModel.insert_tuple(site['site_name'], blank_brand.payload_dictionary(), conn=conn) + + if self_conn: + conn.commit() + conn.close() + + except Exception as error: + with open("logs/process.log", "a+") as file: + file.write(f"{datetime.datetime.now()} --- ERROR --- {error}\n") + conn.rollback() + raise error \ No newline at end of file diff --git a/application/administration/templates/site.html b/application/administration/templates/site.html index b4dbf59..7d6a365 100644 --- a/application/administration/templates/site.html +++ b/application/administration/templates/site.html @@ -204,7 +204,7 @@ payload: payload }), }); - location.href = '/administration' + //location.href = '/administration' } async function postEditSite(){ diff --git a/application/database_postgres/BaseModel.py b/application/database_postgres/BaseModel.py index d73028e..7c446c3 100644 --- a/application/database_postgres/BaseModel.py +++ b/application/database_postgres/BaseModel.py @@ -1,5 +1,6 @@ from abc import ABC import psycopg2 +import psycopg2.extras import datetime import uuid import json @@ -9,7 +10,6 @@ from copy import deepcopy import config - def validateUUID(uuid_string, version): try: u = uuid.UUID(uuid_string, version=version) @@ -58,6 +58,10 @@ def getUUID(n): class BasePayload(ABC): """BasePayloads holds the bare minimum methods required of a Payload. """ + + def __repr__(self): + return self.__dict__ + def payload_dictionary(self): return deepcopy(self.__dict__) @@ -76,6 +80,8 @@ class BaseModel(ABC): """ table_name: str = None # All extended class must assign a table name that CRUD uses to call upon primary_key: str = 'id' # All extended class can assign a different primary key/cloumn which is used to call delete and update queries on. + primary_key_type: str = 'int' + site_agnostic: bool = False #all extended class can set this to true to avoid site injection def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) @@ -225,3 +231,69 @@ class BaseModel(ABC): except Exception as error: raise DatabaseError(error, payload, sql) + + @classmethod + def select_tuple(self, site: str, payload: dict, convert: bool = True, conn=None): + record = () + self_conn = False + + if self.site_agnostic: + sql = f"SELECT * FROM {self.table_name} WHERE {self.primary_key} = %(key)s::{self.primary_key_type};" + else: + sql = f"SELECT * FROM {site}_{self.table_name} WHERE {self.primary_key} = %(key)s::{self.primary_key_type};" + try: + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = True + self_conn = True + + with conn.cursor() as cur: + cur.execute(sql, payload) + rows = cur.fetchone() + if rows and convert: + record = tupleDictionaryFactory(cur.description, rows) + elif rows and not convert: + record = rows + + if self_conn: + conn.commit() + conn.close() + + return record + + except Exception as error: + raise DatabaseError(error, payload, sql) + + @classmethod + def select_tuples(self, site: str, convert: bool = True, conn=None): + records = () + self_conn = False + + if self.site_agnostic: + sql = f"SELECT * FROM {self.table_name};" + else: + sql = f"SELECT * FROM {site}_{self.table_name};" + try: + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = True + self_conn = True + + with conn.cursor() as cur: + cur.execute(sql) + rows = cur.fetchall() + if rows and convert: + records = [tupleDictionaryFactory(cur.description, row) for row in rows] + elif rows and not convert: + records = rows + + if self_conn: + conn.commit() + conn.close() + + return records + + except Exception as error: + raise DatabaseError(error, {}, sql) diff --git a/application/database_postgres/BrandsModel.py b/application/database_postgres/BrandsModel.py index 4f9b329..d763516 100644 --- a/application/database_postgres/BrandsModel.py +++ b/application/database_postgres/BrandsModel.py @@ -7,5 +7,5 @@ class BrandsModel(BaseModel): @dataclass class Payload(BasePayload): - name: str + brand_name: str \ No newline at end of file diff --git a/application/database_postgres/FoodInfoModel.py b/application/database_postgres/FoodInfoModel.py index f3f6151..2f829c9 100644 --- a/application/database_postgres/FoodInfoModel.py +++ b/application/database_postgres/FoodInfoModel.py @@ -5,20 +5,23 @@ from application.database_postgres.BaseModel import BasePayload, BaseModel, lst2 class FoodInfoModel(BaseModel): table_name = "food_info" + primary_key = "item_uuid" + primary_key_type = "uuid" @dataclass class Payload(BasePayload): - 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 + item_uuid: str + item_food_groups: list = field(default_factory=list) + item_ingredients: list = field(default_factory=list) + item_nutrients: dict = field(default_factory=dict) + item_expires: bool = False + item_default_expiration: float = 0.0 def payload_dictionary(self): - return { - 'food_groups': lst2pgarr(self.food_groups), - 'ingrediants': lst2pgarr(self.ingrediants), - 'nutrients': json.dumps(self.nutrients), - 'expires': self.expires, - 'default_expiration': self.default_expiration - } \ No newline at end of file + payload = super().payload_dictionary() + payload['item_food_groups'] = lst2pgarr(self.item_food_groups) + payload['item_ingredients'] = lst2pgarr(self.item_ingredients) + payload['item_nutrients'] = json.dumps(self.item_nutrients) + return payload + + \ No newline at end of file diff --git a/application/database_postgres/ItemInfoModel.py b/application/database_postgres/ItemInfoModel.py index d7fb0f4..2781f8e 100644 --- a/application/database_postgres/ItemInfoModel.py +++ b/application/database_postgres/ItemInfoModel.py @@ -5,20 +5,22 @@ from application.database_postgres.BaseModel import BasePayload, BaseModel, lst2 class ItemInfoModel(BaseModel): table_name = "item_info" + primary_key = "item_uuid" + primary_key_type = "uuid" @dataclass class Payload(BasePayload): - 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) + item_uuid: str + item_uom: str = None + item_packaging: str = "" + item_uom_quantity: float = 1.0 + item_cost: float = 0.0 + item_safety_stock: float = 0.0 + item_lead_time_days: float = 0.0 + item_ai_pick: bool = False + item_prefixes: list = field(default_factory=list) def payload_dictionary(self): payload = super().payload_dictionary() - payload['prefixes'] = lst2pgarr(self.prefixes) + payload['item_prefixes'] = lst2pgarr(self.item_prefixes) return payload \ No newline at end of file diff --git a/application/database_postgres/ItemLocationsModel.py b/application/database_postgres/ItemLocationsModel.py index 0e26ea5..3d023df 100644 --- a/application/database_postgres/ItemLocationsModel.py +++ b/application/database_postgres/ItemLocationsModel.py @@ -3,16 +3,12 @@ from application.database_postgres.BaseModel import BasePayload, BaseModel, lst2 class ItemLocationsModel(BaseModel): table_name = "item_locations" + primary_key = "item_location_uuid" + primary_key_type = 'uuid' @dataclass class Payload(BasePayload): - part_id: int - location_id: int - quantity_on_hand: float = 0.0 - cost_layers: list = field(default_factory=list) - - def payload_dictionary(self): - payload = super().payload_dictionary() - payload['cost_layers'] = lst2pgarr(self.cost_layers) - return payload + item_uuid: str + location_uuid: str + item_quantity_on_hand: float = 0.0 \ No newline at end of file diff --git a/application/database_postgres/ItemsModel.py b/application/database_postgres/ItemsModel.py index 545c175..337ecbe 100644 --- a/application/database_postgres/ItemsModel.py +++ b/application/database_postgres/ItemsModel.py @@ -1,31 +1,106 @@ from dataclasses import dataclass, field import json +import psycopg2 +import datetime -from application.database_postgres.BaseModel import BasePayload, BaseModel, lst2pgarr - +from application.database_postgres.BaseModel import BasePayload, BaseModel, lst2pgarr, DatabaseError, tupleDictionaryFactory +import config class ItemsModel(BaseModel): table_name = "items" + primary_key = "item_uuid" + primary_key_type = "uuid" @dataclass class Payload(BasePayload): - item_info_id: int - item_info_uuid: str - logistics_info_id: int - logistics_info_uuid: str - food_info_id: int - food_info_uuid: str - barcode: str = "" - item_name: str = "" - 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 ="" + item_category: str + item_name: str + item_created_at: datetime.datetime = field(init=False) + item_updated_at: datetime.datetime = field(init=False) + item_description: str = "" + item_tags: list = field(default_factory=list) + item_links: dict = field(default_factory=dict) + item_brand_uuid: str = None + item_search_string: str = "" + item_inactive: bool = False + + def __post_init__(self): + self.item_created_at = datetime.datetime.now() + self.item_updated_at = datetime.datetime.now() def payload_dictionary(self): payload = super().payload_dictionary() - payload['tags'] = lst2pgarr(self.tags) - payload['links'] = json.dumps(self.links) - return payload \ No newline at end of file + payload['item_tags'] = lst2pgarr(self.item_tags) + payload['item_links'] = json.dumps(self.item_links) + return payload + + @classmethod + def paginate_items_with_qoh(self, site:str, payload: dict, convert: bool=True, conn = None): + recordset = () + count = 0 + self_conn = False + with open('application/database_postgres/sql/ItemsModel/paginateItemsWithQOH.sql', 'r+') as file: + sql = file.read().replace("%%site_name%%", site).replace("%%sort_order%%", payload['sort_order']) + sql_count = f"SELECT COUNT(*) FROM {site}_{self.table_name} items WHERE items.item_search_string LIKE '%%' || %(search_string)s || '%%';" + recordset = () + count = 0 + try: + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = True + self_conn = True + + with conn.cursor() as cur: + cur.execute(sql, payload) + rows = cur.fetchall() + if rows and convert: + recordset = [tupleDictionaryFactory(cur.description, row) for row in rows] + if rows and not convert: + recordset = rows + + cur.execute(sql_count, payload) + count = cur.fetchone()[0] + + if self_conn: + conn.close() + + return recordset, count + + except Exception as error: + raise DatabaseError(error, payload, sql) + + @classmethod + def paginate_items_for_modal(self, site:str, payload: dict, convert: bool=True, conn = None): + recordset = () + count = 0 + self_conn = False + with open('application/database_postgres/sql/ItemsModel/paginateItemsForModal.sql', 'r+') as file: + sql = file.read().replace("%%site_name%%", site).replace("%%sort_order%%", payload['sort_order']) + sql_count = f"SELECT COUNT(*) FROM {site}_{self.table_name} items WHERE items.item_search_string LIKE '%%' || %(search_string)s || '%%';" + recordset = () + count = 0 + try: + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = True + self_conn = True + + with conn.cursor() as cur: + cur.execute(sql, payload) + rows = cur.fetchall() + if rows and convert: + recordset = [tupleDictionaryFactory(cur.description, row) for row in rows] + if rows and not convert: + recordset = rows + + cur.execute(sql_count, payload) + count = cur.fetchone()[0] + + if self_conn: + conn.close() + + return recordset, count + + except Exception as error: + raise DatabaseError(error, payload, sql) \ No newline at end of file diff --git a/application/database_postgres/LocationsModel.py b/application/database_postgres/LocationsModel.py index 54f3efe..fa2db25 100644 --- a/application/database_postgres/LocationsModel.py +++ b/application/database_postgres/LocationsModel.py @@ -7,7 +7,8 @@ class LocationsModel(BaseModel): @dataclass class Payload(BasePayload): - uuid: str - name: str - zone_id: int + location_shortname: str + location_name: str + zone_uuid: str + \ No newline at end of file diff --git a/application/database_postgres/LogisticsInfoModel.py b/application/database_postgres/LogisticsInfoModel.py index f7796b9..9678425 100644 --- a/application/database_postgres/LogisticsInfoModel.py +++ b/application/database_postgres/LogisticsInfoModel.py @@ -4,12 +4,14 @@ from application.database_postgres.BaseModel import BasePayload, BaseModel class LogisticsInfoModel(BaseModel): table_name = "logistics_info" + primary_key_type = "uuid" + primary_key = "item_uuid" @dataclass class Payload(BasePayload): - barcode: str - primary_location: int - primary_zone: int - auto_issue_location: int - auto_issue_zone: int + item_uuid: str + item_primary_location: str = None + item_primary_zone: str = None + item_auto_issue_location: str = None + item_auto_issue_zone: str = None \ No newline at end of file diff --git a/application/database_postgres/RolesModel.py b/application/database_postgres/RolesModel.py new file mode 100644 index 0000000..dcef71b --- /dev/null +++ b/application/database_postgres/RolesModel.py @@ -0,0 +1,50 @@ +from dataclasses import dataclass, field +import json +import config +import psycopg2 + +from application.database_postgres.BaseModel import BasePayload, BaseModel, DatabaseError, tupleDictionaryFactory + +class RolesModel(BaseModel): + table_name = "roles" + primary_key = "role_uuid" + + @dataclass + class Payload(BasePayload): + role_name: str + role_description: str + role_site_uuid: str + role_flags: dict = field(default_factory=dict) + + def payload_dictionary(self): + payload = super().payload_dictionary() + payload['role_flags'] = json.dumps(self.role_flags) + return payload + + @classmethod + def delete_tuples(self, payload: tuple, convert: bool = True, conn=None): + deleted = () + self_conn = False + sql = f"WITH deleted_rows AS (DELETE FROM {self.table_name} WHERE {self.primary_key} IN ({','.join(['%s'] * len(payload))}) RETURNING *) SELECT * FROM deleted_rows;" + try: + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = True + self_conn = True + + with conn.cursor() as cur: + cur.execute(sql, payload) + rows = cur.fetchall() + if rows and convert: + deleted = [tupleDictionaryFactory(cur.description, r) for r in rows] + elif rows and not convert: + deleted = rows + + if self_conn: + conn.commit() + conn.close() + + return deleted + except Exception as error: + raise DatabaseError(error, payload, sql) \ No newline at end of file diff --git a/application/database_postgres/SitesModel.py b/application/database_postgres/SitesModel.py new file mode 100644 index 0000000..4a9db09 --- /dev/null +++ b/application/database_postgres/SitesModel.py @@ -0,0 +1,121 @@ +from dataclasses import dataclass, field +import json +import datetime +import psycopg2 + +from application.database_postgres.BaseModel import ( + BasePayload, BaseModel, tupleDictionaryFactory, DatabaseError, updateStringFactory + ) +import config + +class SitesModel(BaseModel): + table_name = "sites" + primary_key = "site_uuid" + primary_key_type = "uuid" + site_agnostic = True + + @dataclass + class Payload(BasePayload): + site_name: str + site_description: str + site_created_by: str + site_default_zone_uuid: str = None + site_default_auto_issue_location_uuid: str = None + site_default_primary_location_uuid: str = None + site_created_on: datetime.datetime = field(init=False) + site_flags: dict = field(default_factory=dict) + + def __post_init__(self): + self.site_created_on = datetime.datetime.now() + + def payload_dictionary(self): + payload = super().payload_dictionary() + payload['site_flags'] = json.dumps(self.site_flags) + return payload + + @classmethod + def delete_tuples(self, payload: tuple, convert: bool = True, conn=None): + deleted = () + self_conn = False + sql = f"WITH deleted_rows AS (DELETE FROM {self.table_name} WHERE {self.primary_key} IN ({','.join(['%s'] * len(payload))}) RETURNING *) SELECT * FROM deleted_rows;" + try: + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = True + self_conn = True + + with conn.cursor() as cur: + cur.execute(sql, payload) + rows = cur.fetchall() + if rows and convert: + deleted = [tupleDictionaryFactory(cur.description, r) for r in rows] + elif rows and not convert: + deleted = rows + + if self_conn: + conn.commit() + conn.close() + + return deleted + except Exception as error: + raise DatabaseError(error, payload, sql) + + @classmethod + def update_tuple(self, payload: dict, convert=True, conn=None): + """ payload (dict): {'key': row_id, 'update': {... column_to_update: value_to_update_to...}} """ + updated = () + self_conn = False + set_clause, values = updateStringFactory(payload['update']) + values.append(payload['key']) + sql = f"UPDATE {self.table_name} SET {set_clause} WHERE {self.primary_key}=%s RETURNING *;" + try: + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = False + self_conn = True + + with conn.cursor() as cur: + cur.execute(sql, values) + rows = cur.fetchone() + if rows and convert: + updated = tupleDictionaryFactory(cur.description, rows) + elif rows and not convert: + updated = rows + + if self_conn: + conn.commit() + conn.close() + + return updated + except Exception as error: + raise DatabaseError(error, payload, sql) + + @classmethod + def select_all(self, payload: dict, convert=True, conn=None): + record = () + self_conn = False + sql = f"SELECT * FROM {self.table_name} WHERE {self.primary_key}=%(key)s;" + try: + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = False + self_conn = True + + with conn.cursor() as cur: + cur.execute(sql, payload) + rows = cur.fetchone() + if rows and convert: + record = tupleDictionaryFactory(cur.description, rows) + elif rows and not convert: + record = rows + + if self_conn: + conn.commit() + conn.close() + + return record + except Exception as error: + raise DatabaseError(error, payload, sql) \ No newline at end of file diff --git a/application/database_postgres/TransactionsModel.py b/application/database_postgres/TransactionsModel.py index 932ba1e..aa5bd9e 100644 --- a/application/database_postgres/TransactionsModel.py +++ b/application/database_postgres/TransactionsModel.py @@ -6,20 +6,25 @@ from application.database_postgres.BaseModel import BasePayload, BaseModel class TransactionsModel(BaseModel): table_name = "transactions" + primary_key = "item_uuid" + primary_key_type = "uuid" @dataclass class Payload(BasePayload): - timestamp: datetime.datetime - logistics_info_id: int - barcode: str - name: str + item_uuid: str + transaction_created_by: str + transaction_name: str transaction_type: str - quantity: float - description: str - user_id: int - data: dict = field(default_factory=dict) + transaction_created_at: datetime.datetime = field(init=False) + transaction_quantity: float = 0.00 + transaction_description: str = '' + transaction_cost: float = 0.00 + transaction_data: dict = field(default_factory=dict) + + def __post_init__(self): + self.transaction_created_at = datetime.datetime.now() def payload_dictionary(self): payload = super().payload_dictionary() - payload['data'] = json.dumps(self.data) + payload['transaction_data'] = json.dumps(self.transaction_data) return payload \ No newline at end of file diff --git a/application/database_postgres/UnitsModel.py b/application/database_postgres/UnitsModel.py new file mode 100644 index 0000000..ad52c57 --- /dev/null +++ b/application/database_postgres/UnitsModel.py @@ -0,0 +1,17 @@ +from dataclasses import dataclass, field + +from application.database_postgres.BaseModel import BasePayload, BaseModel, lst2pgarr, tupleDictionaryFactory, DatabaseError + +class UnitsModel(BaseModel): + table_name = "units" + primary_key = "units_uuid" + primary_key_type = "uuid" + site_agnostic = True + + @dataclass + class Payload(BasePayload): + unit_plural:str + unit_single:str + unit_fullname: str + unit_description: str + \ No newline at end of file diff --git a/application/database_postgres/UsersModel.py b/application/database_postgres/UsersModel.py new file mode 100644 index 0000000..4aa8e36 --- /dev/null +++ b/application/database_postgres/UsersModel.py @@ -0,0 +1,82 @@ +from dataclasses import dataclass, field +import json +import datetime +import psycopg2 + +from application.database_postgres.BaseModel import BasePayload, BaseModel, lst2pgarr, tupleDictionaryFactory, DatabaseError +import config + +class UsersModel(BaseModel): + table_name = "users" + primary_key = "user_uuid" + primary_key_type = "uuid" + site_agnostic = True + + @dataclass + class Payload(BasePayload): + user_name:str + user_password:str + user_email: str + user_flags: dict = field(default_factory=dict) + user_favorites: dict = field(default_factory=dict) + user_sites: list = field(default_factory=list) + user_roles: list = field(default_factory=list) + user_is_system_admin: bool = False + user_row_type: str = "user" + user_profile_pic_url: str = "" + user_login_type: str = "Internal" + user_joined_on: datetime.datetime = field(init=False) + + def __post_init__(self): + self.creation_date = datetime.datetime.now() + + def payload_dictionary(self): + payload = super().payload_dictionary() + payload['user_flags'] = json.dumps(self.user_flags) + payload['user_favorites'] = json.dumps(self.user_favorites) + payload['user_sites'] = lst2pgarr(self.user_sites) + payload['user_roles'] = lst2pgarr(self.user_roles) + return payload + + @staticmethod + def washUserDictionary(user): + return { + 'user_uuid': user['user_uuid'], + 'user_name': user['user_name'], + 'user_sites': user['user_sites'], + 'user_roles': user['user_roles'], + 'user_is_system_admin': user['user_is_system_admin'], + 'user_flags': user['user_flags'], + 'user_profile_pic_url': user['user_profile_pic_url'], + 'user_login_type': user['user_login_type'] + } + + @classmethod + def select_tuple_by_username(self, payload: dict, convert: bool = True, conn=None): + record = () + self_conn = False + sql = f"SELECT * FROM {self.table_name} WHERE user_name = %(key)s" + + try: + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = True + self_conn = True + + with conn.cursor() as cur: + cur.execute(sql, payload) + rows = cur.fetchone() + if rows and convert: + record = tupleDictionaryFactory(cur.description, rows) + elif rows and not convert: + record = rows + + if self_conn: + conn.commit() + conn.close() + + return record + + except Exception as error: + raise DatabaseError(error, payload, sql) \ No newline at end of file diff --git a/application/database_postgres/VendorsModel.py b/application/database_postgres/VendorsModel.py index 415f4f8..e244641 100644 --- a/application/database_postgres/VendorsModel.py +++ b/application/database_postgres/VendorsModel.py @@ -9,10 +9,10 @@ class VendorsModel(BaseModel): @dataclass class Payload(BasePayload): vendor_name: str - created_by: int + vendor_created_by: str vendor_address: str = "" - creation_date: datetime.datetime = field(init=False) - phone_number: str = "" + vendor_creation_date: datetime.datetime = field(init=False) + vendor_phone_number: str = "" def __post_init__(self): - self.creation_date = datetime.datetime.now() \ No newline at end of file + self.vendor_creation_date = datetime.datetime.now() \ No newline at end of file diff --git a/application/database_postgres/ZonesModel.py b/application/database_postgres/ZonesModel.py index a9db6ab..efef9d1 100644 --- a/application/database_postgres/ZonesModel.py +++ b/application/database_postgres/ZonesModel.py @@ -7,6 +7,6 @@ class ZonesModel(BaseModel): @dataclass class Payload(BasePayload): - name: str - description: str = "" + zone_name: str + zone_description: str = "" \ No newline at end of file diff --git a/application/database_postgres/__pycache__/BarcodesModel.cpython-313.pyc b/application/database_postgres/__pycache__/BarcodesModel.cpython-313.pyc index da0f144e9f2ff0049e7935b9f479034835d5db4c..b2a385c12348f34b5203181d64de8dc6007bde1d 100644 GIT binary patch delta 51 zcmey({)?UaGcPX}0}ym7uiD6M#3UKwY88{5pOP9=keF9eRH<81l$e|ylagAtIh<(; FBLJLM5Z3?z delta 52 zcmeyx{+pfqGcPX}0}#wVzj7nD5tCG?t5r;LeoAUgL1JD>QKfE4QDSm-Om1Rk-sUi- GC5!;d+!6l( diff --git a/application/database_postgres/__pycache__/BaseModel.cpython-313.pyc b/application/database_postgres/__pycache__/BaseModel.cpython-313.pyc index 9378289bc44c49fcbd4e2702732ea5253a7b6d83..611c1b8b21a051ed5905393ce7c40631ff45d394 100644 GIT binary patch delta 3569 zcma)9Yj9J?72dt~>gsVVOP2hSpR#3R3$P{Iazf3k1{-M2YkX0Fu^6F90%MHiti;0v zA_ysu(rN8(nPl22HBH)>wrQaL5oS8mNr{+Hr1E5tk2J(D@YAPXp z60@la=)@i3SEju_PO8NU~6Pe;^V) zH1=0|$h^1)R=q$1gi)0+L8VMo1tzH?tfkZZlvN$m@0Hzpe#R16O2Hg`eORK6^OJ6< z=a(9CnQnGd-e@iToFN!w(E%0=X7bmB649-p!TxwC(wNn*g0YDlfD>fGV*lFlbH~$$ zGFAx+{-I@E9t(iv28WknIF?xKv%5h24vWEt7;7>K3UDfvzjw5Z;nmMHf>~^XJnsal(n(Xv&mn;CV!qy z!Hf+$BdGtv>`wlmKzq4b3kF$GeMt zbby~JcGBm@MvK2Kn!}*KoXe`QyO(#DeMF;U&y0 z9NM@KMSx>VQsQ;Y9Umw2FQs0(HJ|1&Ep6!A*2vaAe`jaYMnb?M)4HF zD+m}UZZj)G`7}Zy!q*Yb0A%#AE74>$!p@=!!=0T&_%e1%EPgmK<@b5+U9^S{k0lnp zC4qBQOG?e(0F$FSr`oyA$u96OEUBrPb@wSw4ll8XUc)68@-+7;o*o>Cgl0VL!WlXd z9`~7j6LsggZfQGxYi#|}MoGfW=Kn6O^2p-Wl+sf* z)knyVpZN9Jw&rML|a>iE@v2y+u%G8K+R?&u^*eW=b4&M8tnc*4m9V9fyv&$DHfp5bSOfv zminDqNljG@FM~`@0>~Fm29pN|`lFe`nKacnSN{Mf@WG9Ag51xupOq)_9cPXA70Yd< z96l5I)g!()S6^D4u5P*V>^0x*frk(mUem_(0nB!nw8g zpGEtTMWb}I=55bqPuksft?u2L+iSXSuIWy{QYbgOAyAo{}IM{7{-47Ye%snwTLl!iyh;ZpR6vo zOD(#XTtWO|Hs&Kf-k@`Gi%SvpJu-j4zM@*CUgFnx&}V2&RAs-|KSL~4hPB0RGr#02 zsTWAh;5T$*0-aan%SK@cyNobJOUoQu&XREu zOU)26kttf3k%=5Q{zNeVP|}uvR{s=AHl=N?K)e5DT~*q)0*O^<^nPeB`k=c0&4%wb zyzUv|KU-SJ->#}^o+v7L_1N$+{?d>H1{c@g+1_)jBJi8-J?X9LZ!24`gx`r@jlY}t zeP!3p?L9;5(-i^UTi2Z0MgM6ew#p0TpFJugR>wm^jouGzl_RBk%NsPO^0#qm|< zPDQ+-=s=!&%miRAx3F!q;C8@+H9y57pqNU-MIdD?!C)vBizh?LeeqZV-o<^`uO(+p z*cs{hc)Xttps0lVLy5$JXtFmRVP8SBAyn(MEXlrw3Vg7#O9j2|Mhrb4 zW|-@=w_CgPdWHYUSGQIB31MtkB51dUVa0)X z)O(tE^ymsswSLJgI>1U_c?+#`;Ui1}lQJ6jve^|Wrt^@u?A R$hKFc-XZZ5g0;5Be*m?W7QFxf delta 1426 zcmaKsOKcNY6o&7eu_u0H{D_la8xkx#jb$2#m(v0T$3aADiGn~kDXBEVG2>~1aWdh0 zOh}-FmWCqU=F&xlcr1{j2myQ!H)sBP z&bjBDdE7jSpXFRHWZ8lE@$|RZ;eFwf%a0$Fq9H7nK8^H>J+_^8Y7ZfTh|uGp4q^!+ z>hz;9BGw=xl2Y3tLNG#9CN+G`MQogIVplu~fjELFXQ5u=Wh8T8)SSbIW!%gTx|<{_4OwFnyW-BWY0u{R zS-dMIPz&F`Y)hxJ2UH`SW)D3(@HSTO4dWX2u{RQ559JL4Rs(7QUo7AgZWLeur_k?~ z-j@O^c_YFT#D4T9rutC=LsvN)wJQ$dh2C&tx18#Thx+=_HcabVP|R1hZ_gC8UOvP8KPl7d zdn}l^aYDcvJL+GHhuCfZcX*I(40K5vlt08K0|}gC_W~ij+k6zbCdz|cpLNkNENg7C z=_yW|`-n#2^X0bekPp;;e`2li`*m#(qi$lS5$UILb~npD7eZG-FuL(o-Ox0oiI4$MRa)vR~Kp z^m~xu^Q2b+H`toaW{(OcAjQwZuZRtFhS>SeTcKCNDr-memjKJLhO(I1uTqiQmDiKq zO?}|33{VE?ID3+cepJae2=|NslCo{#0BIi? diff --git a/application/database_postgres/__pycache__/ConversionsModel.cpython-313.pyc b/application/database_postgres/__pycache__/ConversionsModel.cpython-313.pyc index 119103d9f43761908d826aa91d99f7355fef034a..088ad86209833542945cb21c1507ffedb534f934 100644 GIT binary patch delta 51 zcmdnMzMh@?GcPX}0}ym7uiD7X$Rru=Y88{5pOP9=keF9eRH<81l$e|ylagAtS&gZS F5de4_56A!j delta 52 zcmZ3_zJZ G7b5_Z$`E=0 diff --git a/application/database_postgres/__pycache__/CostLayersModel.cpython-313.pyc b/application/database_postgres/__pycache__/CostLayersModel.cpython-313.pyc index baaeffd8266690a513c0b76902569431680d9e94..f2fd2c53d2cb286ce55deec94d2c9c9fec78f01c 100644 GIT binary patch delta 51 zcmey!@qvT;GcPX}0}ym7uiD5xok=px)hZ@AKP5G$ATh6`s8YA2C^0!ZCMC6O^B$(H Fi~y&{5uyM9 delta 52 zcmeys@sWf3GcPX}0}vcPyK*DLhG3a5eFa&9svlb7qY2 zE-Q${CLl%Xk}a?xD}Ds4SY9-}M4}Q4lpT_b=#q2B<0wwVm3;1b-S3`z9^+Onmu9$r zefQ_j&*F^zNkV5S5z|^l=7?!blQvjU0;w2-*ci#NjX1<{k8j9D1(ad}62&AW$JlK) z!8Cb@X^Ng$LR?y)R$_(3IkAP9NSQ`plpMqNg;KUH$J{K$Mdp3Ow>~!Nj%S!6MZ!m( zX*nY0_`&^Z#Q-SL@y<#_OzR;sM@(lRX$)eTgh9nX*5aUOG9>h57>%7r!^`eyVr`X)$=@n-cLN@^&jAg}kJlqPAL zElOHuh&@)}hL(k!+w46FdM8;SCkDD+Hy@?>JcFkoi=-fl9@8otH7DR@qZD{h7dOHu zhbgFlSF8Gw#&#=UnYQHyzQ~6@ZY_XNUKQC;VSQD#!SXAJ94>JyRCjI&%|>8*Zb|C6 z;7+|lzt(R^9!N)Q2lw{cifyw`u_rr)bETb5hvKszb(*2$j{8!Cye(-GC8x_@z2q3N zm;A$0?S!*I#z&N(D{F#o&0kTXcnKvQmPEqz%bU=PHs~YLkEEaq#jjN@5WUrQ!*kOv zQ9cZ)Lz3Js4hZ?N@3}%TYkO6{eL5~=-*U<#866fW&ckWq+^77FjqQuA50~{4&W~O- z+yLtL0x(KXbkq8>TVYhIj$H~%q0>?`=6|%abHAj2N*^yi zTUc!@tTt!Y$bYhtojUCsI@oL>Jo}Lq`h?24YwTH^i!|qZo>_B<&vO1n&2S=159ehY z{J^nY%k|J4_X7~9HC)#V!Zi4t1I@FL9UG08>Odt53jhP5eb@6G7$jCH<3}rKXJ-J5 zKu9HS1UMDDwZKA1k&8-HvK)u=f&_2iL7#f`A&e46GRhO%f)FOc9wzBGT9om_*8*$W+1J!|voyEc`ND5_nL@My9p{ zn#eZlJ>m$ueG@$pQXN9W>fc53H9Nie`hM}WcVvI#^w!9U-2Xz!zl`OS;lsNvhFdGn z5FwzYoiA-%JIAAtYkvQb*5U>*)E5zm;~d6iR#E!sxmq7V1j7jUE7f mcBoeV2$H|!k|h1XW`Adyzu0zzZNHTJq~XK$e;96Ip#K3qPSv>p literal 1688 zcmZWp&2QsG6dybOOd5Avx@ZXvO{>z@eB^+LN>^PDNL$fVRA@~vizUmAonUZnGvi2< z)1IKmTq*&Vy>3tJ-@so$6rl|R5+IOJZ;P^f=RN0Rn{*Cu-p6m|y?GxqC>HYsmif!Y zXU920{zhTEx=iJ|LY2Bo)e5;q)`%sQ zh$Y)f4erH7wU%fqm&lqT*F86N8==b>mz#kfdWU9?7d~*A|FL@-#;(V86h4YQKjeDI z(sz=UOKAg-kC!rH2JZoLPHaL2i%`)LASjVaww#5LgD|8QVbn&4sA|a&l47d7xEIIX ze$U)73rHt!QJEegS>>gMBuabcJtOd7Zi!Y8Z$}Dag#8E>S zTVcm!VSxuf0XZk1PHacyo(QU$RU6r-aO0$2!A@}F8k2k474wK)Bj6=#;Az|VG+5`R zEhvH3xURK?s=N-Jd1%V?2AJsrl0_shAz1=qYE(sjV&~H5ERMMBwNDZ@3l_M<{IJRJ z$rXNd_qN@DZnhK1Iz4?mrEcR3`@{{RX^opCVbI7DZR3m_!xT74%u=Wrv!{!mobttF zI4+JCG_W`>)Ou?gHENCObtFEKeF)?M`DbP8(b5xhr)%!?R&M`3EtOXv-v8nLBk4!u z!swN^`>(D%-2HyHzwySCjXT|qJO34wCF3bkipBu$A{XS)T;Q7HMDB_2I6Ut-C$ZNK zk)Ly%FWYW7vdlY9GoUODgUF9!$D!B>TnYo0W;x~naHA+rvve>3V8mR)x4y_WmyY8& z%nAg53H%)?Eg|ft<1jqgaNHC+{J5R^ctsg(bQ#Gjh*C%Cp+%5zde}aYZ^>WFtB=>a z%iCv#&L=%>`_~z)$G$shy>sR_?EF{;ZY0yW7(?pG&H43_WeZ zm6_v1Q>U+?u!5w5WEBX{L!lu-m=}uUMDg>b*)g2`nV>1gnGqZhRsr%2=`SolsQ)P& z&qPBuI&TgLY=az8<_D-4#*zIFIxkE05xqIbeu*niB-!53=EYSY}LxhI8 diff --git a/application/database_postgres/__pycache__/ItemInfoModel.cpython-313.pyc b/application/database_postgres/__pycache__/ItemInfoModel.cpython-313.pyc index caa025cd78ae21ec16dc2061e721a42dae56120e..c29b42829b3b979668c50ed015d3e94bb6cc1c52 100644 GIT binary patch delta 747 zcmYk4&ui0Q7{}idOibUZjh!R(6l?}awRQneJK^cNX zgH{BWXQs1((6o%Uwzt=;2V5B4GPAbNsk{=OLkDW9Dj?B_1V}`b7*H~zy$Ze7b49I0t$+^FnvHc`UAcbKg zp#2%mc0d9t$~$_kr8k~! zYpoy`Id=@bu362tq1AP}yE+u4qOxno@^P`B9>%_nzkGaOt(kR0?O+qIZ(D$)sCDCw zYqG8QD9+No>7KK4ac<#6{;{}xwz%xgtvFA5&(2b1KR4lQT!aIEB9&{d%)LnG5J1Kd z@LM~k?%EverxC;TyW1Ep-;wj&$f4wqO**t!^5~>rm~o_I)}u3iY4((kA6Gp(>*ouO z(8H{7wRmIxl;)2&Jv#4C6`fSC?$P4ULg{4Xtgz&WJqmjUl0sX!gjDg@V7x3 z(~U~f?eH@TL9zqQwhp$|GVCC81vJa<7!dw2;QL<8?zkWM>m#?2-x5DNdGOR9o;q0f cN2g9X+5OIcVD7n%_;gxk^1Zb`1h1&?KlY-@q5uE@ delta 668 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 literal 1975 zcmZWq&u`Oq6#qK06T3}P+LF*Tv_u6|0;2;4+HW*X8)!xACcK>L&2nQW7+gDhUq@3p zaoRHNl1byjW!Lt^aku>sRW9KR+K_gbxJ?SvIPSe~oV37K%JcjEp5OQLoMApc#qj+0 z$KI{q(~SK^!uV(jGrWe(eP%LKxxv;IP}WnBs*;?#k%lxr(>K(04Yc(PWY)8gt+EBS z!c29MnVOZkf_dqH-b&Rn&xoz1gyA}|(+C{Sh1T@Fz}={&#q`Gx_r7#?g2-`&LBi*e z>jlCHczmhdasV0>K0cX*8NP$eeP%IGOa>`a!KPB6TAFMljcuq0ZCpu$AZu#aQl^#_ zQ=i4&HkYNIQBI-xq8I(<{u%nT2;1$XhwAH8ar`q;|$Cp!P@-c|=CeCeo~w z<`QX6O7kSu*=5B%y2x(pFmD#&Xq|niK=Bl-mBie^oYsOi$9{uPT;^Xg;P=mNBwIWplhbIL(`5lurE7Gs#?6Ooe8?sgI2} z$R=^Zux+`lw#{h@)#^QQ^V9b%x(=)DgfZ-#kHKl&v9=vQ zd|BhP+kxM3sH{axzlCdUw<8|6z~j~-NVRuhh1xlNNSDBD?e+etbAA=ci+k)zsq%Ac zzjUEry41b?WO;R0eQ4|%{biJT*M9us$%&J{-hFU)_te9)duRJ6uAuh%e(v~SezChT z#0+eY(5>4xLCUtplx=TEZYLl*HHAW|kmUlE}OxDCnV=|1GhVTtU^L{1QqlPKc2DQm-aVua*YC-x|PKDmsB z7X-FlQ-CnU2~!`z8Kfecj1BTA(T4J0ki7VTJuQ{{dU>!=>Fbq2Wx1~}50yRr?_ybz7Q5bEVj2)$-_Xm1qNoe=nvBoeWD2+nJ-!Fg@+pEc@`>W>ypwnVRZ(5BEo7{Tl+ mypIODKTat4C#@*TpX|(IHvKoN@3Z=IwX9Tn<$oDIWcdH7-14XZ diff --git a/application/database_postgres/__pycache__/LocationsModel.cpython-313.pyc b/application/database_postgres/__pycache__/LocationsModel.cpython-313.pyc index 894d7f02ea58020a1b9b5130e4b645cfb4ac16c5..77b5a830e96da3b6e4a3a2259f9fefb3b688e054 100644 GIT binary patch delta 277 zcmbQkzLA~xGcPX}0}!xkt;%rR$ZNvL%M!yH%%Tny3T7>0o9xG^WF(Z6pPX2dnV%P5 zoRMEtl9!m9dW#n>2Ig{B<>#fwmzHLxtYq-hWS`v0=rj2iqoQP}t5r;LeoAUgL1JD> zQKfE4QDSm-OiF6mWKpI7IbonJ?FC&%pr9{Sgen)NCGHcBsIB_$xKuV z#1sb+0w4k`BRTmDlZuG=1ntiZ%&g*{nHkvlKQjSYMG}))n4@hWYJjS5aoFVMrMi!F{Jhlo%#@W3ewrMU7cu&d<$uA$jvotYKZQ%`#E z;-#=hui`(mP>^LnD0qN2|zD7m=g6+OC?dqk2QPjdLKjB1(0_ zi5C*a?1Hc!5#~Q9oiHYKQ3CQTuKS^vEzHo!N%Bw{C&)!qaS$yy3RRr~)f|=Tjz$f) znAX;1ZNoJOwRiKywB!_NxnbHRG5IuZ1dInBdlDVSX}xnep%@JZ0UXGP(Aw=lstUN@ z#9$7o%Fl+97uQr(k*m47Q&^V~sm@55ma}5Y^qiR~Gjdi+Sutg%RI5t0i`Ug|=M6iD zH%2W9`(h?l*`h&{&<+k`kMJOl#Ke&MZ5;VxK6Q~ck8!|Q+y9$9{ci;FKO@@`CdLtI z`WTBc#?83i4#B>G@#{7T^T-&+hXG|g3?e^@WmsjL3h5tO%_;GY+`9i}*g;yYFvw2s!jv7z zdBUxj@dovo`=3U(I-;02la9zj&``D`;XOK=n*A`^ui8Dc(q*Tn-CwEp%!TgGsafrB zZ1v3LPiv>G01&=!TXY)848SbFoCHypvSjz5X<~fvzK;A3 z=AD)=tScT0BStlVbk{|&VC$^X5?I9nzyPyN3GYyUV&%BjpR4q=>HcKpc<)Rfy(s9q z{c$rv@{y>>7)!vB?E_ov+lH_%ze}H`rN#P{er{ju=l12-nrQ?DnVw;d!n~hN%mcb6 gQ{=w|G3dlkRZ*00Xypg0U1*kKcNcym`A9|m0l}m>dH?_b delta 500 zcmX|7O-sW-6nvY_$5yS{FKugstq2kXJqd!~SrmH-f=Hmm8fu_TWLpo4pdctBh_d)` z^Xy&lhZHmfgx&;CLh9Y!s$F<9^Jd?hg}sVhka!f24*)+KZ%@0YH}U)BMc&+k1r(V; zB@N>sL#EKGrk^I*!Bx`4$Bz``#9)l7wwnrdh_pT>Cg&+XCQBt$E03F&i|YAGh3 z;a5E+_m$FDs!*mzwHje}XmXzLHq^FlTJ?9FaCDEl4VxZR>VDO3xxN<;{X2)g=Y%Qt zh0>W3d%xvZ+-7s%sR;WC$_8P&w5EjE^;+ym$_^%ZZo~`1E%qp7_u8T9^{^Q zv^mM)96E{ga&3|e^kSP7gIwX26zBw>p}GK(S2kiaC7QH=jDRr?;XupY zt~(XaZa7r@2$co&*78D$-QTDJuxFf@E^^}xf<)cV>EhedOSF4!h{FKy~g2cR%qDtM8qQvCvnB2t7yv_fZ H_A>$i-8mCu diff --git a/application/database_postgres/__pycache__/PlansModel.cpython-313.pyc b/application/database_postgres/__pycache__/PlansModel.cpython-313.pyc index c031a55b08f653489ef6edfc282a33178bba3efd..3aad5a3a5a41df85f2539dae74f30cb02c48699c 100644 GIT binary patch delta 51 zcmZ3^zKoswGcPX}0}ym7uiD7{mr*j%)hZ@AKP5G$ATh6`s8YA2C^0!ZCMC6Ovl3G) FBLI{o5TXD8 delta 52 zcmZ3+zMP%=GcPX}0}ycV>Uq-1QSF4!h{FKy~g2cR%qDtM8qQvCvnB2t7yv>SC Gt&9NEml8Pu diff --git a/application/database_postgres/__pycache__/ReceiptItemsModel.cpython-313.pyc b/application/database_postgres/__pycache__/ReceiptItemsModel.cpython-313.pyc index 7d45c21550a4c483e81c40b0d3f28ec44e381044..79b7f8e06e00cb9ff84be35b2670b95de7f0035e 100644 GIT binary patch delta 51 zcmcb^bDM|zGcPX}0}ym7uiD6M!z>x$Y88{5pOP9=keF9eRH<81l$e|ylagAtIg$A{ F69AOp5fuOc delta 52 zcmcc3bBBlfGcPX}0}vQKUb&ImhFL1o)hZ@AKP5G$ATh6`s8YA2C^0!ZCO0uNZ*v0k GZzce>cM;QKfE4QDSm-Om1Rk-ezfL GFBSmON)gNe diff --git a/application/database_postgres/__pycache__/RecipeItemsModel.cpython-313.pyc b/application/database_postgres/__pycache__/RecipeItemsModel.cpython-313.pyc index 540ace7ce67a9f10554a792c58ec990c16cdab0c..b4c5087aeb3c67f61aaadda9a6866764683fbf16 100644 GIT binary patch delta 35 pcmZ3?vxtZLGcPX}0}ym7uiD5h%*+@*S)N&xIVH7hvjy{OCIFct2CX-Z%t5r;LeoAUgL1JD>QKfE4QDSm-Om1Rk-sZnd GA}j#8juG$x diff --git a/application/database_postgres/__pycache__/RolesModel.cpython-313.pyc b/application/database_postgres/__pycache__/RolesModel.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1daa3f8bf2261f8d4bcabe5c00c2d7f343aacb14 GIT binary patch literal 3180 zcmZt|OKcm*b@s>QE=7H9eIse5SheWbmf=ut8#{4riIy$JOv)82w_>wcEtfLo)h;!= zOf18weQE{tLZLR?Q(>e)ewWI<}tx*BA9k7_JimQv{Ldq!o+#>~o^=foN~R;;jbZMo@;& z2r7FJ&ioZuTk-xXG>kH$D*TWE0Y#t;QEVE;8C3)U!&p+oSWcr?85}u)ViIZpPPn60 z(>`f}D^=-iec2?fzMxstHE_N_jY^t*jGOl=^2Zp`k~MRB(ekuh#o9#nNphCH=o$<# z3A5Nd%9ZDu|&21*4Ut5G8eqBKfTi#Ck$>i^P=%J}Lt zpemch`++Vd5Wy`LOibDfl}OUEwMZfSk_*}i+2V4+t&nqscTFot#40tBG|i7oO>?Ol zigJ_u*nA~pKqO`gy6xfBBOca`E19hM_VWn6P_XibP7Ro$;Y}k}D>$yVh)p+B=ZC^7 z?grLmqz+g0&C&lAx&uhv!vMayi5_?MtuJnM9^39bzBcvv!07wa?@oW<{o%%kH?|L) zUYjbtzST5T?&w`h?g2ge5J7Z?HH|u1(}=8T%TBfkF{zOzOx zZ3m7y*TV#`x@|k2uaT>1m^zE_)SSNUDoX4JLs1e#(>)L87mA(ro|ZC z50_o)xF6vG%15kfj9XwwBhozr;G18eosQ05HsX(kBJBQ)5n!3wv-FN=0)0HwLnDrq*3(0nbx z|6iyNf0r!cykEcR)fBZ?!+?s*97Abwk~zWHoGPU`t0s{u*+QC2wKZIUZIFsAvcu_c zipE;QxTKF)ZJ{8bpgGns0v@)MmIBT6qfnLD+77ZbEp?!u$SymET($$9??K3x=eq({ zz?r5*!$`})zu#mp_EA?V$mVVNjn=w9N%eoqf~;u?JWP8Q3T`VXE!1RDfPzZ_x5g3Z z0n}WIhHJy9JLeC877Bc}oI^wCFbcS5!P_}F83aiclsxq+H;8h~5Sphk?++2_;0HT0 zKNMgt-Ajjp_G3buvLk0AIVOiHcVluAQ_^$ShUYHKo>!nDnx2`}u=9?qoKcn0$;9i4 z*@SW~^~OcznyAd3PoxscH9@(MR7MGN_!>LnUON<1Qi<7gDtRF}rNBmoI}@|Yp~|g# zmM3RpZA6CR1!cl-4v_-RFGH8ru9&M(Kb^cyILow22y!i#UnJpzyJ|Rv#p8bV*{0zU zSuc8y;VdubJs>M$7n-(cUNsG<4J4d1J>#-&SvU+@kw|^p0iOOP!LS_HBvRt4VfqaT z(@>5zVd^zM)iEuIga;XHB39)m60PK#kK^1G3wl;ig{!i1APnKDq_-=Cp2o^Qw%~k(N6omHL<*}XH6=%9^Z(6eEQz$&EpfFeE;+K z!%w|U4TreLlENv|;ZCZuRD=QDYcQ&uyfO1`A&ahu2WWfYGgE`CYBDBCv z&}o8Qrc;Z}u5r8Uerf!xj;_bUqwgPi_sH+ZN-dAO1|N1E{G#jNR@W;Zi~%U|<>Bv@ z_**UI7nG8;)3*Ok-+JGz{<|-hTRZMV)+4{2c+eUvclF-6zJC2TzbNs*?srpPs&m@| zm%dQvKGz=%j&B$rJNKMVR{k=0VN0DWO>GWbDouU0zvsP!<$>YS)UzmRAN*GS+PyRr>9~UX+q@3vF zJ`q4p1;c!59U{DBvidmE3P1B;vRHWz)OtWQpMW zobU^dW8o=E6Qh78VWs2HT*kB*`+$bAOUZtT%5B{@r_1gAH)nQ)=BKPA^p#%PLon@e zDAck`5xboz)b=&oBaGio?;@BsW}niDQnc++eB~dMJ#_C#?VW786`^Ts!%#gDewhO3 zAc!2SdwLwL1%2@JI9hv2S2U(CEkB|B46G>fymBObs`65J4JUyZq8qu?l;ACf`70WI egd&en^buQKfE4QDSm-Om1Rk-ewV| G4n_d4;Soy! diff --git a/application/database_postgres/__pycache__/ShoppingListItemsModel.cpython-313.pyc b/application/database_postgres/__pycache__/ShoppingListItemsModel.cpython-313.pyc index 4f5465a3199dfa8186a1937ebafdde323a2bac6a..55115039fa0223e328809edb707e439f94ff85c0 100644 GIT binary patch delta 51 zcmbQkGnQKfE4QDSm-Om1Rk-ezOw G7fb-Of)T|4 diff --git a/application/database_postgres/__pycache__/ShoppingListsModel.cpython-313.pyc b/application/database_postgres/__pycache__/ShoppingListsModel.cpython-313.pyc index d110b2592aeac7d9567dde5655b66316c68759d9..bbebb7ca34200ef4f59d48c8094699d84f270472 100644 GIT binary patch delta 51 zcmbQsJ%^k7GcPX}0}ym7uiD7Hok=p%)hZ@AKP5G$ATh6`s8YA2C^0!ZCMC6O^Btyx FOaP5A5l{dC delta 52 zcmbQkJ(rvNGcPX}0}yCDTe*>YJCjtDt5r;LeoAUgL1JD>QKfE4QDSm-Om1Rk-sam( G2bln@)e;y0 diff --git a/application/database_postgres/__pycache__/SitesModel.cpython-313.pyc b/application/database_postgres/__pycache__/SitesModel.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..839751c03bababdd06471d387e4f4f3970b98220 GIT binary patch literal 6515 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 QnI($qo~`*Bkz5_`{|gjNTYD3>-PrEi zH7K{7AQBfiRO+!;;6RU@`3quMRorPMDse!)AzVbc@!oiyI0=ljZ{EC_dGqFb-@I-0 z_VzGr3s-;q{2!IE-)PWjdZe`Ss61d6vy>&4S3t?fAeNzVY)J)G_8Mr|t4n%54)J^f z5*aqmrkSNpFiW=+^XQuz80#@J{*qKPCXyv5bc((c1VUf)T)(u8#?%cba6fi7{fbi( zNg8}qDY<@;_HF>?8H0{7#BHM!!+f+El2!tG z)(tZ$23Nr;2Tn2cD&;^P_cDR2L^|xUi=-F28@yKYN6 zTd`T3lbX7xX?vP3wK#$nw-O5qBy3}4AoAMPp&x@*T1iOSJvi!-qZFj1=X5lemUF!w zpLVa+hd%r4Ufj_y*9=TB=r6E2#TuMocMTY@hG4M3-cw-cEHj72@gtz*qO`nBV9Y{Li5UA_}@;PaJ_SDG1Bere$6R|Ubrck z80)r|+@J_vm2!CSuwo?$@vQ!myXMsVkgs8)D{v@ANkye|BK9%-h_w;>Z|_a5>}mVT zO2qGd3u7Ig6_`)HWLsyZvTUltz~kJ|Ls-;u!Kqe#ujtUFZMwo8d@sC;7hVT9uvfe2D`}mpZi-$m7jS}}b1{2# zQIzJ~fraB3R&X4}7JHt(yqmpJ&tBQhUaM!XHL~w-fAAzd+rmj!?8cB#kr>`Z<*M?4 z?W4FFE3i+o!gNq7E4MpOh^=(qH#v;jG%K(bqchs?=cDrqilq>(3ks4qwhR-fo4U}0 zTGa(HRBaa&Uh?E4cQF0h-2j=Qmuef;puJp$lekMHqQxS~xvWl{2Xu=}W(!VX59esr zh5yIx=wXw%BX|?VYea8!^1Jo==v$4^%iFh}XLCPgf5<*w-JQ?Z=kty1()O*Lk(qjW zwmCepy$sX1$5h$`85RcT^p-GdJ)Cb;N;RMKH0Ssx{Ai}1^ED5G(D%x2xq{Pb5Q0Ff zQ!ZCR$&`R|Ah4xdI6>Mm6j+3@=!P&x21>Wk2rh{YWXLElehlFtKSl0@A$WIcp^FZQ zRuZhD>-(Hz%qH%TWWFv@6N!jc(1#OLL4x}zUVqJA43B=(*UX%07-yOjQw?LPEuYRU z{9LQg-P|!wJ~DPQm+F~I4dZ5WCc9&tc<45aY;)G!F-|=iZWv~BdS=HMe|WB8%sf3k z^Ej-ZUZnBahOzjkrX+7Df5+A2WQ(DYkOI-}d$w$*;5=$@fy6};ITWG?9}+?<>sG1? z-Ujl8-*d5KbPYHaF(6_9{VAnOc$>6$NKoe5bRpIPi3ENDo%a=M4xZY|H;1OSmYQQz zJKD$#z3)|QP@nwve2Zb%QdzveMTWK?+KvCI)ZP!%4caHmG|u3xOrx0Xst36v^&q$R zH*H5u&rAk(C}LqOw#juB7I6u^uwV+sAF84#zp^);vD7ox|2xaS(#Dj@hsnPfb~4a^ E0gARBod5s; delta 959 zcmY*XJ#5oJ6h6mx?EE)PljcWBqmY(HEmZ_90@R{P2#Pusgvo$pVY!Y|ToOB+ogq`Y z06|?^SxATlA$4dMkYGn*VPc@lfVozp8zV?5q9b>1sf35`eeb<{&)>UuUj}{#!~Iam z5AgS4f9Js`&t~`?b!qcsd|YIcpa29q4FpE?&d}rt05iu1Ku%yGzv3-%wwD?!D<@&s zFegrb3zPfcIS>J)1OPn(1?f>BGIx0UH5Ko8&f>i26f_^J2b3cA8}{NHkHxCwicmz7WL-BbnGA?EAzITdM4J{wYz^zRAlW^hPE#yM z27+n3?49h4U}{nr=3yQq;%*<02ZPunD=?QZ96bdGvCP+4aW_`%@kJ+->+v}!ID_p_%XF(fzWj%weVIPM01LP!EEDLQ!MIfN z!6NZS35*dSZv^=*dAY7iWVeu9F=Pq2byI|q+T&h%!uWr{VPcZd6W~Ng))o)g@S!Kn zW;Q1J0GmDyypcW$z=FNS6}%ZzIBtLDW^exA67jPQquQ#gSK%~1L$1lpV)#W<6!jg9 Y?}5M`i2MMHhs+?AdExsFuyGar1)_ZP!TcA zJ$UJ*e}w)uf+)*etay;#gspeyCEK;7f&AuuOy2wPn>QIY8a1MxUH)wW1i1FfrrzKFiQWpqt2yB1j83RJo_*j)#lMbaV28Zg29m~k$x{V0rsjwS0a7!O~vew?yER?*o`gD{rWn2V=bj{(R#BG2M* zJ@Q4ACJg$U%oi#2Wn*2H+f!d`0HolT9ZRf?oZ-4MPKifAa|!6K28OGH=^9|UCRAJt zY|kl(R}^vEt6mac9Tk9@>p;C%?bKxLbrK0)uscVYq?Kbp){2GZc@#(!@zpgdBPGai z?FN;H#8Xv1Os|IFCK13NR`ibmxZ0S&nX-Z{{(!(^!W3(qIeVeo5L*-3Ux*q`-ag>Bf zssaWVfKiqtsbE-)tLSY?IeI>2LO`^ai%^B3sb~(>OXTjxGtXD2>}4zw(0?p|`3GJQ zzPlb^Sr$irQP#uB!K0>G%0&-C?p+Zg>q~ni22864ChK5+P;1 zMA3_Uz(WTdlCsGHTG**LkyI~u+EMe&-(asg}m%_&&v^6gJrcv{X_^y+5Q-sru`txzsb=za&%$VwS|+pzeFts*MEbYH8ubM literal 0 HcmV?d00001 diff --git a/application/database_postgres/__pycache__/UsersModel.cpython-313.pyc b/application/database_postgres/__pycache__/UsersModel.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..93ee206a06351216e59c225d28bc80dd19ead536 GIT binary patch literal 4381 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 literal 0 HcmV?d00001 diff --git a/application/database_postgres/__pycache__/VendorsModel.cpython-313.pyc b/application/database_postgres/__pycache__/VendorsModel.cpython-313.pyc index e450639774bf77dc18bb0704c8444ea850454e07..be15c055cc633948ec6ea5d3030b72eca6d3d00f 100644 GIT binary patch delta 432 zcmaFH^^c49GcPX}0}%Mxt;*2d$ZN%@&Jx2K%&ZO+3T7!{ivjUiLA)4tC5B+OV0KH! zB90i&Afd^5jIyF!V0likYAz{;BJN=BBA&?$7^*rYbBMn^kl)VmKvd!~hr|tO zxyu}KH{=vL_+~I)=1{n4WWFQxqLK3j4w)Gl*X8st%IRO`aK53edVxcFM(|}0)vqj! zoDPgdGC(tmR6&HwWEU1gZZK0B!f#-aQ3o;G7;XqkHF!N><@>_K!Ro*`LHjcUi2lsX zz{dX>EHwE5OQO39NE_HdNg(SMhfQvNN@-52U6Ce`%Lv59AjuERjEs!;88mJ)@IPnp S`ohA)2sF|1D+7oI>jnUk#&MYd delta 432 zcmeyz^^A-6GcPX}0}xm~SeYTXk=Ke*oh^nnm{}bt6wFe@9>W6Uv4VIp97+tqY{Bf7 zj76L=TtOm}^B84CxxwT7clBFa!tOn1aN-lA=o8lA^@q?3mod%)H5`n5tA2HLoigT~su>u4r~q z(d@FKMTg5(9=XpTM`|(_2?HrVO}@#Q%-*cGm^1TACU0R5@wNtvwlh2smAK3yaYIDB zoBxKKLI>Xr=F1!kH?<8mq+Zmv>ENAEaa~;FqPWIo4x1Y?aveMqk}q?}ePvMv3Q5vr5JSq#0wOcgL6M1Z9wK`ag!!3Ja$D*%Z$h8u!X4PFme`Mxl5uv$*g{>%WR zJ~K10@qY#hOuojF=&B0R1~=svhfQvNN@-52U6B@0f)R*|rGUf-W=2NF`wSYl8Tg+w Tczt2vVYHlJ`IP}kf%O6a`#5Wd diff --git a/application/database_postgres/__pycache__/ZonesModel.cpython-313.pyc b/application/database_postgres/__pycache__/ZonesModel.cpython-313.pyc index 49db8eacf670ad08e10044bf7a6b14da5889a439..79ff6e896ae41ad8504192d7ce4109632c196d66 100644 GIT binary patch delta 140 zcmZo;pTN%hnU|M~0SFv5S7pd;V delta 130 zcmbQh-p0=RnU|M~0SGSKTA3lTkvEW$lO-=PH}w|7E$+#Mj7?HOu2wP0`6;O}1&Mhj zMU}cGMTyDTF}aDEd6RjVd_)C-TH6^O@CkNv-jJ5<;GPhCjYD>F9#ff++yw2<3{0$Y fpP3oh_&+lNS(Cpp_43M1ko?NP#wb@L0n`ZqO6w*X diff --git a/application/database_postgres/__pycache__/__init__.cpython-313.pyc b/application/database_postgres/__pycache__/__init__.cpython-313.pyc index bb8b1a57a30b3540120817f71313c0fa2504149e..007c9f487aeb9c0d1cb8373d7dd7b501b4852ecc 100644 GIT binary patch delta 31 lcmdnUxPg)TGcPX}0}ym7ubRkh%;-PSUX(c{wQOQUBmjOZ2xI^N delta 32 mcmdnMxRH_jGcPX}0}!xoT{)54m@#0Yy(mj=VrJgN@JIlMVhGp( diff --git a/application/database_postgres/sql/CREATE/cost_layers.sql b/application/database_postgres/sql/CREATE/cost_layers.sql index 8c5549d..566c74a 100644 --- a/application/database_postgres/sql/CREATE/cost_layers.sql +++ b/application/database_postgres/sql/CREATE/cost_layers.sql @@ -5,5 +5,5 @@ CREATE TABLE IF NOT EXISTS %%site_name%%_cost_layers ( layer_cost FLOAT8 DEFAULT 0.00 NOT NULL, layer_currency_type VARCHAR(16) DEFAULT 'USD' NOT NULL, layer_expires TIMESTAMP DEFAULT NULL, - layer_vendor INTEGER DEFAULT 0 NOT NULL + layer_vendor UUID DEFAULT NULL ); \ No newline at end of file diff --git a/application/database_postgres/sql/CREATE/item_info.sql b/application/database_postgres/sql/CREATE/item_info.sql index bb56e38..02f9fe2 100644 --- a/application/database_postgres/sql/CREATE/item_info.sql +++ b/application/database_postgres/sql/CREATE/item_info.sql @@ -1,6 +1,6 @@ CREATE TABLE IF NOT EXISTS %%site_name%%_item_info ( item_uuid UUID PRIMARY KEY REFERENCES %%site_name%%_items(item_uuid) ON DELETE CASCADE, - item_uom INTEGER NOT NULL, + item_uom UUID DEFAULT NULL REFERENCES units(unit_uuid) ON DELETE SET NULL, item_packaging VARCHAR(255) DEFAULT '' NOT NULL, item_uom_quantity FLOAT8 DEFAULT 0.00 NOT NULL, item_cost FLOAT8 DEFAULT 0.00 NOT NULL, diff --git a/application/database_postgres/sql/CREATE/items.sql b/application/database_postgres/sql/CREATE/items.sql index 38efd9a..be5f34f 100644 --- a/application/database_postgres/sql/CREATE/items.sql +++ b/application/database_postgres/sql/CREATE/items.sql @@ -10,8 +10,5 @@ CREATE TABLE IF NOT EXISTS %%site_name%%_items( item_category VARCHAR(255) NOT NULL, item_search_string TEXT DEFAULT '' NOT NULL, item_inactive BOOLEAN DEFAULT false NOT NULL, - CONSTRAINT fk_brand - FOREIGN KEY(item_brand_uuid) - REFERENCES %%site_name%%_brands(brand_uuid) - ON DELETE SET NULL + UNIQUE(item_name) ); diff --git a/application/database_postgres/sql/CREATE/roles.sql b/application/database_postgres/sql/CREATE/roles.sql new file mode 100644 index 0000000..66444a1 --- /dev/null +++ b/application/database_postgres/sql/CREATE/roles.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS roles( + role_uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4() NOT NULL, + role_name VARCHAR(255) NOT NULL, + role_description TEXT DEFAULT '' NOT NULL, + role_site_uuid UUID REFERENCES sites(site_uuid) ON DELETE CASCADE NOT NULL, + role_flags JSONB DEFAULT '{}' NOT NULL, + UNIQUE(role_name, role_site_uuid) +); \ No newline at end of file diff --git a/application/database_postgres/sql/CREATE/sites.sql b/application/database_postgres/sql/CREATE/sites.sql new file mode 100644 index 0000000..5014481 --- /dev/null +++ b/application/database_postgres/sql/CREATE/sites.sql @@ -0,0 +1,12 @@ +CREATE TABLE IF NOT EXISTS sites ( + site_uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4() NOT NULL, + site_name VARCHAR(120) NOT NULL, + site_description TEXT DEFAULT '' NOT NULL, + site_created_on TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL, + site_created_by UUID REFERENCES users(user_uuid) ON DELETE SET NULL, + site_flags JSONB DEFAULT '{}' NOT NULL, + site_default_zone_uuid UUID DEFAULT NULL, + site_default_auto_issue_location_uuid UUID DEFAULT NULL, + site_default_primary_location_uuid UUID DEFAULT NULL, + UNIQUE(site_name) +); \ No newline at end of file diff --git a/application/database_postgres/sql/CREATE/transactions.sql b/application/database_postgres/sql/CREATE/transactions.sql index 95f404f..a351004 100644 --- a/application/database_postgres/sql/CREATE/transactions.sql +++ b/application/database_postgres/sql/CREATE/transactions.sql @@ -6,6 +6,6 @@ CREATE TABLE IF NOT EXISTS %%site_name%%_transactions ( transaction_quantity FLOAT8 DEFAULT 0.00 NOT NULL, transaction_description TEXT DEFAULT '' NOT NULL, transaction_cost FLOAT8 DEFAULT 0.00 NOT NULL, - transaction_created_by INTEGER NOT NULL, + transaction_created_by UUID DEFAULT NULL REFERENCES users(user_uuid) ON DELETE SET NULL, transaction_data JSONB DEFAULT '{}' NOT NULL ); \ No newline at end of file diff --git a/application/database_postgres/sql/CREATE/units.sql b/application/database_postgres/sql/CREATE/units.sql new file mode 100644 index 0000000..27ec669 --- /dev/null +++ b/application/database_postgres/sql/CREATE/units.sql @@ -0,0 +1,10 @@ +CREATE TABLE IF NOT EXISTS units ( + unit_uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + unit_plural VARCHAR(32) NOT NULL, + unit_single VARCHAR(32) NOT NULL, + unit_fullname VARCHAR(255) NOT NULL, + unit_description TEXT DEFAULT '' NOT NULL, + unique(unit_plural), + unique(unit_single), + unique(unit_fullname) +); \ No newline at end of file diff --git a/application/database_postgres/sql/CREATE/users.sql b/application/database_postgres/sql/CREATE/users.sql new file mode 100644 index 0000000..a5780e0 --- /dev/null +++ b/application/database_postgres/sql/CREATE/users.sql @@ -0,0 +1,17 @@ +CREATE TABLE IF NOT EXISTS users( + user_uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4() NOT NULL, + user_name VARCHAR(255) NOT NULL, + user_password VARCHAR(255) DEFAULT NULL, + user_email VARCHAR(255) UNIQUE NOT NULL, + user_favorites JSONB DEFAULT '{}' NOT NULL, + user_sites UUID [] DEFAULT '{}' NOT NULL, + user_roles UUID [] DEFAULT '{}' NOT NULL, + user_is_system_admin BOOLEAN DEFAULT FALSE NOT NULL, + user_flags JSONB DEFAULT '{}' NOT NULL, + user_row_type VARCHAR(50) DEFAULT 'user' NOT NULL, + user_profile_pic_url VARCHAR(255) DEFAULT '' NOT NULL, + user_login_type VARCHAR(32) DEFAULT 'Internal' NOT NULL, + UNIQUE(user_name), + CHECK (user_email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$') +); + diff --git a/application/database_postgres/sql/CREATE/vendors.sql b/application/database_postgres/sql/CREATE/vendors.sql index 3e42781..22831a7 100644 --- a/application/database_postgres/sql/CREATE/vendors.sql +++ b/application/database_postgres/sql/CREATE/vendors.sql @@ -4,6 +4,6 @@ CREATE TABLE IF NOT EXISTS %%site_name%%_vendors ( vendor_address VARCHAR(255) DEFAULT '' NOT NULL, vendor_phone_number VARCHAR(32) DEFAULT '' NOT NULL, vendor_creation_date TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL, - vendor_created_by INTEGER NOT NULL, + vendor_created_by UUID NOT NULL, UNIQUE(vendor_name, vendor_address, vendor_phone_number) ); \ No newline at end of file diff --git a/application/database_postgres/sql/DROP/units.sql b/application/database_postgres/sql/DROP/units.sql new file mode 100644 index 0000000..13e5997 --- /dev/null +++ b/application/database_postgres/sql/DROP/units.sql @@ -0,0 +1 @@ +DROP TABLE units CASCADE; \ No newline at end of file diff --git a/application/database_postgres/sql/INSERT/brands.sql b/application/database_postgres/sql/INSERT/brands.sql index f107386..873bd8c 100644 --- a/application/database_postgres/sql/INSERT/brands.sql +++ b/application/database_postgres/sql/INSERT/brands.sql @@ -1,4 +1,4 @@ INSERT INTO %%site_name%%_brands -(name) -VALUES (%(name)s) +(brand_name) +VALUES (%(brand_name)s) RETURNING *; \ No newline at end of file diff --git a/application/database_postgres/sql/INSERT/food_info.sql b/application/database_postgres/sql/INSERT/food_info.sql index 0258ae4..6fc514c 100644 --- a/application/database_postgres/sql/INSERT/food_info.sql +++ b/application/database_postgres/sql/INSERT/food_info.sql @@ -1,4 +1,18 @@ INSERT INTO %%site_name%%_food_info -(ingrediants, food_groups, nutrients, expires, default_expiration) -VALUES (%(ingrediants)s, %(food_groups)s, %(nutrients)s, %(expires)s, %(default_expiration)s) +( + item_uuid, + item_food_groups, + item_ingredients, + item_nutrients, + item_expires, + item_default_expiration +) +VALUES ( + %(item_uuid)s, + %(item_food_groups)s, + %(item_ingredients)s, + %(item_nutrients)s, + %(item_expires)s, + %(item_default_expiration)s +) RETURNING *; \ No newline at end of file diff --git a/application/database_postgres/sql/INSERT/item_info.sql b/application/database_postgres/sql/INSERT/item_info.sql index 83ecc49..71ed7fa 100644 --- a/application/database_postgres/sql/INSERT/item_info.sql +++ b/application/database_postgres/sql/INSERT/item_info.sql @@ -1,4 +1,24 @@ INSERT INTO %%site_name%%_item_info -(barcode, packaging, uom_quantity, uom, cost, safety_stock, lead_time_days, ai_pick, prefixes) -VALUES (%(barcode)s, %(packaging)s, %(uom_quantity)s, %(uom)s, %(cost)s, %(safety_stock)s, %(lead_time_days)s, %(ai_pick)s, %(prefixes)s) +( + item_uuid, + item_uom, + item_packaging, + item_uom_quantity, + item_cost, + item_safety_stock, + item_lead_time_days, + item_ai_pick, + item_prefixes + ) +VALUES( + %(item_uuid)s, + %(item_uom)s, + %(item_packaging)s, + %(item_uom_quantity)s, + %(item_cost)s, + %(item_safety_stock)s, + %(item_lead_time_days)s, + %(item_ai_pick)s, + %(item_prefixes)s +) RETURNING *; \ No newline at end of file diff --git a/application/database_postgres/sql/INSERT/item_locations.sql b/application/database_postgres/sql/INSERT/item_locations.sql index b020ce2..f5b5fe8 100644 --- a/application/database_postgres/sql/INSERT/item_locations.sql +++ b/application/database_postgres/sql/INSERT/item_locations.sql @@ -1,4 +1,4 @@ INSERT INTO %%site_name%%_item_locations -(part_id, location_id, quantity_on_hand, cost_layers) -VALUES (%(part_id)s, %(location_id)s, %(quantity_on_hand)s, %(cost_layers)s) +(item_uuid, location_uuid, item_quantity_on_hand) +VALUES (%(item_uuid)s, %(location_uuid)s, %(item_quantity_on_hand)s) RETURNING *; \ No newline at end of file diff --git a/application/database_postgres/sql/INSERT/items.sql b/application/database_postgres/sql/INSERT/items.sql index ef69cc3..077b445 100644 --- a/application/database_postgres/sql/INSERT/items.sql +++ b/application/database_postgres/sql/INSERT/items.sql @@ -1,7 +1,27 @@ INSERT INTO %%site_name%%_items -(barcode, item_name, brand, description, tags, links, item_info_id, item_info_uuid, -logistics_info_id, logistics_info_uuid, food_info_id, food_info_uuid, row_type, item_type, search_string) -VALUES(%(barcode)s, %(item_name)s, %(brand)s, %(description)s, %(tags)s, %(links)s, %(item_info_id)s, %(item_info_uuid)s, -%(logistics_info_id)s, %(logistics_info_uuid)s, %(food_info_id)s, %(food_info_uuid)s, -%(row_type)s, %(item_type)s, %(search_string)s) +( + item_category, + item_name, + item_created_at, + item_updated_at, + item_description, + item_tags, + item_links, + item_brand_uuid, + item_search_string, + item_inactive + + ) +VALUES( + %(item_category)s, + %(item_name)s, + %(item_created_at)s, + %(item_updated_at)s, + %(item_description)s, + %(item_tags)s, + %(item_links)s, + %(item_brand_uuid)s, + %(item_search_string)s, + %(item_inactive)s +) RETURNING *; \ No newline at end of file diff --git a/application/database_postgres/sql/INSERT/locations.sql b/application/database_postgres/sql/INSERT/locations.sql index 06f1f58..7cf5950 100644 --- a/application/database_postgres/sql/INSERT/locations.sql +++ b/application/database_postgres/sql/INSERT/locations.sql @@ -1,4 +1,4 @@ INSERT INTO %%site_name%%_locations -(uuid, name, zone_id) -VALUES (%s, %s, %s) +(location_shortname, location_name, zone_uuid) +VALUES (%(location_shortname)s, %(location_name)s, %(zone_uuid)s) RETURNING *; \ No newline at end of file diff --git a/application/database_postgres/sql/INSERT/logistics_info.sql b/application/database_postgres/sql/INSERT/logistics_info.sql index 1d2ca28..cc49c42 100644 --- a/application/database_postgres/sql/INSERT/logistics_info.sql +++ b/application/database_postgres/sql/INSERT/logistics_info.sql @@ -1,4 +1,16 @@ INSERT INTO %%site_name%%_logistics_info -(barcode, primary_location, primary_zone, auto_issue_location, auto_issue_zone) -VALUES (%(barcode)s, %(primary_location)s, %(primary_zone)s, %(auto_issue_location)s, %(auto_issue_zone)s) +( + item_uuid, + item_primary_location, + item_primary_zone, + item_auto_issue_location, + item_auto_issue_zone +) +VALUES ( + %(item_uuid)s, + %(item_primary_location)s, + %(item_primary_zone)s, + %(item_auto_issue_location)s, + %(item_auto_issue_zone)s +) RETURNING *; \ No newline at end of file diff --git a/application/database_postgres/sql/INSERT/roles.sql b/application/database_postgres/sql/INSERT/roles.sql new file mode 100644 index 0000000..af608a9 --- /dev/null +++ b/application/database_postgres/sql/INSERT/roles.sql @@ -0,0 +1,4 @@ +INSERT INTO roles +(role_name, role_description, role_site_uuid, role_flags) +VALUES (%(role_name)s, %(role_description)s, %(role_site_uuid)s, %(role_flags)s) +RETURNING *; \ No newline at end of file diff --git a/application/database_postgres/sql/INSERT/sites.sql b/application/database_postgres/sql/INSERT/sites.sql new file mode 100644 index 0000000..4f16227 --- /dev/null +++ b/application/database_postgres/sql/INSERT/sites.sql @@ -0,0 +1,7 @@ +INSERT INTO sites +(site_name, site_description, site_created_by, site_default_zone_uuid, site_default_auto_issue_location_uuid, +site_default_primary_location_uuid, site_created_on, site_flags) +VALUES (%(site_name)s, %(site_description)s, %(site_created_by)s, %(site_default_zone_uuid)s, +%(site_default_auto_issue_location_uuid)s, %(site_default_primary_location_uuid)s, +%(site_created_on)s, %(site_flags)s) +RETURNING *; \ No newline at end of file diff --git a/application/database_postgres/sql/INSERT/transactions.sql b/application/database_postgres/sql/INSERT/transactions.sql index dd0ee91..90135ba 100644 --- a/application/database_postgres/sql/INSERT/transactions.sql +++ b/application/database_postgres/sql/INSERT/transactions.sql @@ -1,6 +1,24 @@ INSERT INTO %%site_name%%_transactions -(timestamp, logistics_info_id, barcode, name, transaction_type, -quantity, description, user_id, data) -VALUES (%(timestamp)s, %(logistics_info_id)s, %(barcode)s, %(name)s, %(transaction_type)s, -%(quantity)s, %(description)s, %(user_id)s, %(data)s) +( + item_uuid, + transaction_created_by, + transaction_name, + transaction_type, + transaction_created_at, + transaction_quantity, + transaction_description, + transaction_cost, + transaction_data +) +VALUES ( + %(item_uuid)s, + %(transaction_created_by)s, + %(transaction_name)s, + %(transaction_type)s, + %(transaction_created_at)s, + %(transaction_quantity)s, + %(transaction_description)s, + %(transaction_cost)s, + %(transaction_data)s +) RETURNING *; \ No newline at end of file diff --git a/application/database_postgres/sql/INSERT/units.sql b/application/database_postgres/sql/INSERT/units.sql new file mode 100644 index 0000000..7f1d99c --- /dev/null +++ b/application/database_postgres/sql/INSERT/units.sql @@ -0,0 +1,3 @@ +INSERT INTO units (unit_plural, unit_single, unit_fullname, unit_description) +VALUES (%(unit_plural)s, %(unit_single)s,%(unit_fullname)s,%(unit_description)s) +RETURNING *; \ No newline at end of file diff --git a/application/database_postgres/sql/INSERT/users.sql b/application/database_postgres/sql/INSERT/users.sql new file mode 100644 index 0000000..a181289 --- /dev/null +++ b/application/database_postgres/sql/INSERT/users.sql @@ -0,0 +1,3 @@ +INSERT INTO users (user_name, user_password, user_email) +VALUES (%(user_name)s, %(user_password)s, %(user_email)s) +RETURNING *; \ No newline at end of file diff --git a/application/database_postgres/sql/INSERT/vendors.sql b/application/database_postgres/sql/INSERT/vendors.sql index 6c8376a..a7b328a 100644 --- a/application/database_postgres/sql/INSERT/vendors.sql +++ b/application/database_postgres/sql/INSERT/vendors.sql @@ -1,4 +1,4 @@ INSERT INTO %%site_name%%_vendors -(vendor_name, vendor_address, creation_date, created_by, phone_number) -VALUES (%(vendor_name)s, %(vendor_address)s, %(creation_date)s, %(created_by)s, %(phone_number)s) +(vendor_name, vendor_address, vendor_creation_date, vendor_created_by, vendor_phone_number) +VALUES (%(vendor_name)s, %(vendor_address)s, %(vendor_creation_date)s, %(vendor_created_by)s, %(vendor_phone_number)s) RETURNING *; \ No newline at end of file diff --git a/application/database_postgres/sql/INSERT/zones.sql b/application/database_postgres/sql/INSERT/zones.sql index e6a7da2..3d682bc 100644 --- a/application/database_postgres/sql/INSERT/zones.sql +++ b/application/database_postgres/sql/INSERT/zones.sql @@ -1,4 +1,4 @@ INSERT INTO %%site_name%%_zones -(name, description) -VALUES (%(name)s, %(description)s) +(zone_name, zone_description) +VALUES (%(zone_name)s, %(zone_description)s) RETURNING *; \ No newline at end of file diff --git a/application/database_postgres/sql/ItemsModel/paginateItemsWithQOH.sql b/application/database_postgres/sql/ItemsModel/paginateItemsWithQOH.sql new file mode 100644 index 0000000..8a4025d --- /dev/null +++ b/application/database_postgres/sql/ItemsModel/paginateItemsWithQOH.sql @@ -0,0 +1,17 @@ +WITH sum_cte AS ( + SELECT items.item_uuid, SUM(items_locations.item_quantity_on_hand)::FLOAT8 AS total_sum + FROM %%site_name%%_item_locations items_locations + JOIN %%site_name%%_items items ON items_locations.item_uuid = items.item_uuid + GROUP BY items.item_uuid + ) + +SELECT items.item_uuid, items.item_description, items.item_name, sum_cte.total_sum as quantity_on_hand, units.unit_fullname +FROM %%site_name%%_items items +LEFT JOIN sum_cte ON items.item_uuid = sum_cte.item_uuid +LEFT JOIN %%site_name%%_item_info item_info ON items.item_uuid = item_info.item_uuid +LEFT JOIN units ON item_info.item_uom = units.unit_uuid +WHERE items.item_search_string LIKE '%%' || %(search_string)s || '%%' + AND items.item_inactive IS false +ORDER BY %%sort_order%% +LIMIT %(limit)s OFFSET %(offset)s; + diff --git a/application/database_postgres/sql/ItemsModel/paginateitemsForModal.sql b/application/database_postgres/sql/ItemsModel/paginateitemsForModal.sql new file mode 100644 index 0000000..9e97c2b --- /dev/null +++ b/application/database_postgres/sql/ItemsModel/paginateitemsForModal.sql @@ -0,0 +1,7 @@ +SELECT items.item_uuid, items.item_name, units.unit_uuid as uom FROM %%site_name%%_items items +LEFT JOIN %%site_name%%_item_info item_info ON item_info.item_uuid = items.item_uuid +LEFT JOIN units ON units.unit_uuid = item_info.item_uom +WHERE items.item_search_string LIKE '%%' || %(search_string)s || '%%' + ANd items.item_inactive IS false +ORDER BY %%sort_order%% +LIMIT %(limit)s OFFSET %(offset)s; \ No newline at end of file diff --git a/application/items/__pycache__/__init__.cpython-313.pyc b/application/items/__pycache__/__init__.cpython-313.pyc index e1f329cf7216cc7f7a860b4e6ee94b4faa275283..45b2a0a93845227db99baa3d9dfc34c063c7f666 100644 GIT binary patch delta 31 lcmZ3=xP+1WGcPX}0}ym7ubRkh%;-GPUX(c{wQOR9F93SW2s8iy delta 32 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 diff --git a/application/items/__pycache__/items_models.cpython-313.pyc b/application/items/__pycache__/items_models.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2e3c630c71a21824ba0641db21a65d63fecef699 GIT binary patch literal 2391 zcma(TOKclObk@7;^*_$9&WFYuQ<}KYeA=W)39VFWCvDRtl{aocF(s^Bb!XRY z6Dq_B<>)}G1|dNN5(G}=#I=-gcRvENtx6=sp|^qy74*WJwKq;BD2(MdZ{E!N&U54jKOpT2}qzO5GJ!@f;#TN4xZrj1bv*r%yAa8)|#4d zVrLtQAc5&X0-JIS(^WB;o2R1A2V^l7rOd_|EUCIA8(B@&C$+Sai#p65Mj@Y5Vp)qS z;l(l7*YKk0k4c7fTGEv`#u}EZ0;|wjTU3Ssxq?y%Qv$*cfx>i(5jcTPu>zBF4lx4T zfzC2#so@#}<^(5p%{!tlvn_tnP}H=N-e!|!(gO|-PnnFKH58M}ON%*8O3QX&h#(jI ziG!6jhZQ6$Nd+K4`)QRGXbWcmcM7Zu>Qq-3N;18K4@Md+o~xn&6I)mvi6Glp)jbFD zt0TeKSbc_YvjZsUj!_3F72HlTxmwr}5;)bBWD@TBX&bYpJCN#0dJI3LYh0TR!oi ziAl8Dob}n30jHTns7|-BZTsA;f5@>5BErL8^<_{udI{MY(iB1&*a?HzJeoRmkPf2^ z)s5!bY)KrFn!w7y9K@zNk^#s8)-E^&3PBAV4rHhds&Yr&Q%eCMejM3x#Z&Kghq(yS0kyBO?~|#Iea^Gq97(E@X|U&QFhx32{Qu zG8tU~%IH#SP0x|(>o@h5gBN73&Np`4DY@Mxf*}m{bxbebZ8YUcrXR5GzbtKbJGz07O|UsD{a5 zMb>ay2O97J5zaKEYpskdi7PbNYBE^6pqn&6ldbZy3TbsZsS*C^VUEW~QZjVRR9;dI zyto%GP<}I&R?a_7kn;IlR+fm5DRPB+6^aX_+3J1yMKh9@=Ci6~C{JCAczRLXx^i>$ z-+E);e>1zlcs&A%{0`a-cC8w#nbNMMQsAv+y1e7X_1OA>(x6;wNiVz0O)bk@xut#C z{g7#Q58Q7K-HY^ozW0;8pY|6U?uEiXgnDj;dTxeB*82eznQ~-zk-6MZevU7?Hyc~8 zcCK_@-nsUCIoN#FyW(9vawixqhdQoaTDkQ8+eHS%eKh{#?A-0H)UDaM(n;Y?c=($9 zwf2>EUH?8jadURAI9}>X702(lwtv=B?ur!0H@8b_>-^aFf$zh>+PRx;(IWe}88wCf z*nxuW-OvVd>! zo>(8Zv4iYS^Mkz9HqM{u7go-0VX z+D?NgX0li}a#>YTHQ4sbIZ4+S6yuDRhI}?4HPi4MW9Z??Y!yY_fP|7o$uMyCG*p%@ zik8j_>?6FJ0P<#9fFODVz`Ll-27h&g*@0{FLj=?Hp+{t@(8$?XA#^-wGeuoE0Jzz| zEq(j8Y9_Dg#ynQ^zHKjc_3HtX_s;BjHrK2031L0#RvWOY8k0AzYBPyl)ez!`>3XFa rv_qJDT0j;fy$b-`7e(Dg4ZontpU~@fk^d3XO%0U71HU7fE&jg&Xy8ln literal 0 HcmV?d00001 diff --git a/application/items/__pycache__/items_processes.cpython-313.pyc b/application/items/__pycache__/items_processes.cpython-313.pyc index a9c1524cc9ec8411212c02cf61c82630a9997afb..0c45d1b87a5cceef0ca78236051cffb15a896f27 100644 GIT binary patch delta 51 zcmdlUwk?eNGcPX}0}ym7uiD7n$0OoxJ!>^if{ zHPBO)r;3+Krk5x=RccWywK?@A_jFbJk|tE@Be|rFyO&-gwSCB2T4-tSUiy7&uQ4P? zUTzRG-+Vjs&CEC7%x}CA2zUu3<~P5(bQPq(VZ~0)im-7BgzH2kA~Q}XmX2{|%t0Lj z%Ir8h#!=3axpC*1i@L^m%3F12+)drBq=$&kb|Sh`+;O&SlX@&I4_bH1QPq0qn26^o zHkphtX2Ud=dMa+;}{6nNs3TLB-9}?ludCWFS048$faDzIGppl+`G*2N(ah|F6y3h zMBHX5e#KDKjFQ=&%u_csn>0B+XDB9LkQVcrl#y*0q6r)rm%nk9f71@>9VY zOP*Rw8Q7zw4}7`Hze#s>tRS@D5^dX-ZONXeK$84^(oGH#yH5tL2!JnJg*yj_{L9QK?YK=VS@b zR}{i_UXSXR^U*oQz_1?GXwCT>fGa5UEOplc!THxFe=n0b2xr7 zCBvnZDoCn97Y`d$lE0J6DDUl5)c81M`8YL?H<>lz~ z8)u%mNT_MqRcdNm_B`X7JcFM%wmk0X`{m)E9sYT=SpT>s{HW!?r!5EWx178cg{;Vx zdR{AX*Xm2J2u076hUOa`D;?K5S6?j!8*lhld^bl{TOI}@rIz*^@2|Z7!S{ z;q?9(82e(Y9a>!P{BpAu8f_59Gx#_ZUUjb0TSp&+qU-+Xm(N&GexaX+#s9E-* zjCf3U+6wM;+VrN=uvrUv1!R9ZeR)C3W7tEjKAq0ysBYwQs-kM3@X2{e*XI@El9quu zGa!62KqYzr?3&H#v~IvLlhcx6(A-6cay^~4#@vAKguaHE+s;tXdm#G(DLI4xbaXfe zZ^_RH6u*vbVzI#zSHlMC=@Glr*Uq?BI|6HN0Sm%UaZQukXKe@MaPAi5RxQYhOp>XC z_rg(Dbt0rd=0J7r-Uuklsk}A0VW{{uBJ&v#1}^ml6XK;6{#X`Aqfx7 zu4G@*6(r$BvMYTvVS@rLa&H2oW>>Op>;TdLU|}0b;J>7=k-X#A9Z{k?8p)dg47j|j zkOa#BlHDokx5s|lG0{PY4{)LKs{iG{0(&o7%SGFswU#~LD!TC|Rs(y0Pk=RXi7IBd z=WhE0#^4fw;@zp{J3kXR=GYG&5#V3-XZOP7*(wBEZHpBM*3E)%$|+d~G2q+*!MfqP z8VGtOm+Hj$Tk(-;;h->@n3@ol-q~>v3o~!V6LG=1qZbx(8R3l3+gGhc^phto?OPWn zE=m7JXJI~b`9cVy4u&b(l-Q`WsPI!*Ba zMCvF$oG8$rP~1V=Yf(7s)3+Kkm|G>zv6vGU&U-cwqpUFKx=GBdqJUNaM$t^Wb4{9dkWHA zPL<$EQ47HI#lYhcKp=;q5sEqtC#P$MvBg@Mr7x{3Oj+m*8(*VZNC{W zo?Z8M|LZh=%TvK<&r2}+2KeenK+qxbyUt@VfxX)x#5!DehrR*wy}l!$U+Zv! zye4>K1N_=S9yLSWcz|6y6^Ogp-@Bb4TLDTVU@PLG2ce+-m>t6GFlKNa5*o#95VJFw z4Pl1&X&Gr25%Cuq#SDp4OT@5q@HP+;iaWM#}FT_<%4TM-&*I+wVUqK z<)_D4ikkxAOLX)dymuJpGgAK#GVvF3?yuxqpOL^O*U1dtY~LhMY@Y3A2G_%b|0Ymc GzW6_yG=bs( literal 0 HcmV?d00001 diff --git a/application/items/__pycache__/services.cpython-313.pyc b/application/items/__pycache__/services.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6f550a35d445076e64f708451b8b9d9edc613daa GIT binary patch literal 3162 zcma)8O>Y`U7#?7OKvk^?V&!V>cz+W3>FTHHbk=2G*WJk9QE9pg$0~Cj#t{5_kHJ?nP=Xad3N`0 zHZwra?Hx=;jGXC^iw%&l{jY zvGoz-JPAp~HbhMGW@uJyW5hCVg;vGZL~PJTSt&AVr%X`?WscgX4V1+P?hH5}Ho z+Fa$bp@mSb0HB=XfHqyxe5qk|l%CdQns~~#z!sunI+NV`FdJ{@-a~sy)!l+gN z+%hkBs9vj9nbJDScuvP!XbVN^Q8R6z$U#)Ex>W2D3=9ebYa8}(2G(ORFf`j*j8KE!x`9)!N5qFXyft;b=Fp`=4uv&;uleFy4AMBbJMmG6V54RKBr~vZM_cX zXg>x=I~v=fjA>QaTtYjh?wr1nH{a3hl#{1d^5$4=VGrlzI0h&EYR}O#HW_Lu8)c^) z=Lpu>*5}ZfX+S%VF}IT@)%zLmN4d}~_o`f4wYq`h+zHbd9PJNdlgCI%SK;5QRuHFa|^})J?mL|s% zbxdq6FFV`gY&^>xj0pK0^Cw!dtV=P1Y(NWGPp4!u$LAAVE;T6|@sgN?Ae-Zx0+-;j z*|Y$$UdzUt@qC7hCuM7zManM3gw0%rkzKiZIF?K+-v_jCh?NcEGb5HYrz1=v0C{lUa``sdwNFTZ{7y zcJV^2^$ln+C9o%VC^0-ptie_8I=Gq5xE(|J=Elh#&H0-UOIv<<%(U;rN4|XZd^CYR zxzSvl6<~e@^+;?)lgzz?fp{*LN!LH0QDxpJO6$FJg5gJVdD$9ICSxq~V+=RJNi-48 z4c~*lLIqHDx}TDd$!8}k&VfR>>20r7oWVnUkN4@V$G1wSMt8Io&s1S?*Xb#Q-&h-HCnQI2+0iRHCb!2>Q6#Fq-XihHQFV70O|LusQs+Rqb3p8zd~QNT zVd1sKBUu7vOJI9i3SJk3*DJyK($}J>^$>)TAHB63A!@zZRxu;(U~ZOcLSeqKM(_m>DH`B^w#`il7FV` zpAr4x7o(^sp&>nkl4rW?nci8EW|QJJd*#W-E(pz)) zOYYIKd-S;=g>H+X+m+C2X>DC}r;4Wc2GDt0at)VV!&O(;)&tS?S+#el>K&|l``?-L zorXf>y;XsXmtEsK?wtqH3@y&kl^G>Lu0$d7#^|h))?X|SEst#2Fh4Z^Y~9lXvSS|| z5)jlZqFZ$eS@KZZDI0CxmK?_mDToeQ7@v3LZ?JyJhd&Juql;AUfv>qdesquj|6taI z;S&=b_ybj>LmoSNqLr2$HWs94%_r#L^&k9)t&5c(FHOe_5dF*|T~qEKJc)vE3KtW& znY@tYlA9UkBD{cH{J+Z2pkhy_(P&-*$3I~16}b8eEGgv;WV{5ncf_Q|v&HWLRBfMD Ms-3BSv8N3DAC@%d+5i9m literal 0 HcmV?d00001 diff --git a/application/items/items_API.py b/application/items/items_API.py index 1f1b8c6..e1ebbe5 100644 --- a/application/items/items_API.py +++ b/application/items/items_API.py @@ -15,6 +15,11 @@ import application.postsqldb as db from application.items import database_items from application.items import items_processes import application.database_payloads as dbPayloads +from application.database_postgres.UsersModel import UsersModel +from application.database_postgres.SitesModel import SitesModel +from application.database_postgres.UnitsModel import UnitsModel +from application.items import models +from application.database_postgres.ItemsModel import ItemsModel items_api = Blueprint('items_api', __name__, template_folder="templates", static_folder="static") @@ -28,59 +33,51 @@ def update_session_user(): @items_api.route("/") @access_api.login_required def items(): - update_session_user() - sites = [site[1] for site in db.get_sites(session['user']['sites'])] + sites = [SitesModel.select_tuple('', {'key': site})['site_name'] for site in session['user'].get('user_sites', [])] + access_api.update_session_user() return render_template("index.html", current_site=session['selected_site'], sites=sites) -@items_api.route("/") +@items_api.route("/") @access_api.login_required -def item(id): - sites = [site[1] for site in db.get_sites(session['user']['sites'])] - database_config = config() - with psycopg2.connect(**database_config) as conn: - units = db.UnitsTable.getAll(conn) +def item(item_uuid): + sites = [SitesModel.select_tuple('', {'key': site})['site_name'] for site in session['user'].get('user_sites', [])] + units = UnitsModel.select_tuples('') return render_template("item_new.html", id=id, units=units, current_site=session['selected_site'], sites=sites) @items_api.route("/transaction") @access_api.login_required def transaction(): - sites = [site[1] for site in db.get_sites(session['user']['sites'])] - database_config = config() - with psycopg2.connect(**database_config) as conn: - units = db.UnitsTable.getAll(conn) - return render_template("transaction.html", units=units, current_site=session['selected_site'], sites=sites, proto={'referrer': request.referrer}) + sites = [SitesModel.select_tuple('', {'key': site})['site_name'] for site in session['user'].get('user_sites', [])] + units = UnitsModel.select_tuples('') + return render_template("transaction.html", units=units, current_site=session['selected_site'], sites=sites, proto={'referrer': request.referrer}) -@items_api.route("/transactions/") +@items_api.route("/transactions/") @access_api.login_required -def transactions(id): - sites = [site[1] for site in db.get_sites(session['user']['sites'])] - return render_template("transactions.html", id=id, current_site=session['selected_site'], sites=sites) - -@items_api.route("//itemLink/") -@access_api.login_required -def itemLink(parent_id, id): - sites = [site[1] for site in db.get_sites(session['user']['sites'])] - return render_template("itemlink.html", current_site=session['selected_site'], sites=sites, proto={'referrer': request.referrer}, id=id) +def transactions(item_uuid): + sites = [SitesModel.select_tuple('', {'key': site})['site_name'] for site in session['user'].get('user_sites', [])] + return render_template("transactions.html", item_uuid=item_uuid, current_site=session['selected_site'], sites=sites) # API CALLS -@items_api.route("/getTransactions", methods=["GET"]) +@items_api.route("/api/getTransactions", methods=["GET"]) @access_api.login_required def getTransactions(): if request.method == "GET": recordset = [] count = 0 - logistics_info_id = int(request.args.get('logistics_info_id', 1)) + item_uuid = request.args.get('item_uuid', None) page = int(request.args.get('page', 1)) limit = int(request.args.get('limit', 50)) site_name = session['selected_site'] offset = (page - 1) * limit - recordset, count = database_items.getTransactions(site_name, (logistics_info_id, limit, offset)) + filter = {'item_uuid': item_uuid, 'limit': limit, 'offset': offset} + recordset, count = models.ExtendedTransactionModel.paginate_transactions_by_item_uuid(site_name, filter) 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("/getTransaction", methods=["GET"]) +#potential unused, was used on the transactions.html page +@items_api.route("/api/getTransaction", methods=["GET"]) @access_api.login_required def getTransaction(): transaction = () @@ -91,18 +88,32 @@ 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("/getItem", methods=["GET"]) +@items_api.route("/api/getItem", methods=["GET"]) @access_api.login_required def get_item(): if request.method == "GET": id = int(request.args.get('id', 1)) site_name = session['selected_site'] item = () + item = database_items.getItemAllByID(site_name, (id, )) return jsonify({'item': item, 'error': False, 'message': ''}) return jsonify({'item': item, 'error': True, 'message': f'method {request.method} not allowed.'}) -@items_api.route("/getItemsWithQOH", methods=['GET']) +@items_api.route("/api/getTransactionItem", methods=["GET"]) +@access_api.login_required +def getTransactionItem(): + if request.method == "GET": + item_uuid = request.args.get('item_uuid', None) + site_name = session['selected_site'] + item = () + if item_uuid: + item = models.ExtendedItemsModel.get_item_for_transactions(site_name, {'item_uuid': item_uuid}) + return jsonify({'item': item, 'error': False, 'message': ''}) + return jsonify({'item': item, 'error': True, 'message': f'method {request.method} not allowed.'}) + + +@items_api.route("/api/getItemsWithQOH", methods=['GET']) @access_api.login_required def pagninate_items(): items = [] @@ -118,13 +129,14 @@ def pagninate_items(): if sort == 'total_qoh': sort_order = f"{sort} {order}" else: - sort_order = f"item.{sort} {order}" + sort_order = f"items.{sort} {order}" - items, count = database_items.getItemsWithQOH(site_name, (search_string, limit, offset, sort_order)) + filter = {'search_string': search_string, 'limit': limit, 'offset': offset, 'sort_order': sort_order} + items, count = ItemsModel.paginate_items_with_qoh(site_name, filter) 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('/getModalItems', methods=["GET"]) +@items_api.route('/api/getModalItems', methods=["GET"]) @access_api.login_required def getModalItems(): recordset, count = tuple(), 0 @@ -134,7 +146,9 @@ def getModalItems(): search_string = request.args.get('search_string', '') site_name = session['selected_site'] offset = (page - 1) * limit - recordset, count = database_items.getModalSKUs(site_name, (search_string, limit, offset)) + sort_order = "item_name ASC" + filter = {'search_string': search_string, 'limit': limit, 'offset': offset, 'sort_order': sort_order} + recordset, count = ItemsModel.paginate_items_for_modal(site_name, filter) 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."}) diff --git a/application/items/models.py b/application/items/models.py new file mode 100644 index 0000000..27791fe --- /dev/null +++ b/application/items/models.py @@ -0,0 +1,72 @@ +import psycopg2 + +from application.database_postgres.TransactionsModel import TransactionsModel +from application.database_postgres.ItemsModel import ItemsModel +from application.database_postgres.BaseModel import tupleDictionaryFactory, DatabaseError +import config + +class ExtendedItemsModel(ItemsModel): + @classmethod + def get_item_for_transactions(self, site: str, payload: dict, convert=True, conn=None): + with open('application/items/sql/getItemForTransaction.sql', 'r+') as file: + sql = file.read().replace("%%site_name%%", site) + + record = () + self_conn = False + + try: + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = True + self_conn = True + + with conn.cursor() as cur: + cur.execute(sql, payload) + rows = cur.fetchone() + if rows and convert: + record = tupleDictionaryFactory(cur.description, rows) + elif rows and not convert: + record = rows + + if self_conn: + conn.close() + + return record + except Exception as error: + raise DatabaseError(error, {}, sql) + +class ExtendedTransactionModel(TransactionsModel): + @classmethod + def paginate_transactions_by_item_uuid(self, site:str, payload:dict, convert=True, conn=None): + sql = f"SELECT * FROM {site}_transactions WHERE item_uuid = %(item_uuid)s::uuid LIMIT %(limit)s OFFSET %(offset)s;" + sql_count = f"SELECT COUNT(*) FROM {site}_transactions WHERE item_uuid=%(item_uuid)s::uuid;" + records = () + self_conn = False + + try: + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = True + self_conn = True + + with conn.cursor() as cur: + cur.execute(sql, payload) + rows = cur.fetchall() + if rows and convert: + records = [tupleDictionaryFactory(cur.description, row) for row in rows] + elif rows and not convert: + records = rows + + cur.execute(sql_count, payload) + count = cur.fetchone()[0] + + if self_conn: + conn.commit() + conn.close() + + return records, count + + except Exception as error: + raise DatabaseError(error, {}, sql) \ No newline at end of file diff --git a/application/items/services.py b/application/items/services.py new file mode 100644 index 0000000..6e4fb09 --- /dev/null +++ b/application/items/services.py @@ -0,0 +1,61 @@ +import psycopg2 + +from application.database_postgres.ItemsModel import ItemsModel +from application.database_postgres.ItemInfoModel import ItemInfoModel +from application.database_postgres.LogisticsInfoModel import LogisticsInfoModel +from application.database_postgres.FoodInfoModel import FoodInfoModel +from application.database_postgres.TransactionsModel import TransactionsModel +from application.database_postgres.ItemLocationsModel import ItemLocationsModel +import config + +def add_new_item(site: str, data: dict, user_uuid: str, conn=None): + item_data = data.get('item_data') + food_info = data.get('food_data', {}) + item_info = data.get('item_info', {}) + logistics_info = data.get('logistics_info', {}) + + self_conn = False + + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = False + self_conn = True + + item_payload = ItemsModel.Payload(**item_data) + + item = ItemsModel.insert_tuple(site, item_payload.payload_dictionary(), conn=conn) + + item_info['item_uuid'] = item['item_uuid'] + item_info_payload = ItemInfoModel.Payload(**item_info) + item_info = ItemInfoModel.insert_tuple(site, item_info_payload.payload_dictionary(), conn=conn) + + logistics_info['item_uuid'] = item['item_uuid'] + logistics_info_payload = LogisticsInfoModel.Payload(**logistics_info) + logistics_info = LogisticsInfoModel.insert_tuple(site, logistics_info_payload.payload_dictionary(), conn=conn) + + + if 'item_primary_location' in logistics_info.keys(): + items_location = ItemLocationsModel.Payload( + item_uuid=item['item_uuid'], + location_uuid=logistics_info['item_primary_location'] + ) + items_location = ItemLocationsModel.insert_tuple(site, items_location.payload_dictionary(), conn=conn) + + if item['item_category'] in ['FOOD', 'FOOD PLU']: + food_info['item_uuid'] = item['item_uuid'] + food_info_payload = FoodInfoModel.Payload(**food_info) + food_info = FoodInfoModel.insert_tuple(site, food_info_payload.payload_dictionary(), conn=conn) + + + transaction = TransactionsModel.Payload( + item_uuid=item['item_uuid'], + transaction_created_by=user_uuid, + transaction_name="Item Created", + transaction_type="SYSTEM" + ) + transaction = TransactionsModel.insert_tuple(site, transaction.payload_dictionary(), conn=conn) + + if self_conn: + conn.commit() + conn.close() \ No newline at end of file diff --git a/application/items/sql/getItemForTransaction.sql b/application/items/sql/getItemForTransaction.sql new file mode 100644 index 0000000..9d16325 --- /dev/null +++ b/application/items/sql/getItemForTransaction.sql @@ -0,0 +1,28 @@ +WITH parameters AS (SELECT %(item_uuid)s::uuid AS item_uuid), + cte_item_locations_qty AS ( + SELECT SUM(item_locations.item_quantity_on_hand) AS quantity_on_hand + FROM %%site_name%%_item_locations item_locations + WHERE item_locations.item_uuid = (SELECT item_uuid FROM parameters) + ), + cte_item_locations AS ( + SELECT item_locations.item_location_uuid, item_locations.item_quantity_on_hand, locations.location_shortname, zones.zone_uuid, locations.location_uuid + FROM %%site_name%%_item_locations item_locations + LEFT JOIN %%site_name%%_locations locations ON locations.location_uuid = item_locations.location_uuid + LEFT JOIN %%site_name%%_zones zones ON zones.zone_uuid = locations.zone_uuid + WHERE item_locations.item_uuid = (SELECT item_uuid FROM parameters) + ) + +SELECT + items.item_uuid, + items.item_name, + item_info.item_cost, + (SELECT COALESCE(ilsq.quantity_on_hand, 0.00) FROM cte_item_locations_qty ilsq) AS item_quantity_on_hand, + COALESCE(units.unit_fullname, 'ERROR') AS unit_fullname, + (SELECT COALESCE(array_agg(row_to_json(ils)), '{}') FROM cte_item_locations ils) AS item_locations, + (SELECT COALESCE(row_to_json(zones.*), '{}') FROM %%site_name%%_zones zones WHERE zones.zone_uuid = log_info.item_primary_zone) AS primary_zone, + (SELECT COALESCE(row_to_json(locations.*), '{}') FROM %%site_name%%_locations locations WHERE locations.location_uuid = log_info.item_primary_location) AS primary_location +FROM %%site_name%%_items items +LEFT JOIN %%site_name%%_item_info item_info ON item_info.item_uuid = items.item_uuid +LEFT JOIN units ON item_info.item_uom = units.unit_uuid +LEFT JOIN %%site_name%%_logistics_info log_info ON log_info.item_uuid = items.item_uuid +WHERE items.item_uuid = (SELECT item_uuid FROM parameters); \ No newline at end of file diff --git a/application/items/sql/getItemAllByBarcode.sql b/application/items/sql_old/getItemAllByBarcode.sql similarity index 100% rename from application/items/sql/getItemAllByBarcode.sql rename to application/items/sql_old/getItemAllByBarcode.sql diff --git a/application/items/sql/getItemAllByID.sql b/application/items/sql_old/getItemAllByID.sql similarity index 100% rename from application/items/sql/getItemAllByID.sql rename to application/items/sql_old/getItemAllByID.sql diff --git a/application/items/sql/getItemLocations.sql b/application/items/sql_old/getItemLocations.sql similarity index 100% rename from application/items/sql/getItemLocations.sql rename to application/items/sql_old/getItemLocations.sql diff --git a/application/items/sql/getItemsAll.sql b/application/items/sql_old/getItemsAll.sql similarity index 100% rename from application/items/sql/getItemsAll.sql rename to application/items/sql_old/getItemsAll.sql diff --git a/application/items/sql/getItemsWithQOH.sql b/application/items/sql_old/getItemsWithQOH.sql similarity index 100% rename from application/items/sql/getItemsWithQOH.sql rename to application/items/sql_old/getItemsWithQOH.sql diff --git a/application/items/sql/getLocationsWithZone.sql b/application/items/sql_old/getLocationsWithZone.sql similarity index 100% rename from application/items/sql/getLocationsWithZone.sql rename to application/items/sql_old/getLocationsWithZone.sql diff --git a/application/items/sql/getSkuPrefixes.sql b/application/items/sql_old/getSkuPrefixes.sql similarity index 100% rename from application/items/sql/getSkuPrefixes.sql rename to application/items/sql_old/getSkuPrefixes.sql diff --git a/application/items/sql/insertCostLayersTuple.sql b/application/items/sql_old/insertCostLayersTuple.sql similarity index 100% rename from application/items/sql/insertCostLayersTuple.sql rename to application/items/sql_old/insertCostLayersTuple.sql diff --git a/application/items/sql/insertFoodInfoTuple.sql b/application/items/sql_old/insertFoodInfoTuple.sql similarity index 100% rename from application/items/sql/insertFoodInfoTuple.sql rename to application/items/sql_old/insertFoodInfoTuple.sql diff --git a/application/items/sql/insertItemInfoTuple.sql b/application/items/sql_old/insertItemInfoTuple.sql similarity index 100% rename from application/items/sql/insertItemInfoTuple.sql rename to application/items/sql_old/insertItemInfoTuple.sql diff --git a/application/items/sql/insertItemLinksTuple.sql b/application/items/sql_old/insertItemLinksTuple.sql similarity index 100% rename from application/items/sql/insertItemLinksTuple.sql rename to application/items/sql_old/insertItemLinksTuple.sql diff --git a/application/items/sql/insertItemLocationsTuple copy.sql b/application/items/sql_old/insertItemLocationsTuple copy.sql similarity index 100% rename from application/items/sql/insertItemLocationsTuple copy.sql rename to application/items/sql_old/insertItemLocationsTuple copy.sql diff --git a/application/items/sql/insertItemLocationsTuple.sql b/application/items/sql_old/insertItemLocationsTuple.sql similarity index 100% rename from application/items/sql/insertItemLocationsTuple.sql rename to application/items/sql_old/insertItemLocationsTuple.sql diff --git a/application/items/sql/insertItemTuple.sql b/application/items/sql_old/insertItemTuple.sql similarity index 100% rename from application/items/sql/insertItemTuple.sql rename to application/items/sql_old/insertItemTuple.sql diff --git a/application/items/sql/insertLogisticsInfoTuple.sql b/application/items/sql_old/insertLogisticsInfoTuple.sql similarity index 100% rename from application/items/sql/insertLogisticsInfoTuple.sql rename to application/items/sql_old/insertLogisticsInfoTuple.sql diff --git a/application/items/sql/insertSKUPrefixTuple.sql b/application/items/sql_old/insertSKUPrefixTuple.sql similarity index 100% rename from application/items/sql/insertSKUPrefixTuple.sql rename to application/items/sql_old/insertSKUPrefixTuple.sql diff --git a/application/items/sql/insertTransactionsTuple.sql b/application/items/sql_old/insertTransactionsTuple.sql similarity index 100% rename from application/items/sql/insertTransactionsTuple.sql rename to application/items/sql_old/insertTransactionsTuple.sql diff --git a/application/items/sql/itemsModal.sql b/application/items/sql_old/itemsModal.sql similarity index 100% rename from application/items/sql/itemsModal.sql rename to application/items/sql_old/itemsModal.sql diff --git a/application/items/sql/itemsModalCount.sql b/application/items/sql_old/itemsModalCount.sql similarity index 100% rename from application/items/sql/itemsModalCount.sql rename to application/items/sql_old/itemsModalCount.sql diff --git a/application/items/sql/paginateLocationsBySkuZone.sql b/application/items/sql_old/paginateLocationsBySkuZone.sql similarity index 100% rename from application/items/sql/paginateLocationsBySkuZone.sql rename to application/items/sql_old/paginateLocationsBySkuZone.sql diff --git a/application/items/sql/paginateLocationsBySkuZoneCount.sql b/application/items/sql_old/paginateLocationsBySkuZoneCount.sql similarity index 100% rename from application/items/sql/paginateLocationsBySkuZoneCount.sql rename to application/items/sql_old/paginateLocationsBySkuZoneCount.sql diff --git a/application/items/sql/paginateZonesBySku.sql b/application/items/sql_old/paginateZonesBySku.sql similarity index 100% rename from application/items/sql/paginateZonesBySku.sql rename to application/items/sql_old/paginateZonesBySku.sql diff --git a/application/items/sql/paginateZonesBySkuCount.sql b/application/items/sql_old/paginateZonesBySkuCount.sql similarity index 100% rename from application/items/sql/paginateZonesBySkuCount.sql rename to application/items/sql_old/paginateZonesBySkuCount.sql diff --git a/application/items/sql/selectItemByBarcode.sql b/application/items/sql_old/selectItemByBarcode.sql similarity index 100% rename from application/items/sql/selectItemByBarcode.sql rename to application/items/sql_old/selectItemByBarcode.sql diff --git a/application/items/static/ItemListHandler.js b/application/items/static/ItemListHandler.js index 5411c29..9c7949c 100644 --- a/application/items/static/ItemListHandler.js +++ b/application/items/static/ItemListHandler.js @@ -174,6 +174,8 @@ async function updateTableElements(){ let table_body = document.createElement('tbody') + console.log(items) + for (let i = 0; i < items.length; i++){ let table_row = document.createElement('tr') @@ -181,11 +183,11 @@ async function updateTableElements(){ nameCell.innerHTML = items[i].item_name nameCell.setAttribute('class', 'uk-width-1-4') let descriptionCell = document.createElement('td') - descriptionCell.innerHTML = items[i].description + descriptionCell.innerHTML = items[i].item_description descriptionCell.setAttribute('class', 'uk-text-truncate uk-table-expand uk-visible@m') let qtyUOMCell = document.createElement('td') - qtyUOMCell.innerHTML = `${parseFloat(items[i].total_qoh)} ${items[i].fullname}` + qtyUOMCell.innerHTML = `${parseFloat(items[i].quantity_on_hand)} ${items[i].fullname}` let opsCell = document.createElement('td') opsCell.setAttribute('class', 'uk-width-1-4') @@ -196,12 +198,12 @@ async function updateTableElements(){ let viewOp = document.createElement('a') viewOp.innerHTML = `edit ` viewOp.setAttribute('class', 'uk-button uk-button-default uk-button-small') - viewOp.href = `/items/${items[i].id}` + viewOp.href = `/items/${items[i].item_uuid}` let historyOp = document.createElement('a') historyOp.innerHTML = `history ` historyOp.setAttribute('class', 'uk-button uk-button-default uk-button-small') - historyOp.href = `/items/transactions/${items[i].id}` + historyOp.href = `/items/transactions/${items[i].item_uuid}` buttonGroup.append(viewOp, historyOp) opsCell.append(buttonGroup) @@ -237,16 +239,16 @@ async function updateListElements(){ header.classList.add('uk-card-header') header.style = "border-radius: 0px, 10px, 0px, 10px;" - header.innerHTML = `

${items[i].item_name}

Quantity on Hand: ${parseFloat(items[i].total_qoh)} ${items[i].fullname}
` + header.innerHTML = `

${items[i].item_name}

Quantity on Hand: ${parseFloat(items[i].quantity_on_hand)} ${items[i].fullname}
` let content = document.createElement('div') content.classList.add('uk-card-body') - content.innerHTML = `

${items[i].description}

` + content.innerHTML = `

${items[i].item_description}

` let footer = document.createElement('div') footer.classList.add('uk-card-footer') - footer.innerHTML = `edit - History` + footer.innerHTML = `edit + History` listItem.append(header) if(!items[i].description == ""){ @@ -263,7 +265,7 @@ async function updateListElements(){ items_list.append(main_list) } -let sort = "id" +let sort = "item_uuid" async function setSort(sort_string) { sort = sort_string await getItems() @@ -278,7 +280,7 @@ async function setOrder(order_string) { } async function getItems(){ - const url = new URL('/items/getItemsWithQOH', window.location.origin); + const url = new URL('/items/api/getItemsWithQOH', window.location.origin); url.searchParams.append('page', current_page); url.searchParams.append('limit', limit); url.searchParams.append('search_text', searchText); diff --git a/application/items/static/transactionHandler.js b/application/items/static/transactionHandler.js index 8210cfc..5100dc8 100644 --- a/application/items/static/transactionHandler.js +++ b/application/items/static/transactionHandler.js @@ -37,68 +37,62 @@ async function replenishItemsTable(items) { for(let i = 0; i < items.length; i++){ let tableRow = document.createElement('tr') - - let idCell = document.createElement('td') - idCell.innerHTML = items[i].id - let barcodeCell = document.createElement('td') - barcodeCell.innerHTML = items[i].barcode let nameCell = document.createElement('td') nameCell.innerHTML = items[i].item_name - tableRow.append(idCell) - tableRow.append(barcodeCell) - tableRow.append(nameCell) + let opCell = document.createElement('td') - tableRow.onclick = function(){ - selectItem(items[i].id) + let selectButton = document.createElement('button') + selectButton.setAttribute('class', 'uk-button uk-button-small uk-button-primary') + selectButton.innerHTML = "Select" + selectButton.onclick = async function(){ + console.log('clicked') + await selectItem(items[i].item_uuid) } + opCell.append(selectButton) + tableRow.append(nameCell, opCell) itemsTableBody.append(tableRow) } } async function populateForm() { if (item){ - console.log(item) - document.getElementById('database_id').value = item.id - document.getElementById('barcode').value = item.barcode + document.getElementById('database_uuid').value = item.item_uuid document.getElementById('name').value = item.item_name - document.getElementById('transaction_cost').value = parseFloat(item.item_info.cost) + document.getElementById('transaction_cost').value = parseFloat(item.item_cost) await selectLocation( - item.logistics_info.primary_zone.id, - item.logistics_info.primary_location.id, - item.logistics_info.primary_zone.name, - item.logistics_info.primary_location.name + item.primary_zone.zone_uuid, + item.primary_location.location_uuid, + item.primary_zone.zone_name, + item.primary_location.location_name ) - let quantity_on_hand = 0 - let locations = await getItemLocations() - for(let i = 0; i < locations.length; i++){ - quantity_on_hand = quantity_on_hand + locations[i].quantity_on_hand - } - document.getElementById('QOH').value = quantity_on_hand - document.getElementById('UOM').value = item.item_info.uom.fullname + document.getElementById('QOH').value = item.item_quantity_on_hand + document.getElementById('UOM').value = item.unit_fullname - await replenishItemLocationsTable(locations) + await replenishItemLocationsTable(item.item_locations) } } -async function selectItem(id) { +async function selectItem(item_uuid) { + console.log(item_uuid) UIkit.modal(document.getElementById("itemsModal")).hide(); - item = await getItem(id) + item = await getItem(item_uuid) + console.log(item) await populateForm() } -var transaction_zone_id = 0 -var transaction_item_location_id = 0 -async function selectLocation(zone_id, location_id, zone_name, location_name) { +var transaction_zone_uuid = "" +var transaction_location_uuid = "" +async function selectLocation(zone_uuid, location_uuid, zone_name, location_name) { document.getElementById('zone').value = zone_name document.getElementById('location').value = location_name - transaction_zone_id = zone_id - transaction_item_location_id = location_id + transaction_zone_uuid = zone_uuid + transaction_item_location_uuid = location_uuid } async function openItemsModal(elementID){ @@ -139,7 +133,7 @@ async function replenishItemLocationsTable(locations) { for(let i = 0; i < locations.length; i++){ let tableRow = document.createElement('tr') - let loca = locations[i].uuid.split('@') + let loca = locations[i].location_shortname.split('@') let zoneCell = document.createElement('td') zoneCell.innerHTML = loca[0] @@ -148,13 +142,13 @@ async function replenishItemLocationsTable(locations) { locationCell.innerHTML = loca[1] let qohCell = document.createElement('td') - qohCell.innerHTML = parseFloat(locations[i].quantity_on_hand) + qohCell.innerHTML = parseFloat(locations[i].item_quantity_on_hand) tableRow.append(zoneCell, locationCell, qohCell) tableRow.onclick = async function(){ await selectLocation( - locations[i].zone_id, - locations[i].id, + locations[i].zone_uuid, + locations[i].location_uuid, loca[0], loca[1] ) @@ -182,7 +176,7 @@ async function getItemLocations() { let items_limit = 50; async function getItems() { console.log("getting items") - const url = new URL('/items/getModalItems', window.location.origin); + const url = new URL('/items/api/getModalItems', window.location.origin); url.searchParams.append('page', pagination_current); url.searchParams.append('limit', items_limit); url.searchParams.append('search_string', search_string) @@ -193,10 +187,10 @@ async function getItems() { return items; } -async function getItem(id) { - console.log(`selected item: ${id}`) - const url = new URL('/items/getItem', window.location.origin); - url.searchParams.append('id', id); +async function getItem(item_uuid) { + console.log(`selected item: ${item_uuid}`) + const url = new URL('/items/api/getTransactionItem', window.location.origin); + url.searchParams.append('item_uuid', item_uuid); const response = await fetch(url); data = await response.json(); item = data.item; @@ -273,9 +267,7 @@ async function submitTransaction() { 'Content-Type': 'application/json', }, body: JSON.stringify({ - item_id: item.id, - logistics_info_id: item.logistics_info_id, - barcode: item.barcode, + item_uuid: item.item_uuid, item_name: item.item_name, transaction_type: document.getElementById('trans_type').value, quantity: parseFloat(document.getElementById('transaction_quantity').value), @@ -283,7 +275,7 @@ async function submitTransaction() { cost: cost, vendor: 0, expires: null, - location_id: transaction_item_location_id + location_id: transaction_location_uuid }), }); data = await response.json(); diff --git a/application/items/static/transactionsHandler.js b/application/items/static/transactionsHandler.js index 7047302..a2d76c8 100644 --- a/application/items/static/transactionsHandler.js +++ b/application/items/static/transactionsHandler.js @@ -4,21 +4,22 @@ var pagination_end = 10 var item; document.addEventListener('DOMContentLoaded', async function() { - item = await getItem(item_id); + //item = await getItem(item_uuid); let transactions = await getTransactions() + console.log(transactions) replenishTransactionsTable(transactions) updatePaginationElement() }) async function populateTransactionReceipt(transaction) { - document.getElementById('trans_barcode').innerHTML = transaction.barcode - document.getElementById('trans_database_id').innerHTML = transaction.id - document.getElementById('trans_timestamp').innerHTML = transaction.timestamp - document.getElementById('trans_name').innerHTML = transaction.name + document.getElementById('trans_barcode').innerHTML = '' + document.getElementById('trans_database_id').innerHTML = '' + document.getElementById('trans_timestamp').innerHTML = transaction.transaction_created_at + document.getElementById('trans_name').innerHTML = transaction.transaction_name document.getElementById('trans_type').innerHTML = transaction.transaction_type - document.getElementById('trans_qty').innerHTML = transaction.quantity - document.getElementById('trans_description').innerHTML = transaction.description - document.getElementById('trans_user').innerHTML = transaction.user_id + document.getElementById('trans_qty').innerHTML = transaction.transaction_quantity + document.getElementById('trans_description').innerHTML = transaction.transaction_description + document.getElementById('trans_user').innerHTML = transaction.transaction_created_by let receiptTableBody = document.getElementById('receiptTableBody') receiptTableBody.innerHTML = "" @@ -38,8 +39,7 @@ async function populateTransactionReceipt(transaction) { } } -async function inspectTransactions(id) { - let transaction = await getTransaction(id) +async function inspectTransactions(transaction) { await populateTransactionReceipt(transaction) UIkit.modal(document.getElementById("transactionModal")).show(); @@ -56,25 +56,25 @@ async function replenishTransactionsTable(transactions) { let timestampCell = document.createElement('td') - timestampCell.innerHTML = transactions[i].timestamp + timestampCell.innerHTML = transactions[i].transaction_created_at let barcodeCell = document.createElement('td') - barcodeCell.innerHTML = transactions[i].barcode + barcodeCell.innerHTML = '' let nameCell = document.createElement('td') - nameCell.innerHTML = transactions[i].name + nameCell.innerHTML = transactions[i].transaction_name let typeCell = document.createElement('td') typeCell.innerHTML = transactions[i].transaction_type let qtyCell = document.createElement('td') - qtyCell.innerHTML = transactions[i].quantity + qtyCell.innerHTML = transactions[i].transaction_quantity let descriptionCell = document.createElement('td') - descriptionCell.innerHTML = transactions[i].description + descriptionCell.innerHTML = transactions[i].transaction_description let userCell = document.createElement('td') - userCell.innerHTML = transactions[i].user_id + userCell.innerHTML = transactions[i].transaction_created_by tableRow.append( @@ -88,7 +88,7 @@ async function replenishTransactionsTable(transactions) { ) tableRow.onclick = async function() { - await inspectTransactions(transactions[i].id) + await inspectTransactions(transactions[i]) } transactionsTableBody.append(tableRow) @@ -96,7 +96,7 @@ async function replenishTransactionsTable(transactions) { } async function getItem(id) { - const url = new URL('/items/getItem', window.location.origin); + const url = new URL('/items/api/getItem', window.location.origin); url.searchParams.append('id', id); const response = await fetch(url); data = await response.json(); @@ -114,10 +114,10 @@ async function getTransaction(id) { } async function getTransactions(){ - const url = new URL('/items/getTransactions', window.location.origin); + const url = new URL('/items/api/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) + url.searchParams.append('item_uuid', item_uuid) const response = await fetch(url); data = await response.json(); pagination_end = data.end diff --git a/application/items/templates/index.html b/application/items/templates/index.html index 6ab2d81..d5dcb24 100644 --- a/application/items/templates/index.html +++ b/application/items/templates/index.html @@ -15,7 +15,7 @@ - {% if session['user']['flags']['darkmode'] %} + {% if session['user']['user_flags']['darkmode'] %} {% endif %} @@ -25,10 +25,10 @@ - {% if session['user']['flags']['darkmode'] %} + {% if session['user']['user_flags']['darkmode'] %} {% else %} @@ -86,7 +86,7 @@
  • - Profile Picture + Profile Picture {{username}}
    @@ -197,45 +197,9 @@
    - -
    -
    - -
    -

    Add Prefix...

    -
    -
    -

    Add a Prefix to the system by providing a uuid, a name for the item, and a general description.

    -
    - - -
    -
    - - -
    -
    - - -
    -
    - -
    -
    + - \ No newline at end of file diff --git a/application/items/templates/item_new.html b/application/items/templates/item_new.html index cb90d2d..d0a1beb 100644 --- a/application/items/templates/item_new.html +++ b/application/items/templates/item_new.html @@ -16,7 +16,7 @@ - {% if session['user']['flags']['darkmode'] %} + {% if session['user']['user_flags']['darkmode'] %} {% endif %} @@ -42,10 +42,10 @@ - {% if session['user']['flags']['darkmode'] %} + {% if session['user']['user_flags']['darkmode'] %} {% else %} @@ -104,7 +104,7 @@
    • - Profile Picture + Profile Picture {{username}}
      @@ -791,5 +791,5 @@ - + \ No newline at end of file diff --git a/application/items/templates/transaction.html b/application/items/templates/transaction.html index 6db72d4..7a8345a 100644 --- a/application/items/templates/transaction.html +++ b/application/items/templates/transaction.html @@ -14,7 +14,7 @@ - {% if session['user']['flags']['darkmode'] %} + {% if session['user']['user_flags']['darkmode'] %} {% endif %} @@ -29,10 +29,10 @@ - {% if session['user']['flags']['darkmode'] %} + {% if session['user']['user_flags']['darkmode'] %} {% else %} @@ -90,7 +90,7 @@
      • - Profile Picture + Profile Picture {{username}}
        @@ -129,7 +129,7 @@
        - +
        @@ -137,22 +137,18 @@
        -
        - - -
        - +
        - +
        - +
        @@ -187,16 +183,16 @@
        - +
        - +
        - +

        @@ -229,12 +225,11 @@
      - +
      - - + diff --git a/application/items/templates/transactions.html b/application/items/templates/transactions.html index 12c4e39..a7bde19 100644 --- a/application/items/templates/transactions.html +++ b/application/items/templates/transactions.html @@ -17,7 +17,7 @@ - {% if session['user']['flags']['darkmode'] %} + {% if session['user']['user_flags']['darkmode'] %} {% endif %} @@ -27,10 +27,10 @@ - {% if session['user']['flags']['darkmode'] %} + {% if session['user']['user_flags']['darkmode'] %} {% else %} @@ -89,7 +89,7 @@
      • - Profile Picture + Profile Picture {{username}}
        @@ -153,5 +153,5 @@
        - + \ No newline at end of file diff --git a/application/meal_planner/__pycache__/__init__.cpython-313.pyc b/application/meal_planner/__pycache__/__init__.cpython-313.pyc index 36f3107849d32c6a96133cb4bb9774b5046c96ea..b164ad5757c5ea1d0688cf41f5923e4fa7a8834c 100644 GIT binary patch delta 31 lcmZ3(xSEmsGcPX}0}ym7ubRkh%;+`IUX(c{wQOQU2mpM;2v7h3 delta 32 mcmZ3@xQ3DYGcPX}0}xaz&YZ|?%;-JQUX&#_F*9#scnAQ4H3*gf diff --git a/application/meal_planner/__pycache__/meal_planner_api.cpython-313.pyc b/application/meal_planner/__pycache__/meal_planner_api.cpython-313.pyc index e6ecc7a16203dd1fc915a423188cf6f32232d1c7..fd32074bd6bc56486e9d47db80f5204332f66150 100644 GIT binary patch delta 35 qcmdnyzSW)kGcPX}0}ym7uiD7Hg_SXA@&Q&+=9JX3%{N)^$^rnw8Vhs) delta 36 rcmdn$zRjKcGcPX}0}zz1T)B~Z3oB#riTg7zFBbz4bSkgf$X&-K>F;V4lboND8dH#%S5j1|TT+ymoE?*rTDEyH H+ph)yycQCl delta 54 zcmbQ!&osB6iTg7zFBbz4DAY{f$X&-K72s+WlboND8dH#%S5j1|TT+ymoE?*!n3=bE I5!ChpI?yj%=G@NCKajob;$Qhu&hG0FKUsWAnKc_l@ax+O)4$=NZviJ5tu JTbL)!008!G6N>-< diff --git a/application/recipes/__pycache__/recipe_processes.cpython-313.pyc b/application/recipes/__pycache__/recipe_processes.cpython-313.pyc index 9a36f640c080fc0002d0e25147764abdb2f4d327..2fa7a084a64658f56f8a66b9382612c4431d1a60 100644 GIT binary patch delta 51 zcmccaf6br!GcPX}0}ym7uiD7{kweng)hZ@AKP5G$ATh6`s8YA2C^0!ZCMC6Ovl!<= FIRL@S5#0a) delta 52 zcmccSf8C$^GcPX}0}wpivT`H$M-C}JSF4!h{FKy~g2cR%qDtM8qQvCvnB2t7yv?GV G2ju|wV-qX@ diff --git a/application/recipes/__pycache__/recipes_api.cpython-313.pyc b/application/recipes/__pycache__/recipes_api.cpython-313.pyc index cf7a6fc1c8257cdba15e3e53a2992fe350532fb2..b22def11d045c0263edf79b8d33b0ba0aa2da629 100644 GIT binary patch delta 53 zcmaFZ!T6wqk^3_*FBbz4bSkgf$i1Ik(!0P(34PXGV_ diff --git a/application/shoppinglists/__pycache__/__init__.cpython-313.pyc b/application/shoppinglists/__pycache__/__init__.cpython-313.pyc index c460c8982af8f93c03fce667bed79d89c7599ca4..a36956def76a82840ef478a6361a3838debacfef 100644 GIT binary patch delta 31 lcmZ3>xQ3DYGcPX}0}ym7ubRkh%;-JQUX(c{wQOQUC;)v62vh(7 delta 32 mcmZ3(xR#OoGcPX}0}!bG=%2`K%;+=GUX&#_F*9#scqjmik_iz2 diff --git a/application/shoppinglists/__pycache__/shoplist_api.cpython-313.pyc b/application/shoppinglists/__pycache__/shoplist_api.cpython-313.pyc index 851b514f8f47e0d9b165fad645a6227076335ed7..8618f4838a94f25e0123a8d22f0237fc29c8ec3a 100644 GIT binary patch delta 53 zcmX>-gYnc1M()qNyj%=G(5bv?BX>84q`#|GOmcooYD__5UP)1#gYonXM()qNyj%=Gz&UUEM(%D7sQ_22nB@GF)R=p5mS0|4YF66^o~ diff --git a/application/shoppinglists/__pycache__/shoplist_database.cpython-313.pyc b/application/shoppinglists/__pycache__/shoplist_database.cpython-313.pyc index 16eb51c4632763589b89d23b43004ec5e39e7bb4..adbd39ba9d99123098fbfd5ee8977859c8253f29 100644 GIT binary patch delta 37 rcmcb8nDN$OM()qNyj%=G(5bv?BX=+>W60!qR#E1Z)UwSLtjRF|uM()qNyj%=Gux;M*joiVkjG>d`Sw&fL6EpKRm$N3v0090m3}^rV diff --git a/application/shoppinglists/__pycache__/shoplist_processess.cpython-313.pyc b/application/shoppinglists/__pycache__/shoplist_processess.cpython-313.pyc index 31bd45a544938a271d051d99e9504f84580a62db..32f5693bb7ba3f728367fb8d3e855cfd90e15649 100644 GIT binary patch delta 51 zcmdmGxzm#SGcPX}0}ym7uiD5R!y*~xY88{5pOP9=keF9eRH<81l$e|ylagAtxskzgGcPX}0}ym7ubRkh%;-DOUX(c{wQOQUH~@W{2wVUF delta 32 mcmZ3-xSo;wGcPX}0}!bG=%2`K%;-1KUX&#_F*9#scsKxzC
      IDBarcode NameOperations