From 923ac57db6bec1e8adf4a6ffb62ae2cc7eff11d7 Mon Sep 17 00:00:00 2001 From: "admin.suherdy" Date: Wed, 17 Dec 2025 14:32:07 +0700 Subject: [PATCH] first commit --- __init__.py | 2 + __manifest__.py | 35 +++ __pycache__/__init__.cpython-312.pyc | Bin 0 -> 225 bytes data/sign_item_type_data.xml | 12 + models/__init__.py | 4 + models/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 309 bytes models/__pycache__/sign_item.cpython-312.pyc | Bin 0 -> 2217 bytes .../sign_item_type.cpython-312.pyc | Bin 0 -> 672 bytes .../__pycache__/sign_request.cpython-312.pyc | Bin 0 -> 15392 bytes models/sign_item.py | 33 +++ models/sign_item_type.py | 7 + models/sign_request.py | 274 ++++++++++++++++++ static/src/js/sign_backend_patch.js | 105 +++++++ .../src/js/sign_item_custom_popover_patch.js | 61 ++++ .../xml/sign_item_custom_popover_patch.xml | 49 ++++ static/src/xml/sign_item_sequence.xml | 38 +++ views/sign_item_views.xml | 17 ++ 17 files changed, 637 insertions(+) create mode 100644 __init__.py create mode 100644 __manifest__.py create mode 100644 __pycache__/__init__.cpython-312.pyc create mode 100644 data/sign_item_type_data.xml create mode 100644 models/__init__.py create mode 100644 models/__pycache__/__init__.cpython-312.pyc create mode 100644 models/__pycache__/sign_item.cpython-312.pyc create mode 100644 models/__pycache__/sign_item_type.cpython-312.pyc create mode 100644 models/__pycache__/sign_request.cpython-312.pyc create mode 100644 models/sign_item.py create mode 100644 models/sign_item_type.py create mode 100644 models/sign_request.py create mode 100644 static/src/js/sign_backend_patch.js create mode 100644 static/src/js/sign_item_custom_popover_patch.js create mode 100644 static/src/xml/sign_item_custom_popover_patch.xml create mode 100644 static/src/xml/sign_item_sequence.xml create mode 100644 views/sign_item_views.xml diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..a0fdc10 --- /dev/null +++ b/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import models diff --git a/__manifest__.py b/__manifest__.py new file mode 100644 index 0000000..4cc69b5 --- /dev/null +++ b/__manifest__.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +{ + 'name': "Sign Sequence Field", + 'summary': "Add a Sequence field type to Sign templates to auto-generate numbers", + 'description': """ + This module extends the Odoo Sign app to add a new field type 'Sequence'. + When dropped onto a template, it allows linking to an ir.sequence. + Upon document completion, the sequence number is generated and stamped on the document. + User can edit the sequence format directly from the field properties. + """, + 'author': "Mapan", + 'category': 'Sign', + 'version': '0.1', + 'depends': ['sign'], + 'data': [ + 'data/sign_item_type_data.xml', + 'views/sign_item_views.xml', + ], + 'assets': { + 'web.assets_frontend': [ + 'sign_sequence_field/static/src/xml/sign_item_sequence.xml', + ], + 'web.assets_backend': [ + 'sign_sequence_field/static/src/xml/sign_item_sequence.xml', + 'sign_sequence_field/static/src/js/sign_item_custom_popover_patch.js', + 'sign_sequence_field/static/src/js/sign_backend_patch.js', + 'sign_sequence_field/static/src/xml/sign_item_custom_popover_patch.xml', + ], + 'sign.assets_public_sign': [ + 'sign_sequence_field/static/src/xml/sign_item_sequence.xml', + ] + }, + 'demo': [], + 'license': 'LGPL-3', +} diff --git a/__pycache__/__init__.cpython-312.pyc b/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..199e5ff8e94ef46f9735cb4e195a50b153005785 GIT binary patch literal 225 zcmX@j%ge<81pJ0hnYuvwF^B^LOi;#W0U%>KLkdF*V-7KB)0q!y)A>QyEt=O;PjCKe>-=_Tju zWv1u{RF-7q=OqKR>*uHB=Nnq+CzlqNlbIH=fxMN7M7;wC8x%xWv1q& v=*P!r=4F<|$LkeT{^GC!+FY8GYFESov<~E&Vi4m4Gb1D8GX{|&HXsK8J1IMv literal 0 HcmV?d00001 diff --git a/data/sign_item_type_data.xml b/data/sign_item_type_data.xml new file mode 100644 index 0000000..ea35b50 --- /dev/null +++ b/data/sign_item_type_data.xml @@ -0,0 +1,12 @@ + + + + Sequence + sequence + fa-sort-numeric-asc + Auto-generated sequence number + Sequence Number + 0.2 + 0.05 + + diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000..06a9a02 --- /dev/null +++ b/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +from . import sign_item_type +from . import sign_item +from . import sign_request diff --git a/models/__pycache__/__init__.cpython-312.pyc b/models/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b5f89a190443b2272bf679c455e5e5236def0565 GIT binary patch literal 309 zcmX|-u}TCn5QZnadvJ<(g^eISfZJ@i&O$_d0I}XCtjXvGHp!Y~i#&L1zsRq+eEbAo43px6M0wTxvhh>NzExg;*z zl0mBK#TTRTnr+uXKU$$98WGcolpOpgWR7Oz@oB;kZ|iCczNCk~%CaDNfgLIAk@wCZ z4^xj`r6zUK-Cvt->}(@lqZPkPv3FKFh1@CUHWyq@Av)73do{;xgiQ+-yptzyo pA#fqI)lrCMO#9^}h0}mnK3^pNA*@NhOUBqIoV~%xJDmC@MLz*oQ~3Y@ literal 0 HcmV?d00001 diff --git a/models/__pycache__/sign_item.cpython-312.pyc b/models/__pycache__/sign_item.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8139981bbd3bfb8c13667d840cf70d630921d767 GIT binary patch literal 2217 zcmZ`)-)|E~5Z*hV&*vYB9S0H!DLEP_Tq0~hZ3QAkl$176MQ8~X(uwMHc-P4#_MO?Y zX>6q^l_C|Ml01c~t(rcyRq_Y$Cm{93fmF6us)W>sinpM&FFbYj&NdDTYiYeZ^Ud7s zd^5X$baX@zjGJe#SlJ*#fAdXqz}_3XH{fp#VT6So$_u)X7j-co&;xl%mjo^sbHTi< z%K{S7rw9j@5SB<_wzFa1_O}u6H@Z>|YVr%d(Igfy3l@{J7Rh0k$$1BpoXZ456Il35 zm(Z(}IyBSf1+^mU6nJC==>kT&hy^_$A|lOtXKFV<5F-w3N*kgt)nyO2PlPzQDQ|#R zK6J%HCH-Cdj6+!247a>RJX}vZ!e8Ua9$f6LxF}X|Y_p@4pAJBF&K}*zV`nou9A82k z4S=4&i6x|WmAkYgQ>LtGd!k74m9Pt|lm*LIQH!Qra<@e63;{%A1u2%@BDL)4N>{O5 zAcloKxWPA|3{jFZiv(AaO8ToXnW^H1R@$Kw4In!6yHyw}TQ8tvdwMB6=Q;FONJNsYQ=% zMUSoPPowF_QtkZs)AQpyGV18s384!ULN%J+1vJ`2LkI*O4Gb1*2$I@P5{JWRmZ-q> z`%vOu-=7RD90u!I{*5;V7QsS!LkQdb+WrUh6Er^Hc`>~)Je`DtSpHFJHq|%6g0O&$ z);Y?da!6Au7u0c*D-fy{9W?{xUL>li+T?4sNtK#)KsjT%DrZQwvY;iHV3qQ_sFL86 zs8fxuQ%|dvNUJqd6=~|~7pGtFf`rRZvduhU60Z;@`<=!@1;3XjwGfLC+bvN7)Dqea zCM-bgdrbDDVo~^dfwwGF1nuK$5o%PV2RtN1>v9TIl^*1R9%wZ5snt`DE^Q^XTJp?R@=U#N=(qQ4!xy)P zFV==HZw+6r4t-QTbfw;ReEsIHw|=>`K2bfCs`sh&BmFPK!E>FvDA*l;Ft!sz>fnQq z|BUtT#8E<}{Iiq%f-?-BB*S14!+`E6<@h>g7U-HjQT9C+s4bCX^*!YkVfyftIB0N~63Rn9^)b%3lO}9H!rq z7rd8s8s<_y@n_6khG_}a<9&uenflp;m@Pr zk5)Iyb+4q+FE_Jr8y*PJMU#+uJMJvta*AQ3`hL29W&Dj<{ZpRhSWcUF@>j};&(ixE1O!3&2c3L|PX2{X{44bfgAeX4FFaiM55dy2{~rcXC;k8c literal 0 HcmV?d00001 diff --git a/models/__pycache__/sign_item_type.cpython-312.pyc b/models/__pycache__/sign_item_type.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2c56f07c6df7737b95e5651a761815eba61bb61d GIT binary patch literal 672 zcmZ8eJ#W-N5S{hcIZ+NyfP_Soqd~gHCJmxEMTmx6L40vCg8cd^H}^q|IGybao6Bt z7r_}YU?hc%L?p8!i+}}BfLSNNY;G;RzUj)J+Z~Ra+L^e99VV9cUJ9NvrM*lto~q=k z9^k(RIYfj(WHA!i7I0_TO8vJ1P{1K!Hgg1d@19#-F7j$`GSFLdahT6ao*mRH{_2{- zC=eJ1CB~@4SNyo*dBU|nU!!7x40@2nDv6n?87iLgq!cozF=N`7If~{b-$#C*(mc*M zrFxT6oTf?*-lFuliqmz*ry^hQLX?GBhTfR>bb7-`p+?F(xG=gekHUq_c&I9DV0BQ( ziG1-Wi&t?TBr*^zd{&nWnJ2inP%N8?lP^Jx5WG;Z$iHQ}szPMdhy92|WbrEg}%&AxGe;%dj} zXh$myk9MD$PwmKWn8LRD7NSdk+O24}xQ!vftL~urX%j-e!}c%7CK)+>a5jBA{S5}U Gy?+3c&#|Qd literal 0 HcmV?d00001 diff --git a/models/__pycache__/sign_request.cpython-312.pyc b/models/__pycache__/sign_request.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f4cf83f14221b8dd904f4a4163ca9316d8538eb8 GIT binary patch literal 15392 zcmeHu4Nw$WnqX%AQvCl@Kmi351%(142#Tl;7Aj~X4Wd$NS0|MTR9QcmnSfQDrM-K7 zF?h?IxO20@Oz#PM8z)8^(YVnOV#G!?#@=0H+-{g@(N=Y?#<;PwvD*l0F z_r0u23Tbr4+1ZG@xO)Nle!uU1?|a|--uK@7>OZHXBw`SLVQY8(MKgx|Ey~D29ZS40 z;23rtBQX+pW436F+i)qbuqmWiX;VV1aH~9On;J)Llx~ejYtu?;l{>+sv+1O?+MVc0 zvL#7rjXT+sVoQHnXSFRte?lA7i#E1$G!CGd{-1OsdG$E3+gXf2xe8ZPk9l~*@%FPl`pktk$^-NNyhZv@MDT1VEs7o9rhddNZI~@$yGcgWczi%EBjR>Zd(h|c5MI)v5Ow_wMN93)w3CF}Ny4~%-Y5c< zn9@TI9df#3gIUj>qMa;7i^))nh8YDep`YN?79Z-_`dA9A0@a9FW(^%xdZ{8}i*Ca7$HQgS$_;FLpB9jQED zg<%Xnl}f7Q@>b|+Dp!R~HK;H@W>I(33Mlyo^=(l}=}jMt>YM)sm2~0y2LV`=qRQbxjT%|#LSGib<02)}aHjrcrN9WypU(WJlDPdI#o z?|6t&!dvU`)jG+#9zQ$c^EzN&>wKioSHHW?F~+by4?&VXFH^@jhrM=&x-dq09h6-X zgSw~?M5}02#jv%belZWKFWMz)>@3j8j1n|J`DqgnqlbTH5I!Gak(A75C2jN84T{fllox%z0olSk`Xg%VMQE4FLrvMDc07L5cTa!w(B3v0;)Tl@`r*LI{zh+2D zYDg{63ae*x7*Y-^$b{RvPh-4+)up5joYIxMxh1TR^sURu#27#R{nq?&A(A(Rb>fkT#4kjWC)wiy4`T;Wt?$|uTrpU`9R z_wl+)zhB0weu|N)f2@#UX~i@?wG-gC8d+Mj_14&biv6*2sI^O~qSftl5N>9F zt=woQfG`E{`R*f(?|CoWGa&Q~@aKlY=f(v1>lyn9qbm^FJPBS_uBg}Aal$=DiP;hv zu?jn?Kol#SUQyCc>m~R;8ubG^z1r>cIN3m2UrdP`yCpV@Y6+pV^;ncN?0*>DfIf(g z0j06VDC%fxh(aoyvSf=%(A6kdEE0!OBr0OPGoqID!=4Rv=%gZ)0|P_eG@v|v6Tys_ zFywT@{!fvjlJbs=8b-2WM5Pm~426@Fs1OsqUg68kM$*drj-@E@UtRQxM+t^v?E-!UntuD0yCu$__QvPWe8`Pge+4yt5(RWU91jewJzs}+fIhs zPOc?j72Ct6R>9O7HgyQ5j?kg8kZFAS-LDI)BKf6j8q8dC^UVAizP?MSI2x|#6)Jio z`E`rgi@|X75uy1AukVcHS1x2NI6rgWa`XDVYii{QJQo0qOV+fQxetGVDb2>|?&qnP zp?a}6T;K7izGJ00+<8vuJjWAssFRMAZ3~w*3uVpWvUZ`YJyd3$(Tas-UzgN8GuDQU zje@apad>Hh-*xya;}O7IT6HsNK8fGnAs7#ZjqeJ^cO$KxE2To~v2g2gq4jvAao^oz zcaBAB8lP)b$MLe4n5wjVEgLg!3zs(v<&EL;148+M<>qC}O2cy6gJbDA|K`k`Nl2>+rPc8X zb<%=zpfxLpWJpN6^><*E{5o3_M;7Ep=ol8*bwLK{Y*s$}U@Q_7DUj>>R@S?}j{}x1 z_U5g2WybuPwutUcVXHlv!|dxl`7s^FBJC&zHb+ivwV$%<`=Se}j!og8Mw843IVP2) z^0rDo8X+6QwZ%>;?;bD*3YT#cL6DWKgG{JHs(4f2*7s-z_oF#PdUaEf@ds5A8>{{+9CvMv+JTr$v`ay9j8+SPQ2 z+KFfe3+F7NEG3v4Obe!SDO+LWQpr?A50@5;)1xt&_KEUGim7rH=1>RI0X`MsVfV^3 zk*RN%LV0GuMo%~=0u>q1exD2-kGTw4%7dBg0lA!QlT%!#t6fgVV^>E!EvMvplkv%M zFzYSw9+t7i`JeR|E$!VD70U44jaM|Q^JeFI+{Ayk2;^lIRE8wz#AMeZgcpQM8lglQvq4tA#shkSH zc@=5~<$OFwH4-esOUBFPR21)PV4+VkpDg$^wpRnyxvn7@G9J4|;%PZW79x3?#_64$ zo>Wk2_6?S>|3b#X6|?_RPLoxr6=}I${V7<;D9G5sPJPV|40E%E-X*t5dLJ-I z+P4HW9h`sBJ`gniHv4r9pS){lxPBm!_sRz-%$DgTvym;dq1RF_!*%7A=2C{!az?IH z<_qjfs;R$x3n|?$?Nkpp&n&FPt#|JtnLe-?^sYbnK0Rd3`s|mzX~zndb7dg^{~Gvd zl4fR$IgmK7b*;-#yf@CYQ5vLKsQ??@3?tuaKdzNVso^T3`&H2F&3H}A=+3R+DgakG zr;hEQc|c|PhJD4fK1LI8>TRtMvt(IkbLG-L8t#_WQ=h-ZeD07& z>Ur(_y^ZyyC+j6_3UVi$fXtFj_qJpGEiBtNSS=dRUcb4e+8|MX<#p>G@i|!e=Cv%f zDTh9{kWMa3s;z?BE$nkHTdK8im0VR^mjtU}jxBLr5Znec^~1Pa%PFp!+a|Z$ARn^~JA;BpVS8WcKY{vNi<$pqYB|7CdZ1JY>-R^_!j#nk5Luw00`}cXo!;gRc4i zXE<3nq*$8SSI%(2@dvO+Yh71gX((krjTOQCCrmw+T3vfntN3jxr-3`l-LlpXZfAca z!^t>P2=0k~EcY0Xx$SShbCUHl>{2zZ>v1?a#bw4@OdSG#DCrDn zIV;!48a6;htVCASez`Y}+*KZ6b z02_N!#%022a5CBw_xg=M9QOnk#r`|N@X0@o2Fspqcy*IrRs;PfMku4h2k(0n3lFWN z&oSl!XCNau6M22CaS$q{S2q%p4q}Xf_ger!z#E@wQPZI@x7#0kussA1ryAsD-)1-&T^=CYiMz_%xWgty@!D}ICn(*6+;o(5e5KVZf=)>NIVturDgkbFOKN7D& z@4v&eZ)|kXFD3#4ni_^@Mn--fMUSGQS^^eRU&Rs7*7f>WX#5Sj9sA~U_&8H>pt=B3 z(mN)73PI!B1Kq%9nlerhjFIrd%QiDc0kbE($gKx>V)XmQ=ymSb8hhLn0lapAuNUE9 z5ePwgoL+ddgm-fvT`Q`HG1hm2MsE(oKbb}0n*-n^L=CaSfAyCr{FejZE#w>?VZQ+j z9url_tqNYUDL3UnE?R-CRy057sr?m9Yh5e}Z@+cMK!RK&VgX&eE~`(5HLkmqeS_Ym zz!6F11&+hRE|A;3p6Y{16tyrAG{9kK2;tcQF#%Bm?m>izC&HU92pdJmhHwvR9V3+E z{GjimsPK73{4{cTBAvb%_&*##UQZY8041vZD57fstEd9$aYx_j33iNzZh=>?em^g)jk^d9(#DFh1~A3#f%BwYl6Sft6{E*DSO64@hy+0giDbd`Zx9~p!70&6 zw8LB^K(*vADC!_}ghFy9COR3X7dQz{0`Pbblxt{dF@QTGasfjQ1ELa|(_oHCQ{*M- z6x5>$Gn{GwOE2$Q@4F;f58!jQA!bKB^c3)Lt%v z(0M?>DXCNou65A_xK^Nvr@(GcRQNwYNRyG40*{TTL?HW!{v%WlUM^l(U1cb( zLTNDx{8d=L9lQ`hG_@D)%!qS{rAtu*d_u%WfF<^e=;Id?5PgV~qT~XC5*ptaJ37WP z=(SaH{rJs4l4Fo)faMrwzr2~IbPmn4&zr&x2Ze@%%hs^$HNo z=9>rR4=nCpEpL66p8G5-@B030`$Jjflh#O4X}G9aC~6KB?V0TUc~0JS+cn#d&Q2bB zlBvI5bFF4hvzQ&OKPc26Ts|DCKen3r?&QHpL2Ki@ zSoVf2`@;I(IrWPAq3(h1k-m4i>?fJLzIO(FCOZXvFM!{unyuo?TZMvs;et+~pfi$b zoHH%x_>7(Wu0t!zl@kw7JvhZT_Vei{!E=V!SBZrV@RTX+0#6xLVd~`3wOp(V@5g_c zqRmZxj-gL7mU9T7?wlE)@2Ye`r}!S&>8$&sS+xwhGXNRefsbk-g*H!WTe%zMD+ z#hCdVQ)OiSLJ7c=hhAo4xrHx({d%$aW1%-4j=b_->@LuD$Lhwe(@*FT;t7s^ZUZ(Ekb!qsJt~) zvM-dse_8`VQ@4AmQK&sI(>SO5$${nfrW3x-D*V&Lh25X`ts3`4vh%KYUF({;xSCxB zZS}^voqu>KQdPUCo*AD}NA!g^Qf5=;hz0XP-dFm1a6Fpp`$>shJT^D9FupMSmA*-? z*bInT5Uctf&uZ%yOK(w&9}2bYGf8u83;2A;BYpLXVp<(Bl)k_yJmZXF+8QaXyjd_` zuweb{@U6q4(#GkouL~<8dgC*Ld2T{5)J=Cp)>D6zXIN7x^A9VYY-{?Qz8km`_z&iA zd#}*m%lDn;2i^}4ID`QQPY#C$MtG-77;y0e=lMQ&sNEB4_J+3kW{$noVEH>1lfyd> z3Of$2>aDSHMGRFy6foN|Z<#$FF_g`<&5<{q^UnFBi>kXxcaj9tUct~BZBsp8Ef}_= zMvXW3&O>=Eq$397%d~|20uUIz;W~GX3+L4cc{Pg#p}f6+U1)q#QNLIbs@Of#A1N~4 zI5T@@A$wtbk-U5U&UvA(ZMj^i>sT#16fv4^rp~7>njaZ=M@mgMi|30M)zHd$$9X4h zId>&vrSajO2YVhA^8Ewh{ttxy4|swU`bnXa6xt|$*!{TF6Dc#_tevl2Jp8C^FW?B5 zHVUPUi~UR1FFNmaE|Xun?z@E69%0veeBS_n<}81X2%j4g;P1>3KYTtsObf#_&x{Mh zKlsUwT}6`5D60B$PHSjxCX2IPW=ER1Xs$B=UVH9wZC2Ih z@SsN+^zhyb!r%qrI$iJ7r-1ue7{gxHteygzOxUjQ_ z?>)`ifSM1&XDI>xY!pA_3J+ZnhA!~*m@qUZ*v5q3asFcPvEkCI?AZ&eJ5pwv(Smlr z>6`a`i>dPW%^Zr9RNU~-`fndxRDas_sH6$`&w&i_b=DQl!(^D9y8f?>C!a-27sfy1 zZgF23Ti0{|yq1VnY+HCAWP8aWnA&D^krLC5z-(ZlGE}m2M)jm}=VIgC);q1si7V}) zhU4LeQ$ho9!ogF+p@tFO<>g2Hp~}Ea@)JYF9C^E&f2W`SfaHB>$2h3yKs!dwA;k*} zD>|&0!2$V#>NDLf-JgGqKEl@b8jTnmNiWkd;(p`dK*G$`!^14M;sC*anvM$$+ zclX@cBW!D3nh>@fTG8J$8DBPiX}NE?pR{6qc=W+h{`8p#$Ax#!2)obn?>ipr z$;c~c)lJ5@M8+7>sFjV(MkA)9VpWLGm5pNN@+h`>sDeJ*~lLGqtvwzmB_4EyQIWY z(yt!5a%8IO=Xs{NlS^Z(c^#8Sp5&JQ$TxZT=Q#zN%@O!XPKtT6H4;hBzMgt5b>?EY zq)sTQTf{*{tfn`BF?c=gTH2iID=-GN0QWo*OD~j56IRnL$m0AEf0AE#qhPk+r-o?_ zs!X|-GBX7BUW+C%1pp{2zaj47SyGFHhi@vFTe3M+QP!{pY2?pXYbm~<*FG;S zzOY9LR~P7o1zgz^>~?g&Vz-Nlc02q6YYcvyU>B3^_6uW#J64lmcY5K5$;r}30Em7F zA2B87#_pipZo3`ubweow_fyzA(m_S?n3q22f|Nnm6W@J|Mbe5sI`UFguio*?A^3Tl zx=`|>mYP|%(H}x18ok5A{R{)QO7O!)F&8dLs4>>*h95|6_+cZB9IeF!REe6=DwNHF z6Sa?K-Naz6+c{W^pqS_dK}Ng=PGRUOg@!kLOw9jYc`eJLqai~hPp*v^X*V3WyhF}m zIsw(dPY>k203)2VqL(O{8;XHk|L7Pao$tl$cm<-3LBE$|qNjdCyedXqEyS_kWk&A` zls%otr27p~g?>#(rveFd8v3N8k95zJiO5Pr)wR?`2PIuVFwuJ`>2fGls$zXUH-qp? zcTCbHf^y>ax40~5&1VMQTf%s" % (option.value)) + else: + content.append(option.value) + font_size = height * normalFontSize * 0.8 + text = " / ".join(content) + string_width = stringWidth(text.replace("", "").replace("", ""), font, font_size) + p = Paragraph(text, ParagraphStyle(name='Selection Paragraph', fontName=font, fontSize=font_size, leading=12)) + posX = width * (item.posX + item.width * 0.5) - string_width // 2 + posY = height * (1 - item.posY - item.height * 0.5) - p.wrap(width, height)[1] // 2 + p.drawOn(can, posX, posY) + + elif item.type_id.item_type == "textarea": + font_size = height * normalFontSize * 0.8 + can.setFont(font, font_size) + lines = value.split('\n') + y = (1-item.posY) + for line in lines: + empty_space = width * item.width - can.stringWidth(line, font, font_size) + x_shift = 0 + if item.alignment == 'center': + x_shift = empty_space / 2 + elif item.alignment == 'right': + x_shift = empty_space + y -= normalFontSize * 0.9 + line = reshape_text(line) + can.drawString(width * item.posX + x_shift, height * y, line) + y -= normalFontSize * 0.1 + + elif item.type_id.item_type == "checkbox": + can.setFont(font, height*item.height*0.8) + value = 'X' if value == 'on' else '' + can.drawString(width*item.posX, height*(1-item.posY-item.height*0.9), value) + elif item.type_id.item_type == "radio": + x = width * item.posX + y = height * (1 - item.posY) + w = item.width * width + h = item.height * height + # Calculate the center of the sign item rectangle. + c_x = x + w * 0.5 + c_y = y - h * 0.5 + # Draw the outer empty circle. + can.circle(c_x, c_y, h * 0.5) + if value == "on": + # Draw the inner filled circle. + can.circle(x_cen=c_x, y_cen=c_y, r=h * 0.5 * 0.75, fill=1) + elif item.type_id.item_type == "signature" or item.type_id.item_type == "initial": + try: + image_reader = ImageReader(io.BytesIO(base64.b64decode(value[value.find(',')+1:]))) + except UnidentifiedImageError: + raise ValidationError(_("There was an issue downloading your document. Please contact an administrator.")) + _fix_image_transparency(image_reader._image) + can.drawImage(image_reader, width*item.posX, height*(1-item.posY-item.height), width*item.width, height*item.height, 'auto', True) + + can.showPage() + + can.save() + + item_pdf = PdfFileReader(packet, overwriteWarnings=False) + new_pdf = PdfFileWriter() + + for p in range(0, old_pdf.getNumPages()): + page = old_pdf.getPage(p) + page.mergePage(item_pdf.getPage(p)) + new_pdf.addPage(page) + + if isEncrypted: + new_pdf.encrypt(password) + + try: + output = io.BytesIO() + new_pdf.write(output) + except PdfReadError: + raise ValidationError(_("There was an issue downloading your document. Please contact an administrator.")) + + self.completed_document = base64.b64encode(output.getvalue()) + output.close() + diff --git a/static/src/js/sign_backend_patch.js b/static/src/js/sign_backend_patch.js new file mode 100644 index 0000000..e7ba4c9 --- /dev/null +++ b/static/src/js/sign_backend_patch.js @@ -0,0 +1,105 @@ +/** @odoo-module **/ + +import { SignTemplateIframe } from "@sign/backend_components/sign_template/sign_template_iframe"; +import { SignTemplateBody } from "@sign/backend_components/sign_template/sign_template_body"; +// IMPORTANT: We must import SignItemCustomPopover to pass it to this.popover.add +import { SignItemCustomPopover } from "@sign/backend_components/sign_template/sign_item_custom_popover"; +import { patch } from "@web/core/utils/patch"; + +patch(SignTemplateIframe.prototype, { + async openSignItemPopup(signItem) { + const shouldOpenNewPopover = !(signItem.data.id in this.closePopoverFns); + this.closePopover(); + if (shouldOpenNewPopover) { + if (signItem.data.id in this.negativeIds) { + await this.negativeIds[signItem.data.id]; + } + const header_title = signItem.data.type === "radio" ? "Radio Button" : signItem.data.type_id?.[1] || signItem.data.name; + const closeFn = this.popover.add( + signItem.el, + SignItemCustomPopover, + { + debug: this.env.debug, + responsible: signItem.data.responsible, + roles: this.signRolesById, + alignment: signItem.data.alignment, + required: signItem.data.required, + header_title: header_title, + placeholder: signItem.data.placeholder, + id: signItem.data.id, + type: signItem.data.type, + option_ids: signItem.data.option_ids, + num_options: this.getSignItemById(signItem.data.id).data.num_options, + radio_set_id: signItem.data.radio_set_id, + // PATCH START + sequence_id: signItem.data.sequence_id, + sequence_prefix: signItem.data.sequence_prefix, + sequence_padding: signItem.data.sequence_padding, + sequence_number_next: signItem.data.sequence_number_next, + // PATCH END + onValidate: (data) => { + this.updateSignItem(signItem, data); + this.closePopover(); + }, + onDelete: () => { + this.closePopover(); + this.deleteSignItem(signItem); + }, + onClose: () => { + this.closePopover(); + }, + updateSelectionOptions: (ids) => this.updateSelectionOptions(ids), + updateRoles: (id) => this.updateRoles(id), + }, + { + position: "right", + onClose: () => { + this.closePopoverFns = {}; + }, + closeOnClickAway: (target) => !target.closest(".modal"), + popoverClass: "sign-popover", + } + ); + this.closePopoverFns[signItem.data.id] = { + close: closeFn, + signItem, + }; + } + }, + + updateSignItem(signItem, data) { + // Ensure sequence fields are not filtered out if they were missing from initial load + const sequenceFields = ['sequence_id', 'sequence_prefix', 'sequence_padding', 'sequence_number_next']; + for (const field of sequenceFields) { + if (field in data && !(field in signItem.data)) { + signItem.data[field] = false; // Initialize with dummy value to pass the check + } + } + return super.updateSignItem(signItem, data); + } +}); + +patch(SignTemplateBody.prototype, { + prepareTemplateData() { + const [updatedSignItems, Id2UpdatedItem] = super.prepareTemplateData(); + const items = this.iframe?.signItems ?? {}; + + for (const page in items) { + for (const id in items[page]) { + const signItem = items[page][id].data; + // updatedSignItems is keyed by ID. Check if this item is in the updated list. + if (updatedSignItems[id]) { + if (signItem.sequence_id) { + // specific handling for Many2one: take ID if it's an array + updatedSignItems[id].sequence_id = Array.isArray(signItem.sequence_id) ? signItem.sequence_id[0] : signItem.sequence_id; + } + // For related fields, we want to save them if they changed. + if (signItem.sequence_prefix !== undefined) updatedSignItems[id].sequence_prefix = signItem.sequence_prefix; + if (signItem.sequence_padding !== undefined) updatedSignItems[id].sequence_padding = signItem.sequence_padding; + if (signItem.sequence_number_next !== undefined) updatedSignItems[id].sequence_number_next = signItem.sequence_number_next; + } + } + } + return [updatedSignItems, Id2UpdatedItem]; + } +}); diff --git a/static/src/js/sign_item_custom_popover_patch.js b/static/src/js/sign_item_custom_popover_patch.js new file mode 100644 index 0000000..620e4af --- /dev/null +++ b/static/src/js/sign_item_custom_popover_patch.js @@ -0,0 +1,61 @@ +/** @odoo-module **/ + +import { SignItemCustomPopover } from "@sign/backend_components/sign_template/sign_item_custom_popover"; +import { patch } from "@web/core/utils/patch"; + +patch(SignItemCustomPopover.prototype, { + setup() { + super.setup(); + // Add sequence fields to the loaded fields + this.signItemFieldsGet.sequence_id = { type: "many2one", relation: "ir.sequence", string: "Sequence" }; + this.signItemFieldsGet.sequence_prefix = { type: "char", string: "Prefix" }; + this.signItemFieldsGet.sequence_padding = { type: "integer", string: "Padding" }; + this.signItemFieldsGet.sequence_number_next = { type: "integer", string: "Next Number" }; + + // Initialize state from props + this.state.sequence_id = this.props.sequence_id; + this.state.sequence_prefix = this.props.sequence_prefix; + this.state.sequence_padding = this.props.sequence_padding; + this.state.sequence_number_next = this.props.sequence_number_next; + }, + + get recordProps() { + // We need to intercept onRecordChanged to sync changes to this.state + const originalRecordProps = super.recordProps; + const originalOnRecordChanged = originalRecordProps.onRecordChanged; + + return { + ...originalRecordProps, + onRecordChanged: async (record, changes) => { + if (originalOnRecordChanged) { + await originalOnRecordChanged(record, changes); + } + // Sync sequence fields to state if changed + if ("sequence_id" in changes) { + this.state.sequence_id = changes.sequence_id; + // When sequence changes, we might want to reload prefix/padding/etc if they are computed? + // They are related fields, so 'record' should have them updated. + // But 'changes' might only contain the changed field. + // We should check the record data for the others. + + // Actually, if they are related fields, the `Record` model updates them. + // But `changes` object passed here contains changed values. + } + if ("sequence_prefix" in changes) this.state.sequence_prefix = changes.sequence_prefix; + if ("sequence_padding" in changes) this.state.sequence_padding = changes.sequence_padding; + if ("sequence_number_next" in changes) this.state.sequence_number_next = changes.sequence_number_next; + } + }; + } +}); + +import { CharField } from "@web/views/fields/char/char_field"; +import { IntegerField } from "@web/views/fields/integer/integer_field"; + +// Patch static components +const originalComponents = SignItemCustomPopover.components; +SignItemCustomPopover.components = { + ...originalComponents, + CharField, + IntegerField, +}; diff --git a/static/src/xml/sign_item_custom_popover_patch.xml b/static/src/xml/sign_item_custom_popover_patch.xml new file mode 100644 index 0000000..52ef259 --- /dev/null +++ b/static/src/xml/sign_item_custom_popover_patch.xml @@ -0,0 +1,49 @@ + + + + + +
+ + + + + + +
+
+ + + +
+ +
+ +
+
+ + + +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+
+
diff --git a/static/src/xml/sign_item_sequence.xml b/static/src/xml/sign_item_sequence.xml new file mode 100644 index 0000000..c718d7b --- /dev/null +++ b/static/src/xml/sign_item_sequence.xml @@ -0,0 +1,38 @@ + + + + + + + + +
+ + + + + + + + + +
+
+
+ + +
+
+ + + + + + + + + +
+
+
+
diff --git a/views/sign_item_views.xml b/views/sign_item_views.xml new file mode 100644 index 0000000..5e8e49c --- /dev/null +++ b/views/sign_item_views.xml @@ -0,0 +1,17 @@ + + + + sign.item.view.form.inherit.sequence + sign.item + + + + + + + + + + + +