From 72f913e30823e496a2b456ab71914da106a20d5d Mon Sep 17 00:00:00 2001 From: MrWaradana Date: Tue, 18 Nov 2025 12:59:46 +0700 Subject: [PATCH] fix update bulk master data --- .../__pycache__/schema.cpython-311.pyc | Bin 3500 -> 3478 bytes .../__pycache__/service.cpython-311.pyc | Bin 10417 -> 12348 bytes src/masterdata/service.py | 83 +++++++++++++----- 3 files changed, 59 insertions(+), 24 deletions(-) diff --git a/src/masterdata/__pycache__/schema.cpython-311.pyc b/src/masterdata/__pycache__/schema.cpython-311.pyc index 4bb5e38b78c61ef1e0103970597e3589e28eb977..e8d971448379ffd78016945f2bdaeaa3d03f39ea 100644 GIT binary patch delta 751 zcmZvYze~eF6vuN-nlwq%YPH(h*56tJ)>f?Ap$H-fR?xvk+|)#@vEurM6AV zF`3*!g`9O>cushZJEyYYd(s=|D! z)Rz~MlD-i@N*xLsVGbsBuNA>f6eWftq4-gnQCd)HP-;883!Uq?VdPPlrUuFskEaHAN@rz@(U%~ zvxB1}M~53ZX>)YO(UeTDgCk-go7zE0;6Vabm2R7mpoHa&qq`4_(2B*%|Kp_aGzBAS p+UBHSU0v>HPsfI3PFXjr{O#h?lP#_oYXx)I8oCO3_lbZn`3-fgghl`W delta 769 zcmbOxy+)dMIWI340}vd{<;ir~$lJll#RlR6!RI5B=QFP4O5s?;xr~K@VKvL-04C|l zvzg>3cQOf0J`JRI0_opC`X!LoW|o^Q$t=XkH(7^SpPSzXXg~^MFoUMRINF2y6k^m9RAVLyENP!6H z$!#odjA@h4vb3@;V7#DaJvo&1fub@WtJ@&ZPk$xUn#QV74mltpo- za;37Ra81r(lAX-Y&c@9Rb~z8w<-C&>*fr}x4lfb{5yBt>*p{4{xrWI!BQ5FrO5 z*g*t8h>!;n3LruNLN z)npS6FE+3SwaG0U&MIh@qnW1xGEZajYYrKR9*xQOI3*{mar&E}=>x^dEtZtb##U__;Z z=VUtgr9wr>;a4&4V$dZX)q=S-*Dn6N>jGdoJrBPo<|1a%cG?fk_^of*y3N{;B zi4izr^ulSwABnwC>vaqKSD|jagff*#Po~o}+EQ|Xw&AJ02nDsY1JivNMOQma-%HFS zqA#KEhd6T06>!CQ#Rc<q{GP;bO(J|VKfHtMw zH3a}IiKO~lC=@=$;UF{uTy?{Ub)r>QlQ6nh1T%LopE1;2AO(y8e$?WUWsHmwR;{h@ zTczU~o42@h%N9K?+35_LWb}+FX0DA$TGIHeHyW~dha=F)P1iD} zWhY}grvnWegP%mdSN|4QExXX>UyZ9JfT7|lY;M;gx=9)HPJhHTjJe()!W0TmQD-cS zWlKXgu3pptuqa1*taW;{jI~}5V`0qOX4vXx=onl5j5NL>8!hLWCgS?bWZbYBXYd2c zWVV_7)_Ib-a7WoxoQaHMo0Baf+p*VfLJAd)o!h1v4h6;U~V%m;| z!6Ems2cIr|7SF`@%jctU7}Ift78_x#!3yuY)!97m_aKBc+eSnvUPO9dE9~DC2s#&? z$LP7KL@G6te91R|0j11@Z+edQO^gkmpa+nKscm}$G9-ZL`{68-UY8sK1oFbNqJTu( z8)RiNI4&`V35Iw>hdom3eM7mOI}Xy=qZ=))fM;Y*c*$yYq!l$G0OvM`FaRc7Y?5U; z$`>|xv&F0juSuT)H>7XLGEzobP}Xvoq(E+vhOCT{(K<%9!-Q>Il86(>K~Qpp#_kNN zXg~U)hcLp~O)PfuS6@!h38D1RUMy|I$cIo+DHbFN9j~Z!=U6r6qX0A(Z_P@u}Y<^jNHy>qg(2v87~eDV*Sp zA>J6ujh6Ms-0Owrk2~1r!Lr?XjbRP`9~Hpa_~9bw2=R{4nj=zjM80r+-gtkk6g|K> zCV0n0ZldhjwdUz6dAf=VoM(Xd46wF($Yp<0HaTyE-w$&pKX3A{nZhL#qNBVin%h@y zX=BZYi~DXr&326EyRYrd?|l$usl!4H4dI=L-ocKmRc93M74;OIKp_>!4+XUtUYMra z@i_B*awZ*{n~u#UCR6iTIx(9_rs;94Ob>^AcE~k6v00d(Z*W0xqbm9r)SU_-wBaRI zcM3sKo||9MuzJ79ZpZLl@2Pq%#NT0{@$9Ze^GPA%B;0&q9Vm(4Y*$jrAbW9V)PtF z_%%^cOkTKqVJwm22-AqcHkU?` zSyAC|6G^%714n#~ht6XiPa~AV)%%PPYX1cd4+|-zqB%Q%{*{<`M)V~#V-ZhPqTBcQ zG5p9kpCy5Lgg_bTo)RI>G|ZcZmkyPM^m5ORvI=J#;cX*JN2>~8cP<@$7-%oZwRvjpm%Fy>u;auyb(U@jjVb5O5VON7C+BEXf5qI z%6X6R-eY-9xw&Jlxu?|JbN2b-c<14k={93U^sgZZ||uBskOTt>RgfE%<5P4--SAhQ=bHG2N17c z-@PY)v@rhhLCz9>j2ut7R$gE=0R-g+U(s1~-W}qCqkM3b_16Q}Fq%8^kTT~FvcAaO zE{+=EsS%bMS#OIJ7TD-G*S4Q;+s`&_hls#X-m+A~mW}eZ zC~K&Pd%=4_cI*T>}APbM69bVkhe6ge(w(u2AtP~$B0)bw(C$yrr*~TK=q{;933aMDbX-wEMHPaKZxs}1ncIT` zCCy|hikA$Os(3j_@Ff7qO!eEh0^x)ax(x6$JhQ|jiqJA|dPUkM(YD2#)I3Qxpe9Wf z*@6Z%A+mY#YmHA~U6Ao^5Xq{~kB(0VM<%By7UWr3Lt~bl;N8fnziL?^vx>>!i(!{6 zs}78YM@Pm_hr+OSU;m5N3!=OWU_s9BLZ2F{$WHX5;Rn)-CY+Ud+J^ozzVB|tpC}0h z!=vFapOueJzQmic=ym{?mM_C}Glqu6FHFD7^lG4Af$>L9K@eA26jM#2>uLiMO7u#( zKqQpty-ge%H(y3dOO>1)UPN0h+cg3l>zYkaCp74e*{LioP!?`Rep_*aB#2uu3hh-k zpu9pv>3P;q43|f>Vav)2K?oW_JI@N*@+Yht;Okr1-}y!o*D3<4dGFcDQD4-q5e)0K z3wl8Z61!JKjO#>71><^=@anONIrbD8ps!E?Et;YoGnIw2VJsS_TWfZatKX2TS z&e*7Fooz(p8<_Im`5 zA7$1VYY~{W#*&0klF&hxRx4xVwT}g4D6c|i>vd?eRY{gDR#}@!)U>&ovVu034E~*z z&ENPMOhTIgW{I@PSLjJ|to02~{L~W$k5jduZv{mCx(}Uv{vY(gl-~&u!g*@9&+;7R zbOJ=0(6YmZ2KCJ0cVHT-19)605$Q5N^rSI$@T0@`!gr6Sj>nDfR;J*MpTcVXdpyo; zem4MA0r|O;XYxfOgt_(;=qHD}^KBqQwE&N2i6zETkS*$e9^PV^Un=u|J}NRj64R5W zdkT8HK!Vo;AY(O)UNHEcl#X8=73epkdd~+~u?H(I*qkF|dQBcORqna2*wHj?&&gMK ztE;ONT5=AqS4)0^E^Il;xD2axG^@NY7UYBI-z`ojj|&z*fZ+%RT&%LH(W$B6WS~&A zIP`K=x2^#%<_p&#Q0NU1=*)*CTY7SO{Ori|6qM2+KLv|8 zp+W-?HgvIix=%$g9T4kO8-Qr?NhaUyzMP7%l+Ql5R9lzao3eBd?DnhDEMf;+p3)Xtbu~7^&_hbHx9UBFqlV@a18yYl&uJeF=CU!=wYq7-r-J^C%x&meWGK( zy=n<#!?pd#65|jbW@jD*)3IeiFQR|&?Xt$GN#IyE8dr`Z5K@*$>f2}M(?s-&Fbd-k61#>#`@Csrc{ea zJKH6?Jx#Yipe)~0wr?q0JR(wViE^hYHxS_AQVw0$I2I#ywWMJT5`o7Bfwy7Uj_%gG zl{}E&Ek3NTAbYcNxOaIcFtYN~li{o)e-Y!GpKrl%3YyyrY>FIA=T_GPW?S!F< list[MasterData]: """Returns all documents.""" - query = Select(MasterData) + query = Select(MasterData).order_by(MasterData.description.asc()) if search: query = query.filter(MasterData.name.ilike(f"%{search}%")) @@ -138,45 +138,81 @@ async def bulk_update( result = await db_session.execute(query) records = result.scalars().all() - # Create a mapping of id to record for easier access - records_map = {record.id: record for record in records} + # Create a mapping of id (string) to record and name to record for easier access + # IDs coming from the router are strings, so normalize keys to strings to match lookups + records_map = {str(record.id): record for record in records} + records_by_name = {record.name: record for record in records} # Process updates in batches updated_records = [] for masterdata_id, masterdata_in in zip(ids, updates): masterdata = records_map.get(masterdata_id) + print("Processing update for ID:", masterdata) if not masterdata: continue data = masterdata_in.model_dump() update_data = masterdata_in.model_dump(exclude_defaults=True) - def get_value(obj, name): - return next((m.value_num for m in obj if m.name == name), 0) - - # Update direct values - for field in update_data: - setattr(masterdata, field, update_data[field]) + async def get_value(name): + # Prefer values from the current batch (records_by_name) + rd = records_by_name.get(name) + if rd is not None and rd.value_num is not None: + return rd.value_num + + # If not found in batch, try to fetch from DB + query_val = Select(MasterData).where(MasterData.name == name) + res_val = await db_session.execute(query_val) + row = res_val.scalars().one_or_none() + return row.value_num if row and row.value_num is not None else 0 + + # Update direct values (attributes present on MasterData) + # Recognised attribute fields on MasterData + attr_fields = { + "name", + "description", + "unit_of_measurement", + "value_num", + "value_str", + "created_by", + "updated_by", + } + + for field, val in update_data.items(): + if field in attr_fields: + setattr(masterdata, field, val) + else: + # Field is not a direct attribute: treat it as a named masterdata + # e.g. payload included {"discount_rate": 5.0} + query_other = Select(MasterData).where(MasterData.name == field) + res_other = await db_session.execute(query_other) + other = res_other.scalars().one_or_none() + if other: + # Update numeric or string value depending on payload + if isinstance(val, (int, float)): + other.value_num = val + else: + other.value_str = str(val) + # keep updated record available for batch calculations + records_by_name[other.name] = other # Handle interdependent calculations if "loan_portion" in update_data: - equity_portion = 100 - get_value(masterdata, "loan_portion") + equity_portion = 100 - await get_value("loan_portion") setattr(masterdata, "equity_portion", equity_portion) - total_project_cost = get_value(masterdata, "total_project_cost") - loan = total_project_cost * (get_value(masterdata, "loan_portion") / 100) + total_project_cost = await get_value("total_project_cost") + loan = total_project_cost * (await get_value("loan_portion") / 100) setattr(masterdata, "loan", loan) equity = total_project_cost * (equity_portion / 100) setattr(masterdata, "equity", equity) - if any( - field in update_data for field in ["loan", "interest_rate", "loan_tenor"] - ): + if any(field in update_data for field in ["loan", "interest_rate", "loan_tenor"]): pmt = calculate_pmt( - rate=get_value(masterdata, "interest_rate"), - nper=get_value(masterdata, "loan_tenor"), - pv=get_value(masterdata, "loan"), + rate=await get_value("interest_rate"), + nper=await get_value("loan_tenor"), + pv=await get_value("loan"), ) setattr(masterdata, "principal_interest_payment", pmt) @@ -191,19 +227,18 @@ async def bulk_update( ] ): wacc = ( - get_value(masterdata, "loan_portion") + await get_value("loan_portion") * ( - get_value(masterdata, "interest_rate") - * (1 - get_value(masterdata, "corporate_tax_rate")) + await get_value("interest_rate") + * (1 - await get_value("corporate_tax_rate")) ) ) + ( - get_value(masterdata, "wacc_on_equity") - * get_value(masterdata, "equity_portion") + await get_value("wacc_on_equity") * await get_value("equity_portion") ) setattr(masterdata, "wacc_on_project", wacc) - updated_records.append(masterdata) + print("Updated masterdata:", updated_records) # Commit all changes in a single transaction await db_session.commit()