From cbfe74c868d9ccb555bb65b88e96492306f86453 Mon Sep 17 00:00:00 2001 From: Jadowyne Ulve Date: Tue, 30 Sep 2025 15:07:59 -0500 Subject: [PATCH] more database migration --- .gitignore | 1 + .../access_processes.cpython-313.pyc | Bin 0 -> 180 bytes application/database_postgres/BaseModel.py | 35 ++++++ .../database_postgres/CostLayersModel.py | 92 ++++++++++++-- .../database_postgres/ItemLocationsModel.py | 36 +++++- application/database_postgres/ItemsModel.py | 29 +++++ .../database_postgres/TransactionsModel.py | 2 +- .../__pycache__/BaseModel.cpython-313.pyc | Bin 16800 -> 18434 bytes .../CostLayersModel.cpython-313.pyc | Bin 1136 -> 4930 bytes .../ItemLocationsModel.cpython-313.pyc | Bin 1110 -> 2552 bytes .../__pycache__/ItemsModel.cpython-313.pyc | Bin 6074 -> 7442 bytes .../TransactionsModel.cpython-313.pyc | Bin 2128 -> 2141 bytes .../sql/CREATE/cost_layers.sql | 1 + .../sql/CREATE/item_info.sql | 2 +- .../sql/CREATE/transactions.sql | 4 +- .../sql/INSERT/cost_layers.sql | 4 +- .../sql/ItemsModel/getItemAllByUUID.sql | 44 +++++++ .../__pycache__/items_API.cpython-313.pyc | Bin 36469 -> 36773 bytes .../__pycache__/services.cpython-313.pyc | Bin 3162 -> 7190 bytes application/items/items_API.py | 30 ++--- application/items/services.py | 95 ++++++++++++++- application/items/static/itemEditHandler.js | 4 +- .../items/static/transactionHandler.js | 25 ++-- .../items/static/transactionsHandler.js | 4 +- application/items/templates/transaction.html | 2 +- .../site_management_processes.cpython-313.pyc | Bin 0 -> 191 bytes logs/database.log | 114 ++++++++++++++++++ logs/process.log | 23 ++++ 28 files changed, 498 insertions(+), 49 deletions(-) create mode 100644 application/access_module/__pycache__/access_processes.cpython-313.pyc create mode 100644 application/database_postgres/sql/ItemsModel/getItemAllByUUID.sql create mode 100644 application/site_management/__pycache__/site_management_processes.cpython-313.pyc diff --git a/.gitignore b/.gitignore index 2714bdf..6456288 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ test.py .VScodeCounter celerybeat-schedule instance/application.cfg.py +docs diff --git a/application/access_module/__pycache__/access_processes.cpython-313.pyc b/application/access_module/__pycache__/access_processes.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8dfe24818c1887e1d3df633452ddf3f839247d22 GIT binary patch literal 180 zcmey&%ge<81f9yOGC=fW5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa|Lwt5r;LeoAUg zL1JD>QKfE4QDSm-OiF4Qsz72vK~83JVo7FxUQA+ga%ypLd~SY9X-+DPS5TA>5=|}E fE2zB1VFQu0D`ExO0dhkzi1Cq`k&&^88OQ literal 0 HcmV?d00001 diff --git a/application/database_postgres/BaseModel.py b/application/database_postgres/BaseModel.py index 7c446c3..4e345e4 100644 --- a/application/database_postgres/BaseModel.py +++ b/application/database_postgres/BaseModel.py @@ -234,6 +234,7 @@ class BaseModel(ABC): @classmethod def select_tuple(self, site: str, payload: dict, convert: bool = True, conn=None): + ''' payload = {'key': value_to_filter}''' record = () self_conn = False @@ -264,7 +265,41 @@ class BaseModel(ABC): except Exception as error: raise DatabaseError(error, payload, sql) + + @classmethod + def select_tuples_by_key(self, site: str, payload: dict, convert: bool = True, conn=None): + '''payload = {'key'}''' + records = () + 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.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) + @classmethod def select_tuples(self, site: str, convert: bool = True, conn=None): records = () diff --git a/application/database_postgres/CostLayersModel.py b/application/database_postgres/CostLayersModel.py index 0fcfb46..ec70814 100644 --- a/application/database_postgres/CostLayersModel.py +++ b/application/database_postgres/CostLayersModel.py @@ -1,17 +1,93 @@ from dataclasses import dataclass import datetime -from application.database_postgres.BaseModel import BasePayload, BaseModel +import config +import psycopg2 +from application.database_postgres.BaseModel import BasePayload, BaseModel, tupleDictionaryFactory, DatabaseError, updateStringFactory class CostLayersModel(BaseModel): table_name = "cost_layers" + primary_key = "item_location_uuid" + primary_key_type = "uuid" @dataclass class Payload(BasePayload): - aquisition_date: datetime.datetime - quantity: float - cost: float - currency_type: str - vendor: int = 0 - expires: datetime.datetime = None - \ No newline at end of file + item_location_uuid: str + layer_aquisition_date: datetime.datetime + layer_quantity: float + layer_cost: float + layer_currency_type: str + layer_vendor: str = None + layer_expires: datetime.datetime = None + + @classmethod + def delete_by_layer_id(self, site: str, payload: tuple, convert: bool = True, conn=None): + """ Pass a tuple of layer_ids to remove from the database. + + Args: + site (str): name of the site to delete from + payload (tuple): a tuple of layer_ids + convert (bool, optional): whether to return the deleted rows as dictionaries. Defaults to True. + conn (_type_, optional): postgresql connector object. Defaults to None. + + Raises: + DatabaseError: raised for all errors with database handling, logs to database.log + + Returns: + dict, list: returns a list of all deleted rows. + """ + deleted = () + self_conn = False + sql = f"WITH deleted_rows AS (DELETE FROM {site}_{self.table_name} WHERE layer_id 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_by_layer_id(self, site: str, payload:dict, convert=True, conn=None): + updated = () + self_conn = False + set_clause, values = updateStringFactory(payload['update']) + values.append(payload['key']) + sql = f"UPDATE {site}_{self.table_name} SET {set_clause} WHERE layer_id=%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) \ No newline at end of file diff --git a/application/database_postgres/ItemLocationsModel.py b/application/database_postgres/ItemLocationsModel.py index 3d023df..c883df9 100644 --- a/application/database_postgres/ItemLocationsModel.py +++ b/application/database_postgres/ItemLocationsModel.py @@ -1,5 +1,9 @@ from dataclasses import dataclass, field -from application.database_postgres.BaseModel import BasePayload, BaseModel, lst2pgarr +from application.database_postgres.BaseModel import BasePayload, BaseModel, lst2pgarr, tupleDictionaryFactory, DatabaseError + +import config + +import psycopg2 class ItemLocationsModel(BaseModel): table_name = "item_locations" @@ -11,4 +15,32 @@ class ItemLocationsModel(BaseModel): item_uuid: str location_uuid: str item_quantity_on_hand: float = 0.0 - \ No newline at end of file + + @classmethod + def select_by_location_and_item(self, site:str, payload:dict, convert: bool=True, conn = None): + recordset = () + self_conn = False + sql = f"SELECT * FROM {site}_item_locations WHERE item_uuid = %(item_uuid)s AND location_uuid = %(location_uuid)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: + recordset = tupleDictionaryFactory(cur.description, rows) + if rows and not convert: + recordset = rows + + + if self_conn: + conn.close() + + return recordset + + except Exception as error: + raise DatabaseError(error, payload, sql) \ No newline at end of file diff --git a/application/database_postgres/ItemsModel.py b/application/database_postgres/ItemsModel.py index 337ecbe..c0b29b9 100644 --- a/application/database_postgres/ItemsModel.py +++ b/application/database_postgres/ItemsModel.py @@ -33,6 +33,35 @@ class ItemsModel(BaseModel): payload['item_links'] = json.dumps(self.item_links) return payload + @classmethod + def get_item_by_uuid(self, site:str, payload: dict, convert: bool=True, conn = None): + record = () + self_conn = False + with open('application/database_postgres/sql/ItemsModel/getItemAllByUUID.sql', 'r+') as file: + sql = file.read().replace("%%site_name%%", site) + try: + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = True + self_conn = True + + with conn.cursor() as cur: + cur.execute(sql, payload) + rows = cur.fetchone() + if rows and convert: + record = tupleDictionaryFactory(cur.description, rows) + if rows and not convert: + record = rows + + if self_conn: + conn.close() + + return record + + except Exception as error: + raise DatabaseError(error, payload, sql) + @classmethod def paginate_items_with_qoh(self, site:str, payload: dict, convert: bool=True, conn = None): recordset = () diff --git a/application/database_postgres/TransactionsModel.py b/application/database_postgres/TransactionsModel.py index aa5bd9e..bd0c9bc 100644 --- a/application/database_postgres/TransactionsModel.py +++ b/application/database_postgres/TransactionsModel.py @@ -6,7 +6,7 @@ from application.database_postgres.BaseModel import BasePayload, BaseModel class TransactionsModel(BaseModel): table_name = "transactions" - primary_key = "item_uuid" + primary_key = "transaction_uuid" primary_key_type = "uuid" @dataclass diff --git a/application/database_postgres/__pycache__/BaseModel.cpython-313.pyc b/application/database_postgres/__pycache__/BaseModel.cpython-313.pyc index 611c1b8b21a051ed5905393ce7c40631ff45d394..cd77b680a87281e549c56803515b944a56d4cf00 100644 GIT binary patch delta 795 zcmZ3`%-A%6k?%7vFBbz4+{-$Ukz}}$Zo$h3T25orK#~H`SEF)IVGt@wUhbGr6qpx09B}J099xf zTUp&=v@V(g^k0bIWC!zW+-8gn3=Iq)*cgOFHp^S2GfpCFhh_oa&pOlA5E3O`aF%;o@MJuZ2ZB*e5GmlrtJkUMuH3IZi`p@_E}jH)o*n z2lXAfT{RdES(&(sGauGda1~-Z!Y2%5A5k;_iXRnX1+tHdbGfRrA5~-riD+=Su`nMs z;&ruWK5EbE#z`?IqXI2K>q8P9A_jk`H77?qyA$uApC&=NUbOVQ#(?=fOwyo8Q26rFacQ~oEU-3k3gou v3b0Cp4Pcc92f!)~;3^G1I89D;IK;SnvWjE2pcf0H!35^73?RBl9%wB9xm3vL delta 350 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 h{B)3{f$&F>Bm*A4s_7A|M8`J;* diff --git a/application/database_postgres/__pycache__/ItemLocationsModel.cpython-313.pyc b/application/database_postgres/__pycache__/ItemLocationsModel.cpython-313.pyc index ef0d38fbc0424c29a255ca3f224f5664d3e18b71..c7e1a5fe29bfa23b07a816a58da7c9ee7f97edb3 100644 GIT binary patch literal 2552 zcmaJDOKclObk@6T@2)@j$4#0vn>0=0mZVVP3JOIkNeW5YfUkTzWwYl#exvXr!Kc3v!`Sdw@4K76h6%-*@{^lo?{ci`;$sQzSFHT; zu8Xs03a;sZN-L<8sZ-?5u_I|6awVMyuW)hD#hQLDZI~B!GPHa?m#y4Mn&ux(7&UJ@ z?i@Cq^naX9zDPpV^T7?U5j`Vk7l8NBMpNqtu~NMKj?iA}-@hhwt`vS1_Tw%P;%)#_ zgmd7eNGM-T=#Xw(7fkHxew~353c{aWZut@8)f{8NRa!xXO))XJ#AjJ6vSXK`^pG3=#l+v_%9UHK!zh zAB*2+2YlYLV}K_q3C@&uFXZckU%}c=U>PE9+Oq3ozL%iQ_o8m}6!O;?a95dtjJl8v zzp4JCY!}Kf-DtMiUmHRiq>B}vjr*L_U!_8RPcfnYSm9&g43j~XwTa-Am(9m7#gwrV zV$K)Tr!hdzI^H?BFDv z7iJO>5`=`zWao&GcZ#~5pF2coidv@b5=kq#wr(#hWL;1zV8_NJWL`9Ns5~NMOjn<` zEfWh69SIwzqvLFzb~?iAIomObICfDtJr1Th1eNYkU z()%e%o41frfP)_N`U7sESD zGBo}~a~}F1eVQB24*;&9jpo+(#Zq${fciZv?n-8@_tIMI?f2QuK&zPiy(zxlGw}J~ zCxf3JT&i1-cl{Rc|0&-8WBka?g8-JeQcvF!cdf3}Auov=4K2&NukOCqd1G&>zG*pp zHM}zNbA19j%JSmX#gE=u;=tU;udK_5Z#iFI{_^r|?v8wNX&jiJg-UYo()hzDYV7)} z5!JVq>YJCPtJ1Z|MpMUHq~l>52(EWNe$WB3GGfH!dSllO{uX(+(tn=03tzCg`Q7aJqhP=b^+dK^_*EG_1g&5E#^&3n+>MD&})|@?0pO@nC`r7 z;N8>~XTUR0xHGn$!_QN_JrvNUU+Jq>N1o1snbdwDZZE&L)ECn^AANJ^X-DRI#B<#gz@|UXaS+ z+NX=@RHC5WmGp0WU509T>%T5Twa=MK%(U_25Czn6uS#$q#pruoiE3x0M0m0CpuU7h pKm}S5a%2E-n+$Uo4ctYGzo5l?D0&aoJ>dG72A!-~5!+ zoR=(<>lo)Ta!!_I>SGk1yoO1MQDpLYrd(Ejpb?yt9hnuCWPz+Ac@QB2B>XhFi$p;j zF%TgRA|xiqa#(7BRmgz&AZ0~hU2-56CyZbLGK#e)KjhF9`pm@4str*ClJ?W&nC!wC z;00ESs{Iy+O>TZlX-=wLkt$FnBghOXAn}2jk&*E}gT{S^(2ESAUsyO9wP#3vWdKrO FV*!ONKt%um diff --git a/application/database_postgres/__pycache__/ItemsModel.cpython-313.pyc b/application/database_postgres/__pycache__/ItemsModel.cpython-313.pyc index c3ed4ec56c65b3cbbb063c41e84dc8ae0d6b83a4..6c6d4a6d8d18d3c660726c774d576db91ed28366 100644 GIT binary patch delta 1417 zcmZ9K-%ndr9Kg@%ZExG#-u}2Pke1bzjq2()oNgsken>~DGDgu}u8_1_*R{7$a=FFR z65TR`zVQei7ZT&cd~sRg1B3??_d>S*0q8W6n?>jQVo2B^!NhpJCEW<$rk``ZzrUy7 z{`}V0@40qFQ9w+V?rlsf{_kBF|47|<0K4f&_#|TuhtFgM*)7|$4%wb{%0gDWz{_F~ z&6sC!3{sg8kL)7eDQncT{vF=sV?48Q?7MT@gwP!%W8ET~bUuS27)0h|bBy^wOh~sX z)-*@gyg^#_w7hFkaMP#TWCWk49!9Sr#eC`@5fv{Wl!0CiN%M5g?x)ASc5J7smhcG~ zpF@g0fxEG8mU+djy$qFko#|;ev^>N53yP5THtW1PCv1x8Rc99CV+rjG4#m-&ARj_V z;a+O3Y@U{8K8t7iQBfStin`@c9sA)rMfS@m4)&WJdAq_xCnuE5dsv7$9e@{@!$`5v zzocWf)USQqaa6+eZz*^sb@4#yK$&233MXyoyYH+MQeISMwEGi)tQWaTF_^^;p=;UEz=h$?! zxf?fg^Yi7R(GIVuj#i4Q)^*egDb-zuFVR!4Z;{{f2Nvy5MDM1gY))i;6SJGwi(69b zN_06&PyZoBevu;5-O6h1vDCXbxW^&aRlIF(p>@|}@=56Y>fldfYhxSZk3*N2l7ITz zez0zZI+l`qB9e~pdXOiu}A>p*v-9jQ{tp}{kQV$D>9H zb(^Qg(HD-SKn7SP5@(<(=i&mn1nM9l!C(lr{0;43kz@$0Nx%ny4*|n~%M6CCq}B@4 zc};8VlUxDE2w)VD0>BdQd&o(+!vn}!Kp$X?!LXCQ#%il-jr|2G^LmYZ49+w_ZbYyZ zvz3Eq9E=L!6Yy}P@)>>R3)BC^UL0TVaZyV+1Kup)D&QI*$1qN=!?i&F@nx~PKH|TP zBQs!mpCM`*Pemni4)nJH?*PsN^7L6t$}tJoX@EutT3^Ta=u~U0GiZt39o$CjSnX^a z8~*2@?Qn?qZkyO``yyRwy@iuhY8$~r^jg~qT%}*No$@YV99s&!KyZ>bSmFNxp)^6P delta 375 zcmW-aPfG$(6ve%oHagCzNP>)oR94a=2n=aaQdxl|K|;|+bbON&uX*`qq!!WIWq6@Q zi=d^n2ohSfYSEWyzD9o*u6om5{4Shx?z#Qdwu5V*F7B=wS>>pyC0{ z2s)6J4iz3h9nZ&x@9-NMs_IDq21pat=!tZ6y(#Hc)Z3A+-TI!I1-@)>p;DI{e;!CM?Qx%nxjIjMFRBKgUsY(4TI zGmA`t#7YJ~P3|IX5LX98=z<8n$qMY2q9ExaV-R5mBEVYYC+D%tGs;fxV^?BSn!J%+ miA@b8Co}mbyNwi*GN9ls4x8Nkl+v73yCR#(`W#v;AOiro!8zst diff --git a/application/database_postgres/sql/CREATE/cost_layers.sql b/application/database_postgres/sql/CREATE/cost_layers.sql index 566c74a..52e9725 100644 --- a/application/database_postgres/sql/CREATE/cost_layers.sql +++ b/application/database_postgres/sql/CREATE/cost_layers.sql @@ -1,4 +1,5 @@ CREATE TABLE IF NOT EXISTS %%site_name%%_cost_layers ( + layer_id SERIAL UNIQUE, item_location_uuid UUID REFERENCES %%site_name%%_item_locations(item_location_uuid) ON DELETE SET NULL, layer_aquisition_date TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL, layer_quantity FLOAT8 DEFAULT 0.00 NOT NULL, diff --git a/application/database_postgres/sql/CREATE/item_info.sql b/application/database_postgres/sql/CREATE/item_info.sql index 02f9fe2..e85c480 100644 --- a/application/database_postgres/sql/CREATE/item_info.sql +++ b/application/database_postgres/sql/CREATE/item_info.sql @@ -7,5 +7,5 @@ CREATE TABLE IF NOT EXISTS %%site_name%%_item_info ( item_safety_stock FLOAT8 DEFAULT 0.00 NOT NULL, item_lead_time_days FLOAT8 DEFAULT 0.00 NOT NULL, item_ai_pick BOOLEAN DEFAULT false NOT NULL, - item_prefixes INTEGER [] DEFAULT '{}' NOT NULL + item_prefixes UUID [] DEFAULT '{}' NOT NULL ); \ 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 e24facd..dad4da9 100644 --- a/application/database_postgres/sql/CREATE/transactions.sql +++ b/application/database_postgres/sql/CREATE/transactions.sql @@ -1,6 +1,6 @@ CREATE TABLE IF NOT EXISTS %%site_name%%_transactions ( - item_uuid UUID PRIMARY KEY REFERENCES %%site_name%%_items(item_uuid) ON DELETE CASCADE, - transaction_uuid UUID DEFAULT uuid_generate_v4() NOT NULL; + transaction_uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + item_uuid UUID REFERENCES %%site_name%%_items(item_uuid) ON DELETE CASCADE NOT NULL, transaction_created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL, transaction_name VARCHAR(255) DEFAULT NULL, transaction_type VARCHAR(64) DEFAULT '' NOT NULL, diff --git a/application/database_postgres/sql/INSERT/cost_layers.sql b/application/database_postgres/sql/INSERT/cost_layers.sql index 5515a0e..2651ced 100644 --- a/application/database_postgres/sql/INSERT/cost_layers.sql +++ b/application/database_postgres/sql/INSERT/cost_layers.sql @@ -1,4 +1,4 @@ INSERT INTO %%site_name%%_cost_layers -(aquisition_date, quantity, cost, currency_type, expires, vendor) -VALUES (%(aquisition_date)s, %(quantity)s, %(cost)s, %(currency_type)s, %(expires)s, %(vendor)s) +(item_location_uuid, layer_aquisition_date, layer_quantity, layer_cost, layer_currency_type, layer_expires, layer_vendor) +VALUES (%(item_location_uuid)s::uuid, %(layer_aquisition_date)s, %(layer_quantity)s, %(layer_cost)s, %(layer_currency_type)s, %(layer_expires)s, %(layer_vendor)s) RETURNING *; \ No newline at end of file diff --git a/application/database_postgres/sql/ItemsModel/getItemAllByUUID.sql b/application/database_postgres/sql/ItemsModel/getItemAllByUUID.sql new file mode 100644 index 0000000..c63242e --- /dev/null +++ b/application/database_postgres/sql/ItemsModel/getItemAllByUUID.sql @@ -0,0 +1,44 @@ +WITH passed_uuid AS (SELECT %(item_uuid)s::uuid AS passed_uuid), + cte_conversions AS ( + SELECT * + FROM %%site_name%%_conversions conversion + WHERE conversion.item_uuid = (SELECT passed_uuid FROM passed_uuid) + ), + cte_item_info AS (SELECT item_info.*, + COALESCE((SELECT json_agg(convs) FROM cte_conversions convs), '[]'::json) AS conversions, + COALESCE((SELECT json_agg(p.*) FROM %%site_name%%_sku_prefix p WHERE p.sku_prefix_uuid = ANY(item_info.item_prefixes)), '[]'::json) as prefixes + FROM %%site_name%%_item_info item_info + WHERE item_info.item_uuid = (SELECT passed_uuid FROM passed_uuid) + ), + cte_food_info AS (SELECT * + FROM %%site_name%%_food_info food_info + WHERE food_info.item_uuid = (SELECT passed_uuid FROM passed_uuid) + ), + cte_logistics_info AS (SELECT * + FROM %%site_name%%_logistics_info log_info + WHERE log_info.item_uuid = (SELECT passed_uuid FROM passed_uuid) + ), + cte_item_locations AS ( + SELECT * FROM %%site_name%%_item_locations item_locations + LEFT JOIN %%site_name%%_locations locations ON locations.location_uuid = item_locations.location_uuid + WHERE item_locations.item_uuid = (SELECT passed_uuid FROM passed_uuid) + ), + cte_barcodes AS ( + SELECT + barcode.barcode As barcode, + barcode.in_exchange AS in_exchange, + barcode.out_exchange AS out_exchange, + barcode.descriptor AS descriptor + FROM %%site_name%%_barcodes AS barcode + LEFT JOIN %%site_name%%_items AS item ON item.item_uuid = (SELECT passed_uuid FROM passed_uuid) + WHERE barcode.item_uuid = item.item_uuid + ) + +SELECT item.*, +(SELECT COALESCE(row_to_json(ii), '{}') FROM cte_item_info ii) AS item_info, +(SELECT COALESCE(row_to_json(fi), '{}') FROM cte_food_info fi) AS food_info, +(SELECT COALESCE(row_to_json(li), '{}') FROM cte_logistics_info li) AS logistics_info, +(SELECT COALESCE(array_agg(row_to_json(ils)), '{}') FROM cte_item_locations ils) AS item_locations, +(SELECT COALESCE(array_agg(row_to_json(bar)), '{}') FROM cte_barcodes bar) AS item_barcodes +FROM %%site_name%%_items item +WHERE item.item_uuid=(SELECT passed_uuid FROM passed_uuid) diff --git a/application/items/__pycache__/items_API.cpython-313.pyc b/application/items/__pycache__/items_API.cpython-313.pyc index a6f45a00ee90b3f35ec3bec0a56a4e80806d7f15..da55cdb8dd13d56908e327c7eb804572d8c82b51 100644 GIT binary patch delta 8338 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 delta 7960 zcmbVR3vg7`8Q#0EY_dr<3E4p2n>QQsfaR4yFiU_y5=ek05J)6hvp31gW_LYzLt?51 zDgsud%0b1-XneIk$H#W2%+yvLM;&KqCD0Lji=C={Odn#X+D=}ni*6GgV z=iGC?^S}T9oO^ll5$zXewV9t~WTa`}SNY1bW3xl|W-j8^+*|W*txe<7xXfMUofV|Q zrWxSMHLmo5N>_#}(`9MalPXtMfreBMRPF4cbEOM1vR87*M57tV-nkuT@^cC_E2Sy< zUt*W>3zG1;D)?F%Z%xAIsoGBpZWEnNQS@z3T+iRE!mmx{w!mnNCw>^d9T&)Q7?;trSx<9UX@;eV z-j;orxjz#o*{_$T<<#shf-$ipsYS8`Nga^s4CFEB=O-J+LgPMC4|8_CsP%e8!-U@( z5=a9=QMBDeQgFN!Nj8#Y^khzvli&#kdr}#vi3zWVIB*sBkX1kgJrEoGB(3#`Bh4^} zstA+x(X3ATur;UVOzU&xob~*k3%#DXZTy9CGG`Vp=!Jj$)u73k;so@?+3NL<@PP%( zxtXsuC@VzVe!)E)@&^2hO`?hLBRnBIvFn*qid~{1NCKgNJvGTZ5J+;6tVV+J37SII zYBUawU6*L44%TEV%`s|_b+EE5++%(0zv!JEvq4Z42Qf`3Qwf7XRZo^F5v=TfAx@=q zkYI_rMj)-gwkbTb@GGs^i4rigNE5xYps<`}L4?^Rm==wJkr9CpwX^I254O^`3Oc!^ zG`sMim8rJ@HzSl~71>PB7UmaL;a1BiAKImaL?C-$wl#dAa2vNv}G*6n791Jj&lbu<+UF- z)1lJUV9=S;8Zhp)(pqjG7}rYsx^u0Xew4s|EMSeBO0=AIdP~+v3}oKK+Gl&?>=RAklR;LW zg8=A~1S;}xSX8Gz?Vpst5uT%e>FQLb|1B9>ArD$?sK7a4|O<0zf3q#!P{%A{*{ z!USOn$Z`a;@Q-M#Xcl-k86F!9@lzp|xzI=hBqSOF!~?ycp7tzWVq{6XnI2l)n1zj$ zY(XMr6@7K_iqb?^B4eEpB2pFR(j0sF%ud|n0%A9_ZcKV{La7NKK8_+8L&7ZEjH7WR zyMYL1AkaLbKdDt(g6>hj*Y6JTEGPoG2~Z(~Z-h6ALz;P`Cc|=QQ#fpI;lLa7wYO-S zoUokW4Z`CbQ=%3}7?o(_caQV7^{x%%0DYpqtjP~6v4xZ_8bT96AOAn}lT8zh zAVM-oiepyPn(a|_z6FSf=qbmSV9$4#`?WJpm{7AvOku|=9IBFG7(g)0Fdgvof|$bl zJ&+^3(gCC{$cpQ0Y(YUv7tDd}kYV(eRXMv915?Ot2uldytvIJxiA*A4lgz}R?L)n8 zxY>1Jf*gkRr^6qu(qqv6u=>1Yq?m%L4}w8htzt^hO`zp?VyY$1&{SVw*o|koK(G@j zhQ_ApH?p)KNF8fSV`|kz?m&*r$lGyr1c_3$tU}RT!2~2?X(T_bVCvSWW^HIL8cvP@ z@JG1A9<9$s1!tBWpSY5edF-~sx1F(G3SU@jMSuLhr9ivS3DCr#$60T@0ZvZO|6w}a z*ILo3#M4ObL{SOxR18c^*iqnQmsZis2bl9O>k4>E)CzPYkqmSZIU3*n(o_TZ(zQ7D9VRCj3e|4*2&GjfZG$-Tj)z$a?>7#Q-;RBB6$kpsKhIdPQGG3)8aUG`P13}Vhb07hKJwEUXtqHS9n zdSg>esV21=N=&g_NU`k!LGa$-sPlD{f(>J&N6ZC&=+YT;^Y0bQX?E=p1vL5@& zgq)gAk8R>{mx`F%AD!tbU-}d>VeFD~4cpgNum1q=VWEEppn5vja{^S^zkP<|PS9WP z$ak>dD52Mr=aA*|NU#V9uD6pP;E;uT+Ci7~*=Wbk0^>HEJWahjtJW)<7(Y~oZ5IqM zVy!09Qr#X;j7~4Wss&n$@ED8rM?3KnbkG%wR%yUgioW;6eQ>96aCA3W^u@r zFc8)@eu@)vAjCART%HJc>ZrtHgS<-KfQ@amdf@wT=l|Kj->j&Sn4xS(o$BfP*VlqN zy8Wx7&Tidb=Xeuk)&Yq#Jqx}M@F6}%#M=PrMf3MD^IzQG1?GRbzm_Xv<})^|@nD>v z)h#1$~vmyiSF#gaQB)izQMQa6t{c1wkZAQtsZ9K8!fG!lM10HK}3 z8O#835lDg_=~4_Ls$2%ZWUMOCUPo>Pb;d{gITmm!hnOZlWcd#LV5}ZAF?*XAscEtw zKy=i*u7%|u^9FtmVHflSRTjW~k?_Kpi@nIWLd5GICBFjj5&9eN-Jpd3#z3SFkeO(X z-j<}q`{Dq4q{!z;u7mx)riHU8mDPb>vf%@qsH4yOiYlv+-GX312zB$5So6^yO+JK` zd(a`bV{A^1hd_;q@f}*b6L%}ES~&!GTtNS{Pv?7q6@8&Tk9(XNgH6dVs@=h=n0re~ z%tf1dOJU0Vwi%cT7-QBlBbNs9G0H=7HvE&I4sKiY^_$Ag2-CseXBRc}^-{Z`9bRzt z^l(*CWx1vwZ+PtI(i-63m}eb(Hq#x&3+UpC1=Z32GjZvgb$(61C9+S`tBI~u!YsW) zN~8j1c~J-`__n}w#xXfbiv&jw7ASmHQ(ieNG$`cOpr&xS@(zbY{f$DvFU1HxA3`cj z_(B3WUjz3e`iPL1hA02C7>&Icjb&G|ZI`nfFJ?Dh$tyk+n9FOpl3RGDV=lM)O1Aa% z=mWlceV4K;uBGU6(vBNHPt#;Aq(z~8uJ7?xAzju9U?rUv>;!3|?UVIZX^1O-M)DUV zpU~rztGQ14+GI}|OK?<}oC6|S+Te{FK5`1%rRxeDcT1;AT(bzkOcPT#O%3xwywH;X zJV%@&X51j{8Pv6?rUKausH$3+Vh< zzP*8b3J>HOkZ$r<9Dau6b0Be7hSRj`mc?uSj%yJOXK~D8m#mjra3AEOihT*dx$yJ1 z{8%eFlPSXz;3}P(vDJK21zw$5+!Ct*>k`2&*#KTAmIm&!aKrVxG@KA#9g4@n1K z2W}3+Yp7Q6l2p3%NCQ_z2an_#tg!Y4oj&r3u@Cm!x6<)DR$5Dt)lMWvSSBVU8T3bY zbitQ0-BA~Jj$S=lL8p%rdhO_vnOp#hroll!e4rQ{Bv#y4jdKPPmh@)X7HzC2#3FKQy4kI~+7Hk5f&Utq z@bTA?N?7GI&=Q1py6gBR;|H3`4>YaxvEu_dA?`DsMq7xaY+kS9LLAMy^M#HSZc15b zp4a2^e2zwIK300T^q!KZs-9YSsPqE2?0wFBnJbv%3a)8Q+=#NpF|Wtx`P{JSJ5T7j c0{Z;Pk~|YPpdeNwVt|{^qaU7pO^;gt2Z71|2><{9 diff --git a/application/items/__pycache__/services.cpython-313.pyc b/application/items/__pycache__/services.cpython-313.pyc index 6f550a35d445076e64f708451b8b9d9edc613daa..1186726b66da770625440e360253485f9c6c12f8 100644 GIT binary patch literal 7190 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 VBA&YGOpqv#^Qt_Teq~Z0{{t0mSf~I1 diff --git a/application/items/items_API.py b/application/items/items_API.py index e1ebbe5..5981073 100644 --- a/application/items/items_API.py +++ b/application/items/items_API.py @@ -18,8 +18,9 @@ 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.items import models, services from application.database_postgres.ItemsModel import ItemsModel +from application.database_postgres.TransactionsModel import TransactionsModel items_api = Blueprint('items_api', __name__, template_folder="templates", static_folder="static") @@ -44,7 +45,7 @@ def items(): 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) + return render_template("item_new.html", item_uuid=item_uuid, units=units, current_site=session['selected_site'], sites=sites) @items_api.route("/transaction") @access_api.login_required @@ -82,9 +83,10 @@ def getTransactions(): def getTransaction(): transaction = () if request.method == "GET": - id = int(request.args.get('id', 1)) - site_name = session['selected_site'] - transaction = database_items.getTransaction(site_name, (id, )) + transaction_uuid = int(request.args.get('transaction_uuid', None)) + if transaction_uuid: + site_name = session['selected_site'] + transaction = TransactionsModel.select_tuple(site_name, {'key': transaction_uuid}) return jsonify({"transaction": transaction, "error": False, "message": ""}) return jsonify({"transaction": transaction, "error": True, "message": f"method {request.method} is not allowed."}) @@ -92,11 +94,13 @@ def getTransaction(): @access_api.login_required def get_item(): if request.method == "GET": - id = int(request.args.get('id', 1)) - site_name = session['selected_site'] + item_uuid = request.args.get('item_uuid', None) item = () - - item = database_items.getItemAllByID(site_name, (id, )) + print(item_uuid) + if item_uuid: + site_name = session['selected_site'] + item = ItemsModel.get_item_by_uuid(site_name, {'item_uuid': item_uuid}) + print(item) return jsonify({'item': item, 'error': False, 'message': ''}) return jsonify({'item': item, 'error': True, 'message': f'method {request.method} not allowed.'}) @@ -424,15 +428,13 @@ def getItemLocations(): return jsonify({"locations":recordset, "end":math.ceil(count/limit), "error":False, "message":"item fetched succesfully!"}) return jsonify({"locations":recordset, "end": math.ceil(count/limit), "error":True, "message":"There was an error with this GET statement"}) + @items_api.route('/postTransaction', methods=["POST"]) @access_api.login_required def post_transaction(): if request.method == "POST": - result = items_processes.postAdjustment( - site_name=session['selected_site'], - user_id=session['user_id'], - data=dict(request.json) - ) + print(session.keys()) + result = services.postAdjustment(site_name=session['selected_site'], user_uuid=session['user_uuid'], data=request.get_json()) return jsonify(result) return jsonify({"error":True, "message":"There was an error with this POST statement"}) diff --git a/application/items/services.py b/application/items/services.py index 6e4fb09..087a663 100644 --- a/application/items/services.py +++ b/application/items/services.py @@ -1,4 +1,5 @@ import psycopg2 +import datetime from application.database_postgres.ItemsModel import ItemsModel from application.database_postgres.ItemInfoModel import ItemInfoModel @@ -6,6 +7,7 @@ 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 +from application.database_postgres.CostLayersModel import CostLayersModel import config def add_new_item(site: str, data: dict, user_uuid: str, conn=None): @@ -42,7 +44,7 @@ def add_new_item(site: str, data: dict, user_uuid: str, conn=None): ) items_location = ItemLocationsModel.insert_tuple(site, items_location.payload_dictionary(), conn=conn) - if item['item_category'] in ['FOOD', 'FOOD PLU']: + 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) @@ -58,4 +60,93 @@ def add_new_item(site: str, data: dict, user_uuid: str, conn=None): if self_conn: conn.commit() - conn.close() \ No newline at end of file + conn.close() + +def postAdjustment(site_name, user_uuid, data: dict, conn=None): + """ This process handles manual transactions found at /items/transaction endpoint + + Args: + site_name (_type_): _description_ + user_uuid (_type_): _description_ + data (dict): dataKEYS = {'item_uuid', 'item_name', 'transaction_type', 'transaction_quantity', 'transaction_description', 'transaction_cost', 'transaction_vendor', 'trans_action_expires', 'location_uuid'} + conn (_type_, optional): _description_. Defaults to None. + """ + def quantityFactory(quantity_on_hand:float, quantity:float, transaction_type:str): + if transaction_type == "Adjust In": + quantity_on_hand += quantity + return quantity_on_hand + if transaction_type == "Adjust Out": + quantity_on_hand -= quantity + return quantity_on_hand + raise Exception("The transaction type is wrong!") + + self_conn = False + + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = False + self_conn = True + + transaction_data = { + 'item_uuid': data['item_uuid'], + 'transaction_created_by': user_uuid, + 'transaction_name': data['item_name'], + 'transaction_type': data['transaction_type'], + 'transaction_quantity': data['transaction_quantity'], + 'transaction_cost': data['transaction_cost'], + 'transaction_description': data['transaction_description'] + } + + transaction = TransactionsModel.Payload(**transaction_data) + + location = ItemLocationsModel.select_by_location_and_item(site_name, {'item_uuid': data['item_uuid'], 'location_uuid': data['location_uuid']}) + + cost_layer_data = { + 'item_location_uuid': location['item_location_uuid'], + 'layer_aquisition_date': datetime.datetime.now(), + 'layer_quantity': data['transaction_quantity'], + 'layer_cost': data['transaction_cost'], + 'layer_currency_type': "USD" + } + + new_cost_layer = CostLayersModel.Payload(**cost_layer_data) + + cost_layers = list(CostLayersModel.select_tuples_by_key(site_name, {'key': location['item_location_uuid']}, conn=conn)) + print(cost_layers) + cost_layers.sort(key=lambda x: x['layer_aquisition_date']) + + if data['transaction_type'] == "Adjust In": + CostLayersModel.insert_tuple(site_name, new_cost_layer.payload_dictionary(), conn=conn) + + if data['transaction_type'] == "Adjust Out": + if float(location['item_quantity_on_hand']) < float(data['transaction_quantity']): + pass + else: + qty = float(data['transaction_quantity']) + for layer in cost_layers: + if qty >= float(layer['layer_quantity']): + qty -= float(layer['layer_quantity']) + layer['layer_quantity'] = 0.0 + else: + layer['layer_quantity'] -= qty + CostLayersModel.update_by_layer_id(site_name, {'key': layer['layer_id'], 'update': {'layer_quantity': layer['layer_quantity']}}, conn=conn) + qty = 0.0 + + if layer['layer_quantity'] == 0.0: + CostLayersModel.delete_by_layer_id(site_name, (layer['layer_id'],), conn=conn) + + quantity_on_hand = quantityFactory(float(location['item_quantity_on_hand']), data['transaction_quantity'], data['transaction_type']) + + ItemLocationsModel.update_tuple(site_name, {'key': location['item_location_uuid'], 'update': {'item_quantity_on_hand': quantity_on_hand}}, conn=conn) + + transaction.data = {'location': location['item_location_uuid']} + + TransactionsModel.insert_tuple(site_name, transaction.payload_dictionary(), conn=conn) + + if self_conn: + conn.commit() + conn.close() + return False + + return conn \ No newline at end of file diff --git a/application/items/static/itemEditHandler.js b/application/items/static/itemEditHandler.js index 457f224..270119e 100644 --- a/application/items/static/itemEditHandler.js +++ b/application/items/static/itemEditHandler.js @@ -1117,8 +1117,8 @@ async function fetchLocations(logis) { } async function fetchItem() { - const url = new URL('/items/getItem', window.location.origin); - url.searchParams.append('id', item_id); + const url = new URL('/items/api/getItem', window.location.origin); + url.searchParams.append('item_uuid', item_uuid); const response = await fetch(url); data = await response.json(); item = data.item; diff --git a/application/items/static/transactionHandler.js b/application/items/static/transactionHandler.js index 5100dc8..92386ce 100644 --- a/application/items/static/transactionHandler.js +++ b/application/items/static/transactionHandler.js @@ -87,7 +87,7 @@ async function selectItem(item_uuid) { } var transaction_zone_uuid = "" -var transaction_location_uuid = "" +var transaction_item_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 @@ -131,6 +131,7 @@ async function replenishItemLocationsTable(locations) { let itemLocationTableBody = document.getElementById('itemLocationTableBody') itemLocationTableBody.innerHTML = "" for(let i = 0; i < locations.length; i++){ + console.log(locations[i]) let tableRow = document.createElement('tr') let loca = locations[i].location_shortname.split('@') @@ -198,7 +199,7 @@ async function getItem(item_uuid) { } async function validateTransaction() { - let database_id = document.getElementById("database_id") + let database_uuid = document.getElementById("database_uuid") let transaction_type = document.getElementById("trans_type") let transaction_zone = document.getElementById("zone") let transaction_location = document.getElementById("location") @@ -207,11 +208,11 @@ async function validateTransaction() { let error_count = 0 - if(database_id.value === ""){ + if(database_uuid.value === ""){ error_count = error_count + 1 - database_id.classList.add("uk-form-danger") + database_uuid.classList.add("uk-form-danger") } else { - database_id.classList.remove("uk-form-danger") + database_uuid.classList.remove("uk-form-danger") } if(transaction_type.value === "0"){ error_count = error_count + 1 @@ -270,12 +271,12 @@ async function submitTransaction() { item_uuid: item.item_uuid, item_name: item.item_name, transaction_type: document.getElementById('trans_type').value, - quantity: parseFloat(document.getElementById('transaction_quantity').value), - description: document.getElementById('transaction_description').value, - cost: cost, - vendor: 0, - expires: null, - location_id: transaction_location_uuid + transaction_quantity: parseFloat(document.getElementById('transaction_quantity').value), + transaction_description: document.getElementById('transaction_description').value, + transaction_cost: cost, + transaction_vendor: null, + trans_action_expires: null, + location_uuid: transaction_item_location_uuid }), }); data = await response.json(); @@ -291,7 +292,7 @@ async function submitTransaction() { timeout: 5000 }); - item = await getItem(item.id) + item = await getItem(item.item_uuid) await populateForm() document.getElementById('transaction_quantity').value = '0.00' diff --git a/application/items/static/transactionsHandler.js b/application/items/static/transactionsHandler.js index a2d76c8..b210375 100644 --- a/application/items/static/transactionsHandler.js +++ b/application/items/static/transactionsHandler.js @@ -104,9 +104,9 @@ async function getItem(id) { return item; } -async function getTransaction(id) { +async function getTransaction(transaction_uuid) { const url = new URL('/items/getTransaction', window.location.origin); - url.searchParams.append('id', id); + url.searchParams.append('transaction_uuid', transaction_uuid); const response = await fetch(url); data = await response.json(); let transaction = data.transaction; diff --git a/application/items/templates/transaction.html b/application/items/templates/transaction.html index 7a8345a..324f984 100644 --- a/application/items/templates/transaction.html +++ b/application/items/templates/transaction.html @@ -128,7 +128,7 @@
- +
diff --git a/application/site_management/__pycache__/site_management_processes.cpython-313.pyc b/application/site_management/__pycache__/site_management_processes.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..decd595571536d79d233ede14f0c229fcddc50d9 GIT binary patch literal 191 zcmey&%ge<81f9yOGC=fW5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa|Lct5r;LeoAUg zL1JD>QKfE4QDSm-OiF4Qsz72vK~83JVo7FxUQBUjNoss