From 0701e8176c79739d48b1d5e81cb2ff7db20ccf51 Mon Sep 17 00:00:00 2001 From: Sayem Shafayet Date: Thu, 18 Oct 2018 17:33:40 +0600 Subject: [PATCH 001/224] npm run deploy (all) --- client/src/torque-app.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/torque-app.html b/client/src/torque-app.html index b658c667e..ffeadae80 100644 --- a/client/src/torque-app.html +++ b/client/src/torque-app.html @@ -359,7 +359,7 @@ type: Object, value: () => { '%__deploy-start' - return JSON.parse('{"build":130,"datetimeStamp":1539667221109}'); + return JSON.parse('{"build":131,"datetimeStamp":1539862178674}'); '%__deploy-end' } }, From 6fe6088ab1ef1963b89966e89143266b2a00a2af Mon Sep 17 00:00:00 2001 From: iLGunners Date: Thu, 18 Oct 2018 18:02:13 +0600 Subject: [PATCH 002/224] file names changed --- .../instruction-1.png | Bin 122374 -> 0 bytes .../instruction-2.png | Bin 29591 -> 0 bytes .../instruction-3.png | Bin 36984 -> 0 bytes .../instruction-4.png | Bin 83133 -> 0 bytes .../instruction-5.png | Bin 123182 -> 0 bytes .../instruction-6.png | Bin 134232 -> 0 bytes .../instruction-7.png | Bin 176389 -> 0 bytes client/src/page-edit-product-category.html | 274 ---------- meta/api-docs/add-product-category.md | 59 --- meta/api-docs/delete-product-category.md | 48 -- meta/api-docs/edit-product-category.md | 58 --- meta/api-docs/get-product-category-list.md | 66 --- meta/server-db-docs/product-category.md | 29 -- server/src/apis/add-product-category.js | 50 -- server/src/apis/edit-product-category.js | 60 --- server/src/apis/get-product-category-list.js | 39 -- .../src/apis/mixins/product-category-mixin.js | 30 -- server/src/collections/product-category.js | 102 ---- .../__depr__add-product-category.js | 67 --- .../__depr__edit-product-category.js | 66 --- .../__depr__get-product-category-list.js | 41 -- .../legacy-apis/delete-product-category.js | 59 --- .../legacy-collections/product-category.js | 124 ----- server/test/test-product-category-apis.js | 486 ------------------ 24 files changed, 1658 deletions(-) delete mode 100644 client/images/bulk-product-category-instruction/instruction-1.png delete mode 100644 client/images/bulk-product-category-instruction/instruction-2.png delete mode 100644 client/images/bulk-product-category-instruction/instruction-3.png delete mode 100644 client/images/bulk-product-category-instruction/instruction-4.png delete mode 100644 client/images/bulk-product-category-instruction/instruction-5.png delete mode 100644 client/images/bulk-product-category-instruction/instruction-6.png delete mode 100644 client/images/bulk-product-category-instruction/instruction-7.png delete mode 100644 client/src/page-edit-product-category.html delete mode 100644 meta/api-docs/add-product-category.md delete mode 100644 meta/api-docs/delete-product-category.md delete mode 100644 meta/api-docs/edit-product-category.md delete mode 100644 meta/api-docs/get-product-category-list.md delete mode 100644 meta/server-db-docs/product-category.md delete mode 100644 server/src/apis/add-product-category.js delete mode 100644 server/src/apis/edit-product-category.js delete mode 100644 server/src/apis/get-product-category-list.js delete mode 100644 server/src/apis/mixins/product-category-mixin.js delete mode 100644 server/src/collections/product-category.js delete mode 100644 server/src/legacy-apis/__depr__add-product-category.js delete mode 100644 server/src/legacy-apis/__depr__edit-product-category.js delete mode 100644 server/src/legacy-apis/__depr__get-product-category-list.js delete mode 100644 server/src/legacy-apis/delete-product-category.js delete mode 100644 server/src/legacy-collections/product-category.js delete mode 100644 server/test/test-product-category-apis.js diff --git a/client/images/bulk-product-category-instruction/instruction-1.png b/client/images/bulk-product-category-instruction/instruction-1.png deleted file mode 100644 index bd5c2169b39f76f4b138659965c0f664c0cb6515..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 122374 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 aAqi~ImN(ihIe%b<&&z#kxkdRe75@+R#n5^H diff --git a/client/images/bulk-product-category-instruction/instruction-2.png b/client/images/bulk-product-category-instruction/instruction-2.png deleted file mode 100644 index cd2b29dbd0795d04177529dc88365dd210ee9c2d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29591 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 diff --git a/client/images/bulk-product-category-instruction/instruction-3.png b/client/images/bulk-product-category-instruction/instruction-3.png deleted file mode 100644 index bc88ec5e60cb2688a1b171ac6b29889856d17633..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36984 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 diff --git a/client/images/bulk-product-category-instruction/instruction-5.png b/client/images/bulk-product-category-instruction/instruction-5.png deleted file mode 100644 index 6218b4a8cd984a555c2e831d17c427e1550587bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 123182 zcmeHw4R}*UzV~p6DIN4 ztd`u~XKRXDQgopKuXx~5Oi{4uu6-L+Amv(2@C6%p!3HhMsTNJ%wVJH0_E^H*cg{)E zrr+OfN@t!t$;_OY|Hp6sXZ|yDCTCvw)#?XhufOej06=WoLn}1^{7V*$uf+ZZem54y z{3raGTc}K1gJD?H7Y{eXBwwHUYrXb~P5R$&DA)+Jk3Xf?Z!927K8Xfk5lCB^yyodS zVBz*JpP6&=Jpp&@{y*KKKH@B;Anz1^JOYkobZv4$#?Kj;VHMg_#>P@mow?372`?v^BYvuNH zZ@skF_US)5!PO|yFV{YyQ{NsntpZIp^Q)ikC7wJs_vU$!(cGAXr0mU)-7ks+l{vfm z_FrRJ!8qCOe|q2MWx+&be_#FlVE!NMKyX7)%(?R)eCnBW^dFG^jrOg}`udyo^bc3A z0&w%v^vALv{I@R5`j?&doT+z*tFpd(bq4PR@E$vUStRN_@!#L5Gj5so=9?Y?qJH+s z^Z%92`ok{|>I(nVu<8lf-06{Iyj;JkE-w8@T5L8*VnCPIcjerab09NBezaePxAC_hz~pk2Kfw5zG&IAMIE?)de87C*?JFnZS@ zp)2&8FHm1S_)OW8^RENI|9`1J{PE5C<>XSBh{%8zPKR%nFHRi)PO+I>-dtbr8NT$j zUZS(T9SDUt&rn|oK$GoHZL2nCLth9(lxU^iY~FaS`@(RWjJ|zk{^q4;|McJAh3BF; zLSW(%xboNc>^Ex26xJ8Q!2fr$EP{O@lyK^2iX1RA=<90J@w(3Y3+}n6O}_WF4?LPj zL^DYEi4(Q+%j%~$9FGE>-+mX9TJ++KT{i9f;>)(%(-L}siMnalErv%gS-imWr5Au% zr+H7aqu3N6;QQ+G4U2lspRbG5d0`L@0A{@HL< zuEpz7Kg;M^EdB2Ej;+OSE!g?U{28hze0OIT|>IU@&4gd#qVGLv=ej!*_Kxx zkJJ}>B6m29p?rG$1J6HzG4}&luCv_}b-XD%Cv0vpOzms&@=f<7C6w7Nbb|Ra(W6A& z{b7T_s0`xqeo^kh0)E!#% z9C6526Wq4ohmSt{J6LinxZ%FMiu(XK|6h6bh#8jkmmNJv?2k!UPUmTx<0f+Vq1O() z20w>h%8sf(-W0*j87BIqMLRxqHN}b|V#DHCbWBkF>gE%B_MYV)qji39{Keuwe&B&K z$Jr7(zo^|seUbF5`~P*eXpHonE(ET-^TCI%yhqj2KX4LU`NrjH22t2_i8DN3G*cW3 zqt3I(8*W#T-D7He<;s|+k2h{km=Sx#hth1+*VqNF_3$|&=)4-0kiK=RWYiQQrNaD- zl0SgGd!Jlq>SX_okh1yt$9_H&=e(oCv-AAH-#+%Ixm@%8ug%ZrM9NO0@P$*jnw_xr z`I3ic=ac%;wdc$zJr~#f{_&bOC`0}aVD1%gJ^stp&yaUUuI%U#sK{-bNs?@8&gkhc z{B-f{FG*zPTr>DgjUtc>I`*jBlF=bh-B64zf)0Ti8*Zo;LI5Fv5I_iAg9yx`XNMoO zEPieh_5xgkMuAj92p|Ly0<#=}h1D=6eS`o)03m=77y<#_M<0DO#AS2| zA%GA-2p|Ly0n|h=WA9))7Fi@mgQu1R;PBKnNfN5CYdS0u%oq z{cyD4|7ah0;DPWQ6rXkkKL7mlB}z~pQ%w~YPgK8DW|>`LSvkm z2LVKjrQ?JVRYwRQ1P}rUfolN)ic0tf+w0778qAuymjZ2$Dl zr!{F$A8(&feFFBpg>wYTa(AGctk=9sKvZ`pQDn*y2v~_&iT-prUw7$>X!QYqcvg7a zwn-!_cCk5LJ5@u6Um8W6Gln_Qp>Rul$|kdOP@cmRW{wbor1G&!Gdz8IcS`hiA!%a; zBB|C;Jpi{JaE2#2T#06zAxjazP$UuwL<=Ru;B&EDA<$Zx;b_9^pBQA<^#Cux6~uFP zZ@+r}gId8VbN~E)1&7Y?QYt~0wR9H@xC~N(dS8$>U9J=f5(sOs>CYM@(R1cp$9?96 zBC(3{i`|t6iG-U|tRP;RlKZUH9qPWu*(e7OA1)uqUI2GBN+BhGo!L-59y=D2?{sv9a~9QQqb9$rhnxs8CxJ$iu`wua zo5x-xu;vh@=lgQ$2B+_ZQK#MI*@S(8(b44bczhlgrEE451-5Yv9>OHRG}Jd;z|++v z*smqK992&lekMg9CWSp2V~Yv38&sY{ls(L6Gp8+9uZqm%Xn#UkK%ObhMOAa2aydl! zpZ~n_&wrjX$zxQnws7$_N8#d;a1WVNo5!(FC#=^i(n?(%--&CKk%$36z-8G^IeNQ8 z{I@4Mpk7!0>3cuu zX@-)=WH-<4Lu4Y7JRw;^dbaB;FZZL??lR{nBzSy09xqvKuyFsb9;+C96v9!H$7(SLUZk65=IdiA+;Ez=*dWACxh21X%pHnoHNK)k;~sIK^;` z1>II4R+hOyok3Y(^H%47QCcarKuy7}63&D~MCo&C z$wIXm+D5Zguad;WNmPO&cVDp6gQEHD#afvdPOK!_9nN46?5#1VrSb6z@Y7wAaqZ1j zh@s8I$BVl&{lUz5u|y&#%1;mNcQA40Hgc)UUPps~Q@>1-x5JQv#}h}GE^weMjGNk$ zB4Ouwl9U1~%|Ya^D<$NRsYJ3YCGUVc%t!QVlK}0$D3GNog&21!PQLB1<3yQ~&!+tL z=d?1Y5xiMeXiHD^5p`6G0RCO1K2zj4W@maU;y*&-YgypOsR%!v)PkzYW8Jzk_Wf2S zlT@*quyIi)Nn~j}2Q3pfM#hB8HAbt2^qUL^SPeprGH|;)UnCR>u$e?`x5E~P#s;lR z2D=paNy#z0xEZH)y+HPCXz*7<2v(HB1an9$P?b_egnE@ik|0sDR!39owdPM;rgT~E zkv>hs5fA%fj^)MUdEH|L*7B9*G&J>Fw52cRs#m2bmPr&_Duc!Y;L5>jS$qPNN~R83 zR2Z?rq7jV7WsBvUGAo-Pa2pk}5DrpgYw zNLHu~HC&sWFO>-hYgb3)E|qf&knEa-ByV?lt_FGyNqkbCwHXZEaSh-OqoOBcUf9)I zm{f1ha2tyH&I8=LNkg-?w&~KPCPyKU$}6+86?ni^N2~YLo;Y(t2h7A%G_k+-gtL}t zGns2xQ*)INg%?y7*QG|c!<<+}Y^e;|a~Cu8Gfo5`{xxpt&e~dg!-*}x{2bNOXC)h{ zjbeUAMdSP1q!Ts59fA&qMYr4Sz~E@9)h%$B=jdF?mPU_$EB}Az=s}|1fnkc!G%-u@^ zZzY*!iQejfEE;RFJSBwjba(N75uQSmfmt@G^}DV1^IoNBLRnm8>Ry*a?b>Od)m;q3y6x zTqE`aVyqc4a&4N(QSa#X!-KTDo_Hl%Uk|6?ZqouQnP2G#?(z+!BZ+E&&2+9dtJO#p zcMYuu5QPVr`dWH5Rr|i9@rcG#2CWV!_nVY4QhiOstC_6NA0lAMga)gkjt0G^x2oEq z$Ru!Bq1|Q=6jr@|!r`P8^*a3_F939T4q;0)H8eFgx-81`y4>9%-#i3^?n@DghWnt! zlR+vmp(Nf6Sor1qcPwWBuW0xAwH9E?~ zwj7eZsRs$sfQI*`@i6ot4x^~b@QMoz*>Mfv4hNw;QQ3TXafRxPUf*Q|U1Y^iIXt#P z9CnC2p)yJ0Yo=Kn;YEv*wQ|;|aB%{LC#$3}FcTN!z~lA7MTx2!TDeijW&*x^6CtUu z>Y6onQAI!FLjdBRs!_$UMDRp0=J9yPKM@>IBCoT2cWrcMOmi9+mtcDQBwudb>w2Um*%JGG%>Z-dKeunF!Yg9>ViU(DugAVOO$31Vzz21 z1YET>v^<-@VP=fnc#N*vi2LiR>_Vb2ovi{E5oD^z`VRLvHdLwiFJHCCVc)J~>dpEz zTyP6~j#%Ec_>pXCY_eo6fWEl<5j6>EtK^r0WS;J!NTT zrSw)?>|%mk&c%;Vt|U^!T9-I$y}2UAQ;zR2?FC#eXf?W-J-G+m*7d2D{wQO^>-?HIz_} zmJT>0WV~)hoE5x;VVtSK1P@LSf^iI%;eyZahs#rv_@ZDEbntK=+X;3hXY4WN zd^X1395DiZ&`8*MPf!c5W3Qq7odzF(Iexkw5Nt73#ZgoFE=ysW&drk%1t#N0vA5Ol zsmqN&%GQISqa7&O+c3lAshwS%g?lSuSU(s;!O5q!xO{i?HA03qk@wD`GA(477R1VN8yPn|;_2 z=)mH{et4e-SqC$R{Ejpu|nrqZMPq0 zhyU&!3dK<4^k5bE4f{vFwxsdZ^d5z~uc64u5D+Bcn2= zQbNM&c{R0f)n0Ac&V&yFyZkY{1p-cXBDjF9>u+{5eDQ$dGS!8|E1uaTG6146X6>yjb-w_(uAjzj1Bfet}74E9WXB7^L=zHl%3Uv zX{BjKgvar*rTSWDA$P|QSSu{k>#{n5GD8~Fi=U`0EFvs&M}r^uwiCwYLVKN#ol2b6 zOVa&e)iB{m6v0#!U6sYIuM8W;tq99ONNms=iY(65XiKxo5Zq96)?)GN?B8_nEAdxm z$>8Q1%?9WYCL;K4l{G$PajyZ^K<$qCzX>@zIJWQt5!bL){kfq>r!`Ed>8^E1#*L$55rlsacE8FG`O;~H^j_^k(O6?;5Y z8-fPTZG_IIoKKmC7#?yeLkkaUvH&|Y7Q(?OkGs=?VP z$2J&izdF5V%VQh2AOCK2=#Q|pFsp2v=#Zi7=@e@N7`~WYY=YY+sCQ@vhG8l3q}p^* z?LI2FNx^%xGev>1%m*}Xo>=P@->!k7xw#6iw9A0SPICdTEDTW%I z4P0%wTsW-9TIfc$`5AY$74Ej_8x|>5cQsgEal&qcxo9Tj+KLN(iv-jDR==Mw5es1o z?l_?w(5T=b^K5B~$`D*-7bc3?%_{+*$WSrW71qmaJ*K%*zdp2iD@bUx-#oE8O=Lg7 zS!TPde65T;;=z)&%F7mt+n)s5-BfHBoVM0i!JP~8n&y4yJ{+#bRuk{C0~^xZ`eN} zJ8T&5CsxB+vKBWQ*M+)V$aA1}m!+ve4OQ6;FZ=DqD%n=MA6nPa2FpQi!xlqRha7mC z3O+6u2}PbJH#9jA@LH9V_egOl1UwgmYfirOJIz_{-JTtAzN@e>$HUt{1K>_?W z>#+_+k_;?RsiX~+dxP7^{LUS@aBo(y>~P-^iAJ-9aw@dxVXd=NtKwO^ci0S}lma%{ zQ7YKc`|IJhWu-a^193tDfZNg7dd%4>+r`1}EI3XbyyNQc4x{dWa^GvOANoHh-nYK~ z+PE4GR}LAiNyvPkVNl1Wx5QsBOa=kyAFJ>XweF?5+kdsN3>lAPt}T@I`OhR+fcSQ8d12i zSZbj`YR{9Ws5K-Nqf}xHyZNJ65bF+@6{ktDNQ9?a_{A2(J-se>Cxn@W27yH-mMM}5 z%L0Qb8>`x)HOG*J%iVgoc^U53OfrEjX8m%2r5dXWqD`r6rX-zWk~NC3TeX|uh$EQ1 z*kW8@QNulq30ixinc9p4VHWke)>Eut`AP{Zrc&J%>i+uEU979BE)*TrDHaL@w+IAq zpB+O~9oZ%Zm@3~Ots^AMWD1QLY%=K*9s0CV=hZF}5vNE{E=*2lVfJGfk;Bc|TCL`4 zOrz;F9pI;Gl@~44C;hP)VUCwt&HZaMRwP|#rjyKet4Vo*036iI3|-0dM(i(pn?vkb|JDb(qV zIW1m2vh6Rz3{vHHgA9Uwpc@RBLLOsQ%VqJ)@)}ajCU%bwUbvr(H3#;yY6s@|wYCY806oE>mGVj8nfthaG4z zF6X$xe_pJJ`qNKoq4xqf^qwkBx2GI|K-Ug3BT#lSiUa`+A4*LI7eo^wfDk|kAOr?O zfN||MSQqL}1pVS*rBMEqAkcr|bV}GFCI|t90777vBhXq&;3Ab(Y}!MPY|qv#$7|Bn zN3+98hZr$H2p|Ly0s|lrmt~>5{%+QDb#bA?$Nt?0g{emX%?_s?bHouLfDk|kAOs=~ zfjK8no}5&n;Qp;irH_b53 z9>bQFmL*G;Odfqic?<|l*M_v|;yVV2rlra?gU?hc^26c$8>XeDB9Q6YT|Y#iR5ME? z=&I0j#F^D(NLqvdLI5E!Fal_NFfcB|<#rDy&~wD$phDLX0tf+w0776EAb{NAENEn; zB0>NmfDjk~0;*3_UP5~TIG~Vg9D%){A_xJ5073vEaIGVN+~Kv}!bm@a073vEfDjk~ z0t2|iE6wkfKa=<5lX*`TQy+ah0vaPP(rhziDdHExKV=9+3*j-SX7=QQ$qfFZdKu)k zs(kQMKS#_y;500Y&pXz)yls<6Ry@SvuYJo8nD!sk3RaoD@S4|7)r{&e&;jXe?QM_UkKH#+#sU@tb$IG(HLw3%%59X!7*OSBC zq+IHg2*Z6qMcQ4|rj&U2s_|Py5|!S{9W20}xG~|1T(%ih@zHpmz327WlfY$y4fX2} z-#Ig}edGiSh{*LI&`I0MHm`XQhSj;x{`vFHo-3hPe|PxzclW=`U-!a`FIMQK^qWUN z50xDMue&^(urDw=nmitl&*P$$%|@btJ3e~+vF*v2WFuwrZ-HeDIDnF0hgL z6gy~}a78Yd)#>HTUD95EUY>9+6W;h=pL)ac;a8y}M<$#+A~=xn!D#1*Iq(~$4jINbrDs%N}xT+&Lx=(m3gZq@i;sTMUs+V zNyD*Zz^Pv*$=hK_!Q+V|Jspf|M@sG?kLOUXY<0Q&vvP%qD18nN*wyCC@Z?1s9MQ)o zz>`GSW5Zb@ENORTehNHh1s*RV)0EoT`@V1Roh>Kil6W|}U!}J}Isn)T#L6->>ITIYy*v&caxT;qkiw%D zc)Xd*TIEkK)UoG4VR*J)6R=vGgQzzR*#jy9MCry?ck0-)i=iSJ3XFEc8)xDbiBTAj zo-nC;z1?#ZTw319S-Ew8<+D%dHs?H&ne+U+UkBaQ*B|aFAXaB&X09Qg*l7=DepvEg z&aOAg*00Xoc?N*Czg8M@GT7NeX2G5hgOk32YMm*CwGQtue&UJ8iPdX1@A;?={Ae%B z-}{%AKO3^Q{<&ou8Vs=<6#}2xPqf|t=-MSw07TuUepGqY{_bD<66o(5!FBu_;CYJR z3SD#j@4vk7j=nXNb==YD?-Yt}z@ppj zc3^O{)an+{^-2|pQ-rk<$)`HoBO#!Kg_vY1-yCs5c*-TEqgX9JuXJMw`!+hFf;+Y|`a=*;clid=kwi7P8ynBnX0;lL zVkZ+LH}1`Hml$lVjB7_>WrEQ>QKy}cUgSusA(Urpq|oib5OCYy_b8GT7#syQiNp!e zhj3vLjtAWCW`@2%;|biE*f5(X7UMqpVqX*Msxl2f11SP9aaxvg5hqQn%Y48tV9(B- z1Vt7|rEs03R~RLiA5WbLJi5cYcmIL^xYPB*OYcF?_VpVjh7TA0&Qg8kNTqSb-(Gn3 zE%uQ_R1}~;pznPCog)P+Ivp<=tn(gy^}YArd;52{)|Kpdw{4JGU@o}uLGv92fBfUi zmX|ZZkpqAJ`g-Nor_$nXd)Roe?BPj#$4M_xP-3hIbbi(R_57dBht7}*fXDthHtEM z*HG58G{X~UtQw?WK++nF<^dce3lWB7}SmIP!X#z*R12o%h z=Ve5;l!X&&x=!J#tA(0k_ZZBXjrtrySBab0gH=%oLx7hEdnv+66>ycVh@NwcqMXrD#n6POMv+l+;r$|c zCC+ws*WY>X^4Pz)E`YC3efU}8!@ph;3p1neht)!}#2JQBtMyHdeL2d~gaV z9#Y;6qp|lE?0>uXq1eG|#r-n*UT%bQ+af%wwY3$km$DGfl<-qqCy0uk7ZP{gyz4<* z$4{YT*Y6xlde%E|ou^-blS9+XpWmCWPo6)49+6|imu41LWU?=I_g}D6C3)I(#_rtK z>j^?g9G~A01VTYD$-_kpu;7*x`l!jU*Y0iu7!ML*`0`*0Oc2M}YpD7FzdQjy=ve(8 zALB44$Cn2S1v;=eu|EJAGPVLqd{I}`4j%4nh3+wo5)TszyV4k6fVKNsf7}&>S+Cf@ zPIy?lg1jtHtTULlXJM^S1%8(b0PEr&st_r^Jie%Fya9#9n2QVp@d3i_Oe{`>`GcBb zaHs&!8ux5;sE|htZ@*T%AuG?GXsmrjgTq@YfMKi?43!A@J6IQq33@p}e@8nI^ti}S z;pzSDqexH~o^;!pj&1FQ7PpMQxlTyD>JFn<{jE>+g(Rd}9~nQC#ljL2J#M*WIInp4jxZ7verr1m?v? zhdaTxpMC_fJub3~N`H5F!R2ih)S zxSw_=*o_EcFwo}j8bJrV9&U{lT&D7JY~sUqODZJer&i-sc-`URUBmM@kpNTboV8@g z4Y2hBE`A(eThjPydiT)2AGrYI^F!&_0*v3s@NwwsJl&=E(yWqKvq}I1H_c@bdHdv5 z&_jRY_XGg@bO|ut3m$B7uSX0l&`=06Q@TP&0JmrwXk=Ga#li>zu)iX}>pWx!$ystJW(=;(&l2yZ?9JPhQ=!Nx z;kw%PeLI{VvT%Oi_7m&c_;EMRTe0@QA0Or#p&MuT)G!pHZstd|`M&Q;{`l?J--5W| zYlUi~--$usxedQLJ+*rQqUPhbeMQrq;5K$tboCP55{KjLIzf2ow|aW@{JSUV%kP<< z?C$hJGoo3!L^XVAw1zuUT}ie9Qv!~skw!dmD?)=e<(YHI_xB*|Z)z|Y&P z7ua0-V1*6#)G)MreE|V3W{urjXSSYWm^MFmVKiS0R4Qph!E_Zz9ZPCUcR9+FTlhZwdvCOjh2?Ex8Vj#nSB;Suus$ zbqdEGqa7M8T#PKE{28Pw1}^Yx@uTJDv#@y?_ug^~UqjpzIZ6$vNJqm#9a&_B+l8QA zx(-?!_!8*Nr$&*{aS2?gtNo1K71;J+-A6zDLYfF}z3<*TF1-6TmkIaKy#8$2JAMD( zl&H85)c^Tic1^YI%**-2t}|^TsMXVEa{>6VEjat^$waY;MBt9(l=&aNak#M!fKK}D zH;%?8r!DI%Xup}_TxtFROW#rxl3uKS|3-?pLd{UC&2LZreL7H~4Qb>y|q1&#qmxSgyd%wMcnHoHhOO1p zWOv+qAKzeDLuZ%n&*|5<_7p?0NI>Abxev>dpA<;ZFF}@7ZJQgdgKMEq_u8w*S^^M zY$;@e6q;Z?&OR!f)RCOlMs{DEYfj=7H1U(j35(np+j3sWO3ucSa*iG32G^Iw?vc(5 zjnreh+((tuoqP2M0m4QggupY0gJ$N=eDi(3`R4m(=8iN!d;GEgy6&(3-CrRHvaarX zk7^O*Fa8&ZtI)py|Nl*|?t9?l9+Rr>`zVTzeEK(|ASpE0{?M#@YOnbxyIS`ky1#wK zY~IsKb$(QWARCdoN2|a8>>A|!LDh3>Zr!>Cisv`H|Ni@Ff&ziy+u#0nIte4ifWU_z zez+{1vRv&J1LpJLVb$PM7(oit%6vMm7|K927TF5`zzrKVERskOR=;uMM#9>2(*+nH z0uTX+07L*H01;Tl2ta4QiaQsI1`&V=Km;HH5CIMW=DgPQyDMsr0G`s~!6|Ni>;efO`uXL9n(`xHNFd#3Jz6(ztlX2&nT{`2FW zkN+@(F! z7~^?+h8{hx<)5V?O{g3Ws#c-;EJ5AfpVd0Dvo{NgDGeQsoY@=F9+uXw_^ zui=}e%fiUAH~Po(&;Nv}?{40l;N+7Yg8BZ1Vg+f39g* zKffxguGHFZ{NROuXyQElXQvFNHv`+BQrxpLh5TKAN7wuE4u?fnicE&j*;=3dCH88I zq8NPN5(2t*bJNccH-Ne7#HZ(ubUb@xt;76qF&JH-S1%>>Y6*p38q#YwQwd0yhWu)v zIypi=_lr*-f8oUc408kg$1a$WE0PZ*E-u?0s%Qum=i2C6p zKR@@`51PI)k6Np)$l7l-B)9axxcFjQ$Lr;XfAIAcDkOMqKM5A~E0p_uu>G&x%hcis zlb^l*Ea^$`vbOY*r;hDgF|Udyubg^v#~Vr375k1p^GGqgBPot)8Q|ADav!uYv^ zdyWPF6{M6uaX&-wd3WEx=(|VuewY&bkAE!xQ7XRlpU~Og8RL)7y&L`mVr_mG6rCIo zuRS-?v^!&6cxM8uuEc#0S5+MHT%AO|z7qZA+6TUI|EZ5hkg9JbJ7(h30JBaZ%Uph} zx-Od`&>uej_~rwT6vtd`>RiYSCW}9rJ`z#7(s^k0MCG zw!>326V1Y-B6_4{+F`u{a!_>d@AE19*njwuF|TIJn=&3-`t%i zWOD1zl~MYhil*nEdHt6^H<07VJ(Eaj#UndjcnbVZ8w>?U0N#vOaL9|G!R5L{Yq{Dj z0?6mZ!m7ciFoG1Om3j4C5fp-GEV2^-fbeF#MPdl6Lj)iK5CMn)L;xbNoCvJy_xJe( zw(hG>^yT#|Kt7p+v>*Zy0f+!Z03tA71kN8+JqNx2e9?mCAOa8phyX+YBCtXc5WN5X z`zw@n$Tma(A^;J92tWiN0`oxtuI%T75-bA|fCxYYAOaA96^sCM_J!@dqr;oQ?K?8BeqmdNtw97J0uTX+07L*F03AP61Bd`b03rYpfC#K|1lGLs&O57IL?|9a03rYp zfCxYY?rsE@n5o!U$5B zRu*{{T2gFHh7AOa8phyX+&2LzzA z&jGAeT=cEKdkb7Y3V#=16?ZWd4I%&$fCwZIn9K2xfBdUwwRO*)8_z3Haj$X18j;E# zil@m&9NR@?e>w>x1xFz6CfCW#7c<)pb!{ur^u#i=GUJ}Tn4%-drLe`pnUql6zC!t`lK$ouxlZYg6FdndF^HjCw43 zLb3tFFcG#vb_aZ}*$I)(&EAP7c>a+^aqkrHBAg(f_eN&bi=EPmwmW9$&nkHPj0mk3 zCAB48FdnqXMVb={+KgR=i7H5UqU$eNWF>3X+{^#1Nk!HX_G{_Nb3`)LX`M(SuWA0N zJCrhC`RtTA+Mkgf>Zko<)AD%zNlBCvtwd_}PZ`5$*BlkwC ztiuU*Z-u?>BKK~x>&i?bX>evf$T=OdH<8|Qt8XM64oAa5S~Y4VTRn@I4rOqFYU$6D zz%%_6a#BYnUGG#GVI$4FL5Zg_78VohcB#XCv^T>{vKO81h?;8TX@5jJ5u>dxNcZ!; ze=9)v&wu{YpZ|Q>rYu#za)XcG@|iZxr}dON-3t3o7{o(nqOL1pp7Yl!^I-!);z8#@ z+BeNS0CN55&(W9FC=ip6PlRkr!Tf0ET&k$iNJ$fOpYjq1EuuH*C7JZ3nwjxA zKf{#*5DXNcQHP{~R?_m`3`$eU`O%yzU-(#dXMca4rSr#%`|nu-egtBwlL{X+_uiaE zd_3sbO~`PG1eeGjvv~Ld-n|&n>agfFHQOYTDzp2&Uh@-NT(-T-n=pH9sJEp?Rw0o| zWeROqmKDmZc3{T*5#aD|b9C+0XjBS`w8nCl1KraqRUHZ<7c8n)Po&=n`Xada)rnYX zcv6F0@CM>Yd}60_Qi*1dYmMTWX9wp$rc= zt54!g=$c4TrxK(5qcY`zH+<(0sf1iA=6znL01|=0vQ5|Z-V9cA5(E)GM41~g-&rp^ zU69ym3V(tFevyI*>nR=L>OGs5mGz!{+7(H>PJooaU?S3{ds;x~`X+ zHQQ^5Eiz(%Z$fzxu+#IHLQ(;^QfN{dl|gK-Xc41v)9Jjd(l?PL?x9l}!b56twCZ@9 zqv<;9r}yhLRWivIfNDaEVqBMj@w-JP+orJ$@O^frOCciNNkbIsQt}(d+#Zt5c+}pk z1ztlYsWQ4pkp&qp6tAOnl}sqaw>8`8WEw-(Z?2FSFC@Fe-zt-|dO^ao8`x625jd|_ zZ#K2z%4aiHTz>SDom6t>PO{yc@Qhq;BFtMWvOD{=B}=iUPx6M!&5nkUrG4faE_9T# zjOqqQu3sPVnFO@)5C;$JUMvYKS10nh0!9j1}{C>poJUwNRtF81Nsj#7Y`0WF7K}Iuw z_kUadZ9dKdWYWwa&qYzRWZdgTEWR6qhVqbow;@>V91e$>0Ugs$Sub%#*ejl(+!hFh z!@a7|K^v!SF}G=NFLp3+edPLZ4-sfH(_qpt=CpJ$x-&q3u3tZ66rT0?#u4_6MRzDl z4Gdoo4g1uwD~|2!vp$oCuzNyE`J*lxj=BCdG^*dFZBr#y_rDUTReVEiuIBC8U zX92O$j8dhp4)YE9(ld@Z`rS;vTw)#qTPY!1xtnU~jUgfXF3ML$2S7Jn9@LLn$&TbC zG7BpLgdG}W9-{}}_6?ubh7SR)!>N-tRjJ%O5O}4Lo888#1`G0>!J<@=}nAOK~I|iGr;&*8rp1W8g9o zp>i)N8*(LA|4FLwV{r%o{A;!9GL8sdDMiEK$l@3DSviXM?MDYo>a@gd{5^uJ8+lzA z^?HH|eUqGXP?e^^5wGPTjwrfOP1SRz65U2fk#KknD20|D8Sbv*6bOA-r9i_BunQ=L zl``Iwz5Z)4WTW2p#sO6+;u~-w^#*Da=YxcG25sq}dk9453M_NFi@r)|Yq`M>9KO7P zlyX&5As!qYV3bWH4l+xX*0YRj4<5VV@`_1QJy!+UNK&<7_gnlih*YKNpF;I9pZB1O z9delKaM613{@$M4#HzLpk2v+^pg1b6R$>t*oKZ7JL6F!-wC!|MRZjp;mqrjozLQi8 z(auqh%SgY|Dc1iU+Mef(q%U$m2;)8g-kR1ziW+V}^_xm7Hr`K|Q zB2*vtg-+^)K)bFeNM5^usUdd|;R}NqM~+g|Qbf^84Y`%?`%N-D&THL_BDYBjlw>33 zImN%Jx#@BTRoVt#J-}42kyofvrMg<=8w!9WKG1oph2Q2urBo9U81PKZ!f(?=>QT|5 z5%<%*CFD--<)E;RQe94J2+(S@Ql;L^K`O1l(Jd-{ppvx#Nr~ins zid2{TJVPv($R1E-gI3I06``~ei$*fXO7;q=+=x>v60PU{N=igTXIOXAbpY6z1v?!< z30kGqZcLB|GR{()tj3BfjuJblgySO{RZ+ihN*i<1it`6m&)v?vCOBJ?|3a1Zpi0qZ z@hUB8`s`J=rIYsEnBb;9kq|4@Cmx|-T9gO^u)<5kQBZ~t(O3-Zq{<}i$s!X1Jj&Vm zWD8wRXxH08Qb}2?QSO*XCtG>Bt3PeJF?t#R@QA8+8 z^)f2tV%(T76$ulH!7Wx3Qbc^mE%vtpQ3T|~m~n*UimAKL*z8A~raD7Npdedq);-e5 zSS);@S#pM}2g1ZSqT;Mes*ObMkVUcH%12SrCKc7|G;B(=f{MzfmqdVOibTSMY@kJs zMP@9##3<9c9!IEyz&5Xu`(-Hj&jG5M37CSzRF7Xg$&Oj&ewdzZkn5xAtC2$ zUgnAqOB~pZvn)z`I#t>kKxW96Ym^B`P%$<|FMGeU!Y!gC<%A|(1KK6V5}F1ODZ|iM zEP9)^frXl>b+;)!^O0OnN|m(pre59e9D~CYqi}HA}kK&|uGi1+$M;imVA{Sr+FsM9QT~Qbr#PWI@URaf5mS4D?7W zqss(BOoaHkp*mMUOG;D2FkvO;DmXJ=OuECb)PmfYXSc!_*BSWL1lVNH?sR5W&yp4i zMN_O~QJZYW3XlL^ddprZgG{U0{#Q*FVuAozfwPiVfq=2%ntz!D~mEWz-UQX#qrSaO$;wjCFe(F zL8a2eX?iK*Tg%j84qSBMM7be83Vj?(y(I; z!(w=u5Z!DZ^qY8lHoMC7oJi2^M^p{+gj~W(m8qR{Dt&<%5w?(z|0p5B3|s$<&pFL1(}7Hs+3`%RTpYKR zT5_A!-S1`l8O_tFsZKl=1?}-26pgW95(4HNB2;DY4^pW~KyIxT9U3ur&meIu?An!3 zaQ<5(O|<_R+ifPlyvRVqGrBC!P5i}Rsx9D0T4MGSXD9O4Kfo}mI}b>=SuAJO-L+uI zWp_GrZPZP${6b?My)~DyLgFmYfutpgMOh&Zv@y9$k&9Mj-OHbR_oY2Q-en#9-Nj@3 zf4t}5xi99yXzoSC`a=h>K1=egFsIxozKPrV1c$Ncw-^?LI}#Phb@kZb2|97~DRQQ( zxy>E9V=Y)969KTBxLfM&G|-p0x5I42Pq$b+n<%O}vrzUpdM_}6x0_UFuXd+guo^>e zFkw~;_G=Qq3oy*70vqaggY$ed!$y_*p@8#cKd_DLH3zBGbzhxXB$1Ad#bQF4R18w! zOdIV33I^5*m%3`ymc;uXaix?y9v4T51~uE??Y_y?V@G?S6AJ2d7_HW+ZaQiH1SB#ZqFb6BmpCD_hjE`{uZ}wE=ilE0_CxMtE@m@9l(Dg~ zXvsz?H*CNG!IR!_JeoOdicSz-JhYY~<8Et*lRLM^`7>~Cua~VSGY+Z?)eXwP$-JE5 zV#YOtL1^->hJF`cbWR z+KNtCt5aQb(jU#nVxBn>`Z4}j64qhz9S)|-;dL9P%p#z%HA}w>>ut{ch^-o&)R!8s zs+>pkVnk}{0q5i>1vsiN*SI&@ogEyX%KaA2h=r)qXuugVr3U<3Rpy(oX)L0Irk-`w zNi_32R3~nr)UgoKKyn8u5&ux5qR_7|Fb=H7N9segj_)c z#(GH{Q@f?MW7PZ(ghI$50t-X{uJRWM9IOTrfCxYY7K=cc-pM4tdpt#IlgSt5zK00| zL?Dj{z*T-8!GxqB0uTX+07L*HP>cwydFP#XmYOec=5?u?fb<~(5CMn)L;xbNU0-ZQxJ_s_st&{O`%8$B-}B4e_y2C1KENIKm;HH5P~(Orn=NICoiQswTiflS`Q2D> zJ6_^`%NB|8?93vby_lk7t{V|&7M?2|JEaqCcW}2UMZEL?GgqX7U|q`-Yb#+ z23H})QMctWpk*QvCL#9H!Ne6$@rb8gBjZ$EqR`t@dd`MU8Ee2LKOncNnf|EAaL+2p zc#z&(BUumb?7?JevzxzsjJuUD?}FU)Sk#gd+~}Pes!PyHp7oFM^mw^p3&RRPV7}_a z^k~4{Nr}rd6Jo{A8fBP<9$f)K&c))I8*Sx3%j)s}<<}>FE!^?5mtN{N%b8!D`D88% zx#x%MO{BNn>Kh4%!_jb%R*hQ8R?m|9{JN4uIV-0hmh60!=ydfRkeV_Mfp6pUDFxrb_@IR}?_ zL9RM9^>+3l`Lti`$a^b!hkDdwKkVV}Da|`+NHE_B%v+uCUENmPW$g4ON9VZT+UmkP zyQz)&W~qSmvO4>(eXrHJ^WSq{N%-2=M1Nu@^TtmcTudxd)Ty|u#z81k9(cod{*VgX z^B6gyNiJ?d{ur$hX?Tmm29J{pTq2Vbns&NtyGVD6n;5WHdyU&=5**wKDXVGe1;7FP)$Dd~OV)9}SuP>___Yk{x5y>B9=NUnNQuCb zo^Dr*!53SFO5Jvx8DUt2@$?6X{bpqu_@xW6wv`f}ae|wjgKk%>-elmez(w&Uvo`K_ z`WN4^Pk!~!kL3F-Kvtm;tls*{VS^luSRhmbfifX5aPluyDPcWx-lm@RQ}Q{q0(Pv{ z>$+ZQ)@-jKw#bP6DV5GPu)&qZS%597gj=bQD1Zv_4q~ZfSOndyjvu*K_?3GB(LXT*=lg!9ir94`Y#aE@@zVQ6P`r-z4w-tG!74HiTKP&ndjHTbsD!_4*T zAK36S21`HirYI^1d;C~;=(@9UilF?N^RMi{@LkT~w@lKnpe5s8FJkfC7&MeK_T8kX z(iRv2#GR@uhGzbD<6tLgx~g^tuMdZOj!GA~zc(@Vr}&G9y+erk?C|v)z5_z1u`~1f zcZPnh8)@-f4-H?x?9#J??vDVoy@s7G{BQtlYxr6aN0TKrZ=51IEmm!%_eg~e-NSFs z>l*rLbr+mqy@O%O6}%q4+%3CcF!upGL-t*iuZj+YhKDZ?>c^~Pho3D~T94~Pofgj+ z8+_5!TVZwV5-+&TRa8-|Y|_esg9jlV^1dA=stFX#aYry|1@Mr3*ofotP-v88t}=K9 zo>WkQREkHLYco|WS!EeMvI&Bq(mK6rBhSrP(9z=+)v4uzB@m=sE}yAQIkqzH@y1QA zx6k|h;0k-7#sJvt!yHAC&_IYdeSP?_ialc{UoJ5ZfdLw_mAk2y-q;+q5CIY#2+0o* z4tfLU_alzyX{J=$eT2j|QqB)t)TU+VFxX@+2;`_vn+)CUBKk?I|DxS=N@O`=0Q1nS z4#I!L+$**Y16MK}&{LO!C`C%pJ_wy8l>SI3dK@~gX zFxTOt_24DGJ?lz3(Ok^$3sq{gTY+r^OCm~}u&0Qaax=b8C1)|q2-OyZ8I6Ky34+KQ zNGX?ln}Dkum7D<{^>~5`s)gHN0M?9ZA_4!@n39dQ#K_`s&R1BZMg z1^y>c=yvn4l&SS~BHxe?8TERuD99!`$1IR^2NAw72qZ-xwm2Gx&AUlMFK#=cpJ|*6 z%yzLg$1=TpNqU35y9ofpvMgGX?rVXt1Z5{!Hp(JsDNxl!Kub!I==ek`Su~&11mU@g z2fMbFtXWUkSp(exRJbsLhxMp zm4)iVzR*d%FspF@&jlq|tyaxjA-qzGhQpDmvCol$p|}yqQJoM3-`8yx(dxSHlD2LG zn9R-LAjB9J{lHD12}W3<@)_qVyQb|<6%Qt07cyG_YkJ!+Klx8B=IXEK(be-U3FVEZ z?ndtNr`T05-D%X-vtIv!X)oL?jj^k^&qnM-x*f=8Vm9+#9-upaMVy0Gud+ zipo+8sT75op8}CEF=L2@qpZ(bEwLv`#3#@)X)F#JWIe4iNqe&1gaD6@0V9w>Mu1}C zWEvHU(D4{I(MSfd{$&qx*g@|jsGA~UhsAbKkB$Kmgh>$)G}(HpLa}@bgjjMS1(-$I zYb*=!LB#1yvP1&AZ1Y#u3q!?@tw8-p?6=B$yG%4}36jV4U$DY9{Mg*N~|og5(I7Kq#86##x3r zClVNx{7j6EjIqKkQv;M6+t;!h2d);1rYCCL>H>xpg+Pw#1Y{FP4Nq#UdlAzfDKBYu z2Z2B7^px7VUm~sgC{{~Z4_Vb4ixHTt&i+rI^wL3jOGkCSZxDzC-F`&Xz`gzhL0G9W zwUbVzFAzg?nc$4G2_q>gMUh*v#PN2V)SD;omuxz@#GLK z!$hdc;2)$?)`6=R4+_hKy3XNO>eDN-S>)nuGzPfi3b3&#E5w1F2&YR4<@(N7^qmL` z-r_Wn#uG{xn16g83&#=e8bc-kSPsT4dCk40k$!tG}uKsrx|4!r}s&IF9aYWkVz}T z-cStL$}9V|P)H!vZA5V?&V)iN!tdnAhqI_r=}5b)AI6;N>*8v8sodmoQ55d^z)tyFFJxsnjb zQJr|vz}p{G0NxiT%1QZuK*|zk*yz=XJn4AA( zdzueFIsV?%&+smi`Ej3PuZ}wEPk3-77IGhRF`EgZ3_-;hdV?9}oFE(OcL(^rGT}A? zzv6LU<#L%*-5%hFKp2li<04$jff>2ra9?KGTQUAYF8|8aYI&gdc#tCvPV2nbZ0rdk z0=cdp8$3Y+!wf>?OjmQ8JHkaX1MBT`@~8YF?tZUGshT?cKkhZFbawumR;XAZ#kk3V zuu4rtoHRJs0|=fr5_MfZ7Qw_=cm((aFx7}S`$5LEfP#IM7}(z^V2B6YeLNH~ulwu- zQKdjOs??hU&Qp9Q%g}v3oO^2vL@<~~NXH?YqaCC7btT{{Afv_ERHS!*vrp@EroBGL zrdYRw@Ex;yfzpB<@OOQSM_$bBY=_5O{t1ov>wpIQ;@|GUwk}$T(;Oa*# z2Mt6g!_B2pOi@d@PMf&xl3J5RP8yXpI4+jfsZ^+!1>9{bgSpyZkW#3 z;!;Ajs??!T2AG>Maf8FzCaErFo}(p@yoP)4~Z%!;J-Cf6ZeRW=`R9*#CQ zNv&guXp7E%$^xcP#S-Qa#PG5++-j-WKt&N$RH-ue**G1}w;;ivDu{z%e1mfjh-Mv) zpsMc6gH%qfpoDU^8fP2knScxHZO*F(Lp5e%5s{p9zRaCU$!Z)jYfZ)FYIPY&N{7vT zz5@?WWy4r8B9N0hWfLHqgAl6sQ=V3htP`9iM}!qx$B|-O)&ptQ{PwrM%_dad^1!{g z-~H})^FS^h>QWNJ?O|s_VH{u{ST0%;%XMw$aN( z-mT_g8X7Yc3nu4*K;kC3Re3#LLa1!p`18P}Kt*D1a)ZoxhTGDPU8G&3glgN}I+P29 zIBW(YkUs?ede5)+r_KW8k1C`D5r7CR00H3g(^Hw^*-y+Sh)`=i>}zh7V*5) zqTCmKVSorg1hOJf?9M(b(=Zbv01prusHSfIhPG)*3 z;^4=qmx3&$2@!w@+%*VjoLj$xAYa*9_h|L^?;4?@pv#27zJJX3Z_g}~AfQ>`_-D^x z1mFJlx0jTq4?q0y!3Q5)(k3ABToG8V1HH@DZm#$&SNTk>X>uMi+)UEn&o&O)E{UT!k diff --git a/client/images/bulk-product-category-instruction/instruction-7.png b/client/images/bulk-product-category-instruction/instruction-7.png deleted file mode 100644 index 7452a017ba4bbef5656aa673f62402a6e3912e83..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176389 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

i|t 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

u6FaW53=#JYVcVaL6)YKY#o)4c?gYp z_R2~EaQvaI|50KA;xcAg+0PTZe5xMdG*Weo4{jht++9~jROK@*zVnH5#0x?IA%GA- z2p|L&0D+nJh!>!NWhEhDj#_;C7;-IEfD?-CoUJC`UyCj)T&Qt`073vEfDl-12&`^5 zkKyatGkxr8>og<}LI5Fv5I_hZ1ad=Qb#L0|h5;&t5I_hZ1P}rUfmMpY6aMcjVo!j7 zR-3bYnkP>G4TgQ+UiD(dPjar5R!}OP?tquHLg*49fDk|kAOsKs2!TaF;4uorUj+14 zB+2CpI9;xGS4t - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/meta/api-docs/add-product-category.md b/meta/api-docs/add-product-category.md deleted file mode 100644 index e6f98f2d6..000000000 --- a/meta/api-docs/add-product-category.md +++ /dev/null @@ -1,59 +0,0 @@ -This API handles adding new product category. - -url: `api/add-product-category` - -method: `POST` - -### request: -```js -{ - apiKey: Joi.string().length(64).required(), - organizationId: Joi.number().max(999999999999999).required(), - - name: Joi.string().min(1).max(64).required(), - unit: Joi.string().max(64).required(), - defaultDiscountType: Joi.string().valid('percent', 'fixed').required(), - defaultDiscountValue: Joi.number().when( - 'defaultDiscountType', { - is: 'percent', - then: Joi.number().min(0).max(100).required(), - otherwise: Joi.number().max(999999999999999).required() - } - ), - defaultPurchasePrice: Joi.number().max(999999999999999).required(), - defaultVat: Joi.number().max(999999999999999).required(), - defaultSalePrice: Joi.number().max(999999999999999).required(), - isReturnable: Joi.boolean().required() -} -``` - -### response (on error): -```js -{ - hasError: true, - error: { - code, - message - } -} -``` - -Possible Error Codes: -```js -{ code: VALIDATION_ERROR } // validation error on one of the fields -{ code: APIKEY_INVALID } // the api key is invalid -{ code: ORGANIZATION_INVALID } // the organization id is invalid -{ code: DISCOUNT_VALUE_INVALID } // the discount value is more than sale price -``` - -### response (on success): -```js -{ - hasError: false, - status: "success", - productCategoryId: Joi.number().max(999999999999999).required() -} -``` - -### db changes: -updates the `product-category` collection in db. \ No newline at end of file diff --git a/meta/api-docs/delete-product-category.md b/meta/api-docs/delete-product-category.md deleted file mode 100644 index d4941be1b..000000000 --- a/meta/api-docs/delete-product-category.md +++ /dev/null @@ -1,48 +0,0 @@ -# WARNING! API DISABLED! - -This API has been disabled. - -# API - -This API handles attempt to delete product-category. - -url: `api/delete-product-category` - -method: `POST` - -### request: -```js -{ - apiKey: Joi.string().length(64).required(), - productCategoryId: Joi.number().max(999999999999999).required() -} -``` - -### response (on error): -```js -{ - "hasError": true, - "error": { - code, - message - } -} -``` - -Possible Error Codes: -```js -{ code: VALIDATION_ERROR } // validation error on one of the fields -{ code: APIKEY_INVALID } // the api key is invalid -{ code: PRODUCT_CATEGORY_INVALID } // product category does not exist -``` - -### response (on success): -```js -{ - "hasError": false, - "status": "success" -} -``` - -### db changes: -updates the `product-category` collection in db. \ No newline at end of file diff --git a/meta/api-docs/edit-product-category.md b/meta/api-docs/edit-product-category.md deleted file mode 100644 index de83d4577..000000000 --- a/meta/api-docs/edit-product-category.md +++ /dev/null @@ -1,58 +0,0 @@ -This API handles attempt to update the product category. - -url: `api/edit-product-category` - -method: `POST` - -### request: -```js -{ - apiKey: Joi.string().length(64).required(), - productCategoryId: Joi.number().max(999999999999999).required(), - - name: Joi.string().min(1).max(64).required(), - unit: Joi.string().max(64).required(), - defaultDiscountType: Joi.string().valid('percent', 'fixed').required(), - defaultDiscountValue: Joi.number().when( - 'defaultDiscountType', { - is: 'percent', - then: Joi.number().min(0).max(100).required(), - otherwise: Joi.number().max(999999999999999).required() - } - ), - defaultPurchasePrice: Joi.number().max(999999999999999).required(), - defaultVat: Joi.number().max(999999999999999).required(), - defaultSalePrice: Joi.number().max(999999999999999).required(), - isReturnable: Joi.boolean().required() -} -``` - -### response (on error): -```js -{ - hasError: true, - error: { - code, - message - } -} -``` - -Possible Error Codes: -```js -{ code: VALIDATION_ERROR } // validation error on one of the fields -{ code: APIKEY_INVALID } // the api key is invalid -{ code: PRODUCT_CATEGORY_INVALID } // product category not found -{ code: DISCOUNT_VALUE_INVALID } // the discount value is more than sale price -``` - -### response (on success): -```js -{ - hasError: false, - status: "success" -} -``` - -### db changes: -updates the `product-category` collection in db. \ No newline at end of file diff --git a/meta/api-docs/get-product-category-list.md b/meta/api-docs/get-product-category-list.md deleted file mode 100644 index 24fead9d3..000000000 --- a/meta/api-docs/get-product-category-list.md +++ /dev/null @@ -1,66 +0,0 @@ -This API handles get product categories to populate product category tree view and search result while adding product to inventory requirement. - -url: `api/get-product-category-list` - -method: `POST` - -### request: -```js -{ - apiKey: Joi.string().length(64).required(), - organizationId: Joi.number().max(999999999999999).required(), - searchString: Joi.string().min(0).max(64).allow('').optional() -} -``` - -### response (on error): -```js -{ - hasError: true, - error: { - code, - message - } -} -``` - -Possible Error Codes: -```js -{ code: VALIDATION_ERROR } // validation error on one of the fields -{ code: APIKEY_INVALID } // the api key is invalid -{ code: ORGANIZATION_INVALID } // the organization id is invalid -``` - -### response (on success): -```js -{ - hasError: false, - - productCategoryList: Joi.array().items( - Joi.object().keys({ - id: Joi.number().max(999999999999999).required(), - createdDatetimeStamp: Joi.number().max(999999999999999).required(), - lastModifiedDatetimeStamp: Joi.number().max(999999999999999).required(), - name: Joi.string().min(1).max(64).required(), - organizationId: Joi.number().max(999999999999999).required(), - unit: Joi.string().max(64).required(), - defaultDiscountType: Joi.string().valid('percent', 'fixed').required(), - defaultDiscountValue: Joi.number().when( - 'defaultDiscountType', { - is: 'percent', - then: Joi.number().min(0).max(100).required(), - otherwise: Joi.number().max(999999999999999).required() - } - ), - defaultPurchasePrice: Joi.number().max(999999999999999).required(), - defaultVat: Joi.number().max(999999999999999).required(), - defaultSalePrice: Joi.number().max(999999999999999).required(), - isDeleted: Joi.boolean().required(), - isReturnable: Joi.boolean().required() - }); - ) -} -``` - -### db changes: -updates no collection in db. \ No newline at end of file diff --git a/meta/server-db-docs/product-category.md b/meta/server-db-docs/product-category.md deleted file mode 100644 index e201157a8..000000000 --- a/meta/server-db-docs/product-category.md +++ /dev/null @@ -1,29 +0,0 @@ -This collection contains an product category - -## signature -```js -Joi.object().keys({ - - createdDatetimeStamp: Joi.number().max(999999999999999).required(), - lastModifiedDatetimeStamp: Joi.number().max(999999999999999).required(), - - name: Joi.string().min(1).max(64).required(), - organizationId: Joi.number().max(999999999999999).required(), - unit: Joi.string().max(64).required(), - defaultDiscountType: Joi.string().valid('percent', 'fixed').required(), - defaultDiscountValue: Joi.number().when( - 'defaultDiscountType', { - is: 'percent', - then: Joi.number().min(0).max(100).required(), - otherwise: Joi.number().max(999999999999999).required() - } - ), - defaultPurchasePrice: Joi.number().max(999999999999999).required(), - defaultVat: Joi.number().max(999999999999999).required(), - defaultSalePrice: Joi.number().max(999999999999999).required(), - - isDeleted: Joi.boolean().required(), - isReturnable: Joi.boolean().required() - -}); -``` \ No newline at end of file diff --git a/server/src/apis/add-product-category.js b/server/src/apis/add-product-category.js deleted file mode 100644 index 99b1367f4..000000000 --- a/server/src/apis/add-product-category.js +++ /dev/null @@ -1,50 +0,0 @@ -const { Api } = require('./../api-base'); -const Joi = require('joi'); -const { throwOnFalsy, throwOnTruthy, CodedError } = require('./../utils/coded-error'); -const { extract } = require('./../utils/extract'); -const { ProductCategoryMixin } = require('./mixins/product-category-mixin'); - -exports.AddProductCategoryApi = class extends Api.mixin(ProductCategoryMixin) { - - get autoValidates() { return true; } - - get requiresAuthentication() { return true; } - - get requestSchema() { - return Joi.object().keys({ - organizationId: Joi.number().max(999999999999999).required(), - - name: Joi.string().min(1).max(64).required(), - unit: Joi.string().max(64).required(), - defaultDiscountType: Joi.string().valid('percent', 'fixed').required(), - defaultDiscountValue: Joi.number().when( - 'defaultDiscountType', { - is: 'percent', - then: Joi.number().min(0).max(100).required(), - otherwise: Joi.number().max(999999999999999).required() - } - ), - defaultPurchasePrice: Joi.number().max(999999999999999).required(), - defaultVat: Joi.number().max(999999999999999).required(), - defaultSalePrice: Joi.number().max(999999999999999).required(), - isReturnable: Joi.boolean().required() - }); - } - - get accessControl() { - return [{ - organizationBy: "organizationId", - privilegeList: [ - "PRIV_MODIFY_ALL_PRODUCT_CATEGORIES" - ] - }]; - } - - async handle({ body }) { - let { organizationId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable } = body; - this._checkIfDiscountValueIsValid({ defaultDiscountType, defaultDiscountValue, defaultSalePrice, defaultVat }); - let productCategoryId = await this._createProductCategory({ organizationId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable }); - return { status: "success", productCategoryId }; - } - -} \ No newline at end of file diff --git a/server/src/apis/edit-product-category.js b/server/src/apis/edit-product-category.js deleted file mode 100644 index e76f3afe6..000000000 --- a/server/src/apis/edit-product-category.js +++ /dev/null @@ -1,60 +0,0 @@ -const { Api } = require('./../api-base'); -const Joi = require('joi'); -const { throwOnFalsy, throwOnTruthy, CodedError } = require('./../utils/coded-error'); -const { extract } = require('./../utils/extract'); -const { ProductCategoryMixin } = require('./mixins/product-category-mixin'); - -exports.EditProductCategoryApi = class extends Api.mixin(ProductCategoryMixin) { - - get autoValidates() { return true; } - - get requiresAuthentication() { return true; } - - get requestSchema() { - return Joi.object().keys({ - productCategoryId: Joi.number().max(999999999999999).required(), - - name: Joi.string().min(1).max(64).required(), - unit: Joi.string().max(64).required(), - defaultDiscountType: Joi.string().valid('percent', 'fixed').required(), - defaultDiscountValue: Joi.number().when( - 'defaultDiscountType', { - is: 'percent', - then: Joi.number().min(0).max(100).required(), - otherwise: Joi.number().max(999999999999999).required() - } - ), - defaultPurchasePrice: Joi.number().max(999999999999999).required(), - defaultVat: Joi.number().max(999999999999999).required(), - defaultSalePrice: Joi.number().max(999999999999999).required(), - isReturnable: Joi.boolean().required() - }); - } - - get accessControl() { - return [{ - organizationBy: { - from: "product-category", - query: ({ productCategoryId }) => ({ id: productCategoryId }), - select: "organizationId", - errorCode: "PRODUCT_CATEGORY_INVALID" - }, - privilegeList: [ - "PRIV_MODIFY_ALL_PRODUCT_CATEGORIES" - ] - }]; - } - - async _updateProductCategory({ productCategoryId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable }) { - let result = await this.database.productCategory.setDetails({ id: productCategoryId }, { name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable }); - this.ensureUpdate(result, 'product-category'); - } - - async handle({ body }) { - let { productCategoryId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable } = body; - await this._checkIfDiscountValueIsValid({ defaultDiscountType, defaultDiscountValue, defaultSalePrice, defaultVat }); - await this._updateProductCategory({ productCategoryId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable }); - return { status: "success" }; - } - -} \ No newline at end of file diff --git a/server/src/apis/get-product-category-list.js b/server/src/apis/get-product-category-list.js deleted file mode 100644 index 962117a35..000000000 --- a/server/src/apis/get-product-category-list.js +++ /dev/null @@ -1,39 +0,0 @@ -const { Api } = require('./../api-base'); -const Joi = require('joi'); - -exports.GetProductCategoryListApi = class extends Api { - - get autoValidates() { return true; } - - get requiresAuthentication() { return true; } - - get autoPaginates() { return ['productCategoryList']; } - - get requestSchema() { - return Joi.object().keys({ - organizationId: Joi.number().max(999999999999999).required(), - searchString: Joi.string().min(0).max(64).allow('').optional() - }); - } - - get accessControl() { - return [{ - organizationBy: "organizationId", - privilegeList: [ - "PRIV_VIEW_ALL_INVENTORIES" - ] - }]; - } - - async _getProductCategoryList({ organizationId, searchString }) { - return await this.database.productCategory.listByOrganizationIdAndSearchString({ organizationId, searchString }); - } - - async handle({ body }) { - let { organizationId, searchString } = body; - let productCategoryList = await this._getProductCategoryList({ organizationId, searchString }); - - return { productCategoryList }; - } - -} \ No newline at end of file diff --git a/server/src/apis/mixins/product-category-mixin.js b/server/src/apis/mixins/product-category-mixin.js deleted file mode 100644 index 6b3f209f4..000000000 --- a/server/src/apis/mixins/product-category-mixin.js +++ /dev/null @@ -1,30 +0,0 @@ -const { Api } = require('./../../api-base'); -const { throwOnFalsy, throwOnTruthy, CodedError } = require('../../utils/coded-error'); - -/** @param {typeof Api} SuperApiClass */ -exports.ProductCategoryMixin = (SuperApiClass) => class extends SuperApiClass { - - async _createProductCategory({ organizationId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable }) { - return await this.database.productCategory.create({ organizationId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable }); - } - - _checkIfDiscountValueIsValid({ defaultDiscountType, defaultDiscountValue, defaultSalePrice, defaultVat }) { - let salePriceAfterVat = defaultSalePrice + defaultSalePrice * defaultVat / 100; - if (defaultDiscountValue && defaultDiscountType === 'fixed' && defaultDiscountValue > salePriceAfterVat) { - throw new CodedError("DISCOUNT_VALUE_INVALID", "the discount value is more than sale price"); - } - if (defaultDiscountValue && defaultDiscountType === 'percent' && defaultDiscountValue > 100) { - throw new CodedError("DISCOUNT_VALUE_INVALID", "the discount percentage is more than 100"); - } - } - - async _verifyProductCategoriesExist({ productList }) { - await this.crossmap({ - source: productList, - sourceKey: 'productCategoryId', - target: 'productCategory', - onError: (inventory) => { throw new CodedError("PRODUCT_CATEGORY_INVALID", "Unable to find all products in productList"); } - }); - } - -} \ No newline at end of file diff --git a/server/src/collections/product-category.js b/server/src/collections/product-category.js deleted file mode 100644 index d3e0a22ce..000000000 --- a/server/src/collections/product-category.js +++ /dev/null @@ -1,102 +0,0 @@ - -const { Collection } = require('./../collection-base'); -const Joi = require('joi'); - -exports.ProductCategoryCollection = class extends Collection { - - get name() { return 'product-category'; } - - get joiSchema() { - return Joi.object().keys({ - createdDatetimeStamp: Joi.number().max(999999999999999).required(), - lastModifiedDatetimeStamp: Joi.number().max(999999999999999).required(), - name: Joi.string().min(1).max(64).required(), - organizationId: Joi.number().max(999999999999999).required(), - unit: Joi.string().max(64).required(), - defaultDiscountType: Joi.string().valid('percent', 'fixed').required(), - defaultDiscountValue: Joi.number().when( - 'defaultDiscountType', { - is: 'percent', - then: Joi.number().min(0).max(100).required(), - otherwise: Joi.number().max(999999999999999).required() - } - ), - defaultPurchasePrice: Joi.number().max(999999999999999).required(), - defaultVat: Joi.number().max(999999999999999).required(), - defaultSalePrice: Joi.number().max(999999999999999).required(), - isDeleted: Joi.boolean().required(), - isReturnable: Joi.boolean().required() - }); - } - - get uniqueKeyDefList() { - return [ - { - filters: {}, - keyList: ['organizationId+name'] - } - ]; - } - - get foreignKeyDefList() { - return [ - { - targetCollection: 'organization', - foreignKey: 'id', - referringKey: 'organizationId' - } - ]; - } - - get deletionIndicatorKey() { return 'isDeleted'; } - - async create({ organizationId, - name, - unit, - defaultDiscountType, - defaultDiscountValue, - defaultPurchasePrice, - defaultVat, - defaultSalePrice, - isReturnable }) { - return await this._insert({ - createdDatetimeStamp: (new Date).getTime(), - lastModifiedDatetimeStamp: (new Date).getTime(), - organizationId, - name, - unit, - defaultDiscountType, - defaultDiscountValue, - defaultPurchasePrice, - defaultVat, - defaultSalePrice, - isReturnable, - isDeleted: false - }); - } - - async setDetails({ id }, { name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable }) { - return await this._update({ id }, { - $set: { - name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable - } - }); - } - - async listByOrganizationId({ organizationId }) { - return await this._find({ organizationId }); - } - - async listByOrganizationIdAndSearchString({ organizationId, searchString }) { - let query = { organizationId }; - if (searchString) { - searchString = this.escapeRegExp(searchString); - let searchRegex = new RegExp(searchString, 'i'); - query.$or = [ - { name: searchRegex } - ]; - } - return await this._find(query); - } - -} diff --git a/server/src/legacy-apis/__depr__add-product-category.js b/server/src/legacy-apis/__depr__add-product-category.js deleted file mode 100644 index 7c65aab93..000000000 --- a/server/src/legacy-apis/__depr__add-product-category.js +++ /dev/null @@ -1,67 +0,0 @@ - -let { LegacyApi } = require('../legacy-api-base'); -let Joi = require('joi'); - -let { collectionCommonMixin } = require('./mixins/collection-common'); - -exports.AddProductCategoryApi = class extends collectionCommonMixin(LegacyApi) { - - get autoValidates() { return true; } - - get requiresAuthentication() { return true; } - - get requestSchema() { - return Joi.object().keys({ - // apiKey: Joi.string().length(64).required(), - - organizationId: Joi.number().max(999999999999999).required(), - - name: Joi.string().min(1).max(64).required(), - unit: Joi.string().max(64).required(), - defaultDiscountType: Joi.string().valid('percent', 'fixed').required(), - defaultDiscountValue: Joi.number().when( - 'defaultDiscountType', { - is: 'percent', - then: Joi.number().min(0).max(100).required(), - otherwise: Joi.number().max(999999999999999).required() - } - ), - defaultPurchasePrice: Joi.number().max(999999999999999).required(), - defaultVat: Joi.number().max(999999999999999).required(), - defaultSalePrice: Joi.number().max(999999999999999).required(), - isReturnable: Joi.boolean().required() - }); - } - - get accessControl() { - return [{ - organizationBy: "organizationId", - privilegeList: [ - "PRIV_MODIFY_ALL_PRODUCT_CATEGORIES" - ] - }]; - } - - _createProductCategory(productCategory, cbfn) { - this.legacyDatabase.productCategory.create(productCategory, (err, productCategoryId) => { - if (err) return this.fail(err); - return cbfn(productCategoryId); - }); - } - - _checkAndCreateProductCategory({ organizationId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable }, cbfn) { - let productCategory = { - organizationId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable - } - - this._createProductCategory(productCategory, cbfn); - } - - handle({ body }) { - let { organizationId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable } = body; - this._checkAndCreateProductCategory({ organizationId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable }, (productCategoryId) => { - this.success({ status: "success", productCategoryId }); - }); - } - -} \ No newline at end of file diff --git a/server/src/legacy-apis/__depr__edit-product-category.js b/server/src/legacy-apis/__depr__edit-product-category.js deleted file mode 100644 index 7b4df21df..000000000 --- a/server/src/legacy-apis/__depr__edit-product-category.js +++ /dev/null @@ -1,66 +0,0 @@ -let { LegacyApi } = require('../legacy-api-base'); -let Joi = require('joi'); - -let { collectionCommonMixin } = require('./mixins/collection-common'); - -exports.EditProductCategoryApi = class extends collectionCommonMixin(LegacyApi) { - - get autoValidates() { return true; } - - get requiresAuthentication() { return true; } - - get requestSchema() { - return Joi.object().keys({ - // apiKey: Joi.string().length(64).required(), - productCategoryId: Joi.number().max(999999999999999).required(), - - name: Joi.string().min(1).max(64).required(), - unit: Joi.string().max(64).required(), - defaultDiscountType: Joi.string().valid('percent', 'fixed').required(), - defaultDiscountValue: Joi.number().when( - 'defaultDiscountType', { - is: 'percent', - then: Joi.number().min(0).max(100).required(), - otherwise: Joi.number().max(999999999999999).required() - } - ), - defaultPurchasePrice: Joi.number().max(999999999999999).required(), - defaultVat: Joi.number().max(999999999999999).required(), - defaultSalePrice: Joi.number().max(999999999999999).required(), - isReturnable: Joi.boolean().required() - }); - } - - get accessControl() { - return [{ - organizationBy: { - from: "product-category", - query: ({ productCategoryId }) => ({ id: productCategoryId }), - select: "organizationId", - errorCode: "PRODUCT_CATEGORY_INVALID" - }, - privilegeList: [ - "PRIV_MODIFY_ALL_PRODUCT_CATEGORIES" - ] - }]; - } - - _checkAndUpdateProductCategory({ productCategoryId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable }, cbfn) { - this._updateProductCategory({ productCategoryId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable }, cbfn); - } - - _updateProductCategory({ productCategoryId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable }, cbfn) { - this.legacyDatabase.productCategory.update({ productCategoryId }, { name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable }, (err, wasUpdated) => { - if (!this._ensureUpdate(err, wasUpdated, "product-category")) return; - return cbfn(); - }); - } - - handle({ body, userId }) { - let { productCategoryId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable } = body; - this._checkAndUpdateProductCategory({ productCategoryId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable }, () => { - this.success({ status: "success" }); - }); - } - -} \ No newline at end of file diff --git a/server/src/legacy-apis/__depr__get-product-category-list.js b/server/src/legacy-apis/__depr__get-product-category-list.js deleted file mode 100644 index c4f3d89e8..000000000 --- a/server/src/legacy-apis/__depr__get-product-category-list.js +++ /dev/null @@ -1,41 +0,0 @@ -let { LegacyApi } = require('../legacy-api-base'); -let Joi = require('joi'); - -exports.GetProductCategoryListApi = class extends LegacyApi { - - get autoValidates() { return true; } - - get requiresAuthentication() { return true; } - - get requestSchema() { - return Joi.object().keys({ - // apiKey: Joi.string().length(64).required(), - - organizationId: Joi.number().max(999999999999999).required() - }); - } - - get accessControl() { - return [{ - organizationBy: "organizationId", - privilegeList: [ - "PRIV_VIEW_ALL_INVENTORIES" - ] - }]; - } - - _getProductCategoryList({ organizationId }, cbfn) { - this.legacyDatabase.productCategory.listByOrganizationId({ organizationId }, (err, productCategoryList) => { - if (err) return this.fail(err); - cbfn(productCategoryList); - }) - } - - handle({ body }) { - let { organizationId } = body; - this._getProductCategoryList({ organizationId }, (productCategoryList) => { - this.success({ productCategoryList }); - }); - } - -} \ No newline at end of file diff --git a/server/src/legacy-apis/delete-product-category.js b/server/src/legacy-apis/delete-product-category.js deleted file mode 100644 index b3bd33f2c..000000000 --- a/server/src/legacy-apis/delete-product-category.js +++ /dev/null @@ -1,59 +0,0 @@ -let { LegacyApi } = require('./../legacy-api-base'); -let Joi = require('joi'); - -let { collectionCommonMixin } = require('./mixins/collection-common'); - -exports.DeleteProductCategoryApi = class extends collectionCommonMixin(LegacyApi) { - - get isEnabled() { return false; } - - get autoValidates() { return true; } - - get requiresAuthentication() { return true; } - - get requestSchema() { - return Joi.object().keys({ - // apiKey: Joi.string().length(64).required(), - productCategoryId: Joi.number().max(999999999999999).required(), - }); - } - - get accessControl() { - return [{ - organizationBy: { - from: "product-category", - query: ({ productCategoryId }) => ({ id: productCategoryId }), - select: "organizationId", - errorCode: "PRODUCT_CATEGORY_INVALID" - }, - privilegeList: [ - "PRIV_MODIFY_ALL_PRODUCT_CATEGORIES" - ] - }]; - } - - _checkAndDeleteProductCategory({ productCategoryId }, cbfn) { - // FIXME: below method listChildren is not present anymore as product categories are flat - this.legacyDatabase.productCategory.listChildren({ productCategoryId }, (err, productCategoryChildList) => { - if (err) return this.fail(err); - if (productCategoryChildList.length > 0) { - err = new Error("product category is parent of atleast one category"); - err.code = "PRODUCT_CATEGORY_NOT_CHILDLESS"; - return this.fail(err); - } - this._deleteProductCategory({ productCategoryId }, cbfn); - }) - } - - _deleteProductCategory({ productCategoryId }, cbfn) { - this._deleteDocById(this.legacyDatabase.productCategory, { productCategoryId }, cbfn); - } - - handle({ body, userId }) { - let { productCategoryId } = body; - this._checkAndDeleteProductCategory({ productCategoryId }, _ => { - this.success({ status: "success" }); - }); - } - -} \ No newline at end of file diff --git a/server/src/legacy-collections/product-category.js b/server/src/legacy-collections/product-category.js deleted file mode 100644 index f9c228f27..000000000 --- a/server/src/legacy-collections/product-category.js +++ /dev/null @@ -1,124 +0,0 @@ -const { LegacyCollection } = require('./../legacy-collection-base'); - -/* ================================================================= -+++++++++++++ WARNING +++++++++++++ -++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -++ This is a LegacyCollection. It uses async callbacks. New APIs ++ -++ should not be using this. Even when using with legacy APIs, if ++ -++ you make a change, pleaase replicate the change in the ++ -++ non-legacy Collection of the same name. ++ -++ Talk to @iShafayet if unsure. ++ -================================================================= */ - -const Joi = require('joi'); - -exports.ProductCategoryCollection = class extends LegacyCollection { - - constructor(...args) { - super(...args); - - this.collectionName = 'product-category'; - - this.joiSchema = Joi.object().keys({ - createdDatetimeStamp: Joi.number().max(999999999999999).required(), - lastModifiedDatetimeStamp: Joi.number().max(999999999999999).required(), - name: Joi.string().min(1).max(64).required(), - organizationId: Joi.number().max(999999999999999).required(), - unit: Joi.string().max(64).required(), - defaultDiscountType: Joi.string().valid('percent', 'fixed').required(), - defaultDiscountValue: Joi.number().when( - 'defaultDiscountType', { - is: 'percent', - then: Joi.number().min(0).max(100).required(), - otherwise: Joi.number().max(999999999999999).required() - } - ), - defaultPurchasePrice: Joi.number().max(999999999999999).required(), - defaultVat: Joi.number().max(999999999999999).required(), - defaultSalePrice: Joi.number().max(999999999999999).required(), - isDeleted: Joi.boolean().required(), - isReturnable: Joi.boolean().required() - }); - - this.uniqueDefList = [ - { - additionalQueryFilters: {}, - uniqueKeyList: [] - } - ]; - - this.foreignKeyDefList = [ - { - targetCollection: 'organization', - foreignKey: 'id', - referringKey: 'organizationId' - } - ]; - } - - /** - * - * - * @param {any} { organizationId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable } - * @param {any} cbfn - */ - create(data, cbfn) { - let { - organizationId, - name, - unit, - defaultDiscountType, - defaultDiscountValue, - defaultPurchasePrice, - defaultVat, - defaultSalePrice, - isReturnable - } = data; - let doc = { - createdDatetimeStamp: (new Date).getTime(), - lastModifiedDatetimeStamp: (new Date).getTime(), - organizationId, - name, - unit, - defaultDiscountType, - defaultDiscountValue, - defaultPurchasePrice, - defaultVat, - defaultSalePrice, - isReturnable, - isDeleted: false - } - this._insert(doc, (err, id) => { - return cbfn(err, id); - }); - } - - listByOrganizationId({ organizationId }, cbfn) { - this._find({ organizationId }, cbfn); - } - - findById({ productCategoryId }, cbfn) { - this._findOne({ id: productCategoryId }, cbfn) - } - - // FIXME: naming issue - listByIdList({ idList }, cbfn) { - this._find({ id: { $in: idList } }, cbfn); - } - - update({ productCategoryId }, { name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable }, cbfn) { - let modifications = { - $set: { - name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable - } - } - this._update({ id: productCategoryId }, modifications, cbfn); - } - - delete({ productCategoryId }, cbfn) { - let modifications = { - $set: { isDeleted: true } - } - this._update({ id: productCategoryId }, modifications, cbfn); - } -} \ No newline at end of file diff --git a/server/test/test-product-category-apis.js b/server/test/test-product-category-apis.js deleted file mode 100644 index f10ae81a9..000000000 --- a/server/test/test-product-category-apis.js +++ /dev/null @@ -1,486 +0,0 @@ -let expect = require('chai').expect; - -let { callApi } = require('./utils'); -let { - rnd, - generateInvalidId, - initializeServer, - terminateServer, - registerUser, - loginUser, - addOrganization, - validateProductCategorySchema, - validateAddProductCategoryApiSuccessResponse, - validateGenericApiFailureResponse, - validateGetProductCategoryListApiSuccessResponse, - validateGenericApiSuccessResponse, - validateBulkImportProductCategoriesApiSuccessResponse -} = require('./lib'); - -const prefix = 's'; - -const email = `${rnd(prefix)}@gmail.com`; -const password = "123545678"; -const fullName = "Test User"; -const phone = rnd(prefix, 11); - -const orgEmail = 'o' + `${rnd(prefix)}@gmail.com`; -const orgName = "Test Organization"; -const orgBusinessAddress = "My Address"; -const orgPhone = 'o' + rnd(prefix, 11); - -let apiKey = null; -let organizationId = null; - -let productCategoryList = null; -let productCategoryOne = null; -let productCategoryTwo = null; -let productCategoryThree = null; - -let productCategoryToBeModified = null; -let invalidOrganizationId = generateInvalidId(); -let invalidProductCategoryId = generateInvalidId(); - -describe('Product Category', _ => { - - it('START', testDoneFn => { - initializeServer(_ => { - registerUser({ - password, fullName, phone - }, _ => { - loginUser({ - emailOrPhone: phone, password - }, (data) => { - apiKey = data.apiKey; - addOrganization({ - apiKey, - name: orgName, - primaryBusinessAddress: orgBusinessAddress, - phone: orgPhone, - email: orgEmail - }, (data) => { - organizationId = data.organizationId; - testDoneFn(); - }) - }); - }); - }); - }); - - // ADD-GET - - it('api/add-product-category (Valid)', testDoneFn => { - - callApi('api/add-product-category', { - json: { - apiKey, - organizationId, - name: "1st product category", - unit: "kg", - defaultDiscountType: "percent", - defaultDiscountValue: 10, - defaultPurchasePrice: 99, - defaultVat: 2, - defaultSalePrice: 111, - isReturnable: true - } - }, (err, response, body) => { - expect(response.statusCode).to.equal(200); - validateAddProductCategoryApiSuccessResponse(body); - testDoneFn(); - }) - - }); - - it('api/add-product-category (Invalid copy name)', testDoneFn => { - - callApi('api/add-product-category', { - json: { - apiKey, - organizationId, - name: "1st product category", - unit: "kg", - defaultDiscountType: "percent", - defaultDiscountValue: 10, - defaultPurchasePrice: 99, - defaultVat: 2, - defaultSalePrice: 111, - isReturnable: true - } - }, (err, response, body) => { - expect(response.statusCode).to.equal(200); - validateGenericApiFailureResponse(body); - testDoneFn(); - }) - - }); - - it('api/add-product-category (Valid)', testDoneFn => { - - callApi('api/add-product-category', { - json: { - apiKey, - organizationId, - name: "2nd product category", - unit: "kg", - defaultDiscountType: "fixed", - defaultDiscountValue: 11, - defaultPurchasePrice: 99, - defaultVat: 2, - defaultSalePrice: 111, - isReturnable: false - } - }, (err, response, body) => { - expect(response.statusCode).to.equal(200); - validateAddProductCategoryApiSuccessResponse(body); - testDoneFn(); - }) - - }); - - it('api/add-product-category (Invalid organization)', testDoneFn => { - - callApi('api/add-product-category', { - json: { - apiKey, - organizationId: invalidOrganizationId, - name: "invalid product category", - unit: "kg", - defaultDiscountType: "percent", - defaultDiscountValue: 10, - defaultPurchasePrice: 99, - defaultVat: 2, - defaultSalePrice: 111, - isReturnable: true - } - }, (err, response, body) => { - expect(response.statusCode).to.equal(200); - validateGenericApiFailureResponse(body); - expect(body.error.code).equal('ORGANIZATION_INVALID'); - testDoneFn(); - }) - - }); - - it('api/get-product-category-list (Valid searchString)', testDoneFn => { - - callApi('api/get-product-category-list', { - json: { - apiKey, - organizationId, - searchString: '1ST' - } - }, (err, response, body) => { - expect(response.statusCode).to.equal(200); - - validateGetProductCategoryListApiSuccessResponse(body); - - body.productCategoryList.forEach(productCategory => { - validateProductCategorySchema(productCategory); - }); - - testDoneFn(); - }); - - }); - - it('api/get-product-category-list (Valid)', testDoneFn => { - - callApi('api/get-product-category-list', { - json: { - apiKey, - organizationId - } - }, (err, response, body) => { - expect(response.statusCode).to.equal(200); - validateGetProductCategoryListApiSuccessResponse(body); - - body.productCategoryList.forEach(productCategory => { - validateProductCategorySchema(productCategory); - }); - - body.productCategoryList.reverse(); - productCategoryOne = body.productCategoryList[0]; - - testDoneFn(); - }); - - }); - - it('api/get-product-category-list (Invalid Organization)', testDoneFn => { - - callApi('api/get-product-category-list', { - json: { - apiKey, - organizationId: invalidOrganizationId - } - }, (err, response, body) => { - expect(response.statusCode).to.equal(200); - validateGenericApiFailureResponse(body); - expect(body.error.code).equal('ORGANIZATION_INVALID'); - testDoneFn(); - }); - - }); - - it('api/get-product-category-list (Valid)', testDoneFn => { - - callApi('api/get-product-category-list', { - json: { - apiKey, - organizationId - } - }, (err, response, body) => { - expect(response.statusCode).to.equal(200); - validateGetProductCategoryListApiSuccessResponse(body); - body.productCategoryList.forEach(productCategory => { - validateProductCategorySchema(productCategory); - }); - - body.productCategoryList.reverse(); - productCategoryOne = body.productCategoryList[0]; - productCategoryTwo = body.productCategoryList[1]; - productCategoryThree = body.productCategoryList[2]; - - testDoneFn(); - }); - - }); - - it('api/add-product-category (Invalid fixed defaultDiscountValue)', testDoneFn => { - - callApi('api/add-product-category', { - json: { - apiKey, - organizationId, - name: "1st product category", - unit: "kg", - defaultDiscountType: "fixed", - defaultDiscountValue: 114, - defaultPurchasePrice: 99, - defaultVat: 2, - defaultSalePrice: 111, - isReturnable: true - } - }, (err, response, body) => { - expect(response.statusCode).to.equal(200); - validateGenericApiFailureResponse(body); - expect(body.error.code).equal('DISCOUNT_VALUE_INVALID'); - testDoneFn(); - }) - - }); - - // EDIT - - it('api/edit-product-category (Valid)', testDoneFn => { - - callApi('api/edit-product-category', { - json: { - apiKey, - productCategoryId: productCategoryOne.id, - name: "new 1st product category name", // modification - unit: "kg", - defaultDiscountType: "percent", - defaultDiscountValue: 10, - defaultPurchasePrice: 99, - defaultVat: 2, - defaultSalePrice: 111, - isReturnable: false // modification - } - }, (err, response, body) => { - expect(response.statusCode).to.equal(200); - validateGenericApiSuccessResponse(body); - testDoneFn(); - }) - - }); - - it('api/edit-product-category (Invalid copy name)', testDoneFn => { - - callApi('api/edit-product-category', { - json: { - apiKey, - productCategoryId: productCategoryOne.id, - name: "2nd product category", // copy modification - unit: "kg", - defaultDiscountType: "percent", - defaultDiscountValue: 10, - defaultPurchasePrice: 99, - defaultVat: 2, - defaultSalePrice: 111, - isReturnable: true - } - }, (err, response, body) => { - expect(response.statusCode).to.equal(200); - validateGenericApiFailureResponse(body); - testDoneFn(); - }) - - }); - - it('api/edit-product-category (Invalid productCategoryId)', testDoneFn => { - - callApi('api/edit-product-category', { - json: { - apiKey, - productCategoryId: invalidProductCategoryId, - - name: "new product category name", // modification - unit: "kg", - defaultDiscountType: "percent", - defaultDiscountValue: 10, - defaultPurchasePrice: 99, - defaultVat: 2, - defaultSalePrice: 111, - isReturnable: false // modification - } - }, (err, response, body) => { - expect(response.statusCode).to.equal(200); - validateGenericApiFailureResponse(body); - expect(body.error.code).equal('PRODUCT_CATEGORY_INVALID'); - testDoneFn(); - }) - - }); - - it('api/get-product-category-list (Valid modification check)', testDoneFn => { - - callApi('api/get-product-category-list', { - json: { - apiKey, - organizationId - } - }, (err, response, body) => { - expect(response.statusCode).to.equal(200); - validateGetProductCategoryListApiSuccessResponse(body); - body.productCategoryList.forEach(productCategory => { - validateProductCategorySchema(productCategory); - }); - - body.productCategoryList.reverse(); - productCategoryList = body.productCategoryList; - expect(body.productCategoryList[0].name).to.equal("new 1st product category name"); - expect(body.productCategoryList[0].isReturnable).to.equal(false); - - testDoneFn(); - }); - - }); - - // DELETE - - it('api/delete-product-category (Confirm that API is disabled)', testDoneFn => { - - callApi('api/delete-product-category', { - json: { - apiKey, - productCategoryId: invalidProductCategoryId - } - }, (err, response, body) => { - expect(response.statusCode).to.equal(200); - validateGenericApiFailureResponse(body); - expect(body.error.code).equal('API_DISABLED'); - testDoneFn(); - }) - - }); - - it('api/bulk-import-product-categories (Valid and unique)', testDoneFn => { - - callApi('api/bulk-import-product-categories', { - json: { - apiKey, - organizationId, - rowList: [ - ["Should Be Unique 1", "pc", 300, 500, 10, "percent", 100, "Yes"], - ["Should Be Unique 2", "haali", 10, 10, 10, "fixed", 0, "No"] - ] - } - }, (err, response, body) => { - expect(response.statusCode).to.equal(200); - validateBulkImportProductCategoriesApiSuccessResponse(body); - expect(body.ignoredRowList).to.deep.equal([]); - expect(body.successfulCount).to.equal(2); - testDoneFn(); - }) - - }); - - it('api/bulk-import-product-categories (Valid but not unique)', testDoneFn => { - - callApi('api/bulk-import-product-categories', { - json: { - apiKey, - organizationId, - rowList: [ - ["Should Be Unique 3", "pc", 300, 500, 10, "percent", 100, "Yes"], - ["Should Be Unique 2", "haali", 10, 10, 10, "fixed", 0, "No"] - ] - } - }, (err, response, body) => { - expect(response.statusCode).to.equal(200); - validateBulkImportProductCategoriesApiSuccessResponse(body); - expect(body.ignoredRowList).to.deep.equal([ - { - "reason": "name-duplication", - "rowNumber": 2 - } - ]); - expect(body.successfulCount).to.equal(1); - testDoneFn(); - }) - - }); - - it('api/bulk-import-product-categories (Invalid)', testDoneFn => { - - callApi('api/bulk-import-product-categories', { - json: { - apiKey, - organizationId, - rowList: [ - ["Should Be Unique 4", "pc", 300, 500, 10, "percent", 100, "FFYes"], - ["Should Be Unique 5", "haali", 10, 10, 10, "fixed", 0, "No"] - ] - } - }, (err, response, body) => { - expect(response.statusCode).to.equal(200); - validateGenericApiFailureResponse(body); - expect(body.error.code).to.equal('MODIFIED_VALIDATION_ERROR'); - expect(body.error.message).to.equal('Cell #8 must be one of [Yes, No]'); - expect(body.error.rowNumber).to.equal(1); - expect(body.error.cellNumber).to.equal(8); - testDoneFn(); - }) - - }); - - it('api/bulk-import-product-categories (Invalid)', testDoneFn => { - - callApi('api/bulk-import-product-categories', { - json: { - apiKey, - organizationId, - rowList: [ - ["Should Be Unique 5", "pc", 300, 500, 10, "percent", 100, "Yes"], - ["", "haali", 10, 10, 10, "fixed", 0, "No"] - ] - } - }, (err, response, body) => { - expect(response.statusCode).to.equal(200); - validateGenericApiFailureResponse(body); - expect(body.error.code).to.equal('MODIFIED_VALIDATION_ERROR'); - expect(body.error.message).to.equal('Cell #1 is not allowed to be empty'); - expect(body.error.rowNumber).to.equal(2); - expect(body.error.cellNumber).to.equal(1); - testDoneFn(); - }) - - }); - - it('END', testDoneFn => { - terminateServer(testDoneFn); - }); - -}); \ No newline at end of file From 3fe2dab3ce34d5cf7088ee5a8554a309eb5343c6 Mon Sep 17 00:00:00 2001 From: iLGunners Date: Thu, 18 Oct 2018 18:02:54 +0600 Subject: [PATCH 003/224] files renamed --- .../instruction-1.png | Bin 0 -> 122374 bytes .../instruction-2.png | Bin 0 -> 29591 bytes .../instruction-3.png | Bin 0 -> 36984 bytes .../instruction-4.png | Bin 0 -> 83133 bytes .../instruction-5.png | Bin 0 -> 123182 bytes .../instruction-6.png | Bin 0 -> 134232 bytes .../instruction-7.png | Bin 0 -> 176389 bytes client/src/page-edit-product-blueprint.html | 274 ++++++++++ meta/api-docs/add-product-blueprint.md | 59 +++ meta/api-docs/delete-product-blueprint.md | 48 ++ meta/api-docs/edit-product-blueprint.md | 58 +++ meta/api-docs/get-product-blueprint-list.md | 66 +++ meta/server-db-docs/product-blueprint.md | 29 ++ server/src/apis/add-product-blueprint.js | 50 ++ server/src/apis/edit-product-blueprint.js | 60 +++ server/src/apis/get-product-blueprint-list.js | 39 ++ .../apis/mixins/product-blueprint-mixin.js | 30 ++ server/src/collections/product-blueprint.js | 102 ++++ .../__depr__add-product-blueprint.js | 67 +++ .../__depr__edit-product-blueprint.js | 66 +++ .../__depr__get-product-blueprint-list.js | 41 ++ .../legacy-apis/delete-product-blueprint.js | 59 +++ .../legacy-collections/product-blueprint.js | 124 +++++ server/test/test-product-blueprint-apis.js | 486 ++++++++++++++++++ 24 files changed, 1658 insertions(+) create mode 100644 client/images/bulk-product-blueprint-instruction/instruction-1.png create mode 100644 client/images/bulk-product-blueprint-instruction/instruction-2.png create mode 100644 client/images/bulk-product-blueprint-instruction/instruction-3.png create mode 100644 client/images/bulk-product-blueprint-instruction/instruction-4.png create mode 100644 client/images/bulk-product-blueprint-instruction/instruction-5.png create mode 100644 client/images/bulk-product-blueprint-instruction/instruction-6.png create mode 100644 client/images/bulk-product-blueprint-instruction/instruction-7.png create mode 100644 client/src/page-edit-product-blueprint.html create mode 100644 meta/api-docs/add-product-blueprint.md create mode 100644 meta/api-docs/delete-product-blueprint.md create mode 100644 meta/api-docs/edit-product-blueprint.md create mode 100644 meta/api-docs/get-product-blueprint-list.md create mode 100644 meta/server-db-docs/product-blueprint.md create mode 100644 server/src/apis/add-product-blueprint.js create mode 100644 server/src/apis/edit-product-blueprint.js create mode 100644 server/src/apis/get-product-blueprint-list.js create mode 100644 server/src/apis/mixins/product-blueprint-mixin.js create mode 100644 server/src/collections/product-blueprint.js create mode 100644 server/src/legacy-apis/__depr__add-product-blueprint.js create mode 100644 server/src/legacy-apis/__depr__edit-product-blueprint.js create mode 100644 server/src/legacy-apis/__depr__get-product-blueprint-list.js create mode 100644 server/src/legacy-apis/delete-product-blueprint.js create mode 100644 server/src/legacy-collections/product-blueprint.js create mode 100644 server/test/test-product-blueprint-apis.js diff --git a/client/images/bulk-product-blueprint-instruction/instruction-1.png b/client/images/bulk-product-blueprint-instruction/instruction-1.png new file mode 100644 index 0000000000000000000000000000000000000000..bd5c2169b39f76f4b138659965c0f664c0cb6515 GIT binary patch literal 122374 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 aAqi~ImN(ihIe%b<&&z#kxkdRe75@+R#n5^H literal 0 HcmV?d00001 diff --git a/client/images/bulk-product-blueprint-instruction/instruction-2.png b/client/images/bulk-product-blueprint-instruction/instruction-2.png new file mode 100644 index 0000000000000000000000000000000000000000..cd2b29dbd0795d04177529dc88365dd210ee9c2d GIT binary patch literal 29591 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 literal 0 HcmV?d00001 diff --git a/client/images/bulk-product-blueprint-instruction/instruction-3.png b/client/images/bulk-product-blueprint-instruction/instruction-3.png new file mode 100644 index 0000000000000000000000000000000000000000..bc88ec5e60cb2688a1b171ac6b29889856d17633 GIT binary patch literal 36984 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 literal 0 HcmV?d00001 diff --git a/client/images/bulk-product-blueprint-instruction/instruction-5.png b/client/images/bulk-product-blueprint-instruction/instruction-5.png new file mode 100644 index 0000000000000000000000000000000000000000..6218b4a8cd984a555c2e831d17c427e1550587bb GIT binary patch literal 123182 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 literal 0 HcmV?d00001 diff --git a/client/images/bulk-product-blueprint-instruction/instruction-7.png b/client/images/bulk-product-blueprint-instruction/instruction-7.png new file mode 100644 index 0000000000000000000000000000000000000000..7452a017ba4bbef5656aa673f62402a6e3912e83 GIT binary patch literal 176389 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

i|t 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

u6FaW53=#JYVcVaL6)YKY#o)4c?gYp z_R2~EaQvaI|50KA;xcAg+0PTZe5xMdG*Weo4{jht++9~jROK@*zVnH5#0x?IA%GA- z2p|L&0D+nJh!>!NWhEhDj#_;C7;-IEfD?-CoUJC`UyCj)T&Qt`073vEfDl-12&`^5 zkKyatGkxr8>og<}LI5Fv5I_hZ1ad=Qb#L0|h5;&t5I_hZ1P}rUfmMpY6aMcjVo!j7 zR-3bYnkP>G4TgQ+UiD(dPjar5R!}OP?tquHLg*49fDk|kAOsKs2!TaF;4uorUj+14 zB+2CpI9;xGS4t + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/meta/api-docs/add-product-blueprint.md b/meta/api-docs/add-product-blueprint.md new file mode 100644 index 000000000..e6f98f2d6 --- /dev/null +++ b/meta/api-docs/add-product-blueprint.md @@ -0,0 +1,59 @@ +This API handles adding new product category. + +url: `api/add-product-category` + +method: `POST` + +### request: +```js +{ + apiKey: Joi.string().length(64).required(), + organizationId: Joi.number().max(999999999999999).required(), + + name: Joi.string().min(1).max(64).required(), + unit: Joi.string().max(64).required(), + defaultDiscountType: Joi.string().valid('percent', 'fixed').required(), + defaultDiscountValue: Joi.number().when( + 'defaultDiscountType', { + is: 'percent', + then: Joi.number().min(0).max(100).required(), + otherwise: Joi.number().max(999999999999999).required() + } + ), + defaultPurchasePrice: Joi.number().max(999999999999999).required(), + defaultVat: Joi.number().max(999999999999999).required(), + defaultSalePrice: Joi.number().max(999999999999999).required(), + isReturnable: Joi.boolean().required() +} +``` + +### response (on error): +```js +{ + hasError: true, + error: { + code, + message + } +} +``` + +Possible Error Codes: +```js +{ code: VALIDATION_ERROR } // validation error on one of the fields +{ code: APIKEY_INVALID } // the api key is invalid +{ code: ORGANIZATION_INVALID } // the organization id is invalid +{ code: DISCOUNT_VALUE_INVALID } // the discount value is more than sale price +``` + +### response (on success): +```js +{ + hasError: false, + status: "success", + productCategoryId: Joi.number().max(999999999999999).required() +} +``` + +### db changes: +updates the `product-category` collection in db. \ No newline at end of file diff --git a/meta/api-docs/delete-product-blueprint.md b/meta/api-docs/delete-product-blueprint.md new file mode 100644 index 000000000..d4941be1b --- /dev/null +++ b/meta/api-docs/delete-product-blueprint.md @@ -0,0 +1,48 @@ +# WARNING! API DISABLED! + +This API has been disabled. + +# API + +This API handles attempt to delete product-category. + +url: `api/delete-product-category` + +method: `POST` + +### request: +```js +{ + apiKey: Joi.string().length(64).required(), + productCategoryId: Joi.number().max(999999999999999).required() +} +``` + +### response (on error): +```js +{ + "hasError": true, + "error": { + code, + message + } +} +``` + +Possible Error Codes: +```js +{ code: VALIDATION_ERROR } // validation error on one of the fields +{ code: APIKEY_INVALID } // the api key is invalid +{ code: PRODUCT_CATEGORY_INVALID } // product category does not exist +``` + +### response (on success): +```js +{ + "hasError": false, + "status": "success" +} +``` + +### db changes: +updates the `product-category` collection in db. \ No newline at end of file diff --git a/meta/api-docs/edit-product-blueprint.md b/meta/api-docs/edit-product-blueprint.md new file mode 100644 index 000000000..de83d4577 --- /dev/null +++ b/meta/api-docs/edit-product-blueprint.md @@ -0,0 +1,58 @@ +This API handles attempt to update the product category. + +url: `api/edit-product-category` + +method: `POST` + +### request: +```js +{ + apiKey: Joi.string().length(64).required(), + productCategoryId: Joi.number().max(999999999999999).required(), + + name: Joi.string().min(1).max(64).required(), + unit: Joi.string().max(64).required(), + defaultDiscountType: Joi.string().valid('percent', 'fixed').required(), + defaultDiscountValue: Joi.number().when( + 'defaultDiscountType', { + is: 'percent', + then: Joi.number().min(0).max(100).required(), + otherwise: Joi.number().max(999999999999999).required() + } + ), + defaultPurchasePrice: Joi.number().max(999999999999999).required(), + defaultVat: Joi.number().max(999999999999999).required(), + defaultSalePrice: Joi.number().max(999999999999999).required(), + isReturnable: Joi.boolean().required() +} +``` + +### response (on error): +```js +{ + hasError: true, + error: { + code, + message + } +} +``` + +Possible Error Codes: +```js +{ code: VALIDATION_ERROR } // validation error on one of the fields +{ code: APIKEY_INVALID } // the api key is invalid +{ code: PRODUCT_CATEGORY_INVALID } // product category not found +{ code: DISCOUNT_VALUE_INVALID } // the discount value is more than sale price +``` + +### response (on success): +```js +{ + hasError: false, + status: "success" +} +``` + +### db changes: +updates the `product-category` collection in db. \ No newline at end of file diff --git a/meta/api-docs/get-product-blueprint-list.md b/meta/api-docs/get-product-blueprint-list.md new file mode 100644 index 000000000..24fead9d3 --- /dev/null +++ b/meta/api-docs/get-product-blueprint-list.md @@ -0,0 +1,66 @@ +This API handles get product categories to populate product category tree view and search result while adding product to inventory requirement. + +url: `api/get-product-category-list` + +method: `POST` + +### request: +```js +{ + apiKey: Joi.string().length(64).required(), + organizationId: Joi.number().max(999999999999999).required(), + searchString: Joi.string().min(0).max(64).allow('').optional() +} +``` + +### response (on error): +```js +{ + hasError: true, + error: { + code, + message + } +} +``` + +Possible Error Codes: +```js +{ code: VALIDATION_ERROR } // validation error on one of the fields +{ code: APIKEY_INVALID } // the api key is invalid +{ code: ORGANIZATION_INVALID } // the organization id is invalid +``` + +### response (on success): +```js +{ + hasError: false, + + productCategoryList: Joi.array().items( + Joi.object().keys({ + id: Joi.number().max(999999999999999).required(), + createdDatetimeStamp: Joi.number().max(999999999999999).required(), + lastModifiedDatetimeStamp: Joi.number().max(999999999999999).required(), + name: Joi.string().min(1).max(64).required(), + organizationId: Joi.number().max(999999999999999).required(), + unit: Joi.string().max(64).required(), + defaultDiscountType: Joi.string().valid('percent', 'fixed').required(), + defaultDiscountValue: Joi.number().when( + 'defaultDiscountType', { + is: 'percent', + then: Joi.number().min(0).max(100).required(), + otherwise: Joi.number().max(999999999999999).required() + } + ), + defaultPurchasePrice: Joi.number().max(999999999999999).required(), + defaultVat: Joi.number().max(999999999999999).required(), + defaultSalePrice: Joi.number().max(999999999999999).required(), + isDeleted: Joi.boolean().required(), + isReturnable: Joi.boolean().required() + }); + ) +} +``` + +### db changes: +updates no collection in db. \ No newline at end of file diff --git a/meta/server-db-docs/product-blueprint.md b/meta/server-db-docs/product-blueprint.md new file mode 100644 index 000000000..e201157a8 --- /dev/null +++ b/meta/server-db-docs/product-blueprint.md @@ -0,0 +1,29 @@ +This collection contains an product category + +## signature +```js +Joi.object().keys({ + + createdDatetimeStamp: Joi.number().max(999999999999999).required(), + lastModifiedDatetimeStamp: Joi.number().max(999999999999999).required(), + + name: Joi.string().min(1).max(64).required(), + organizationId: Joi.number().max(999999999999999).required(), + unit: Joi.string().max(64).required(), + defaultDiscountType: Joi.string().valid('percent', 'fixed').required(), + defaultDiscountValue: Joi.number().when( + 'defaultDiscountType', { + is: 'percent', + then: Joi.number().min(0).max(100).required(), + otherwise: Joi.number().max(999999999999999).required() + } + ), + defaultPurchasePrice: Joi.number().max(999999999999999).required(), + defaultVat: Joi.number().max(999999999999999).required(), + defaultSalePrice: Joi.number().max(999999999999999).required(), + + isDeleted: Joi.boolean().required(), + isReturnable: Joi.boolean().required() + +}); +``` \ No newline at end of file diff --git a/server/src/apis/add-product-blueprint.js b/server/src/apis/add-product-blueprint.js new file mode 100644 index 000000000..b639f9f56 --- /dev/null +++ b/server/src/apis/add-product-blueprint.js @@ -0,0 +1,50 @@ +const { Api } = require('../api-base'); +const Joi = require('joi'); +const { throwOnFalsy, throwOnTruthy, CodedError } = require('../utils/coded-error'); +const { extract } = require('../utils/extract'); +const { ProductCategoryMixin } = require('./mixins/product-category-mixin'); + +exports.AddProductCategoryApi = class extends Api.mixin(ProductCategoryMixin) { + + get autoValidates() { return true; } + + get requiresAuthentication() { return true; } + + get requestSchema() { + return Joi.object().keys({ + organizationId: Joi.number().max(999999999999999).required(), + + name: Joi.string().min(1).max(64).required(), + unit: Joi.string().max(64).required(), + defaultDiscountType: Joi.string().valid('percent', 'fixed').required(), + defaultDiscountValue: Joi.number().when( + 'defaultDiscountType', { + is: 'percent', + then: Joi.number().min(0).max(100).required(), + otherwise: Joi.number().max(999999999999999).required() + } + ), + defaultPurchasePrice: Joi.number().max(999999999999999).required(), + defaultVat: Joi.number().max(999999999999999).required(), + defaultSalePrice: Joi.number().max(999999999999999).required(), + isReturnable: Joi.boolean().required() + }); + } + + get accessControl() { + return [{ + organizationBy: "organizationId", + privilegeList: [ + "PRIV_MODIFY_ALL_PRODUCT_CATEGORIES" + ] + }]; + } + + async handle({ body }) { + let { organizationId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable } = body; + this._checkIfDiscountValueIsValid({ defaultDiscountType, defaultDiscountValue, defaultSalePrice, defaultVat }); + let productCategoryId = await this._createProductCategory({ organizationId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable }); + return { status: "success", productCategoryId }; + } + +} \ No newline at end of file diff --git a/server/src/apis/edit-product-blueprint.js b/server/src/apis/edit-product-blueprint.js new file mode 100644 index 000000000..955a21df4 --- /dev/null +++ b/server/src/apis/edit-product-blueprint.js @@ -0,0 +1,60 @@ +const { Api } = require('../api-base'); +const Joi = require('joi'); +const { throwOnFalsy, throwOnTruthy, CodedError } = require('../utils/coded-error'); +const { extract } = require('../utils/extract'); +const { ProductCategoryMixin } = require('./mixins/product-blueprint-mixin'); + +exports.EditProductCategoryApi = class extends Api.mixin(ProductCategoryMixin) { + + get autoValidates() { return true; } + + get requiresAuthentication() { return true; } + + get requestSchema() { + return Joi.object().keys({ + productCategoryId: Joi.number().max(999999999999999).required(), + + name: Joi.string().min(1).max(64).required(), + unit: Joi.string().max(64).required(), + defaultDiscountType: Joi.string().valid('percent', 'fixed').required(), + defaultDiscountValue: Joi.number().when( + 'defaultDiscountType', { + is: 'percent', + then: Joi.number().min(0).max(100).required(), + otherwise: Joi.number().max(999999999999999).required() + } + ), + defaultPurchasePrice: Joi.number().max(999999999999999).required(), + defaultVat: Joi.number().max(999999999999999).required(), + defaultSalePrice: Joi.number().max(999999999999999).required(), + isReturnable: Joi.boolean().required() + }); + } + + get accessControl() { + return [{ + organizationBy: { + from: "product-category", + query: ({ productCategoryId }) => ({ id: productCategoryId }), + select: "organizationId", + errorCode: "PRODUCT_CATEGORY_INVALID" + }, + privilegeList: [ + "PRIV_MODIFY_ALL_PRODUCT_CATEGORIES" + ] + }]; + } + + async _updateProductCategory({ productCategoryId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable }) { + let result = await this.database.productCategory.setDetails({ id: productCategoryId }, { name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable }); + this.ensureUpdate(result, 'product-category'); + } + + async handle({ body }) { + let { productCategoryId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable } = body; + await this._checkIfDiscountValueIsValid({ defaultDiscountType, defaultDiscountValue, defaultSalePrice, defaultVat }); + await this._updateProductCategory({ productCategoryId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable }); + return { status: "success" }; + } + +} \ No newline at end of file diff --git a/server/src/apis/get-product-blueprint-list.js b/server/src/apis/get-product-blueprint-list.js new file mode 100644 index 000000000..94086d45e --- /dev/null +++ b/server/src/apis/get-product-blueprint-list.js @@ -0,0 +1,39 @@ +const { Api } = require('../api-base'); +const Joi = require('joi'); + +exports.GetProductCategoryListApi = class extends Api { + + get autoValidates() { return true; } + + get requiresAuthentication() { return true; } + + get autoPaginates() { return ['productCategoryList']; } + + get requestSchema() { + return Joi.object().keys({ + organizationId: Joi.number().max(999999999999999).required(), + searchString: Joi.string().min(0).max(64).allow('').optional() + }); + } + + get accessControl() { + return [{ + organizationBy: "organizationId", + privilegeList: [ + "PRIV_VIEW_ALL_INVENTORIES" + ] + }]; + } + + async _getProductCategoryList({ organizationId, searchString }) { + return await this.database.productCategory.listByOrganizationIdAndSearchString({ organizationId, searchString }); + } + + async handle({ body }) { + let { organizationId, searchString } = body; + let productCategoryList = await this._getProductCategoryList({ organizationId, searchString }); + + return { productCategoryList }; + } + +} \ No newline at end of file diff --git a/server/src/apis/mixins/product-blueprint-mixin.js b/server/src/apis/mixins/product-blueprint-mixin.js new file mode 100644 index 000000000..601c48f91 --- /dev/null +++ b/server/src/apis/mixins/product-blueprint-mixin.js @@ -0,0 +1,30 @@ +const { Api } = require('../../api-base'); +const { throwOnFalsy, throwOnTruthy, CodedError } = require('../../utils/coded-error'); + +/** @param {typeof Api} SuperApiClass */ +exports.ProductCategoryMixin = (SuperApiClass) => class extends SuperApiClass { + + async _createProductCategory({ organizationId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable }) { + return await this.database.productCategory.create({ organizationId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable }); + } + + _checkIfDiscountValueIsValid({ defaultDiscountType, defaultDiscountValue, defaultSalePrice, defaultVat }) { + let salePriceAfterVat = defaultSalePrice + defaultSalePrice * defaultVat / 100; + if (defaultDiscountValue && defaultDiscountType === 'fixed' && defaultDiscountValue > salePriceAfterVat) { + throw new CodedError("DISCOUNT_VALUE_INVALID", "the discount value is more than sale price"); + } + if (defaultDiscountValue && defaultDiscountType === 'percent' && defaultDiscountValue > 100) { + throw new CodedError("DISCOUNT_VALUE_INVALID", "the discount percentage is more than 100"); + } + } + + async _verifyProductCategoriesExist({ productList }) { + await this.crossmap({ + source: productList, + sourceKey: 'productCategoryId', + target: 'productCategory', + onError: (inventory) => { throw new CodedError("PRODUCT_CATEGORY_INVALID", "Unable to find all products in productList"); } + }); + } + +} \ No newline at end of file diff --git a/server/src/collections/product-blueprint.js b/server/src/collections/product-blueprint.js new file mode 100644 index 000000000..d3e0a22ce --- /dev/null +++ b/server/src/collections/product-blueprint.js @@ -0,0 +1,102 @@ + +const { Collection } = require('./../collection-base'); +const Joi = require('joi'); + +exports.ProductCategoryCollection = class extends Collection { + + get name() { return 'product-category'; } + + get joiSchema() { + return Joi.object().keys({ + createdDatetimeStamp: Joi.number().max(999999999999999).required(), + lastModifiedDatetimeStamp: Joi.number().max(999999999999999).required(), + name: Joi.string().min(1).max(64).required(), + organizationId: Joi.number().max(999999999999999).required(), + unit: Joi.string().max(64).required(), + defaultDiscountType: Joi.string().valid('percent', 'fixed').required(), + defaultDiscountValue: Joi.number().when( + 'defaultDiscountType', { + is: 'percent', + then: Joi.number().min(0).max(100).required(), + otherwise: Joi.number().max(999999999999999).required() + } + ), + defaultPurchasePrice: Joi.number().max(999999999999999).required(), + defaultVat: Joi.number().max(999999999999999).required(), + defaultSalePrice: Joi.number().max(999999999999999).required(), + isDeleted: Joi.boolean().required(), + isReturnable: Joi.boolean().required() + }); + } + + get uniqueKeyDefList() { + return [ + { + filters: {}, + keyList: ['organizationId+name'] + } + ]; + } + + get foreignKeyDefList() { + return [ + { + targetCollection: 'organization', + foreignKey: 'id', + referringKey: 'organizationId' + } + ]; + } + + get deletionIndicatorKey() { return 'isDeleted'; } + + async create({ organizationId, + name, + unit, + defaultDiscountType, + defaultDiscountValue, + defaultPurchasePrice, + defaultVat, + defaultSalePrice, + isReturnable }) { + return await this._insert({ + createdDatetimeStamp: (new Date).getTime(), + lastModifiedDatetimeStamp: (new Date).getTime(), + organizationId, + name, + unit, + defaultDiscountType, + defaultDiscountValue, + defaultPurchasePrice, + defaultVat, + defaultSalePrice, + isReturnable, + isDeleted: false + }); + } + + async setDetails({ id }, { name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable }) { + return await this._update({ id }, { + $set: { + name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable + } + }); + } + + async listByOrganizationId({ organizationId }) { + return await this._find({ organizationId }); + } + + async listByOrganizationIdAndSearchString({ organizationId, searchString }) { + let query = { organizationId }; + if (searchString) { + searchString = this.escapeRegExp(searchString); + let searchRegex = new RegExp(searchString, 'i'); + query.$or = [ + { name: searchRegex } + ]; + } + return await this._find(query); + } + +} diff --git a/server/src/legacy-apis/__depr__add-product-blueprint.js b/server/src/legacy-apis/__depr__add-product-blueprint.js new file mode 100644 index 000000000..7c65aab93 --- /dev/null +++ b/server/src/legacy-apis/__depr__add-product-blueprint.js @@ -0,0 +1,67 @@ + +let { LegacyApi } = require('../legacy-api-base'); +let Joi = require('joi'); + +let { collectionCommonMixin } = require('./mixins/collection-common'); + +exports.AddProductCategoryApi = class extends collectionCommonMixin(LegacyApi) { + + get autoValidates() { return true; } + + get requiresAuthentication() { return true; } + + get requestSchema() { + return Joi.object().keys({ + // apiKey: Joi.string().length(64).required(), + + organizationId: Joi.number().max(999999999999999).required(), + + name: Joi.string().min(1).max(64).required(), + unit: Joi.string().max(64).required(), + defaultDiscountType: Joi.string().valid('percent', 'fixed').required(), + defaultDiscountValue: Joi.number().when( + 'defaultDiscountType', { + is: 'percent', + then: Joi.number().min(0).max(100).required(), + otherwise: Joi.number().max(999999999999999).required() + } + ), + defaultPurchasePrice: Joi.number().max(999999999999999).required(), + defaultVat: Joi.number().max(999999999999999).required(), + defaultSalePrice: Joi.number().max(999999999999999).required(), + isReturnable: Joi.boolean().required() + }); + } + + get accessControl() { + return [{ + organizationBy: "organizationId", + privilegeList: [ + "PRIV_MODIFY_ALL_PRODUCT_CATEGORIES" + ] + }]; + } + + _createProductCategory(productCategory, cbfn) { + this.legacyDatabase.productCategory.create(productCategory, (err, productCategoryId) => { + if (err) return this.fail(err); + return cbfn(productCategoryId); + }); + } + + _checkAndCreateProductCategory({ organizationId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable }, cbfn) { + let productCategory = { + organizationId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable + } + + this._createProductCategory(productCategory, cbfn); + } + + handle({ body }) { + let { organizationId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable } = body; + this._checkAndCreateProductCategory({ organizationId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable }, (productCategoryId) => { + this.success({ status: "success", productCategoryId }); + }); + } + +} \ No newline at end of file diff --git a/server/src/legacy-apis/__depr__edit-product-blueprint.js b/server/src/legacy-apis/__depr__edit-product-blueprint.js new file mode 100644 index 000000000..7b4df21df --- /dev/null +++ b/server/src/legacy-apis/__depr__edit-product-blueprint.js @@ -0,0 +1,66 @@ +let { LegacyApi } = require('../legacy-api-base'); +let Joi = require('joi'); + +let { collectionCommonMixin } = require('./mixins/collection-common'); + +exports.EditProductCategoryApi = class extends collectionCommonMixin(LegacyApi) { + + get autoValidates() { return true; } + + get requiresAuthentication() { return true; } + + get requestSchema() { + return Joi.object().keys({ + // apiKey: Joi.string().length(64).required(), + productCategoryId: Joi.number().max(999999999999999).required(), + + name: Joi.string().min(1).max(64).required(), + unit: Joi.string().max(64).required(), + defaultDiscountType: Joi.string().valid('percent', 'fixed').required(), + defaultDiscountValue: Joi.number().when( + 'defaultDiscountType', { + is: 'percent', + then: Joi.number().min(0).max(100).required(), + otherwise: Joi.number().max(999999999999999).required() + } + ), + defaultPurchasePrice: Joi.number().max(999999999999999).required(), + defaultVat: Joi.number().max(999999999999999).required(), + defaultSalePrice: Joi.number().max(999999999999999).required(), + isReturnable: Joi.boolean().required() + }); + } + + get accessControl() { + return [{ + organizationBy: { + from: "product-category", + query: ({ productCategoryId }) => ({ id: productCategoryId }), + select: "organizationId", + errorCode: "PRODUCT_CATEGORY_INVALID" + }, + privilegeList: [ + "PRIV_MODIFY_ALL_PRODUCT_CATEGORIES" + ] + }]; + } + + _checkAndUpdateProductCategory({ productCategoryId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable }, cbfn) { + this._updateProductCategory({ productCategoryId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable }, cbfn); + } + + _updateProductCategory({ productCategoryId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable }, cbfn) { + this.legacyDatabase.productCategory.update({ productCategoryId }, { name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable }, (err, wasUpdated) => { + if (!this._ensureUpdate(err, wasUpdated, "product-category")) return; + return cbfn(); + }); + } + + handle({ body, userId }) { + let { productCategoryId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable } = body; + this._checkAndUpdateProductCategory({ productCategoryId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable }, () => { + this.success({ status: "success" }); + }); + } + +} \ No newline at end of file diff --git a/server/src/legacy-apis/__depr__get-product-blueprint-list.js b/server/src/legacy-apis/__depr__get-product-blueprint-list.js new file mode 100644 index 000000000..c4f3d89e8 --- /dev/null +++ b/server/src/legacy-apis/__depr__get-product-blueprint-list.js @@ -0,0 +1,41 @@ +let { LegacyApi } = require('../legacy-api-base'); +let Joi = require('joi'); + +exports.GetProductCategoryListApi = class extends LegacyApi { + + get autoValidates() { return true; } + + get requiresAuthentication() { return true; } + + get requestSchema() { + return Joi.object().keys({ + // apiKey: Joi.string().length(64).required(), + + organizationId: Joi.number().max(999999999999999).required() + }); + } + + get accessControl() { + return [{ + organizationBy: "organizationId", + privilegeList: [ + "PRIV_VIEW_ALL_INVENTORIES" + ] + }]; + } + + _getProductCategoryList({ organizationId }, cbfn) { + this.legacyDatabase.productCategory.listByOrganizationId({ organizationId }, (err, productCategoryList) => { + if (err) return this.fail(err); + cbfn(productCategoryList); + }) + } + + handle({ body }) { + let { organizationId } = body; + this._getProductCategoryList({ organizationId }, (productCategoryList) => { + this.success({ productCategoryList }); + }); + } + +} \ No newline at end of file diff --git a/server/src/legacy-apis/delete-product-blueprint.js b/server/src/legacy-apis/delete-product-blueprint.js new file mode 100644 index 000000000..56e18f726 --- /dev/null +++ b/server/src/legacy-apis/delete-product-blueprint.js @@ -0,0 +1,59 @@ +let { LegacyApi } = require('../legacy-api-base'); +let Joi = require('joi'); + +let { collectionCommonMixin } = require('./mixins/collection-common'); + +exports.DeleteProductCategoryApi = class extends collectionCommonMixin(LegacyApi) { + + get isEnabled() { return false; } + + get autoValidates() { return true; } + + get requiresAuthentication() { return true; } + + get requestSchema() { + return Joi.object().keys({ + // apiKey: Joi.string().length(64).required(), + productCategoryId: Joi.number().max(999999999999999).required(), + }); + } + + get accessControl() { + return [{ + organizationBy: { + from: "product-category", + query: ({ productCategoryId }) => ({ id: productCategoryId }), + select: "organizationId", + errorCode: "PRODUCT_CATEGORY_INVALID" + }, + privilegeList: [ + "PRIV_MODIFY_ALL_PRODUCT_CATEGORIES" + ] + }]; + } + + _checkAndDeleteProductCategory({ productCategoryId }, cbfn) { + // FIXME: below method listChildren is not present anymore as product categories are flat + this.legacyDatabase.productCategory.listChildren({ productCategoryId }, (err, productCategoryChildList) => { + if (err) return this.fail(err); + if (productCategoryChildList.length > 0) { + err = new Error("product category is parent of atleast one category"); + err.code = "PRODUCT_CATEGORY_NOT_CHILDLESS"; + return this.fail(err); + } + this._deleteProductCategory({ productCategoryId }, cbfn); + }) + } + + _deleteProductCategory({ productCategoryId }, cbfn) { + this._deleteDocById(this.legacyDatabase.productCategory, { productCategoryId }, cbfn); + } + + handle({ body, userId }) { + let { productCategoryId } = body; + this._checkAndDeleteProductCategory({ productCategoryId }, _ => { + this.success({ status: "success" }); + }); + } + +} \ No newline at end of file diff --git a/server/src/legacy-collections/product-blueprint.js b/server/src/legacy-collections/product-blueprint.js new file mode 100644 index 000000000..cf35d2ec4 --- /dev/null +++ b/server/src/legacy-collections/product-blueprint.js @@ -0,0 +1,124 @@ +const { LegacyCollection } = require('../legacy-collection-base'); + +/* ================================================================= ++++++++++++++ WARNING +++++++++++++ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +++ This is a LegacyCollection. It uses async callbacks. New APIs ++ +++ should not be using this. Even when using with legacy APIs, if ++ +++ you make a change, pleaase replicate the change in the ++ +++ non-legacy Collection of the same name. ++ +++ Talk to @iShafayet if unsure. ++ +================================================================= */ + +const Joi = require('joi'); + +exports.ProductCategoryCollection = class extends LegacyCollection { + + constructor(...args) { + super(...args); + + this.collectionName = 'product-category'; + + this.joiSchema = Joi.object().keys({ + createdDatetimeStamp: Joi.number().max(999999999999999).required(), + lastModifiedDatetimeStamp: Joi.number().max(999999999999999).required(), + name: Joi.string().min(1).max(64).required(), + organizationId: Joi.number().max(999999999999999).required(), + unit: Joi.string().max(64).required(), + defaultDiscountType: Joi.string().valid('percent', 'fixed').required(), + defaultDiscountValue: Joi.number().when( + 'defaultDiscountType', { + is: 'percent', + then: Joi.number().min(0).max(100).required(), + otherwise: Joi.number().max(999999999999999).required() + } + ), + defaultPurchasePrice: Joi.number().max(999999999999999).required(), + defaultVat: Joi.number().max(999999999999999).required(), + defaultSalePrice: Joi.number().max(999999999999999).required(), + isDeleted: Joi.boolean().required(), + isReturnable: Joi.boolean().required() + }); + + this.uniqueDefList = [ + { + additionalQueryFilters: {}, + uniqueKeyList: [] + } + ]; + + this.foreignKeyDefList = [ + { + targetCollection: 'organization', + foreignKey: 'id', + referringKey: 'organizationId' + } + ]; + } + + /** + * + * + * @param {any} { organizationId, name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable } + * @param {any} cbfn + */ + create(data, cbfn) { + let { + organizationId, + name, + unit, + defaultDiscountType, + defaultDiscountValue, + defaultPurchasePrice, + defaultVat, + defaultSalePrice, + isReturnable + } = data; + let doc = { + createdDatetimeStamp: (new Date).getTime(), + lastModifiedDatetimeStamp: (new Date).getTime(), + organizationId, + name, + unit, + defaultDiscountType, + defaultDiscountValue, + defaultPurchasePrice, + defaultVat, + defaultSalePrice, + isReturnable, + isDeleted: false + } + this._insert(doc, (err, id) => { + return cbfn(err, id); + }); + } + + listByOrganizationId({ organizationId }, cbfn) { + this._find({ organizationId }, cbfn); + } + + findById({ productCategoryId }, cbfn) { + this._findOne({ id: productCategoryId }, cbfn) + } + + // FIXME: naming issue + listByIdList({ idList }, cbfn) { + this._find({ id: { $in: idList } }, cbfn); + } + + update({ productCategoryId }, { name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable }, cbfn) { + let modifications = { + $set: { + name, unit, defaultDiscountType, defaultDiscountValue, defaultPurchasePrice, defaultVat, defaultSalePrice, isReturnable + } + } + this._update({ id: productCategoryId }, modifications, cbfn); + } + + delete({ productCategoryId }, cbfn) { + let modifications = { + $set: { isDeleted: true } + } + this._update({ id: productCategoryId }, modifications, cbfn); + } +} \ No newline at end of file diff --git a/server/test/test-product-blueprint-apis.js b/server/test/test-product-blueprint-apis.js new file mode 100644 index 000000000..f10ae81a9 --- /dev/null +++ b/server/test/test-product-blueprint-apis.js @@ -0,0 +1,486 @@ +let expect = require('chai').expect; + +let { callApi } = require('./utils'); +let { + rnd, + generateInvalidId, + initializeServer, + terminateServer, + registerUser, + loginUser, + addOrganization, + validateProductCategorySchema, + validateAddProductCategoryApiSuccessResponse, + validateGenericApiFailureResponse, + validateGetProductCategoryListApiSuccessResponse, + validateGenericApiSuccessResponse, + validateBulkImportProductCategoriesApiSuccessResponse +} = require('./lib'); + +const prefix = 's'; + +const email = `${rnd(prefix)}@gmail.com`; +const password = "123545678"; +const fullName = "Test User"; +const phone = rnd(prefix, 11); + +const orgEmail = 'o' + `${rnd(prefix)}@gmail.com`; +const orgName = "Test Organization"; +const orgBusinessAddress = "My Address"; +const orgPhone = 'o' + rnd(prefix, 11); + +let apiKey = null; +let organizationId = null; + +let productCategoryList = null; +let productCategoryOne = null; +let productCategoryTwo = null; +let productCategoryThree = null; + +let productCategoryToBeModified = null; +let invalidOrganizationId = generateInvalidId(); +let invalidProductCategoryId = generateInvalidId(); + +describe('Product Category', _ => { + + it('START', testDoneFn => { + initializeServer(_ => { + registerUser({ + password, fullName, phone + }, _ => { + loginUser({ + emailOrPhone: phone, password + }, (data) => { + apiKey = data.apiKey; + addOrganization({ + apiKey, + name: orgName, + primaryBusinessAddress: orgBusinessAddress, + phone: orgPhone, + email: orgEmail + }, (data) => { + organizationId = data.organizationId; + testDoneFn(); + }) + }); + }); + }); + }); + + // ADD-GET + + it('api/add-product-category (Valid)', testDoneFn => { + + callApi('api/add-product-category', { + json: { + apiKey, + organizationId, + name: "1st product category", + unit: "kg", + defaultDiscountType: "percent", + defaultDiscountValue: 10, + defaultPurchasePrice: 99, + defaultVat: 2, + defaultSalePrice: 111, + isReturnable: true + } + }, (err, response, body) => { + expect(response.statusCode).to.equal(200); + validateAddProductCategoryApiSuccessResponse(body); + testDoneFn(); + }) + + }); + + it('api/add-product-category (Invalid copy name)', testDoneFn => { + + callApi('api/add-product-category', { + json: { + apiKey, + organizationId, + name: "1st product category", + unit: "kg", + defaultDiscountType: "percent", + defaultDiscountValue: 10, + defaultPurchasePrice: 99, + defaultVat: 2, + defaultSalePrice: 111, + isReturnable: true + } + }, (err, response, body) => { + expect(response.statusCode).to.equal(200); + validateGenericApiFailureResponse(body); + testDoneFn(); + }) + + }); + + it('api/add-product-category (Valid)', testDoneFn => { + + callApi('api/add-product-category', { + json: { + apiKey, + organizationId, + name: "2nd product category", + unit: "kg", + defaultDiscountType: "fixed", + defaultDiscountValue: 11, + defaultPurchasePrice: 99, + defaultVat: 2, + defaultSalePrice: 111, + isReturnable: false + } + }, (err, response, body) => { + expect(response.statusCode).to.equal(200); + validateAddProductCategoryApiSuccessResponse(body); + testDoneFn(); + }) + + }); + + it('api/add-product-category (Invalid organization)', testDoneFn => { + + callApi('api/add-product-category', { + json: { + apiKey, + organizationId: invalidOrganizationId, + name: "invalid product category", + unit: "kg", + defaultDiscountType: "percent", + defaultDiscountValue: 10, + defaultPurchasePrice: 99, + defaultVat: 2, + defaultSalePrice: 111, + isReturnable: true + } + }, (err, response, body) => { + expect(response.statusCode).to.equal(200); + validateGenericApiFailureResponse(body); + expect(body.error.code).equal('ORGANIZATION_INVALID'); + testDoneFn(); + }) + + }); + + it('api/get-product-category-list (Valid searchString)', testDoneFn => { + + callApi('api/get-product-category-list', { + json: { + apiKey, + organizationId, + searchString: '1ST' + } + }, (err, response, body) => { + expect(response.statusCode).to.equal(200); + + validateGetProductCategoryListApiSuccessResponse(body); + + body.productCategoryList.forEach(productCategory => { + validateProductCategorySchema(productCategory); + }); + + testDoneFn(); + }); + + }); + + it('api/get-product-category-list (Valid)', testDoneFn => { + + callApi('api/get-product-category-list', { + json: { + apiKey, + organizationId + } + }, (err, response, body) => { + expect(response.statusCode).to.equal(200); + validateGetProductCategoryListApiSuccessResponse(body); + + body.productCategoryList.forEach(productCategory => { + validateProductCategorySchema(productCategory); + }); + + body.productCategoryList.reverse(); + productCategoryOne = body.productCategoryList[0]; + + testDoneFn(); + }); + + }); + + it('api/get-product-category-list (Invalid Organization)', testDoneFn => { + + callApi('api/get-product-category-list', { + json: { + apiKey, + organizationId: invalidOrganizationId + } + }, (err, response, body) => { + expect(response.statusCode).to.equal(200); + validateGenericApiFailureResponse(body); + expect(body.error.code).equal('ORGANIZATION_INVALID'); + testDoneFn(); + }); + + }); + + it('api/get-product-category-list (Valid)', testDoneFn => { + + callApi('api/get-product-category-list', { + json: { + apiKey, + organizationId + } + }, (err, response, body) => { + expect(response.statusCode).to.equal(200); + validateGetProductCategoryListApiSuccessResponse(body); + body.productCategoryList.forEach(productCategory => { + validateProductCategorySchema(productCategory); + }); + + body.productCategoryList.reverse(); + productCategoryOne = body.productCategoryList[0]; + productCategoryTwo = body.productCategoryList[1]; + productCategoryThree = body.productCategoryList[2]; + + testDoneFn(); + }); + + }); + + it('api/add-product-category (Invalid fixed defaultDiscountValue)', testDoneFn => { + + callApi('api/add-product-category', { + json: { + apiKey, + organizationId, + name: "1st product category", + unit: "kg", + defaultDiscountType: "fixed", + defaultDiscountValue: 114, + defaultPurchasePrice: 99, + defaultVat: 2, + defaultSalePrice: 111, + isReturnable: true + } + }, (err, response, body) => { + expect(response.statusCode).to.equal(200); + validateGenericApiFailureResponse(body); + expect(body.error.code).equal('DISCOUNT_VALUE_INVALID'); + testDoneFn(); + }) + + }); + + // EDIT + + it('api/edit-product-category (Valid)', testDoneFn => { + + callApi('api/edit-product-category', { + json: { + apiKey, + productCategoryId: productCategoryOne.id, + name: "new 1st product category name", // modification + unit: "kg", + defaultDiscountType: "percent", + defaultDiscountValue: 10, + defaultPurchasePrice: 99, + defaultVat: 2, + defaultSalePrice: 111, + isReturnable: false // modification + } + }, (err, response, body) => { + expect(response.statusCode).to.equal(200); + validateGenericApiSuccessResponse(body); + testDoneFn(); + }) + + }); + + it('api/edit-product-category (Invalid copy name)', testDoneFn => { + + callApi('api/edit-product-category', { + json: { + apiKey, + productCategoryId: productCategoryOne.id, + name: "2nd product category", // copy modification + unit: "kg", + defaultDiscountType: "percent", + defaultDiscountValue: 10, + defaultPurchasePrice: 99, + defaultVat: 2, + defaultSalePrice: 111, + isReturnable: true + } + }, (err, response, body) => { + expect(response.statusCode).to.equal(200); + validateGenericApiFailureResponse(body); + testDoneFn(); + }) + + }); + + it('api/edit-product-category (Invalid productCategoryId)', testDoneFn => { + + callApi('api/edit-product-category', { + json: { + apiKey, + productCategoryId: invalidProductCategoryId, + + name: "new product category name", // modification + unit: "kg", + defaultDiscountType: "percent", + defaultDiscountValue: 10, + defaultPurchasePrice: 99, + defaultVat: 2, + defaultSalePrice: 111, + isReturnable: false // modification + } + }, (err, response, body) => { + expect(response.statusCode).to.equal(200); + validateGenericApiFailureResponse(body); + expect(body.error.code).equal('PRODUCT_CATEGORY_INVALID'); + testDoneFn(); + }) + + }); + + it('api/get-product-category-list (Valid modification check)', testDoneFn => { + + callApi('api/get-product-category-list', { + json: { + apiKey, + organizationId + } + }, (err, response, body) => { + expect(response.statusCode).to.equal(200); + validateGetProductCategoryListApiSuccessResponse(body); + body.productCategoryList.forEach(productCategory => { + validateProductCategorySchema(productCategory); + }); + + body.productCategoryList.reverse(); + productCategoryList = body.productCategoryList; + expect(body.productCategoryList[0].name).to.equal("new 1st product category name"); + expect(body.productCategoryList[0].isReturnable).to.equal(false); + + testDoneFn(); + }); + + }); + + // DELETE + + it('api/delete-product-category (Confirm that API is disabled)', testDoneFn => { + + callApi('api/delete-product-category', { + json: { + apiKey, + productCategoryId: invalidProductCategoryId + } + }, (err, response, body) => { + expect(response.statusCode).to.equal(200); + validateGenericApiFailureResponse(body); + expect(body.error.code).equal('API_DISABLED'); + testDoneFn(); + }) + + }); + + it('api/bulk-import-product-categories (Valid and unique)', testDoneFn => { + + callApi('api/bulk-import-product-categories', { + json: { + apiKey, + organizationId, + rowList: [ + ["Should Be Unique 1", "pc", 300, 500, 10, "percent", 100, "Yes"], + ["Should Be Unique 2", "haali", 10, 10, 10, "fixed", 0, "No"] + ] + } + }, (err, response, body) => { + expect(response.statusCode).to.equal(200); + validateBulkImportProductCategoriesApiSuccessResponse(body); + expect(body.ignoredRowList).to.deep.equal([]); + expect(body.successfulCount).to.equal(2); + testDoneFn(); + }) + + }); + + it('api/bulk-import-product-categories (Valid but not unique)', testDoneFn => { + + callApi('api/bulk-import-product-categories', { + json: { + apiKey, + organizationId, + rowList: [ + ["Should Be Unique 3", "pc", 300, 500, 10, "percent", 100, "Yes"], + ["Should Be Unique 2", "haali", 10, 10, 10, "fixed", 0, "No"] + ] + } + }, (err, response, body) => { + expect(response.statusCode).to.equal(200); + validateBulkImportProductCategoriesApiSuccessResponse(body); + expect(body.ignoredRowList).to.deep.equal([ + { + "reason": "name-duplication", + "rowNumber": 2 + } + ]); + expect(body.successfulCount).to.equal(1); + testDoneFn(); + }) + + }); + + it('api/bulk-import-product-categories (Invalid)', testDoneFn => { + + callApi('api/bulk-import-product-categories', { + json: { + apiKey, + organizationId, + rowList: [ + ["Should Be Unique 4", "pc", 300, 500, 10, "percent", 100, "FFYes"], + ["Should Be Unique 5", "haali", 10, 10, 10, "fixed", 0, "No"] + ] + } + }, (err, response, body) => { + expect(response.statusCode).to.equal(200); + validateGenericApiFailureResponse(body); + expect(body.error.code).to.equal('MODIFIED_VALIDATION_ERROR'); + expect(body.error.message).to.equal('Cell #8 must be one of [Yes, No]'); + expect(body.error.rowNumber).to.equal(1); + expect(body.error.cellNumber).to.equal(8); + testDoneFn(); + }) + + }); + + it('api/bulk-import-product-categories (Invalid)', testDoneFn => { + + callApi('api/bulk-import-product-categories', { + json: { + apiKey, + organizationId, + rowList: [ + ["Should Be Unique 5", "pc", 300, 500, 10, "percent", 100, "Yes"], + ["", "haali", 10, 10, 10, "fixed", 0, "No"] + ] + } + }, (err, response, body) => { + expect(response.statusCode).to.equal(200); + validateGenericApiFailureResponse(body); + expect(body.error.code).to.equal('MODIFIED_VALIDATION_ERROR'); + expect(body.error.message).to.equal('Cell #1 is not allowed to be empty'); + expect(body.error.rowNumber).to.equal(2); + expect(body.error.cellNumber).to.equal(1); + testDoneFn(); + }) + + }); + + it('END', testDoneFn => { + terminateServer(testDoneFn); + }); + +}); \ No newline at end of file From 3ca774eec51f0324f027ee3911cc3fa197f399cf Mon Sep 17 00:00:00 2001 From: iLGunners Date: Thu, 18 Oct 2018 18:11:11 +0600 Subject: [PATCH 004/224] replaced category and categories words in all cases with blueprint and blueprints --- .../elem-common-employment-details-input.html | 2 +- client/src/lang-bn-bd.json | 50 ++--- client/src/lang-en-us.json | 76 ++++---- client/src/page-add-products.html | 136 +++++++------- ... page-bulk-import-product-blueprints.html} | 78 ++++---- client/src/page-edit-product-blueprint.html | 120 ++++++------ client/src/page-edit-product.html | 2 +- client/src/page-edit-sales-return.html | 2 +- ...ml => page-manage-product-blueprints.html} | 72 +++---- client/src/page-pos-select-products.html | 2 +- client/src/page-print-sales-receipt.html | 2 +- client/src/page-report-inventories.html | 2 +- client/src/page-report-sales.html | 2 +- client/src/page-view-inventory.html | 2 +- client/src/page-view-sales-return.html | 2 +- client/src/page-view-sales.html | 6 +- client/src/torque-app.html | 14 +- client/src/torque-http-behavior.html | 20 +- legacy/fragments/removed-tests.js | 40 ++-- legacy/plans/version-0.0.1-masterplan.md | 2 +- meta/api-docs/add-product-blueprint.md | 8 +- meta/api-docs/add-product-to-inventory.md | 4 +- meta/api-docs/add-sales-return.md | 4 +- ...s.md => bulk-import-product-blueprints.md} | 6 +- meta/api-docs/delete-product-blueprint.md | 10 +- meta/api-docs/edit-product-blueprint.md | 10 +- .../get-aggregated-inventory-details.md | 6 +- meta/api-docs/get-employee-list.md | 2 +- meta/api-docs/get-employee.md | 2 +- meta/api-docs/get-product-blueprint-list.md | 6 +- meta/api-docs/get-product.md | 2 +- meta/api-docs/get-sales-list.md | 2 +- meta/api-docs/get-sales.md | 8 +- meta/api-docs/report-inventory-details.md | 4 +- meta/server-db-docs/employment.md | 2 +- meta/server-db-docs/product-blueprint.md | 2 +- meta/server-db-docs/product.md | 2 +- server/scripts/bulk.js | 34 ++-- server/src/apis/add-product-blueprint.js | 10 +- server/src/apis/add-product-to-inventory.js | 12 +- ...s.js => bulk-import-product-blueprints.js} | 36 ++-- server/src/apis/edit-inventory-product.js | 2 +- server/src/apis/edit-product-blueprint.js | 24 +-- .../apis/get-aggregated-inventory-details.js | 2 +- server/src/apis/get-product-blueprint-list.js | 12 +- server/src/apis/get-sales-list.js | 6 +- server/src/apis/mixins/inventory-mixin.js | 10 +- .../apis/mixins/product-blueprint-mixin.js | 14 +- server/src/apis/mixins/product-mixin.js | 10 +- server/src/collections/employment.js | 4 +- server/src/collections/product-blueprint.js | 4 +- server/src/collections/product.js | 10 +- server/src/database-service.js | 4 +- server/src/fixtures/privilege-list.json | 4 +- server/src/index.js | 24 +-- .../__depr__add-product-blueprint.js | 20 +- .../__depr__add-product-to-inventory.js | 10 +- .../__depr__edit-product-blueprint.js | 26 +-- ..._depr__get-aggregated-inventory-details.js | 12 +- .../__depr__get-product-blueprint-list.js | 12 +- .../src/legacy-apis/__depr__get-sales-list.js | 14 +- server/src/legacy-apis/add-new-employee.js | 2 +- .../legacy-apis/delete-product-blueprint.js | 34 ++-- server/src/legacy-apis/edit-employment.js | 2 +- server/src/legacy-apis/get-sales-return.js | 16 +- server/src/legacy-apis/get-sales.js | 18 +- .../src/legacy-apis/hire-user-as-employee.js | 2 +- .../src/legacy-apis/mixins/product-common.js | 18 +- server/src/legacy-collections/employment.js | 6 +- .../legacy-collections/product-blueprint.js | 16 +- server/src/legacy-collections/product.js | 10 +- server/test/lib.js | 48 ++--- server/test/test-employee-apis.js | 16 +- server/test/test-inventory-apis.js | 42 ++--- server/test/test-outlet-apis.js | 12 +- server/test/test-product-blueprint-apis.js | 172 ++++++++--------- server/test/test-sales-apis.js | 176 +++++++++--------- server/test/test-sales-return-apis.js | 70 +++---- server/test/test-warehouse-apis.js | 12 +- 79 files changed, 850 insertions(+), 850 deletions(-) rename client/src/{page-bulk-import-product-categories.html => page-bulk-import-product-blueprints.html} (81%) rename client/src/{page-manage-product-categories.html => page-manage-product-blueprints.html} (72%) rename meta/api-docs/{bulk-import-product-categories.md => bulk-import-product-blueprints.md} (92%) rename server/src/apis/{bulk-import-product-categories.js => bulk-import-product-blueprints.js} (70%) diff --git a/client/src/elem-common-employment-details-input.html b/client/src/elem-common-employment-details-input.html index 2d390ab77..e18b2d6c2 100644 --- a/client/src/elem-common-employment-details-input.html +++ b/client/src/elem-common-employment-details-input.html @@ -100,7 +100,7 @@ "PRIV_MODIFY_SALES_RETURN", "PRIV_VIEW_ALL_INVENTORIES", - "PRIV_MODIFY_ALL_PRODUCT_CATEGORIES", + "PRIV_MODIFY_ALL_PRODUCT_BLUEPRINTS", "PRIV_TRANSFER_ALL_INVENTORIES", "PRIV_ADD_PRODUCTS_TO_ALL_INVENTORIES", diff --git a/client/src/lang-bn-bd.json b/client/src/lang-bn-bd.json index 7a64738cb..3c8cdc20a 100644 --- a/client/src/lang-bn-bd.json +++ b/client/src/lang-bn-bd.json @@ -109,7 +109,7 @@ "employees": "কর্মচারী", "outlets": "দোকান", "warehouses": "গুদাম", - "productCategories": "পণ্যের ধরন", + "productBlueprints": "পণ্যের ধরন", "sales": "বিক্রয় রেকর্ড", "salesReturn": "বিক্রয় ফেরত রেকর্ড", "customers": "গ্রাহক", @@ -226,42 +226,42 @@ "damagedInventoryName": "ক্ষতিগ্রস্ত", "noOutletFound": "আপনার সংস্থার কোনো দোকান তৈরি করা হয়নি।" }, - "productCategory": { - "manageProductCategoriesTitle": "পণ্যের ধরন পরিচালনা", - "editProductCategoryTitle": "পণ্যের ধরন সম্পাদনা করুন", - "addProductCategoryTitle": "পণ্যের ধরন যুক্ত করুন", - "noProductCategoryFound": "কোনো পণ্যের ধরন পাওয়া যায় নি।", + "productBlueprint": { + "manageProductBlueprintsTitle": "পণ্যের ধরন পরিচালনা", + "editProductBlueprintTitle": "পণ্যের ধরন সম্পাদনা করুন", + "addProductBlueprintTitle": "পণ্যের ধরন যুক্ত করুন", + "noProductBlueprintFound": "কোনো পণ্যের ধরন পাওয়া যায় নি।", "parentNameNotifierStart": "নতুন পণ্যের ধরণটি যুক্ত করা হবে", "parentNameNotifierEnd": " পণ্যের ধরণের অধীনে। ", - "productCategoryNameInput": "পণ্যের ধরণের নাম", - "productCategoryNameInputError": "একটি সঠিক নাম লিখুন", - "productCategoryUnitInput": "একক নির্দিষ্ট করুন (যেমন কেজি, পিস্)", - "productCategoryUnitInputError": "একটি সঠিক একক লিখুন", + "productBlueprintNameInput": "পণ্যের ধরণের নাম", + "productBlueprintNameInputError": "একটি সঠিক নাম লিখুন", + "productBlueprintUnitInput": "একক নির্দিষ্ট করুন (যেমন কেজি, পিস্)", + "productBlueprintUnitInputError": "একটি সঠিক একক লিখুন", "defaultPurchasePriceInput": "ক্রয় মূল্য", "defaultPurchasePriceInputError": "একটি সঠিক অংক লিখুন", "defaultVatInput": "ভ্যাট", "defaultVatInputError": "একটি সঠিক অংক লিখুন", - "productCategoryDefaultSalePriceInput": "বিক্রয় মূল্য", - "productCategoryDefaultSalePriceInputError": "একটি সঠিক অংক লিখুন", + "productBlueprintDefaultSalePriceInput": "বিক্রয় মূল্য", + "productBlueprintDefaultSalePriceInputError": "একটি সঠিক অংক লিখুন", "isProductReturnable": "গ্রাহক এই ধরণের পণ্য ফেরত দিতে পারবে।", "defaultDiscountType": "ছাড় এর ধরণ", "defaultDiscountValueInput": "ছাড় পরিমান", "defaultDiscountValueInputError": "একটি সঠিক অংক লিখুন", - "createProductCategory": "পণ্যের ধরণ যুক্ত করুন", - "updateProductCategory": "পণ্যের ধরণ আপডেট করুন", - "productCategoryCreated": "নতুন পণ্যের ধরণ যুক্ত করা হয়েছে।", - "productCategoryUpdated": "পণ্যের ধরণ আপডেট করা হয়েছে।", + "createProductBlueprint": "পণ্যের ধরণ যুক্ত করুন", + "updateProductBlueprint": "পণ্যের ধরণ আপডেট করুন", + "productBlueprintCreated": "নতুন পণ্যের ধরণ যুক্ত করা হয়েছে।", + "productBlueprintUpdated": "পণ্যের ধরণ আপডেট করা হয়েছে।", "buyingPrice": "ক্রয় মূল্য", "sellingPrice": "বিক্রয় মূল্য", "moveHere": "এখানে সরান", - "subCategory": "উপ বিভাগ", + "subBlueprint": "উপ বিভাগ", "moveToRoot": "বাহিরে সরান", - "createNewProductCategory": "নতুন পণ্য ধরণ তৈরি করুন", - "searchForProductCategoryInput": "পণ্য তালিকার নাম", - "searchForProductCategoryInputError": "পণ্য তালিকার সন্ধানে ত্রুটি", + "createNewProductBlueprint": "নতুন পণ্য ধরণ তৈরি করুন", + "searchForProductBlueprintInput": "পণ্য তালিকার নাম", + "searchForProductBlueprintInputError": "পণ্য তালিকার সন্ধানে ত্রুটি", "bulkImport": "একাধিক পণ্যের ধরন তৈরি", - "whatIsProductCategoryMessage": "পণ্যের ধরণ একটু পণ্যের নকশা। প্রতি পণ্যের জন্য একবারই পণ্যের ধরণ তৈরি করতে হবে। পণ্যের ধরণে দেয়া তথ্য পণ্য সংক্রান্ত কাজে ব্যবহত হবে।" + "whatIsProductBlueprintMessage": "পণ্যের ধরণ একটু পণ্যের নকশা। প্রতি পণ্যের জন্য একবারই পণ্যের ধরণ তৈরি করতে হবে। পণ্যের ধরণে দেয়া তথ্য পণ্য সংক্রান্ত কাজে ব্যবহত হবে।" }, "customerSelector": { "customerNameOrPhoneInput": "গ্রাহকের নাম বা ফোন", @@ -323,11 +323,11 @@ "addProductsTitle": "পণ্য যুক্ত করুন", "addProducts": "পণ্য যুক্ত করুন", "productAddedToInventory": "পণ্য সফলভাবে তালিকাতে যুক্ত করা হয়েছে।", - "productCategory": "পণ্যের ধরন", + "productBlueprint": "পণ্যের ধরন", "count": "গণনা", "sellFor": "বিক্রয় মূল্য", "purchasedFor": "ক্রয় মূল্য", - "noProductCategoryFound": "কোনো পণ্যের ধরন পাওয়া যায় নি। প্রথমে পণ্যটি একটি \"পণ্যের ধরণ\" হিসেবে যোগ করুন, এর পর এই পণ্য তালিকায় পণ্য হিসেবে যোগ করুন।" + "noProductBlueprintFound": "কোনো পণ্যের ধরন পাওয়া যায় নি। প্রথমে পণ্যটি একটি \"পণ্যের ধরণ\" হিসেবে যোগ করুন, এর পর এই পণ্য তালিকায় পণ্য হিসেবে যোগ করুন।" }, "addProducts": { "searchInputLabel": "পণ্যের ধরণের নাম", @@ -671,11 +671,11 @@ "recheckVerificationStatus": "প্যাকেজ স্থিতি পুনরায় যাচাই করুন", "useDifferentOrganization": "ভিন্ন সংস্থা ব্যবহার করুন" }, - "bulkImportProductCategory": { + "bulkImportProductBlueprint": { "pageTitle": "একাধিক পণ্যের ধরন তৈরি", "downloadSectionTip": "ফাইলটি সংগ্রহ করুন, আমরা এই ফাইলটি ব্যবহার করে একাধিক পণ্যের ধরণ একসাথে তৈরি করবো;", "downloadButtonLabel": "ফাইল সংগ্রহ করুন", - "instructionOne": "১. সংগ্রহীত ফাইলটি: 'Lipi Product Category Bulk Upload CSV.csv', একটি CSV (সি এস ভি) জাতীয় ফাইল, যেকোনো স্প্রেডশীট সফটওয়্যার ব্যবহার করে আপনি ফাইলটি দেখতে এবং পাল্টাতে পারবেন। ফাইলটি দেখতে এরকম হবে;", + "instructionOne": "১. সংগ্রহীত ফাইলটি: 'Lipi Product Blueprint Bulk Upload CSV.csv', একটি CSV (সি এস ভি) জাতীয় ফাইল, যেকোনো স্প্রেডশীট সফটওয়্যার ব্যবহার করে আপনি ফাইলটি দেখতে এবং পাল্টাতে পারবেন। ফাইলটি দেখতে এরকম হবে;", "instructionTwo": "২. পণ্য ধরণের একটি নাম (Name) দিন যেটি আগে ব্যবহত হয়নি, এভাবে;", "instructionThree": "৩. পণ্য ধরণের একটি একক (Unit) দিন, যেমন: কেজি, পিস অথবা বাক্স;", "instructionFour": "৪. পণ্য ধরণের কেনা দাম (Purchase Price), বিক্রি দাম (Sale Price) এবং ভ্যাট শতাংশ (VAT) দিন, এভাবে;", diff --git a/client/src/lang-en-us.json b/client/src/lang-en-us.json index a55787849..478c89119 100644 --- a/client/src/lang-en-us.json +++ b/client/src/lang-en-us.json @@ -109,7 +109,7 @@ "employees": "Employees", "outlets": "Outlets", "warehouses": "Warehouses", - "productCategories": "Product Categories", + "productBlueprints": "Product Blueprints", "sales": "Sales Records", "salesReturn": "Sales Return Records", "customers": "Customers", @@ -224,42 +224,42 @@ "damagedInventoryName": "Damaged", "noOutletFound": "No Outlet has been created in your Organization." }, - "productCategory": { - "manageProductCategoriesTitle": "Manage Product Categories", - "editProductCategoryTitle": "Edit Product Category", - "addProductCategoryTitle": "Add Product Category", - "noProductCategoryFound": "No product category found.", - "parentNameNotifierStart": "The Product Category will be available under category - ", + "productBlueprint": { + "manageProductBlueprintsTitle": "Manage Product Blueprints", + "editProductBlueprintTitle": "Edit Product Blueprint", + "addProductBlueprintTitle": "Add Product Blueprint", + "noProductBlueprintFound": "No product blueprint found.", + "parentNameNotifierStart": "The Product Blueprint will be available under blueprint - ", "parentNameNotifierEnd": "", - "productCategoryNameInput": "Name of your Product Category", - "productCategoryNameInputError": "Enter a valid name", - "productCategoryUnitInput": "Specify unit (i.e. kg, pcs etc)", - "productCategoryUnitInputError": "Enter a valid name", + "productBlueprintNameInput": "Name of your Product Blueprint", + "productBlueprintNameInputError": "Enter a valid name", + "productBlueprintUnitInput": "Specify unit (i.e. kg, pcs etc)", + "productBlueprintUnitInputError": "Enter a valid name", "defaultPurchasePriceInput": "Default Purchase Price", "defaultPurchasePriceInputError": "Enter a valid amount", "defaultVatInput": "Default VAT", "defaultVatInputError": "Enter a valid number", - "productCategoryDefaultSalePriceInput": "Default Sale Price", - "productCategoryDefaultSalePriceInputError": "Enter a valid number", + "productBlueprintDefaultSalePriceInput": "Default Sale Price", + "productBlueprintDefaultSalePriceInputError": "Enter a valid number", "isProductReturnable": "Customer can return this product", "defaultDiscountType": "Default Discount Type", "defaultDiscountValueInput": "Default Discount Value", "defaultDiscountValueInputError": "Enter a valid number", - "createProductCategory": "Create Product Category", - "updateProductCategory": "Update Product Category", - "productCategoryCreated": "New product Category has been created.", - "productCategoryUpdated": "Product Category has been updated.", + "createProductBlueprint": "Create Product Blueprint", + "updateProductBlueprint": "Update Product Blueprint", + "productBlueprintCreated": "New product Blueprint has been created.", + "productBlueprintUpdated": "Product Blueprint has been updated.", "buyingPrice": "Buying Price", "sellingPrice": "Selling Price", "moveHere": "Move Here", - "subCategory": "Sub Category", + "subBlueprint": "Sub Blueprint", "moveToRoot": "Move To Root", - "createNewProductCategory": "Create New Product Category", - "searchForProductCategoryInput": "Product Category name", - "searchForProductCategoryInputError": "Error in Search for Product Category", - "bulkImport": "Bulk Product Category Creation", + "createNewProductBlueprint": "Create New Product Blueprint", + "searchForProductBlueprintInput": "Product Blueprint name", + "searchForProductBlueprintInputError": "Error in Search for Product Blueprint", + "bulkImport": "Bulk Product Blueprint Creation", - "whatIsProductCategoryMessage": "Product Category is the blueprint of a Product. Product Category needs to be added only once for a Product. Information of Product Category will be reused in all Product related tasks." + "whatIsProductBlueprintMessage": "Product Blueprint is the blueprint of a Product. Product Blueprint needs to be added only once for a Product. Information of Product Blueprint will be reused in all Product related tasks." }, "customerSelector": { "customerNameOrPhoneInput": "Customer name or phone.", @@ -321,16 +321,16 @@ "addProductsTitle": "Add Products", "addProducts": "Add Products", "productAddedToInventory": "Product(s) were successfully added to the inventory.", - "productCategory": "Product Category", + "productBlueprint": "Product Blueprint", "count": "Count", "sellFor": "Sell", "purchasedFor": "Purchase", - "noProductCategoryFound": "No product category found. Add as \"Product Catogory\" first to add it as a Product to this inventory." + "noProductBlueprintFound": "No product blueprint found. Add as \"Product Catogory\" first to add it as a Product to this inventory." }, "addProducts": { - "searchInputLabel": "Product Category Name", + "searchInputLabel": "Product Blueprint Name", "searchInputError": "Error in Search", - "selectedProducts": "Selected Product Category" + "selectedProducts": "Selected Product Blueprint" }, "about": { "about": "About", @@ -670,23 +670,23 @@ "recheckVerificationStatus": "Recheck Verification Status", "useDifferentOrganization": "Use a different organization" }, - "bulkImportProductCategory": { - "pageTitle": "Product Category Bulk Import", - "downloadSectionTip": "Download the template file, we will be creating multiple Product Categories at once using this file;", + "bulkImportProductBlueprint": { + "pageTitle": "Product Blueprint Bulk Import", + "downloadSectionTip": "Download the template file, we will be creating multiple Product Blueprints at once using this file;", "downloadButtonLabel": "Download File", - "instructionOne": "1. The downloaded file: 'Lipi Product Category Bulk Upload CSV.csv', is a CSV format file, you can use any Spreadsheet software to view and update this file. It should look like this: ", - "instructionTwo": "2. Enter a unique product category Name like so;", - "instructionThree": "3. Enter a Unit of the product category, example: kg, piece or box;", - "instructionFour": "4. Enter Purchase Price, Sale Price and VAT percentage of the product category;", + "instructionOne": "1. The downloaded file: 'Lipi Product Blueprint Bulk Upload CSV.csv', is a CSV format file, you can use any Spreadsheet software to view and update this file. It should look like this: ", + "instructionTwo": "2. Enter a unique product blueprint Name like so;", + "instructionThree": "3. Enter a Unit of the product blueprint, example: kg, piece or box;", + "instructionFour": "4. Enter Purchase Price, Sale Price and VAT percentage of the product blueprint;", "instructionFive": "5. Enter either 'percent' or 'fixed' as Discount Type, and the amount under Discount Amount;", - "instructionSix": "6. Under Returnable enter 'yes' if customers can return this product category, 'no' if not;", + "instructionSix": "6. Under Returnable enter 'yes' if customers can return this product blueprint, 'no' if not;", "instructionSeven": "7. All the fields must be entered, like below, or else Lipi will not accept this file;", - "uploadSectionTip": "Upload the CSV file containing Product Category data;", + "uploadSectionTip": "Upload the CSV file containing Product Blueprint data;", "uploadButtonLabel": "Upload File", - "successfullyImported": "Import Successful, total product categories created", + "successfullyImported": "Import Successful, total product blueprints created", "rowsIgnored": "Following rows had to be ignored", "row": "Row", - "nameUsed": "Product Category with this name already exists", + "nameUsed": "Product Blueprint with this name already exists", "errorOccurred": "Error Occurred, resolve and try again", "unknownErrorWhileReadingFile": "Unknown error while reading file", "multipleFilesSelectedTitle": "Multiple files selected", diff --git a/client/src/page-add-products.html b/client/src/page-add-products.html index 04251d307..9302e2180 100644 --- a/client/src/page-add-products.html +++ b/client/src/page-add-products.html @@ -45,7 +45,7 @@ font-size: 13px; } - .product-category-numeric { + .product-blueprint-numeric { min-width: 20%; max-width: 20%; } @@ -79,14 +79,14 @@ - -