From ae70c22485c1490aed07f04c841398b0c8cc3050 Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 7 Dec 2025 18:31:32 +1100 Subject: [PATCH] [UI] Barcode form inputs (#10973) * Add barcode buttons to related fields - Only field types which support barcodes * Add per-user settings for barcode support * Fill form field with scanned data * Updated docs * Fix duplicate setting * Add playwright tests * Fix duplicate setting in docs * Fix broken link * Fix memo deps * Fix typo * Remove setting * Updated playwright tests * Improved typing --- .../assets/images/barcode/barcode_field.png | Bin 0 -> 15981 bytes .../images/barcode/barcode_field_dialog.png | Bin 0 -> 14777 bytes .../images/barcode/barcode_field_filled.png | Bin 0 -> 16655 bytes docs/docs/barcodes/index.md | 20 ++ docs/docs/settings/global.md | 2 +- docs/docs/settings/user.md | 1 + src/backend/InvenTree/common/setting/user.py | 6 + src/frontend/lib/enums/ModelInformation.tsx | 11 + .../components/barcodes/BarcodeScanDialog.tsx | 20 +- .../src/components/buttons/ScanButton.tsx | 25 +- .../forms/fields/RelatedModelField.tsx | 251 ++++++++++++------ .../src/pages/Index/Settings/UserSettings.tsx | 1 + src/frontend/tests/helpers.ts | 10 +- src/frontend/tests/login.ts | 2 +- .../tests/pages/pui_purchase_order.spec.ts | 4 +- src/frontend/tests/pages/pui_scan.spec.ts | 64 ++++- 16 files changed, 309 insertions(+), 108 deletions(-) create mode 100644 docs/docs/assets/images/barcode/barcode_field.png create mode 100644 docs/docs/assets/images/barcode/barcode_field_dialog.png create mode 100644 docs/docs/assets/images/barcode/barcode_field_filled.png diff --git a/docs/docs/assets/images/barcode/barcode_field.png b/docs/docs/assets/images/barcode/barcode_field.png new file mode 100644 index 0000000000000000000000000000000000000000..37683eb33e111f439435192bc71c2ff08642d2ba GIT binary patch literal 15981 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 bT(Z3rltyYwT8b$77jOR;mh$&q literal 0 HcmV?d00001 diff --git a/docs/docs/assets/images/barcode/barcode_field_dialog.png b/docs/docs/assets/images/barcode/barcode_field_dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..202bf2fcd668e4b451d36058fd4731deb500ad1f GIT binary patch literal 14777 zcmcJ$cUY6nwl^FR1eIn%l#Vn30i|~k6bMT19ix^w=RJF$eZKQu-*tU|+=P4XnKd(OR{5#T?2tY6kwI7 z+8_{72ncj3kn9Rj;=fj*4E$X3&{kFel?*Vf0x!gNPt=}(K;_ZbPhXM%?^oSaj66Uf z%67tYsnfN<8U&J12S0tH>ubI-?H8as-g>dK1!s>Tsd!6tUrN=mBlowC!I&eYRhznq z#n|Q}5{b({xDl^EtjrXvTwd+Q(B3BoDtwE{uc=WVpalwUrVdv_YN7b6FVxlD?*P?VLhh1~kl++7 z+4CH*DUg!k%qQ;_cOJ!&gFr*^zD?%+8FCg|%_k+~?QZ(v_MR{{5I|5^jVW6B-4zP= zJK;1QDMD&Gc@8&00I`%u(VbRHHTEi=Kv(=v$%sK9H`f1UY}BmOz)V8PLjM15^4M>4 z99h5kGupG8f_e2|7U`*f8T3Zn%2NjNWl~TE+%%}72X$P0VV{!sVsvb*PzSE3y!icz zj?l7!Bkm{N#)KIpq1JUlYh@JXbx}|pbD-8AAwnv0(9KwLbM%5)r*5(v>!FmQL?xr= zY?42L=_p+>pGKhXtjAj;>}nIC^TQr#YQQq}BS~6NOG)O%8PVT{jcg_j%uC8!{GZC5 zwl2{L+GkcCe~7#%q!jkD>k+wK!VjImF5TBgNJ@P_WLuL>wQ+TpDszx|R+CNZ8{3rg zOpka`m!k2(V1qm#If)!be=IwRhTDousjTtq2RVVGHW#nX4yXfZaN_BSs zNq%`2E&C-XPxi}-sz;1s z`u_A#?urOH{$k)Y!?5r@L4~)O>~J)%qFkM{i6Hqs7!Q?bTF1iM1<6Yw`F5pQNd?6# z6k~5&1S;NIPBy!r9h{Tu>DOpPYXqp6N%^=lc`Fc6m~E7>&T(5*@yOx8<7@CQ+Jw?9Y zMd^aiW4p_oJ8NJj_Cu;<%o8R$y8AXxii&jjn!BCQ6~ z)iupVUo;z})*3*jm*2s1s&u1|;o}NE7maXZTXv&B)1AH2Q@P}n(^<3q;gu!hGV4+o z2Db>8mMl1X;nxavo!LYEPz&av8nfm-4Ty{V5S99{fQyqW4tMbT)!D+rE99i_cQog? z=9Z8#HjKG;{3sAx+LU&5P@A$BZN|eb^$?8p(XYW&xs)7z@_C*&R%vp*Y5UKl{z*iS zasTO*H{Y>u^g$wHqBX)|)|7&N!h9to0kU=v1oAoV?K6UU3P|nNQM1= zD`LmW-&L+Yehl5W_mfWl10jNwJc)Yu>WfK~o=f7($oQs{Zr16&;H6{xTmiTDdlOae zGNR5c`o%Kq8A=E6sR0=;9-ae|=A_w@A!S}fm6Z?N3!A%?XWL?sMSA`XJpRiTOeat&|xaS+h%YTwBqaM*~pO#ND7jsEmoj$EjnLo&*_utK&J zfhnR&+Cq2j(Cca4Bb&P>(b*ELiy(Qfsu09w;VM%=e?IHTXQ=&kqXB{RD3G+q7U5|0 z8^SLdpXt~JWLKy1AmzyP#`9z^5T-5gD)Z9jrd~&0@U8TjOj?WC5?G)JYK8B@F=NKO zu~YQ!UFm3mhiAL37k{d2eT=iT1gjd$bbT~VRWD8-Qh&Pyaw|1aCLjF{DM3kXHRpew z_TN9uredlE-5naLmmfUlYgM~Tn;w^SDai(dpfGdO54&QTm@CwfgUAzw+Lc4M4-!=& z{Jdl(7kTP&d3yF8mu+xubF*?Ks7~Gh8;3%sX$fhaEmJ_6Z93gJ(-QKXJl?P>s@|C5>5eHmJm)@MjO^WC9;JOCGhW*`x0)nILVp=F zr|sX&i)u*<&iYI~+cLa2>sQ+v?R{@f90go zv%9XpnB~De9zML|NYyp9lsgRFDJ>mh&W>F}e^~kU7oLx>UmUk)oIO%pSr&_9mwCjg ze3@U(U`ij1DMKnf`%#VkC0o|oQKm$$c(2~$y=(n0U@OM9iiOvHQ|XFse=65D+U~6= zyldT6lYIKMAaBILdK%*EZ#w70JYzGd9An(Gmn{~Gj2KaL-$gGP zmUQBH_+6*UdQvuFy(q!@fdJRDxUIE3@BG8&a3QJuwVBxFAGuq>zps13Jf~tt$2M=z z-o_sVR(OtwB8xpP?EMPa9p+(M#hE_i&yZe08BW0`PG(+OkYJ70*MnyMh-&Y+EuVy> zQB$gmmH@DJgN}~Z_>X)Q!K&KzGL$Rjxhx>isap}Ba8oDDy0|RG_ol_{YYMd@`94BJ?8yGGatQ_kyPHhs`^Z*pU%xg zHC8^`LIN@ZcfX{`8fzgR--^IxTijk1i{2Q&9Q<%NqBx89 zc`Q)FX_S8JO1__%Ywfi9Ptow;wK-A!E=Y204#g~Sew=`G{&`CC8`!Sp_F1~shDBKQ z`Or|_MssFUKKmgf9NjxCJKg-c3qLsZWFHbv(8vx=g zhBsyEeSW<|AWBdR7kioN5VXFSxtt-vtNqSpUftmimUMVBq$=;P+J36YXmv-D&e_;c zcsJhxx_)63O57UVvQK)G&G1flk5@Qu;`vFHN3?4XeqXd3J6xBn?i#l)g^Hm(eD_7e zo|;^6eUeu+>aww|?|y?tRrNpvVE8Czb&I}^6wRv7ln9_0;#s)`q@Ep_jl|D#^gEgu zEq$ptz)!c;ynMlfZB}lbovC#_!p=^Hp>;B<)o?z z91DvJ9;BvL2zx0&&R#n4;#Qd^=IQBuQvx-{)2z6~XKr;F_qUc(m5A2Y9yOfk?9O8`mrOHdjByR7B zX}oM)w^_!6%!l9A&)`Kfj=+{$-R`CwzFnhk|8$!gjuk@`>|=*l0(G)NSSB{(Z$nfk ze4?5&K1D$G(JeGnQIs?e4>vHqd+~|b>u)(u9C2MjQ&nqHvt6sSMZCf`^;@sK4b0*p zxW`uJvEG3zC!5WN)fUVFcgREAOK=;O&4-P18>TZC#T}SOy9d8zj#paFMo~37*x}sJ zUH6pU7~P7kGN$7N=musXb)=-Ac>I(qLU_;hKzZ#FXpZR}Da9CD3v<3~QvhTcPToG` zy7|p4J18g)dO$YgxM>(6+Gbz1^|-H31^=h5ax?+9enNZR>!ruTQ&K+7J*1oD&5fNY zOwLnFwUs*UB5(9)3GlhPU#UKXpM5^yocEe75Kk%XHZ<0xk$B>Ac7Jr1-FH3P$JPNq zBYCzea`NFzY4cej80GP?v_(=eJIvZ~sa_E}M-^i7^UHDP_12TsCSkjb8Dm}6?lGKP z-`JnE0D3k6lGUinP#bu%trUu741}EL9*V+nk@un&}@_( zIsO+)ORi3^Y*n#-simc2Q{n;1RDLd<L-( z>F0TL4O+l;*X$MuB>q?_lFnwD>(NLPkxWxVKDJ`Z#_*CR4C!m*N9 zpv*OuBGj(wl9=VOr?da3)WrCb=DFE-fm2zNT1r(saz}{0ThrKs;D>akP~hVo4hK4@ zSJ+6($$*#`F98LN1>+*iVms=KRnWABzglZ5C(RQR9sl?@2@0>Kj|!KyxW0P^^msfi zfZ-OH@|kuh1k?E%)dYFzq#g%~&qzTQnJEHX6nt`y(9@ z8wVkQ&=KvH%H^C6)}o4ir#dtltxG`nx{c zU!l5Y6LpvFBLdND)=f}CYz+Ds{of<{e?-j+3M^9Z06I4_98y02)&9oc;WQl$Q8GV} z*|0x;)$J7d_t?pQmd&#j7u@9*Ny&3)nhCH$ddeOUJ+ z@-><$Elo{o53+q|Q;KY0Jy6rp(NQn(8|QC3pucYkFYl^4Cw`5Ox3<&E{>&hu%@ob$ zNG3f$+Da@B#JJlpMs;!Xbap$hV?Ag5R@B_?D7{L_yFx*?_|PR@k?T(zh(-UDSqH?G z>QPnVR~a9Jj}!?$Kg|65p8=y;R z!eK8T$W+&1vDlmkm)$!rvr@l-01H3<_gCGpmjxvy>gJ_rBQH}U*6pX98X$X~zFVhB z{Gj(9g6I4dC}O4SA;njp>S${l=MA1$zZefkDE8>2=40EoEHWE^rNy|jFpLW885rba zv5wUckBCaTg8Zr~U5}N4@d3D00u1(5D_f~hEmf4&IU*uL*cl@{jzS>FqkfrB4ig;d2fiFnrL|OY;CsJb8BL!erGxM zcyCWY6~^DRsDSH}6I%3I<)BrCr5}%a5I)xXtosrwGc;Y2Ur0zyRE4>;oO$E6W-VyV zJY!)nSXzI4e0robY2Db>1QU)%be zSs8uv_o!%~qg;JfEqZ0qHc6&ApZBxr^_5=rs_Ocj_-IjXqUo7xQlK$FEqYsfYHd)deDMMCT& zWG#+z(*v*?$In_-a4n0!W9pG6wGQohYOoxk!9OMHj8gB%1dg_?nzl#Cr~UD8$%|he z3Rn<|n8;q$@D`c)*!Y+#OgmMCiaU5d_g?))Yq-e5lEGikrq8YYk`jZl=u?m~(f#ti; z&+^?8Dk=rN2F+Tu_iX#q`Y+8D1RgCV(a8q*j!8L)1B$ttm+GSZ@?}=#HT#=nAs?R0 z42UZZ%E&y6$yWu$)V=6S9WGg{etu`e~lv4eZa*^om$iv016c1#T-w z7Br`_w6p|HP8nQ=EpC|E;3k^!MfqVMCe>b9u>r3R@?;F*Lho!9-zwLcpX)pY<#(8QCX(Bd`y0Rs8{GaOT zF({KSwfU_bnvs@P3>b()tIjCa4N!g@&_;nJrSUQ;Pqkw|-NvbRQY8=KAir8C!@Q|@ zy5kKaU;?V0r$n1B&QCCP9{JkZ+QN=w1<>esygWQ3RSu)NCMHD(8&hkDqLTZd``H;9Y?3;8c;;(txW&KWnGc?@qwfk2(94k?1@z{ORnH@{^=K)-ml@t7>da9usUT)+$_o0sZQf;x5P&I7|bv&Y7Cb8~YG z{q~mik3N3WVEMl$cy)VTQ8M70l6k2CV1g&fK~dMEJK3T;vu*pe$BKLJ5-W_`MFd=lPUp)WZ}PC zxRLfuixI5vP6sS9d3#Lff|kC1BB0x8YS`KPdF}W?ozx!J>QM=X2jJwR0Rk2k)`}&{ zYzvjo_(M++w;@kIX(qq|^GZle8rkgFpEx3GB9EOf(APFxJW_zr=phrB`e#7Y$3xNqu$;0J!pvjtJIAZ7K4=AlL-~p!WZ?DfLex690zP4c^G? zt-bM89bx8~+IWB%0nhi+w>B(mTQK*oNED^S%=u{gcvAW9NTJRyf!@UNT~f@PzUhbF zZd{0Kz1ACW0q_BQm5GRBqtR?e+m1D=p=X1@ouT*ao@B!A{7F@2GkS4fUtD1Fqt8n3 zDwh@ym&91L?c-l=@4XxCjyv6Wt1~*Yl+{U+Z@yAEFk|t>VRFdBh}oDsRHF2c&Tk4F zt#(U6oFlCnmTIbba3}GIezWoCadB5CFwI;2l|EbMKaP30xnDEQR>36|(;2|ygUVm- zr03T^LwA{MJecuQYkyLVjO)_ol$py5zVvG-fDMB`SLYdvx|y091lX3>@uN zIgYEAH}CH5rezE-`qiJc;oAqgaqT~AYR~4e?tVdsk+5Lk@i*Npx{jXV$I+r5r!!c@ z*4d@!w5nd)x7jMiLX?9;2GdvvYr4;4#-F6`z3b%kif?=tj2Ed5Hb0?+vG1jVcOBtX zXyy`3%bMH)_q|rCYv%dmoejG=s)+LjT~~Jguyc05 z&Iw6C)Rg%I1WL`LJeqGpB@JQmQUlB>iCqmn%mt^7q@pK5T}R;UPxgL!$Y-}5?5;$2 zZ-)vpcaCiwbJR%EPuX1=TyXJo--vX|wnJVq+K}O^$IuZgF-dieiq2Y~6qOvyLdSz~ zd%OJD91Jx3Ex@dNs16#Z%+oFbvjR-lPV6D8CM+L){XDjGXjO&xMM4uJLvGO_26g`$ z#ClNFJ#koZMnt1K-g=*mvHBC|x?j9H%qNx4aud#MDs`94i?ycLWHFbhIH`0i zurwMQuKqm!T$Sr6uf!vL7w6F&TqTmiCIQTpL9B!RSr zGfR8cTPTLGWtP}OP8OMsaeF$OvCj`I?5)_S?pB?Zmqz0JT4f{E7Z!&t?}Zei;b_B7 zj30D*V{I0tyzy0q-jZ%}P}|#Q9~$aH9{Wm~(3g*vhq^*}Ug*XZUhXp4fuDbNfQ2Tx zY-T8-L4s}q3qlW-#*4pmTy&XOs#)sJ4`jdloY=fq(!x1d>;0%Odkda#@uysg%Fg0l zf^KPa7u~vmPud1Xs>>tjtQlGT<;mZFdwJmRKlZ2p$rSv5*ft(> zBYS9+CuRW!kLIb3e!eG^Rt*sUphyue57s6%lXQ%CY1k08BLND3YkvMasP3p-$I;@V zZF={AjuoaH|8!3fHvzu0%=@xCoanETTK%-|qd1FSaRQwEoA(Q`Ig~4AL-L1HCD9Xi z0aqLI=#}ZMuAe}B{^sdTE3(gG5RsAU};=7DvS`0b|^w8dCm zRyg^93$Ue>P+NI1$=P&5?>C}8T8;7rrkBNmQQnEi+nMhh|p>f>C_CrrX6e_T?^MDL(OCx*dN2vaG5!#>$)J zlk{#5)yHOKwe5~S^=h!p3gf^2f>sWhb&g1O1q+bo#pGKQ^67AfGPJlY229VD1|Ow3 zrvzB#lnE=N^UjpkHvMK(VCTE(YV|*El-wDJTh&EwS9FR8uOa1@&nZ{pmRaFe~>Im)I?a zz}ewsm>eiwDA%N{6p^1Gw}i0#0{8kP&E}j@z5b#!;vcokP!NQszZdFK)Ju^5Su-Wv|CZ7d#N9_qwnEdSiRvyB230^XE zgAOdjCCxj%2jhg(Jg8HT<2;CGkNk2{p+p=JPB8*<9&t2ryZcDSQ0`S5BentuL0;1* z?N2EUg>MZp1h>G(e1134m3d{^PM(|u=9JN_FK9vNP9$ZZ(ZVN@a|POcE7^BXK6%T+ za-!;8lh35+@=qj5mCb<3uAc>qXx*A=(iS**7srl+i<`4m=~pg0U#}CLt@9phs|{%B zOm^kdPxAjsvNlLpDuMT`GVXLoCYCNc9H4kbS`QlN9^)M-%4+W&RLH43AMvW6r*oLb zJ_|Ahn1JKl4b$D4Fk~dp8-@LP#XJ3Mj{Td zjEIw)axiE+G_fpO+(J#a_Ifjlct{-%6O#&Ub?ybA^1Di!3Q!JU`kiP0;v72gTXC=1kzWOG`J_E7c z5>PgbiskWMOXF&giXviqM2Ng#Epy)rr_`6*kl$VcK6 z!MMMT#S9qT9*?!=$d6@H?pfFY4$<5Tn=ekh>CR3_rBeKT9_pX`&EjO0BNB-7kfI5y z2@RqMH=HC*$xH#9^o&on_NW)-FplYuYA1wz(bAKFQ0#h19b&RteQMl1(0`@Kq(rwO zzYl%CHv9Nsz+iUf2s>Jnyypw_)xoO43s!Y4xt&|&)gTPkRzk?E>p85T&tt!T{l3jY z7_QM}2vJd$$Cfm_*e>3Q-!270WjBfmn4VGiuaQ5^~e0j+TGT{Nq0b zX%W%X_8pxLfFFBr-*zu=H9a{pHf=JeDSM08@v>>@+*h}#u4)D(yR>fD8A$bmt=ch*lSS5)&v;anlDTsQe zPl#w}Ky0D%8b$iZ+iSc60)u|iz~oZCG&VL8Q0%KLMqmX0^Fg+MTDZS~Le?!NB8qlR zHD`DwA&SR2I-jt*!W~Q(~Sx@n0+6)6=7^sv1#g=q~#>$G2T|V`x

D^yHQB;Qpu$R*}kw^QK_UdE{?Rj|6d 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 literal 0 HcmV?d00001 diff --git a/docs/docs/assets/images/barcode/barcode_field_filled.png b/docs/docs/assets/images/barcode/barcode_field_filled.png new file mode 100644 index 0000000000000000000000000000000000000000..208cfa4a3a5bb7553ee387bb6aeb72ea65d35585 GIT binary patch literal 16655 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�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 literal 0 HcmV?d00001 diff --git a/docs/docs/barcodes/index.md b/docs/docs/barcodes/index.md index 8a8eb2ab20..b9e53b38b6 100644 --- a/docs/docs/barcodes/index.md +++ b/docs/docs/barcodes/index.md @@ -69,6 +69,19 @@ To access this page, select *Scan Barcode* from the main navigation menu: {{ image("barcode/barcode_nav_menu.png", "Barcode menu item") }} {{ image("barcode/barcode_scan_page.png", "Barcode scan page") }} +### Barcodes in Forms + +The InvenTree user interface supports direct scanning of barcodes within certain forms in the web UI. This means that any form field which points to a model which supports barcodes can accept barcode input. If barcode scanning is supported for a particular field, a barcode icon will be displayed next to the input field: + +{{ image("barcode/barcode_field.png", "Barcode form field") }} + +To scan a barcode into a form field, click this barcode icon. A barcode scanning dialog will be displayed, allowing the user to scan a barcode using their preferred input method: + +{{ image("barcode/barcode_field_dialog.png", "Barcode field scan dialog") }} + +Once scanned, the form field will be automatically populated with the correct item. + +{{ image("barcode/barcode_field_filled.png", "Barcode field populated") }} ## App Integration @@ -81,3 +94,10 @@ If enabled, InvenTree can retain logs of the most recent barcode scans. This can Refer to the [barcode settings](../settings/global.md#barcodes) to enable barcode history logging. The barcode history can be viewed via the admin panel in the web interface. + +## Barcode Settings + +There are a number of settings which control the behavior of barcodes within InvenTree. For more information, refer to the links below: + +- [Global Barcode Settings](../settings/global.md#barcodes) +- [User Preferences for Barcode Scanning](../settings/user.md#display-settings) diff --git a/docs/docs/settings/global.md b/docs/docs/settings/global.md index 4b32f972c0..d443084077 100644 --- a/docs/docs/settings/global.md +++ b/docs/docs/settings/global.md @@ -78,7 +78,7 @@ If this setting is enabled, users can reset their password via email. This requi If this setting is enabled, users must have multi-factor authentication enabled to log in. -#### Auto Fil SSO Users +#### Auto Fill SSO Users Automatically fill out user-details from SSO account-data. If this feature is enabled the user is only asked for their username, first- and surname if those values can not be gathered from their SSO profile. This might lead to unwanted usernames bleeding over. diff --git a/docs/docs/settings/user.md b/docs/docs/settings/user.md index 39871b25d3..faaa7aae8a 100644 --- a/docs/docs/settings/user.md +++ b/docs/docs/settings/user.md @@ -22,6 +22,7 @@ The *Display Settings* screen shows general display configuration options: {{ usersetting("STICKY_HEADER") }} {{ usersetting("STICKY_TABLE_HEADER") }} {{ usersetting("SHOW_SPOTLIGHT") }} +{{ usersetting("BARCODE_IN_FORM_FIELDS") }} {{ usersetting("DATE_DISPLAY_FORMAT") }} {{ usersetting("FORMS_CLOSE_USING_ESCAPE") }} {{ usersetting("DISPLAY_STOCKTAKE_TAB") }} diff --git a/src/backend/InvenTree/common/setting/user.py b/src/backend/InvenTree/common/setting/user.py index 793f9bae1b..b318a00213 100644 --- a/src/backend/InvenTree/common/setting/user.py +++ b/src/backend/InvenTree/common/setting/user.py @@ -41,6 +41,12 @@ USER_SETTINGS: dict[str, InvenTreeSettingsKeyType] = { 'default': False, 'validator': bool, }, + 'BARCODE_IN_FORM_FIELDS': { + 'name': _('Barcode Scanner in Form Fields'), + 'description': _('Allow barcode scanner input in form fields'), + 'default': True, + 'validator': bool, + }, 'SEARCH_PREVIEW_SHOW_PARTS': { 'name': _('Search Parts'), 'description': _('Display parts in search preview window'), diff --git a/src/frontend/lib/enums/ModelInformation.tsx b/src/frontend/lib/enums/ModelInformation.tsx index b529e2c49a..6f93a1ebeb 100644 --- a/src/frontend/lib/enums/ModelInformation.tsx +++ b/src/frontend/lib/enums/ModelInformation.tsx @@ -10,6 +10,7 @@ export interface ModelInformationInterface { url_detail?: string; api_endpoint: ApiEndpoints; admin_url?: string; + supports_barcode?: boolean; icon: keyof InvenTreeIconType; } @@ -31,6 +32,7 @@ export const ModelInformationDict: ModelDict = { url_detail: '/part/:pk/', api_endpoint: ApiEndpoints.part_list, admin_url: '/part/part/', + supports_barcode: true, icon: 'part' }, parameter: { @@ -60,6 +62,7 @@ export const ModelInformationDict: ModelDict = { url_detail: '/purchasing/supplier-part/:pk/', api_endpoint: ApiEndpoints.supplier_part_list, admin_url: '/company/supplierpart/', + supports_barcode: true, icon: 'supplier_part' }, manufacturerpart: { @@ -69,6 +72,7 @@ export const ModelInformationDict: ModelDict = { url_detail: '/purchasing/manufacturer-part/:pk/', api_endpoint: ApiEndpoints.manufacturer_part_list, admin_url: '/company/manufacturerpart/', + supports_barcode: true, icon: 'manufacturers' }, partcategory: { @@ -87,6 +91,7 @@ export const ModelInformationDict: ModelDict = { url_detail: '/stock/item/:pk/', api_endpoint: ApiEndpoints.stock_item_list, admin_url: '/stock/stockitem/', + supports_barcode: true, icon: 'stock' }, stocklocation: { @@ -96,6 +101,7 @@ export const ModelInformationDict: ModelDict = { url_detail: '/stock/location/:pk/', api_endpoint: ApiEndpoints.stock_location_list, admin_url: '/stock/stocklocation/', + supports_barcode: true, icon: 'location' }, stocklocationtype: { @@ -117,6 +123,7 @@ export const ModelInformationDict: ModelDict = { url_detail: '/manufacturing/build-order/:pk/', api_endpoint: ApiEndpoints.build_order_list, admin_url: '/build/build/', + supports_barcode: true, icon: 'build_order' }, buildline: { @@ -155,6 +162,7 @@ export const ModelInformationDict: ModelDict = { url_detail: '/purchasing/purchase-order/:pk/', api_endpoint: ApiEndpoints.purchase_order_list, admin_url: '/order/purchaseorder/', + supports_barcode: true, icon: 'purchase_orders' }, purchaseorderlineitem: { @@ -170,6 +178,7 @@ export const ModelInformationDict: ModelDict = { url_detail: '/sales/sales-order/:pk/', api_endpoint: ApiEndpoints.sales_order_list, admin_url: '/order/salesorder/', + supports_barcode: true, icon: 'sales_orders' }, salesordershipment: { @@ -178,6 +187,7 @@ export const ModelInformationDict: ModelDict = { url_overview: '/sales/index/shipments', url_detail: '/sales/shipment/:pk/', api_endpoint: ApiEndpoints.sales_order_shipment_list, + supports_barcode: true, icon: 'shipment' }, returnorder: { @@ -187,6 +197,7 @@ export const ModelInformationDict: ModelDict = { url_detail: '/sales/return-order/:pk/', api_endpoint: ApiEndpoints.return_order_list, admin_url: '/order/returnorder/', + supports_barcode: true, icon: 'return_orders' }, returnorderlineitem: { diff --git a/src/frontend/src/components/barcodes/BarcodeScanDialog.tsx b/src/frontend/src/components/barcodes/BarcodeScanDialog.tsx index 3457d55534..6e2eb50640 100644 --- a/src/frontend/src/components/barcodes/BarcodeScanDialog.tsx +++ b/src/frontend/src/components/barcodes/BarcodeScanDialog.tsx @@ -19,6 +19,11 @@ export type BarcodeScanResult = { error?: string; }; +export type BarcodeScanSuccessCallback = ( + barcode: string, + response: any +) => void; + // Callback function for handling a barcode scan // This function should return true if the barcode was handled successfully export type BarcodeScanCallback = ( @@ -31,13 +36,15 @@ export default function BarcodeScanDialog({ opened, callback, modelType, - onClose + onClose, + onScanSuccess }: Readonly<{ title?: string; opened: boolean; modelType?: ModelType; callback?: BarcodeScanCallback; onClose: () => void; + onScanSuccess?: BarcodeScanSuccessCallback; }>) { const navigate = useNavigate(); @@ -53,6 +60,7 @@ export default function BarcodeScanDialog({ @@ -65,10 +73,12 @@ export function ScanInputHandler({ callback, modelType, onClose, + onScanSuccess, navigate }: Readonly<{ callback?: BarcodeScanCallback; onClose: () => void; + onScanSuccess?: BarcodeScanSuccessCallback; modelType?: ModelType; navigate: NavigateFunction; }>) { @@ -94,7 +104,13 @@ export function ScanInputHandler({ data[model_type]['pk'] ); onClose(); - navigate(url); + + if (onScanSuccess) { + onScanSuccess(data['barcode'], data); + } else { + navigate(url); + } + match = true; break; } diff --git a/src/frontend/src/components/buttons/ScanButton.tsx b/src/frontend/src/components/buttons/ScanButton.tsx index 46676b6033..542656aed1 100644 --- a/src/frontend/src/components/buttons/ScanButton.tsx +++ b/src/frontend/src/components/buttons/ScanButton.tsx @@ -1,19 +1,32 @@ +import type { ModelType } from '@lib/index'; import { t } from '@lingui/core/macro'; import { ActionIcon, Tooltip } from '@mantine/core'; import { useDisclosure } from '@mantine/hooks'; import { IconQrcode } from '@tabler/icons-react'; -import BarcodeScanDialog from '../barcodes/BarcodeScanDialog'; +import BarcodeScanDialog, { + type BarcodeScanCallback, + type BarcodeScanSuccessCallback +} from '../barcodes/BarcodeScanDialog'; /** * A button which opens the QR code scanner modal */ -export function ScanButton() { +export function ScanButton({ + modelType, + callback, + onScanSuccess +}: { + modelType?: ModelType; + callback?: BarcodeScanCallback; + onScanSuccess?: BarcodeScanSuccessCallback; +}) { const [opened, { open, close }] = useDisclosure(false); return ( <> - + ); } diff --git a/src/frontend/src/components/forms/fields/RelatedModelField.tsx b/src/frontend/src/components/forms/fields/RelatedModelField.tsx index d66fe76aa9..85398bac35 100644 --- a/src/frontend/src/components/forms/fields/RelatedModelField.tsx +++ b/src/frontend/src/components/forms/fields/RelatedModelField.tsx @@ -1,5 +1,6 @@ import { t } from '@lingui/core/macro'; import { + Group, Input, darken, useMantineColorScheme, @@ -15,9 +16,16 @@ import { } from 'react-hook-form'; import Select from 'react-select'; +import { ModelInformationDict } from '@lib/enums/ModelInformation'; import type { ApiFormFieldType } from '@lib/types/Forms'; import { useApi } from '../../../contexts/ApiContext'; +import { + useGlobalSettingsState, + useUserSettingsState +} from '../../../states/SettingsStates'; import { vars } from '../../../theme'; +import { ScanButton } from '../../buttons/ScanButton'; +import Expand from '../../items/Expand'; import { RenderInstance } from '../../render/Instance'; /** @@ -60,6 +68,101 @@ export function RelatedModelField({ const [data, setData] = useState([]); const dataRef = useRef([]); + const globalSettings = useGlobalSettingsState(); + const userSettings = useUserSettingsState(); + + // Search input query + const [value, setValue] = useState(''); + const [searchText] = useDebouncedValue(value, 250); + + // Fetch a single field by primary key, using the provided API filters + const fetchSingleField = useCallback( + (pk: number) => { + if (!definition?.api_url) { + return; + } + + const params = definition?.filters ?? {}; + const url = `${definition.api_url}${pk}/`; + + api + .get(url, { + params: params + }) + .then((response) => { + const pk_field = definition.pk_field ?? 'pk'; + if (response.data?.[pk_field]) { + const value = { + value: response.data[pk_field], + data: response.data + }; + + // Run custom callback for this field (if provided) + if (definition.onValueChange) { + definition.onValueChange(response.data[pk_field], response.data); + } + + setInitialData(value); + dataRef.current = [value]; + setPk(response.data[pk_field]); + } + }); + }, + [ + definition.api_url, + definition.filters, + definition.onValueChange, + definition.pk_field, + setValue, + setPk + ] + ); + + // Memoize the model type information for this field + const modelInfo = useMemo(() => { + if (!definition.model) { + return null; + } + return ModelInformationDict[definition.model]; + }, [definition.model]); + + // Determine whether a barcode field should be added + const addBarcodeField: boolean = useMemo(() => { + if (!modelInfo || !modelInfo.supports_barcode) { + return false; + } + + if (!globalSettings.isSet('BARCODE_ENABLE')) { + return false; + } + + if (!userSettings.isSet('BARCODE_IN_FORM_FIELDS')) { + return false; + } + + return true; + }, [globalSettings, userSettings, modelInfo]); + + // Callback function to handle barcode scan results + const onBarcodeScan = useCallback( + (barcode: string, response: any) => { + // Fetch model information from the response + const modelData = response?.[definition.model ?? ''] ?? null; + + if (modelData) { + const pk_field = definition.pk_field ?? 'pk'; + const pk = modelData[pk_field]; + + if (pk) { + // Perform a full re-fetch of the field data + // This is necessary as the barcode scan does not provide full data necessarily + fetchSingleField(pk); + } + } + }, + [definition.model, definition.pk_field, fetchSingleField] + ); + const [isOpen, setIsOpen] = useState(false); const [autoFilled, setAutoFilled] = useState(false); @@ -144,37 +247,7 @@ export function RelatedModelField({ const id = pk || field.value; if (id !== null && id !== undefined && id !== '') { - const url = `${definition.api_url}${id}/`; - - if (!url) { - setPk(null); - return; - } - - const params = definition?.filters ?? {}; - - api - .get(url, { - params: params - }) - .then((response) => { - const pk_field = definition.pk_field ?? 'pk'; - if (response.data?.[pk_field]) { - const value = { - value: response.data[pk_field], - data: response.data - }; - - // Run custom callback for this field (if provided) - if (definition.onValueChange) { - definition.onValueChange(response.data[pk_field], response.data); - } - - setInitialData(value); - dataRef.current = [value]; - setPk(response.data[pk_field]); - } - }); + fetchSingleField(id); } else { setPk(null); } @@ -185,10 +258,6 @@ export function RelatedModelField({ field.value ]); - // Search input query - const [value, setValue] = useState(''); - const [searchText] = useDebouncedValue(value, 250); - const [filters, setFilters] = useState({}); const resetSearch = useCallback(() => { @@ -377,58 +446,68 @@ export function RelatedModelField({ error={definition.error ?? error?.message} styles={{ description: { paddingBottom: '5px' } }} > - { + setValue(value); + }} + onChange={onChange} + onMenuScrollToBottom={() => setOffset(offset + limit)} + onMenuOpen={() => { + setIsOpen(true); + resetSearch(); + selectQuery.refetch(); + }} + onMenuClose={() => { + setIsOpen(false); + }} + isLoading={ + selectQuery.isFetching || + selectQuery.isLoading || + selectQuery.isRefetching } - }; - }} - /> + isClearable={!definition.required} + isDisabled={definition.disabled} + isSearchable={true} + placeholder={definition.placeholder || `${t`Search`}...`} + loadingMessage={() => `${t`Loading`}...`} + menuPortalTarget={document.body} + noOptionsMessage={() => t`No results found`} + menuPosition='fixed' + styles={{ + menuPortal: (base: any) => ({ ...base, zIndex: 9999 }), + clearIndicator: (base: any) => ({ + ...base, + color: 'red', + ':hover': { color: 'red' } + }) + }} + formatOptionLabel={(option: any) => formatOption(option)} + theme={(theme) => { + return { + ...theme, + colors: { + ...theme.colors, + ...colors + } + }; + }} + /> + + {addBarcodeField && ( + + )} + ); } diff --git a/src/frontend/src/pages/Index/Settings/UserSettings.tsx b/src/frontend/src/pages/Index/Settings/UserSettings.tsx index 0bc5837fb1..55a62ede4b 100644 --- a/src/frontend/src/pages/Index/Settings/UserSettings.tsx +++ b/src/frontend/src/pages/Index/Settings/UserSettings.tsx @@ -59,6 +59,7 @@ export default function UserSettings() { 'STICKY_HEADER', 'STICKY_TABLE_HEADER', 'SHOW_SPOTLIGHT', + 'BARCODE_IN_FORM_FIELDS', 'DATE_DISPLAY_FORMAT', 'FORMS_CLOSE_USING_ESCAPE', 'DISPLAY_STOCKTAKE_TAB', diff --git a/src/frontend/tests/helpers.ts b/src/frontend/tests/helpers.ts index 36a14672d5..a7dfed6e8a 100644 --- a/src/frontend/tests/helpers.ts +++ b/src/frontend/tests/helpers.ts @@ -54,7 +54,11 @@ export const clearTableFilters = async (page: Page) => { await page.waitForLoadState('networkidle'); }; -export const setTableChoiceFilter = async (page: Page, filter, value) => { +export const setTableChoiceFilter = async ( + page: Page, + filter: string, + value: string +) => { await openFilterDrawer(page); await page.getByRole('button', { name: 'Add Filter' }).click(); @@ -116,7 +120,7 @@ export const navigate = async ( /** * CLick on the 'tab' element with the provided name */ -export const loadTab = async (page: Page, tabName, exact?) => { +export const loadTab = async (page: Page, tabName: string, exact?: boolean) => { await page .getByLabel(/panel-tabs-/) .getByRole('tab', { name: tabName, exact: exact ?? false }) @@ -140,7 +144,7 @@ export const activateCalendarView = async (page: Page) => { /** * Perform a 'global search' on the provided page, for the provided query text */ -export const globalSearch = async (page: Page, query) => { +export const globalSearch = async (page: Page, query: string) => { await page.getByLabel('open-search').click(); await page.getByLabel('global-search-input').clear(); await page.getByPlaceholder('Enter search text').fill(query); diff --git a/src/frontend/tests/login.ts b/src/frontend/tests/login.ts index 7571b7dd1e..b4a647a878 100644 --- a/src/frontend/tests/login.ts +++ b/src/frontend/tests/login.ts @@ -14,7 +14,7 @@ interface LoginOptions { /* * Perform form based login operation from the "login" URL */ -export const doLogin = async (page, options?: LoginOptions) => { +export const doLogin = async (page: Page, options?: LoginOptions) => { const username: string = options?.username ?? user.username; const password: string = options?.password ?? user.password; diff --git a/src/frontend/tests/pages/pui_purchase_order.spec.ts b/src/frontend/tests/pages/pui_purchase_order.spec.ts index 1eb5be4001..2ce5412e13 100644 --- a/src/frontend/tests/pages/pui_purchase_order.spec.ts +++ b/src/frontend/tests/pages/pui_purchase_order.spec.ts @@ -225,7 +225,9 @@ test('Purchase Orders - Barcodes', async ({ browser }) => { // Ensure we can scan back to this page, with the associated barcode await page.getByRole('tab', { name: 'Sales' }).click(); await page.waitForTimeout(250); - await page.getByRole('button', { name: 'Open Barcode Scanner' }).click(); + + await page.getByRole('button', { name: 'barcode-scan-button-any' }).click(); + await page.getByPlaceholder('Enter barcode data').fill('1234567890'); await page.getByRole('button', { name: 'Scan', exact: true }).click(); diff --git a/src/frontend/tests/pages/pui_scan.spec.ts b/src/frontend/tests/pages/pui_scan.spec.ts index 78c1e1fee8..139e122724 100644 --- a/src/frontend/tests/pages/pui_scan.spec.ts +++ b/src/frontend/tests/pages/pui_scan.spec.ts @@ -1,24 +1,37 @@ +import type { Page } from '@playwright/test'; import { test } from '../baseFixtures'; import { doCachedLogin } from '../login'; -const scan = async (page, barcode) => { +const scan = async (page: Page, barcode: string) => { await page.getByLabel('barcode-input-scanner').click(); await page.getByLabel('barcode-scan-keyboard-input').fill(barcode); await page.getByRole('button', { name: 'Scan', exact: true }).click(); }; -test('Scanning - Dialog', async ({ browser }) => { +test('Barcode Scanning - Dialog', async ({ browser }) => { const page = await doCachedLogin(browser); - await page.getByRole('button', { name: 'Open Barcode Scanner' }).click(); + // Attempt scan with invalid data + await page.getByRole('button', { name: 'barcode-scan-button-any' }).click(); + await scan(page, 'invalid-barcode-123'); + await page.getByText('No match found for barcode').waitFor(); + + // Attempt scan with "legacy" barcode format await scan(page, '{"part": 15}'); await page.getByText('Part: R_550R_0805_1%', { exact: true }).waitFor(); await page.getByText('Available:').waitFor(); await page.getByText('Required:').waitFor(); + + // Attempt scan with "modern" barcode format + await page.getByRole('button', { name: 'barcode-scan-button-any' }).click(); + await scan(page, 'INV-BO0010'); + + await page.getByText('Build Order: BO0010').waitFor(); + await page.getByText('Making a high level assembly part').waitFor(); }); -test('Scanning - Basic', async ({ browser }) => { +test('Barcode Scanning - Basic', async ({ browser }) => { const page = await doCachedLogin(browser); // Navigate to the 'scan' page @@ -39,7 +52,7 @@ test('Scanning - Basic', async ({ browser }) => { await page.getByText('No match found for barcode').waitFor(); }); -test('Scanning - Part', async ({ browser }) => { +test('Barcode Scanning - Part', async ({ browser }) => { const page = await doCachedLogin(browser, { url: 'scan/' }); await scan(page, '{"part": 1}'); @@ -49,7 +62,7 @@ test('Scanning - Part', async ({ browser }) => { await page.getByRole('cell', { name: 'part', exact: true }).waitFor(); }); -test('Scanning - Stockitem', async ({ browser }) => { +test('Barcode Scanning - Stockitem', async ({ browser }) => { const page = await doCachedLogin(browser, { url: 'scan/' }); await scan(page, '{"stockitem": 408}'); @@ -58,7 +71,7 @@ test('Scanning - Stockitem', async ({ browser }) => { await page.getByRole('cell', { name: 'Quantity: 100' }).waitFor(); }); -test('Scanning - StockLocation', async ({ browser }) => { +test('Barcode Scanning - StockLocation', async ({ browser }) => { const page = await doCachedLogin(browser, { url: 'scan/' }); await scan(page, '{"stocklocation": 3}'); @@ -71,7 +84,7 @@ test('Scanning - StockLocation', async ({ browser }) => { .waitFor(); }); -test('Scanning - SupplierPart', async ({ browser }) => { +test('Barcode Scanning - SupplierPart', async ({ browser }) => { const page = await doCachedLogin(browser, { url: 'scan/' }); await scan(page, '{"supplierpart": 204}'); @@ -80,7 +93,7 @@ test('Scanning - SupplierPart', async ({ browser }) => { await page.getByRole('cell', { name: 'supplierpart', exact: true }).waitFor(); }); -test('Scanning - PurchaseOrder', async ({ browser }) => { +test('Barcode Scanning - PurchaseOrder', async ({ browser }) => { const page = await doCachedLogin(browser, { url: 'scan/' }); await scan(page, '{"purchaseorder": 12}'); @@ -92,7 +105,7 @@ test('Scanning - PurchaseOrder', async ({ browser }) => { .waitFor(); }); -test('Scanning - SalesOrder', async ({ browser }) => { +test('Barcode Scanning - SalesOrder', async ({ browser }) => { const page = await doCachedLogin(browser, { url: 'scan/' }); await scan(page, '{"salesorder": 6}'); @@ -103,7 +116,7 @@ test('Scanning - SalesOrder', async ({ browser }) => { await page.getByRole('cell', { name: 'salesorder', exact: true }).waitFor(); }); -test('Scanning - Build', async ({ browser }) => { +test('Barcode Scanning - Build', async ({ browser }) => { const page = await doCachedLogin(browser, { url: 'scan/' }); await scan(page, '{"build": 8}'); @@ -112,3 +125,32 @@ test('Scanning - Build', async ({ browser }) => { await page.getByText('PCBA build').waitFor(); await page.getByRole('cell', { name: 'build', exact: true }).waitFor(); }); + +test('Barcode Scanning - Forms', async ({ browser }) => { + const page = await doCachedLogin(browser, { + url: '/stock/location/index/stock-items' + }); + + // Open the "Add Stock Item" form + await page + .getByRole('button', { name: 'action-button-add-stock-item' }) + .click(); + + // Fill out the "part" data + await page.getByRole('button', { name: 'barcode-scan-button-part' }).click(); + await page + .getByRole('textbox', { name: 'barcode-scan-keyboard-input' }) + .fill('INV-PA99'); + await page.getByRole('button', { name: 'Scan', exact: true }).click(); + await page.getByText('Red Round Table').waitFor(); + + // Fill out the "location" data + await page + .getByRole('button', { name: 'barcode-scan-button-stocklocation' }) + .click(); + await page + .getByRole('textbox', { name: 'barcode-scan-keyboard-input' }) + .fill('INV-SL37'); + await page.getByRole('button', { name: 'Scan', exact: true }).click(); + await page.getByText('Offsite Storage').waitFor(); +});