From 7c266982ef77adb4477ebe1e086ce82997badfc2 Mon Sep 17 00:00:00 2001 From: Akbar Anung Yudha Saputra Date: Sat, 11 May 2024 22:26:12 +0700 Subject: [PATCH 1/7] FIX #35 (#38) --- docker-compose.yaml | 12 +- package.json | 2 + public/files/1715363520-tables-label.pdf | Bin 0 -> 34949 bytes public/files/1715363558-tables-label.pdf | Bin 0 -> 39514 bytes public/files/1715363617-tables-label.pdf | Bin 0 -> 42431 bytes public/files/1715363683-tables-label.pdf | Bin 0 -> 43697 bytes public/files/1715363705-tables-label.pdf | Bin 0 -> 42410 bytes public/files/1715363753-tables-label.pdf | Bin 0 -> 40741 bytes public/files/1715363799-tables-label.pdf | Bin 0 -> 42349 bytes src/app.controller.ts | 38 ++ src/app/customer/customer.module.ts | 3 +- src/app/customer/table.controller.ts | 32 ++ .../restaurant/table/table.controller.ts | 83 +++- src/core/core.module.ts | 6 + src/core/services/pdf.service.ts | 52 ++ src/core/services/util.service.ts | 26 + src/database/entities/owner/table.entity.ts | 3 +- .../transformers/table.transformer.ts | 12 +- src/library/helpers/config.helper.ts | 4 + src/library/pubsub/pubsub.lib.ts | 1 + templates/error.hbs | 447 ++++++++++++++++++ templates/iframe.hbs | 26 + templates/pdf/label-table.hbs | 143 ++++++ yarn.lock | 136 +++++- 24 files changed, 1013 insertions(+), 13 deletions(-) create mode 100644 public/files/1715363520-tables-label.pdf create mode 100644 public/files/1715363558-tables-label.pdf create mode 100644 public/files/1715363617-tables-label.pdf create mode 100644 public/files/1715363683-tables-label.pdf create mode 100644 public/files/1715363705-tables-label.pdf create mode 100644 public/files/1715363753-tables-label.pdf create mode 100644 public/files/1715363799-tables-label.pdf create mode 100644 src/app/customer/table.controller.ts create mode 100644 src/core/services/pdf.service.ts create mode 100644 src/core/services/util.service.ts create mode 100644 templates/error.hbs create mode 100644 templates/iframe.hbs create mode 100644 templates/pdf/label-table.hbs diff --git a/docker-compose.yaml b/docker-compose.yaml index 0ea5217..8adb43b 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -53,7 +53,7 @@ services: api-local: container_name: ordero-api-local - image: 'ordero-api-local:latest' + image: 'ordero-api-local:develop' restart: on-failure environment: TZ: '${TZ}' @@ -91,14 +91,18 @@ services: AWS_REGION: '${AWS_REGION}' AWS_SECRET_ACCESS_KEY: '${AWS_SECRET_ACCESS_KEY}' SENTRY_DSN: '${SENTRY_DSN}' + SOCKET_TYPE: '${SOCKET_TYPE}' + TWILLIO_SID: '${TWILLIO_SID}' + TWILLIO_TOKEN: '${TWILLIO_TOKEN}' + TWILLIO_SERVICE: '${TWILLIO_SERVICE}' volumes: - 'ordero:/api' ports: - - '4002:3000' + - '${PORT_LOCAL}:3000' expose: - '3000' networks: - net: + order_net: ipv4_address: '${NETWORK_IP_LOCAL}' volumes: @@ -107,5 +111,5 @@ volumes: driver: local networks: - net: + order_net: external: true \ No newline at end of file diff --git a/package.json b/package.json index d51f4d9..970a3ba 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,9 @@ "aws-sdk": "^2.817.0", "axios": "^1.6.7", "bcryptjs": "^2.4.3", + "bwip-js": "^3.0.2", "class-transformer": "0.3.2", + "convert-html-to-pdf": "^1.0.1", "dayjs": "^1.11.4", "dotenv": "^16.0.1", "express-useragent": "^1.0.15", diff --git a/public/files/1715363520-tables-label.pdf b/public/files/1715363520-tables-label.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f4005e2025104cdad8c9692525dc8faed68efcf6 GIT binary patch literal 34949 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 literal 0 HcmV?d00001 diff --git a/public/files/1715363558-tables-label.pdf b/public/files/1715363558-tables-label.pdf new file mode 100644 index 0000000000000000000000000000000000000000..b74068c42fdf841f29083b1c33756f756f9cfe8b GIT binary patch literal 39514 zcmdqI1z225wgw8pf=dV?xC9H@xVr>*4HDel-6d#(2bbXP4ncxD!QI`Z@qSHm&diy6 z&fJ-K-~H~p-|KFmx~i7cTB~;bYwZo0oUkY@fQ}h~Y+`?R6#+oRKxD0FhQQ5DFJxz^ z>tJn1L@8uqXKiWfXi0@YFK1_M;HYm1?o>24)ujj15?Qjbg9m<2Hnp}A)^#u>q7>$2 zWME`wU;!`y*jN~u*r^#9D8L`^JZWo#|9%QDFM^?!!IKJ1e^*6cmk5ABFDOk!FJoDN>#AtD9@dMSyYzg3_5rKO0N5$F{Kh#c%34gZ=K%inn^x!M>K z(W}YonHlOkfXN*79DeqQTk0BvsZ~u4988D+OpFNh?+i_iO∨g{&>C?G$Zv^})2l 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 B2BH7} literal 0 HcmV?d00001 diff --git a/public/files/1715363617-tables-label.pdf b/public/files/1715363617-tables-label.pdf new file mode 100644 index 0000000000000000000000000000000000000000..b5d3df1943d9fb73fa1df1fea077e0dba846d911 GIT binary patch literal 42431 zcmd?R1zeO(*EmcgNJ((y3Z1ZXL$YiB@8Bg(_V%)-XZ z4qygwva_;s(lIkrLoUcT8Q}YWJVZbM*}(Gs{SsJzUy8miDFB)2r3@*PEYQY6*ZlWE zY`+f@5xyVvbEvckDKj#Y^y`PeD);R&(xhz2Op3y!b~g3~e|s+W-{(?twlW}PQhlRm zYM^fi0khY$duWlg&^3ZUtC+mEGbRPFvLG|PGB7bRw)=T60yGEOC|c?2LtsS>98B~L z6eM0kpk;Jz%}6;QvnxOlo7maP8Q6#bEv$f+29|cDT*yq~Cgye!nlg!-L#QWepbvZx z!69v6X#_#S$tEB`YHJ7Kg$1%x@?J9l8VU-kCH?G;1uA+v!q{Ep3zwg0Doo zsaHg;L1qZ9KRxdUzkOsQQ;hC0{BLT7PW@mmu?-Ezb+Is3VN zc!mTE)y(-Woym}?r|P#$Qu=njnuRYVLl4HoUT7Fy<3?Eh`|IQ^}p9>OA>vjefj|Tvn-CW76ev$WrJMI>!|d_*ePj{x{`| zm6`oF<$AdPt8)Ew`4SaT`c<7Cz?E!tEo}`UCgi~`{8TUi7vK*92c7~iLSeiXmlTJB zhG-fz;GFSU7lS z7(_@P2?nGc=Dzp0epqN2On@$#Fx(R?@C?+W`*uO7%OAhN{?8kO%STRqBloeh9WE46pFiZ=iAg?1C*;ZC$9%niQ?=rh0Dp>O7I5T&yzpB#`iDH<3%n z6SJ@GRAt=sTj)=giMh-9ge9#CB@YW5b2(Z&G=Gk=-@%hC%G+SCk#u1`va~wlAX^zY zI#L*suMB=WuHi=B+x0LGtwA4&eVlK-2A`=#fQ``w-djs3jfgpy8B{q}nbknfxbR4JxdAG1per@$0270nUm#Q`= zd6I(iheUB0jYx-|LCZp-Rn!+HkU!mc$}R4}4#(MEn#DM6DN*TB+&v7`uRHDP_Q2d( zd5v+Dfp1lno&0>~QC<_WLmAJ-!4A*)*~<#0>`jhoVw)AOd!U^O<|r-20kcn$Hx!c{ z(PS$xS(bn@T#^%*iXPZ|miUuRo@$d-Uva)>aSB&niiMWw1 z1Dyaf?y-wWi|n`Hq{AFDtAU2=tAU0Fxx%-!wWyU6$<>SZsQyg1tVi~LUSJi<4ydVM zXLcNUTp=la_Puj&_&wBL$fxsXWY!Q&|97yF6#TEE`>$*+jqX>rP5vq%1?1T`v@>%a zWyKzMWyL@xy0@<_eJxU1;~r$>SGl_UJDmHU#B>_aeo&)d8NO!2qT}cCW5kcoSX`4~ zD+h_Eh3-l@be+RlX zTDZM6T=)wVzb<3ET5SH|Xm;AkV0JRwmW#`g-`tU(>p=(h9#HK9qHR^bYTF=wZ?D%q zDg@%gII*s!%$pxuIgnQ-PNBmJnuRQm#ZQ3W)blq0=)ii)VH<8^5z9noP}bJyGVt1M z4~JC`aj7rX6^TU+1gqOcsDPj7>b1c085x|0FvQZniGeyvgpx z%6Y6gL9{+qRV=spWeJ*sQk z1w#S3r#E={HW+^~_fR}G2h`Nmj5fc1zTf-bd%%LH|N2!9VE*Ud<>HVpS!5;!16!cI zjlO{`DI}hN#18t326m(x4`Bn7l7W*QDbs^P{qmQ0kzd{=wUC*_oa`hN?e3!q2&e=r z>3uYy0P*{Ob;|FXRk=y;-3mzaeaFj}KqpcS1~wMb`xgrb>-`HdlY|Y>-s*n%Prvrz zBg82uWqR-itaNP*EcGFNstBazr_=t^uaJC*6z(Vf#S>>^`Q0zIMuzw#5Y)`14EHy` zBGiZM8M0O*TgWoGNkxIk^2mRKe1C|selF>^z5W_2{I=Np$l+nF()Jc6mOxt*JIG2U zNFO-xbKAeT?*JCaTK?p}|7B80>||;1d*lT8Lwxmf8FIRIkTAp&66O88d1!@%I5N6+ zHYO0#GP1KWv$8TXbCCY6JBHsI88{i4Ik>qX@gFHW8y6!tHwPytgb|9yy6;UajqX_? zZf~h?2MHJN??sp)Qv)~v_p@@6GIFuqj}-yjHv+gw8ClsNJsglGQJ{sci6yB9GbsS_ z>DL*4U8F22;GS2IT|+2Bs&OCXJuvWoE+Ek6z3n~V{gZ_m()7zxeg*!kZwU$99;E75 z^8O*t{7I~T5*z-}eFd;Ui1=3&{~7m{o$C();(ynBg>v~v-YYaL91Q%u_ez8T@ofL> z+d{*@Vm{HuA_M$OzAF;f8>Gc0LJIF+tP+eO#`cSSn$$p18A2J&KC)k$DaS-=$1RL2 zYv@BiadTCg7K$luxGZPH*;3P z=zg?UXnP#pzMH^j%+~aJn2m~P+{0=-nsX5pVrWxUwOmNw-jM2W++0vPFJ1E!pEU2+ zD|Q2h_~|MJ0+alj*>N88?#cpQtq>{Jg}1#Q9|pm-))sy#9_Uxw9F|cLzl&=6THI7{ zW!j&eT&CSq$VmBfCM4YsKWf{pZ#h99!Jzi0XXl4SXdk^ZNa|DF$jw)~us zmOokk|ArlBVC80nr1Dr;*?+g5|F1OUT)$K6AEn&@9BiDwf89GJ7plkr2{}<34vL3r zybL}(IfKULZ!@u>i|i0`WvG-4Zb|wqc_(<4`UT&l-a#(CqJbEVQsKtwOSSS#3&D=1 ziw?$O^~^$bGnS3stplt{cV(mJ>v|2jmp6B(y^iZx6E9si!X<^fp55Ku%-AiTbdK)r zyc6&+sbv$^dE{*>kZev_a~C+pj`bp#Ry;Z0>&?@C3-`-6!*qtc{Kuzyx2B%_i zjd|T2!ac-Xn4C>=Go3Q$t^;EuK(svOvtjVTIKl?LBGBVK1;-Yp1OrfPQj?`OlwF#+ zuPV2!8F4ANMVqAOMXp}{$>lI6K_L_ShA)FD#inn6qWQ?j5ehK2w+Qns8Lm35BZ~UV zv3m3~ymyid!H{?vB1WJliDazOCW)%%* zg$>_G5Z)#jr1dLhGy~?jnT$*hDL}X145I^vMpF99wM5?6ifGr5r8CUyw&ixhq8Qd; zv#w-kFi!)RFel_RN?Tva4f=~u+7ud6$z%fav`6ygU)5M%1i*!5d|>{B%F@h9{I)ls z=+)LMiszhGXoRLp>bkag$g?Dg0;DQiMD%R+(lUoC;lr5YzG>D^LAzNw2411`(SN}I zuEuBf#VDaanf{C`s%mUMaU*NMQIsB4m)yvNn64{Uob{;M?hDdfmTMuNPEn;A+6h{+ z0V`8)MdNw}51nBuZ6 zY_$h(c@k1{xD3EE?Z2rimP>D+^Zb4~6fUY{ojUQo(U)*uM;65U^a*Lp8#`G{kEe%y zPM+R_M4MyI&^yAO%om=i9`%CL##bZ}AY($%0A)DZMoUxtrU{foM@dcIGL0j+4q7BD zO+-WKh`u#@Zp$z(w4!;`1l=a#ok;tfK}c(sNNZ7ilK(VmO1?k4s-|QpP?VcU`n1nL z*nTg2q~H~+_HbywpAmLudEyShvIsWBxMc;)5&fOXb>`Bu22nQRF&EV={%o(E#2yBV zp!fwM^Ew3u=9j!M@{Vb1ZzGPwgZRcIB?3LCS%pc|skFl@ECijeOMuUOvNG#JjYK@U z9EgT3P%_?}u=RhQOEin_g7&Fn{sb;oY#2Og?C<(Sfw=!V)>wSU-%CQ|Jozzek;gf5 z=D$|DVbUS;uIRFcCWY?1r<)R+~ABta`R>aG-90=Cv- zlbM5S`QBxpUr}0W*obBu2%8K+OP0mNPjKOv9 zzvQ!8>0%;P(;(XSn>;(J?cGP2lQ6#CL+h#D)8EpyV5-jOn>#xE;l=}xvdA*-!&_gd zjms49y>xBKRM4Ky3#fI=Zppz1WUe^Gl5@BzX9`xuW@HHlp(!)rhe}p^7;pDKPy1|X zg)inGwMj6p(SAE!oXFYaetUIyLT!6tNj<1z33?~s=6Ip#>cQ=)-h`cb2Oy0&sPF;VMz<|b7R4J>>pp%yHmCc4n{v?X?* zsS{xm^|WqCYdIK}_2(pZA#x$zzY<5}KCc9>5ItJ+x}jeeg>Ng{Z;nbD#*O-yzUkJJX4{B2^_aW*3gNsuS z>-&?B^H0LBf1xz@&%>|3RgPn0gM^uXsK<>6qxmwUh|2GKn$(7gd+rXwTjw7kyh=U_ zX>-4>jU8ck*SR>$D!A93$@yqM|^)2(do|6*@rQk-i0rnJ*)LWp&4COl%l!nPC75O41CndyeW zPU87y8B4p>f{=3@wsZdU>X%DEAE3HnKzO6Bo|;w93AE@k*H@v#TFYy9jyLd3ld0c2 z+%()}P90JUs$=ZgZtTWvCGh13S7YF=!F(*;Z;@Z_A- znja6j>%{}DPH0BC(@xv07KREH(`p?Sx(Qq9ee>CUrOPFz_d^>ytriZU$L|yd<}As- zO!<1meC+iA371W_Utym`v=!M7I9jN7y0|0en?)xCMpj0vG-wv0KRJFF2FV*-sMjfk zaIed_Xu9}7(zQtiX#t?@*Gc4kAKDfXuKd!v5@N-^D0J?pC1x-b))Rg{;1PY(Aeng1#T5HZxYwV{s zS|I(wu;p~!w1k>+eT~#Z+3VMZsJ(9p5MZ-vEe<0D969p+a0os^f0~NpP{(^}GRx`C zYqV$2Prqbh`!hii-NrvrH1>HyjY&5mjF5Q;g33*65aKj*1t!hH_WXG(+N~r6T)LAT;dnplQr&pltRGcWzn#>^=7hk^pC9Z9S}2_ktDoB> zI6ol#d#iK|TE;Z9$}g&?0ag{vL&{r%7SZ=`wh2xN55)T)^x*k1+)DD)b}-N>oD8=L zxpt=|;M%1h1Syo9Prs4DDK_Gc`|z(L+=C$G{r3?%o~1NG3JMmbsfl#j1GY?9H21W5 z5FCdr+|q7W6gT4~NbQ14e|g{e@Bp)od}2vO%SjGoc~@2iDUj_|DvpYoO%E6>8{7jz z{e5Qmsn5OGDMo&_2byrWh7?RHdt6Lx8hv{wOm* zfN>)Y?TGLtgC&mrq(AEU?`yRwbmi`=mrlP2d=ej6J3bSw-b=X)fB+Fe790^$v(Z{F zff1tNuVud^;@)5cgp@L7&2zGYCn5ZA`AObK;yY=}GyhqHnE1)-cyGt{2 zb9p-2Dv*e;=pWn(VPDX%dU!A1f)?%ye-bv>F7ENDyEw=xpGEh)1fwesy4(h$KxF^$kbyo?XNg~cO z6`2^eVeQ{-02EYQZrtnXg*b$mv-PL>jq z9E4oIrt1D!n__4CnWSXp`cLL4S%3FR|B$1EI3_=Hl&oBTN>M_V_IFa0EPp9<{M#wY z2PXU}MalA)%LMm$FIwwSW(m$S>3}=l5vaX&9UyBP&BMbAS0xH|jKt4j zdEu71!g-q%;kL&qo7xenR}JOEJaspvd=jS~n~H4J9Juh(7CmlX3azI3yVHg0hvjNQ zS2{9iwFcA4S6_lxg%UGfN>4{36fSDlg#4YXk*1 zGY0A>UR5wt!Y#O;iyMsn0~)2`)N43@%)Itdo#v#B*DaQec$gjD%k)IV{5?As;;BT$QzuMR;{CXiB4#^j3(E9m z*Pt_<0_>f_NtZ}TcZBlbqHkW=3InfSpde~=v=huYuf8vX$!w3m>aJDz9QW}HgU3g5 zZM~85A5zFIq*BpQW?An^Fjy@FDxO$>WF0cm zF*?4XE^LJ0Kv+*V*v5~@5~@K!Lm=E6BZu<#bF4?t3q1}0_PCphgJaB}_Jf ztc!-Gq^$gm)-I+_@Wxra`Zj^rUi7EjFV7CyKH|Ld%YFV09zLZ-|MiOc=@+h#@4V6z zy<@|Ka+u{AUK_n)v88ovzvQ<;oi?+qh2#GCsiTWR%#a7Kk^+TnCzV48d5_L40RKs2 z*ZMKTQ&g#{W+;tPb)fDto>xDXPw#~viR*cy6*+5uXTp-Uk@C{HPx;r9$)d6j*BFfg z+vwKzaEUd(gd3XD5Z73ndMBH_dFm=Ae~%BJvZ(2o%Go|D?lR09B8_DMh{zh)N)&%E z5dphnGC7GQ?d^sYuZU0`CC3{8Ox*mow@b%}hW?y*8a;&SMR4Ts1|mb{{S0M0pqm%X z4L7~vH=SQr+QZNhBTxm_1D{^RC8>1nukVBOdzcr7$y}BeMd4w5j_$UO-5=%oeZ1@^ zqv3n97G_6M<2ee+zy8Zm>7v;%VCakB?vk-zGo+=Rhc#BjZ@Y{@f4c%y#I* zJ4Zyno_C_UN=N33xjgT%q}ONKw#YD&)ksA(UT0;3Gz0)z( zswNKiSP!3#ngR#j|wJ(J1I0E7r!pof6~Pk3?aTAFUlCR4@bUlg(y5mK_>_^r;4Z zjCE{j9e|7t#@2>G+AvorgOe^H#c*mf(~P|HcpXQStI%Nz)>8CjbpFXiyDD8&fl}(*mWtXV3kLgT2(q?(pi%hQbxu*xy z{uPbL`zB#6auXCKK4@g`fHmUyVd3&l=!{Uz)k@Shc^~mUsAID4FA*2uXi?Izt?1?7 zzf|&m^MYMDmx}CbKQ$_#Tc*g}y#JX8mYM3gHEtazsJ^+mGj~5YeTAt*cJOsM|0GfF zvGF@-lr$>6Sn2W+O%#{#3>xy?1GUnI3}Tf~s9<4vxf`=w$|vrI!p-;}f@Z?^pKzq= z&LU%Ff@YRioT($!cP-DZWp^tb#daD#%TNt$yD#7a_c8cXzj#ym&Lb!$P~b3AXAHxk z6ive35ZH<352)kFu;@iA!XYF##~xo*G3hM0Uu{}~_7__>k|wP>W@poOFE5u?Tm;4S zTIo-GLVk!+WuYV(@$=|^RIR-JfIh%w!WUc?@6=QftIuwcUq5M;fJOf_^a8Dq|BTMH z$9eGN@o7ObAo(kyQMB5NQDqwcTNE*rc2tY`#ph=|fo;atEp7Lav95qy*$(%fs0gNOn>Jz(ESR9-OM*4yoK1A>|-vSssqF zKnBSHI7uPbFN1z@|LzB=vXlN&JB1t`0YVPmL)yeSNgswnTDVB>D}z7X7A6^k_a?dz z$BFLqu56I(D?2yDxnPH!WPvzsA`ocE$$eW=$nlhiMJhs0o9Nrg-Jc|av_gJ(@bi|G z<=1umdWz&PhoJvAi1`NsLcF^N2M@B%KNAq*p*=Wh4;_Cb8f5Dax#x$D{}9<8JRAtC z{!BK=Z#4c6*&fLFr|j=N*&t`EM1b}XK0E zMRe_S&4EVu2qE+Qg-tAfVAIdDUjR~8R!ABSA}0Ufml^<8W^S(E-Ehc{HQb%wDTz%S zU(sOS=29ro3nD+oAQcJH)7db!g2|RQ)@RFjo{-@)pEPF z7H1GV=#>(XqVz43x!Rah*oGvpyfV@!XO;eAhr$xmZg)}Vswa{=rUd*9t#g$iOf9zf zuIQJV4z&AK;J5lG`7I~1k*zOpPA0q0Xp=o0);^ArM0xTNjhQDr(ta%Y8SyA>y`)jf z_x}rCq7rulUEmY}q@%A>#P@jW zvA!qg%j~5M*i&BZT`{J21N`w#IH#D5Qs}pF$*`O7(H|N%ks6;~&2cm;H(qRHv>ocI;`<~*D*PP^krEXAjV4r*OjO$s zHyN~Yuy$ysAaHd9o-YLTMd})j@BY*1Yp+N8`xP5Qdw0cSlbbHu9INzOGP+C#TxKts zZU|35XSUU}O;=;A8BK6;0zT3*9`^0|A51f4-PE73QV;NgbXMk0$|jTa`Z;Zhk{q7s zGM!cIdoPKQ-~p8)8Y<(7pYq|fkhsyGmx;~?-bR`i2$cDpWK(w^ycrcS_LCMd6L%I2)eOdnCWi{Ut;rr<%4lwM0@0; zQ}NR9@q~##%KF_$k{u+1FrFuaX0S$WhtLrS=8JLrpE5?v2A*AT-t;}wr%baaPH$Q` zr<&mKAqRj-TzUvMjS`@4`-cu5wZ;x|qO)#`)UFK==HYS`D-<0-9pbDzV>!)ls*LIP zPj-K}3q;}7(JQbvy-0MOy}+Gn#z-7;A2`TLdZ%Gv`pDUfrImyFJaQxXWFiS+SL^)J zJ$vskkPLg^fbPVd4V{MdhNsGiuF9Day{BETh>T71E?HrCV2?c#l+;nL+?qznY~Xl+ zsGEXhOs#8Xai`xw zIh;@Q-WVC_z6BT>_6^-=o*;;6r9kWjSeX7~NX@ZUW*UXa=C)!;)G|bfZ~L-=y*0g- zpV+@%wOEznrm$YCO`^2ke*&LpxxS44@-Sn^dfy=(R1shGaar0Cin`07$Ggz(T{29t za#5E3_H}d{*=>}9H=6@1ZRAS|ujiziR8w!V!LD2BU=UxxN=XW4@w{;x5Rv> zio+x}*GXsWm)dL{786U?gr|lmeUr@1L&I|EN}XFz?cq|n-8gs5PEWWw2zK1Z;-8hK z3ItT5ERyr-AQrR|d%*P}oZ~mGsosj^T zGRCvD?y8nRcX(Zb(eC6$bnMHc0Pm?-(1y4NCTELl$T{`l~we1brj)96x|8*7sN8CSPPdtFit$5uP-}N(-gNR zhscGmS^J8SBj(KMeBM*2@FsVivsS@(9(LI;lV-V5A*M`B1WXFskY?8q2~sA(7 zGJyLcV9Na+dGT5cIkT3H@NKGRlLu*c%PY^<;HyJ z0&HrNUf4H9UAh42c_k7v?M3<{{zf^Jg_N>=)?*Ui^NGhfeEVA&I>JnNnJwovw?}2< z=DAN%3*yqmEF*6E9%-QNMr7nz)&};76$r)}CKY=X^1U3DVH#$$hjHtpWb1!|{_KWG z>`3!_{ZJQKeTz{?uH&$AYTZ#hTY8z$K1LVWnQMfC@H1h|XBhj|;4C42^RQ0Tx-eJQ zt^`M5{r>5N_^sy3-G(FA)%DYzy*qf9B&HGLwBnP)!7U5%IWO0l3&z6~v^)C!Wk5%@ zZ5U7ZBj3gV)h(BW>;heCn7y0*$I)w-cGOhe@^QLpL~O7%4g&jLWTyP{WDMC7S;b3O z2VW${*-uG5mG`rSUsIT0;J#4O5xo^?Gs%r}-uK|$UFSX3dNr9(QS#>O5Hn5jqYJ=> zBy!Ms0u;lUD;Tdf-t&Xt&F=1ORdS<;(@^MU8Ci!j7`xtWg?j_eT<*1b4V#yyNDlQjY@3GKA8drcyKt~Sje6Vh8Trg zbh$SWcER2nyFy=$wYg@md^}Q2)aCIfd_B1IS&aDXV^@|>sH4M;R(5YR{mtVG$+%Oe z;vU;_7!Q)}!JvyGT5uk_Daffd zut@;yT1$LD}REA zZp=lo)`1?UUhwBmkSAhCDx`7K3->(Z2ut5YfD|vkmQO!{pCsK9Ts2Htz`Y$!Rj|s#YJW?h8Gx4Lg)aR zAK}!8-OO!%o&|B8v*T80S074`#M473JuU3cGp&y44vSE*$#QpxN%i7QHyRv#&*3v2 zXwXh{6R;I;BkKB*3{-G`84VWAqQF@nulQ;28K}iwpotn~4vDYGb8-#*+5;6ZYNj+- zHpuPp9pr0;H`s9z$~cM%HaVdj>lyT}f9A;M1*lAZBe>4>pd4 zLLbt)Q62#|VU=dk4vSZM#fYgJC5-rL7l^yyZw_Dy4x#rC@@`x;+9dfniOm2V7@!9F zCD4w2Q_|idw(U(eVv#7xUARk1d0a;ZNH7$TgJ04FmG;a+WJ>;pgeJO>>0X_TqUGPg+OZqJO~H6pt#zOd|G&#Y(fz&mS7>MuZIbZdI*z0Fy;=CB=AxH9m|1{V{UONmr1TC&bETLr~&V|-kti97F(xBEuy~j3}FKf^}GHWQ2Id{gFKm>apJ(`_>AEM0Zz+AwYN5Zn<>s{&GM8CJLn&b1M$#`!Wh90u%QJi8v1&E~^0wh?9a3 zR-}=>%|>+TCHUg)9FWV}&>C<(Rq?(ssf|vvNKSKG#GG33wsB#kAh{2`P5o8D&taFZQI{yHhRF-g26x}qqNuic z3W_0ui6z|Pu+VDJov;@{I9>c0t_U~0cxk=qDg@P&KN#Pep=)OrFYa{DKEA1oU8t73 zz5FrY1-^sd)cEd+dKD%N{@nMuuz?vB@{bw9Urvkrr*k6&d)uWyI!i`_i$q0%VUZu~ z{UC(G+#-a#9f)_EbN=0J>%wcoBA<^mCF*nRbk*0_;^s|GC#U^G%gEwwJ5d>2G8 zUR(s_9m(|ydEQo0#@spSEvqeI65YPa?-o@VX16J;mu?i_{|Jmchyp2G=Y81D-&WcF z&^C!U$NsL@3(WJG@IyFECtD}IM3#QIe(H*8Cu65nxFb;-S2(vUms_HY)QCbky+t*L zdhck5DOSa$dRe7{M)CYt{tW(NBFC=-9s_trk*w_rOZ8)F>&XZ81I|e&WE-xM3X!z5 ze6-}WFK7k2Ke0WhWzVBA5mrc%vYFN#c>g*!QY6x%`%`yG_iYp+RSqTkn*k~9#Co2~ z+Ex2*PD)Uuo6d1-cLb$i)Q$PER5y^yGurJrC6j@|Yx~zSJuT5$wEZSwCUh)%cZ)Zz zGC5xqDP-4D2wY9)@;>AQyEB3#JOsgosxvRDdR4k6b%d4%{wr;Z-JyWBab^Zm&?N+Th?l9%onVp?Ey8YB8Vh$?r^}EXYHtQ_#qm z1)X0kTZfV#aoJj{xU00BC$HLYJ?5wAFA!F|0NMhFfVsh}chom^Hxbt)CkLm%i-=>R zOU0(4+Qu!OnX|W(8AZNYh_!bMXC{->S|pC{V`sXP*IL(g3p-aDle}8jwF}!bvuB2r zW?IE|4RdENCsT_y>Qc9Ai+9e8bQ~=<)o1PBs%T9574;Q;VJxJp&DT-s`UH${vtJ$h zv=On(V*~z#Bf-ecvi0ijQ;EcJ_$o2J_4C_TGnlhWVB)*MrJBGH^g7{ z@1~nad`Ho7J=M>)<|4qm&I5{=E;OfSLz6GT1Xre;bQw&DDdkk>cehL6K>;e-AT05X zP45qI5uVSlwjvf3#cgt1LgGIKsnG`c(E7ck4Q=TbcL+}Rz?PK9mH}W(S!2IJIu!W8 z=0(WmDEpfHDe9N^uQP<%;sb`nqF2%s56} zx!H_zIW-`$eL~LtNF|RclPzV8ER$toM~g$jlO)TU8{LyjxL}3*R*teio0~0eec0{m zXVYV$yJyA>$9d1TaBF=lhhXgl`Cm2VfQCsnBf3nhhFeb`?Frd!XQiBS@e9{&O3ui< z87@1ezGCG!x=U2gi`~v`zG2Xky^w#*P!jrit$a#Ba%WRk3ZML{Cf2z;NLe*oJ(wY` zr!ZEnJh~`yV@Tu_r7(lQv^2VuMatiRfPG|qUBBF`C~IenlRi=`L6DP#*;Ds!#I4IM zKDBaAjOp93VVc3WOT-h}Ufo{I6VCDxx3-&@OV`?P%g;K)2;1plR9xp5 zHK#&G3vFxbxD#Z0Gx-GkrB3!!Hb)%Frbp@I7MO?M<~cWEu9?j*-Vk(nse>0y!;oluu31h{qP2+~RDNd`1`g z6>TyMebmzljis(y??+S)}{5rc*64bY1zB>1&3P~w(1J#I(xAwL7 zS{7t!3pWp6bVr$K!4LfiBR<%M|5nuQrmx8XoVq}gRgv)1>g_MUt|$1Cs5zVNJdRC+ z)ZpUaHGjxR#Bqdi-XPm3`5lh-{1L;XzwQq%_#z+Em8P|`5I`@$-#OeGB-Y$J&rkgbVy)=611fQ-VzJnojg-W)i23Puy3tQ0;|280(^P zpYZJuwU6BBCr^WOXlFg&ZCEiZS?}}EQQNFN4Ks()$}38jn`C@=v{8oewuW1V z207GlIReksyBjMRWsUnN?BSMQ^%MjrhbN#hL}l<(lh~T$Phmp|S~G zYZ=K0uw!=G*=*6QcX_M_Y08VdxO*;oYTmy^G- zYE9Ejv4T&d^U3~3Yp^mpx;WEuU{_*4>R3BnJ2H*)W)_2}m@eN*T5rT}`zVGN@pIv_ z32IzGZj2K{@hUoj=ORV_cVDqXYQ8-xbp~}Dd9qY92i7O{u|hBktao8C@QcJvu2At1EWYO!B4%4ST{I^rTHh$Z#V`+Rf*Yx|+F({M$dcF43QL-VZ~ zNM5TO{h64)*&)dZ%cQD#lo>r7z~9MN(|)C)x#*}~AZ>DE=Zs1l81SLgIAA}&p3bY?=@Ep+U(M)^%{K|UT>BS$3*%{!n04YzG1LpEiH`M<{SiP znL@*|=*t^OLVB&nD9?gpGd2^D68(*2N2f53GdD2Cbo@4&b4lQMx}0R2ZCQ*`T(8+&@7T&x4EuB9udy}UZAQBbsn~^T!Q1FdW{uI=``{MY~7~2&J$@R^A9IV;5~Mns}n&2HP7+3e15$3DON&RketM6 z^#{esxdnH)1to2Gjv7peCw+tzHu(8tFvVlfApTxGRl&A4G~Bhu?B|0eYemB0u3mNC z`f0-t!@|S*pQ%{PRm(SAW@AenA>evo z*@CbMwecDkO0;X2uB;@+&&=B@{3l=c0xnq6iHu^xHO=~q?gR2W*biNio z?Jq)f8z2^)Kc!_5uem^9X2q?;7@M^Awp9IX&s>)8DDr$3Y~*34j`>m~zREzgbpT&O zbyFr>5FN1s{aaj4Y^gdmKi&q&?_O(|2jrDD?W9{igB2f9e!CMxS{rWt+_OhtKgU5| zPk3a?P0^&!Stc0!4!)#jRG4V#a_Q|-eoM^ZVc+5M;re0NQd;13YyV{~FD@e|79CY3 zPhy@q=WKad0UNu0k7XH2#>blX;ct> z6Dg}N70MPQFBgh6_dm{;|3wv1hT@s}P26X13wSWg?PM7{&?E9`7grIy*Bn zJ1coG5<4TFcSB8p=b@1F4i4RDT^}C|xQqH>r$<1=H9Nx{wOJnu#(q;Di~d|7KBLgp z1yCxhH_kcExn*jP%F+9wn>7oU*=3av|Jq2`{9BA!z9>cNi%?4Dled2#erWo!hokd-xvtOj;SxQ|^F)UMm% zPTztka@>`T-{F*YZtu%>PFI;@fgjKIwZOPuX{HZrK4*$ejs7xPwW!N_JlnsjOXBV7 z%U3jMQ+b)_ekP5>eJ;#Fbh%}FntNaZ^6hGMe06PY#k%Yt8ec%$0YG`$0+Z z4Ve6BHroajJ`JMCV~DJU^2&12$Cy=Q;NHf!Lp$16XfvR}<Pt_gc|~ri&)1uV1lK)Gdd|sI zi$UVV@j(;E^D^E&*O*#GN12$5uVx;qS?kHc@{Oxa+_b}X;K^W-h7=!KZ-hDuig64D z+LEU631t)Z-V%!#8^X051#=J62;l@78=?i0N- zwb>UIhQYwm9E`{9YzgN67*R2Wv>JTS9YFfs6Y&)g>k zwW{nrZ%8F;_rRTjQQ+<_KdU8wr!)aZQJ%*!8+-; z_lGm~IQX~L#53QCXTCq)aozWWDg-Z|`4F>fTkVqjcLu4goc-_h@uM^G*7@l!n zTek`4?&MRREzcmfghj&0HE_SOtjbtfnfSXqJJv8esgJTLOhNN=udoisUDH^W8Y}%( z%h^~_LutdI*;~7A)~JWK$Cc`$5MypR)u^2Aq%IuJ-Y;vQVXXcV->Q2_)#++e9)6)%=`Ad^stMb`Grhz4c8PBo1-CF)FsU7HnLt|RGr?e|V*`tCy< z&&zp7pBr0hU{RZ>0ac$}@C|DxefQutC9YjR6P8se3}MSiC?Dg;nnx4W4jH)2vobJs zmNF^Au&Jm(LIno`R1#`&7i{l;EHK5WAQ@ly(O434^ThxlAQY8+TnaCiKlKbcaXT_O zF7nP;Igk~=W4mpbJVm{6ACDY_-{xDc5V-lKSIs`&EW?4Eko00MLu_BGQ^T?spZS3{ zpW0IQA9R;t%B7oTJ@qMazKPtfEz^*a_Fxl@N+u@=3RQ&RqT-{(kV267y}MHl6&h+EhN~R-EX-_{vQJaDN|Q3~z+dU9PJNeI5$@4g zzUK3-h|Nx(sZn6`k2e>k&@dL?R0$R1cRIC5y$VJM{H4dZprs`^Ae ztDJR9X`|g&$7*O(!-H>|e~ua!pJewMt$d9Jdyvl_ytB^S8S4rzmfNL*DJY7fQ9!>=}<-rdh=1 z@}f6K@+T{-vJ$MnIhbmsn3{aXz`2@Jxt*}zznye`#Ars1$Rjg^G?J@HTibI;SExOC z9!e_XDOqzY6>T|;x$wRDYFnX7SMW{vL*&JapFd>ExPrkppcWUO>=2$EAyWasq+p^h zlpG(WMaVOTOsR&^OykknAAz5r_3yUV!pmgnAd4)os081dQsbh+9&>9u+3a|YBwx}2W$ zhB3wqeGMzPzVM4x5LmF&XcQg3wlAF88ObJ$izF^1DUj!6`aPtp9mw5pOh+d=du2EoYPNKl77pFD+@i0 z@VX(mN(=YP@mc14AdUSXXx3MC^Nto!e%Kmhbw)Ha;8>Y*fn19v5NTLbx-I!FZ$=U` z?CQ7f_O+^pMneK;`C0Onk*Dyh1vU3Bb3pc5VWhHXzQq;RnyZzuy*B3lU8i@EvB_PD z8vHuuEk^6CiFP(DN!WLV6fG0o1Z$P$?+GepV>z`(-#xWV{)TB-%pV9K*4$w(v3=_X zyVlevp6XHsWni>1Q!SXXY0=bN+cyr0x8yU;prhF2P<|Fo6K2?pH1og`n5lWfZzWhb zhn*~bix>$mf_FgFS6Cj<9a0KhvF{HU5(ry@DUm#S)o3wSb=2(ITf)UzXtfD;*8ZUpIfLotjn~c~qn1Ozp6u?5!rs5wo^@(aJ`}11P z;q^8#*@bm?Su9ePM4zDoKEE`>d)^Do3k4&jbX7OHtfZ^ZS0(q|T)2DnS`W{zxnFD> zzzzn@>g50eq%@5=oTb@-R^&U;0RgSoOmqMdrvLWM$K=~i1IZ7xc-%-&#TtTb_!aQ6_ z&V@~^eTFck+FRdq*_xeW10pnf=5i6%5flyeD1}4-N1Ay&8upkC^T_vwn@j~Z1=w3G z6XuK@~H;m3Zx!IIV@POmJFX{tkmbGHta-nDpXGakLIl7wA5NS z=<`jM{+2=Jy|qE6fVk8&4BCoWa+an>mL`uCoYXX?U*vbDIECZgUqvz>SgakCWFW!e zO$hZLW*@$(=PB~AD&VBzpwAuq0}3+9l_Q`A6`>_|(~@=$IT+@%J>#B|S~RCw@t#ry z1{>z*SmzfSERI(=cfn3-=VLD&9u}=U%p!}87g4?0Z8<*>Q%^KkIUcJcaN=u*xY_s4lG6 zxNY|YrE)1~C@rpBd~|g5Xbf>q3&r6!9fmNJoPZ%EYQ0rRDtA;^CS_% z>|>(!jc^;-bM*aL2|;O=0r>YC7?@F3fp+2d>y}Vm%o53)SUN}K9a9{3sm0a;EgD1k zQ@X87oOHe!F=0tOJ>zsgq6-Z1b)q~Bn(+->oP)3$;Q$btrL3y`WLC(m$I67#iQJ1^ zLUDCl@kN=g!a7>K(K2Zq2cv--6A|mA`4wo+D zBNBZ+zS4bb1+bf1qod64M0ZZMim>KgGSygu6c(*JUUA3W1ANjVeQ3?M9gk~SPQ+Z> zix9++p1pMP?}^Aq*CD&b9YY(6| zF>s{rE3>YWyCk~oIVf~-vR>e6Xi#VVHm_Y*9_z{AmhkY?E;Dt(w7!AnzTPNa^rGaX zAImmpIzE)2x-Sdfe#R42h286Eg82{|8g+h7o5#$oR<0hq&6uu^;H3^3F!HjAG%AxX zg6Sv+HUE9=SM)NpBXGE6RjqLjOLS%>QDJkVS-I=&4glffpn)`r%OO(GyiyouS=`U@ z92hij*&({+Q~B7>tE|1&>_mvfzG2c+cH2Rd!J|TLtBVE67x^Irs)tbz5r(TX&{(8R&uub8mXySW3cpskhhd$pB;nd4u> z7Xe43za}f346Jla?943hC4Zy*Euv*&WTfL{WMN?YOCp(As}i!a(J`_!a`B_y?-=Z&Hc*e<(x$ZTnw6{yllZ zf2__sKP713X!5tg|6vXOWm+R7A^f-5U-s#pm3sfDY%3-qEMs8zF1Ky`z7+oum3}W) za(8@R2MHTf+rMm_qKO&c-6F=FkW%2?j>qKPyGPE!*u>$lfHUPkyS*1GIosJ;nOOf_ z(RUk-cWa!#I?Gz@tbcd#ogHImr{iFLpSjLoF6&*!i>-$ju?#IqX$nhVKO33l9J^dfm9Bls?mic`r85r0(7c$dJQh<({4i}cB)c(Q?Ww$rMj(85@r}#crn+s;dh5aF90}2!r!Tkk zhXDashs#3+&R62!=QwCLE0fRJz_O=QT0zP5Y8=k*=~fie&Iduc5xphZJi$sZEGK$z z-KCcFUEE^x8pTn&ES2bzj=4NUBZ`I03O>YK-=xh7Q;Wruba3H$%Z7ZICx zEB?>x!r0(FXWw(^oihAKk}AG$zOt=~4dAa=6X8EL{x2c<&x7zEG9>>72-#TQZ<_zx zxnN}C;P~H8#(9Ukm$J$NTk8X7QDIE}Z(Ek#qK%Wl{?vxp>=2{uIfc_fjoGzsiD zGbv1R6O!LxBE@r`auEU*3VI}CD*T$&Hg_9^QFUdVY>Nkk;2i7Yw{7-qjhmILln4iS z9-UPXg-a6heAH$jDP+FdsT#iDfm`%4HT*_3cE{D_fdvE+&bwVynvA4-CbGUCST&?KaH zX44nnk9O?UUz3s!_8f z0|xamhtn4#lqzWZy78Ca+S@mJM?cT6jEl6f49>?KhO zUxUDb4K#vTUgu?ukVVXH_&hKk9;luf3pIlxv^eAu_f%l-w)=%U6a6jR zD<_8Ecyctp|4qpOy+VGZrZQs=DFossz762dlmZ*`#$F9i8Pl1=v9Zw{9(0ln?Y@>h z`ueFG=Dk*?DURHwt%<-*JTLsZG_~mhTW-f1lhN3n~-;CRlZ-G<(a{#Ez;1NTGv(q*bof9IiR74!ZxrgbY83Dw9Jx}^>R zcF%s)n<>;W*IKjSl{A`9g~Cx=pLw?X2=aH;sAoMP_wXqL?!aWhz%KKF7tb$uKfS)0 zH%sh$cKaqjBi6|A1#ON5;ZFXX_}uUlsj=j;>5F+>ReBC~3r^(hJ}O#F+%+^PHL{^8 zl+%X25MU{~Za(EC-}#_zf@sVm_O!3DhA|o~FhEhvP5|XIK%pn#S)+#Yg!T1PmxbTN z*#6Y9s}eIkv$f#Hm-Q3R&n{1VR@OS*kUmvWcdrf`KD%%4ZydM>f4Z)TzmQ?@ zfYFQAuR~mOvk8$l<`t|zCf>339kV|nld`~vHz*cL-GoCD|^R_7WqX)WaJU(S zKrsVStBfwR8nImxEeSS1tt)5!9G85vIHE0u+pq=%32}_n!W@^;0vfp3K9(we_CY*? zKJD|l>iXo-ahDi^j0@7hFSE)%mIQ1`Q;-y^9EUR}jM{R2(E4ldTshpY~z$Gus$bXaI+9yE-wdJf5XS>WS<=QJ&dXv9N?Nf#_vtyt$3 z;qhIlWUTr($ZjZhZ(G0RjedEt{_7E3pSYev%S#S8!l@1nh1^Tj=)b1=Nh;8GMS}km zzm)9^A)!?CFxOA6neH8dx}S@Ft9LIPypx%=V4F}L6gjdLnUiA?m`AW}AYNcVVXlUGo=3V+7rO)@PVW zmV55tk_A|D<3OwwKZ0vEZ1aL1=zmO8ynf(<1VVsXVOi7rd5_Mx4{vbKCm}Fb!KIxO zzMr-Lz7XAb#T^tUBR^Bbdr?-hf)IT_Req8+LW)FZBq{b0a}QpHolpr}^0a-R5vluif%np0 zg6xX)B)a>lHEfeRLT+aK#S6WL=`n@dJjjcA!kC_R#}#&4NxpE6$}1*x5WQVjm*iz9 z7m<%P5l+l%l4`gK;4^DSvg03FulWWIoHbuXaz(!b3}q9Oh36tXn^T_yA-VjFcW=U#--D?-LCS(!< z!2cz!iu2vBXxB`Wlec>ktGp>uh%?OjHPFz`?3%KHMZqUEcMoPRDLQA3+%P@mC1QJb z__X+^5WjCKFokSy{WH*t2O|k)qs@8!`XPLHWVVngOiY_y_Uc1!Mw->(I;YlAPPezp5w16O9-ySz(kJqU zc*Q_4tC0K(Lm`z)^X|`c*}<(qo1#72hN#-@tpEu7)Kj|8hZ&)6D8SKya3Wj=*8$b( z4bvHwNxeM7c4utnvg}6I;ihiMEsaCUBYRRNEL;`yPk5~iWRG{5NW8V;6OTl?d#u1? z=~qUd`X%n(<_@9-U~~wy>-JnZHhb9NJBYswUNn6V$_yQb za@$ud8GuuFb)XH?hS(^=;uP#HipR7Y_Jd=9WDVau2LV&(TY3go65$0jnbOZx+rbRt zgfPNKq7exPjRpD0Yld<7h_XTEFJYmr0H2E2FHb?9tm)nuZ{OaIlKKUMliByGen2X; zT2~}?1@P)km~1~9y($E?+%a54;IzE8AT}dF^Zo!Eo*x!}!Zzd(d5sU9Q|ohx2_F8` zQ8-H;j>iQ?zV1MN8xoo5<`rDCZmw+Kq0@(zw_(<{Ees5KUXX`v!mA&hK8BqV19md` zL@^fif{)@?-SsQI2)bRZ_gubPiDW53%|FAQk>NvVe}}vb;RL>thv*U}k%ZiSeRLX9 zD@uw`FnC^jd(uUAt(#EJBH0XI!|%@o98V#A*_#tsZ}!a5oBfz-{ab(_S8-QH**N)E z$9zVZbXCkai(l(?+s|RrgNB$v{h+LBF}R#y5*CEhWXu>1{Y*dZd>7r{a859k6L#N4 zK(N`cjV_;;sxnsJd=4`^u50V~Pxc+_`Ci;L;kWei=btgmozE3KUU?#eeJ3vFK)28G zNH{k9d47klc}`=~+p`9<#{zwl>^EE{XwQ7|NZjspF^S`SS+t03n1FJJucvxHmSLlr zt(~|pe#wciQjRiYW-`F%LAmJ;7w~b2!Nx1ZTdM!MNpQlVPeP?cdR(B{M?Uk@v#Wdw zXQMVHqu_;q@pc?+9XF2lV3&X`+zrgevp48F}{RRYAt`!|$#_ z*0s)Ng5SLHeI9C*ED9vnn}zJy85iPF>0k2}|WJ-}cNqd-#CM|E%j@ z;UmeJy(}k|4xCHs?TYQOG4 zdO3?0z7*XA#rh7osl3NbM5)9HPGSp-Xj8nm03tuT3)aW>gKx=JxM;+9NuVgCoAj>x z+E!QKDB599_&~<{c18gC>J zT4Z15!Lujp1L=e1b6&S)x2J%UAEd7iVdll1wEq1UmZ#;t7PHW#!misCvgqgLOuCzh zTIPMGDwsV3Bsl@3#T#|OXy5+2^aA1)SL`RgNUywX99N3>$&boo34?<4b~qq}3<|wkzAM}Zc5^el zFPVSd(0MQ2??j55A>Lp=U7AEJDuHE(4!*h$gm*8&@ikzi$gqZ-|N7djr3*VSZr+qK zW(u|J_r~?*h2PQqvbT@BrUP4iVQaISh`IKY>Aq=%GyV%doc5VF!eTEk<|BRt_Xneo zh5p!mz*p*@@MDD(Qv;CiKuFQk>w_92!S0w?n$p&xI>Aq{RMgKk2nXef^?nj3y@GL0 zhR%&kuxd28K=;=cIA9lC;S63`^UvupGFHGwn|vUq=ej(lg2A+W2e`ZJ2dBI&d=cK1 zs#C1HiM9x@Agke<3Csr5UOJMr{gMkmJ@Xcie{rcsX8aPIxwy%U$dH*YLC$@_w*+PW zm;mW}?|wRkPEgV*d9J>ll`s=h_H z4h! zy4VIe;=D0`Tzf=XoV?0BKwq(;>c+Dsac1und5-7UnY`(A_{J^QycrUUeE?)^YH7!5lh2}7*@_}7wWIJWxn`UFrcISf%MqvTQ%Ae z%yPs}=}WZ`5vM}G`WIdV6)=D++1Qr5x;u>PFOvGh8T}>*SHksZ zh*$S`k?AADDzVqoVW^tS0ZKRp6tCE-+d-dNFZ-O)ha^To%= zpnT~iS-G7;t1g)_7-TqHcsc3jLR)lN_?a{r!XwST1o)nsaL*aCmAIq}QLvVl6 z{5l%r2973AAA9h-c0Www;Z`H-eW2X@^YP8ihT8%40>_f&C%d2jW)-l`+TLv7Rda3r zp4;O}M|-UX`07D6j^cW*pDptNW8?O9QeySR7U1dyc063aw(q)ZUWK~2$7q=iH-k7* ztA{`updthXcC~T9f#AM)dlGYl0xfx!ir5B<8#P9x!hedf=)RcuA;7*z473==(=)UEnZU#LbCZsXc&l@Z6 zh(7|m2m-(@S)(tYl9M%8d2q%dBSbPx{E-vn)4@-~FLbkiW`z_cjz1GpU{W4BB}5r; z1?8pBrg870+#a<@d0p9Sn0~HE8RZs8a%`=P{%PZJ>M=ZfpF}WKQaQLrm;v{C>cMY| zU!5}g>CJyN8uGd_w(HhYk4Fp`iHT}IJKBxBC6dvJ8?hNyaL;izRq%wx?}~JC+5LmD zs)vXk1kHx5iI6X{kfvi!;s+8D0rq8EZ7Q zF_&>*iz6FxhIX5$kt^?>w2q6DhK@zKD08u2B!i6ojI#+(ZPyR+(yDEciQPH?X zxSQN7^U$mkG78Ld0w;FP`COWKRTE05bNeS;&Sju@j~~1l=Hh3WOC*d`jAV?o*I|9f zp|>b%6dQ?8;yZtlMaJP&W2rFNFc}BZdnpI9eKK0{Qai~BXfs15GO6L5VdP8YgeWdNEtt-g)`WYzKKZDq!pi5LWaKb+9`x^=oMFNHu|iCR89{o1~sD_eO5sBr(zXgtJms#f{M!~e1Lb={bhx( zCGQ+)alut{(}s5mqLsH3mZHw zkA^R0eVlJ@G1{2*nCr7MzPNhzE{i;q6C`Yw|vm?|rB6~tVUENPDZ zPi*l!8Pb6-YDJE7eWp#pDuDYn^r8srh{z)bzfjD+Y+a)5$le2Gx+L0VR@Wo!VS2Vc zD>DbfF3&6W%?cERU7Y^Cc;yaHN9Iw1mzE8wprC$8`xoZ1?5&gVo#U7zfQXh^j_ok5 zDh6kaxdhU6VwpoMGfzcXwBbOIZC%?cQ&8#%sZi-1S}ejjj;09{N#kS2WA!!w&D^yd zf9AF94A(X6l{r7?EX$EE_9t#i@-9BNHJ=q%mJ)! z)ZMta`62CS=E37(oS4#srLqAe@p1cuM38dy350@Ty1RSs(D8LiK`H&0K2&Ax6G#8L zTGS$~T_;z&T4-m|$UPwrl}+y5j1wO=OAE#soI!3W<;^ByUXlESZ?P~!W_8Jy& zW;*EoncGRfwhs*@gHWUZ^+#Mrd$!Q-dN*8iig&h^rjyLJ;v>hQO$$tDr)l}(0)xg0)iMimJacw16K1a zY0O+jxisA*bE;3S1yeem%sS{tI2(o9yfiuI1N(DBH^wknC&UU&JgD_NqL$PwWF3#) ztYO>f59fBws78IA9tl}(=v_0&b~zo#IMg*HWgYF^3nmq?)uO!BO3(+%Kx<@EcA<66 zxdCZ-q;r?%=?qaQ2Gq0R6lh!&G8rm7%2^?*f7+COJ90Vi(nQU+`h5hA4VyNohs8;C zWBPq!#eg|nihX)W`91#Q^JhBAmhC9K67)I_SUV%^P9F=|ez%P1!^zXR?s0&&ZZ(yBepjnl8bcnNWDzf+5c7C5c$O{?!(g!6VAp^k>`b#>Kl`CCE!K%q5Q zzQjoK8j0CKMDajNoVL~B1=;m;Q%g8AG=3bvBe(Or9bS~UfrKd9<#inA{>f4f7U7aU zDR-jDVxC^l)Sw8Bt3lpVatoA(Fv_pM+Sdr-6i)epVymwF1QBzHf)Ky85P^{gYRyRQ z?uzWO@&wjUrt4)HZR3lt{;Ow=%|ZnXR5t9pBA)U$xQ1wOc^x@45&h?)Wq@2ae#;eW z`#}J%%$A)cv$mF#%~7sKzbEo^HOF)d1(X*GXxc$cink!VhL@RC+C#S;q%r;P>nU#Eih55jSqI}O3<{a7tVO6?voNuq98E3i9#%`a-X?gqE+N1NG782s1h-&7d$Lo~|x zRC0-ydzL1>B_%$tK;>ax=c_60$GN@EmSEVWKJ7J$>D$j+!`crT7bV&yR7w5Dq8K7f zmI}g!4bTc2$Rr1L>U9;j71R}B^QkuDO$!?ATKsjOibAXSjM@?BzMD|ieg=ru$;Pux z-6IE?72BKL0Cu#ohnj}YJ;As0Os-Kg?bEzmjk<7G=Ejn}fk+&HRS&72sXBtTVCJLMx2%g1)Q4scW^LpP68K$WO9bQQ*V`_W(-l3A8mP!odfECGC( z9k&Ku=j1bGZ%tAnGO%mA#c}hT8O**|3CF+E<=bDeBeKTilCIW}SlRnnc{_qQMGb0e zZ2Nkw2fC%Pp<6MdUKg;W8*UO`!^H1?I^-*$)QMW1qFZysA7G^qH8Gg$_c#}Gy4umH z@vZT9g1cY|zkY@9yzD`{!9IpxCzi?6Jce*bvtd209`Mv)wV~{jJpRar!(MZGk9e?+ zuCiD%Yr1BPpN}@@R5?^V0=9*HHla7|M0~Z%vuH(0t$&*t^$migqM#~&<>eyeW_YBq z)7#Bb@oOyPagV*LC(Y+S9@5W3p0l^S!K7cKF;A`{4OiUxn@aD@SL%@j}&dlnk@q{dYPFT0k6rjO(%~#&s zsRm(#4*#ECZU|Sefg#fGpOssu0_mJ`;B=1u|9^tXNZ)*GFqsg_#K}=P*9?A76XKYJ zFB4Kzj`&c#W{Bh9u+cD4Gg<13KK2duUBrgQUQ(i2yUHYPGM0nf3k>YEkoKkC;^I)i z>vU&aCe24>(QR#3OjP;mV`1AD%dmNEX3W$kZ9|zCuosSYs`cQNT(0-#( zu$$hCnvzU!yYx4Kx^}Qd}_K9%PGqmy?xER3^u05s)^Q13@>=ev6K`evWX@JB>QDq2$B7K@mgR@XHWQO5 zYMex-o*-Mzl9hJ_1atM;P~VXlqPQ)lYD52eWTWHjCMB5BD(_?c@HuorfSXyww0=ma zSTrC9dEMxSJSZ_hHF#t}{$TBr{~SLkSNjm&tUw9Tu~wdJj_UEOWP-j#N}mkIO7Rn< ztUkJspqxnK7(EqhEkKlQV<^JGb!6ff;geJC!DJeDRuPFcixno5qYg32=e$>}{!f z>(w21+k>M}b>`$5Hj^HIuKO~gFNRy*_8H3(>C(>(**kCe%A|E$toB(snS%=ZNcH$U zMc$OEHd?uBI&(X1q^F^i=C`r=IzBo`_+viWuh+pp21_fFqSE~S=FimCN65U&iq1CN zBdV@$3ujqZ-9&Td4%ar?v!Jyk2^V*urQR86qMkh8gYIK$5KJ>>W{^fJUH0P`05?05 zRbTB%Tdu3zLOW()P$Y~7dn#kGel@Y>l5iSx` zC<;-jVDK7#)mnbKdAO47ns-wpzgf>l`v}@zKD_!Ge1jf;92M7ED*5Qbkvf^9{x5F} z3Bd@-*g_vADd|G-5k#|vt5&;`G+caqtmTSqQl?)X81eGbQsk?#!;QwZh0>~7!;;Jz z0_3A#1M!p=#`9qE{NuW4%R9`c8rXb;KH%hptUUXwt0nV#b6!)_-y#B z*h82GG-%7{-;!x+r!i58G2#RIIChq_VNrg#sDj+NBQKe0?1oHudumGZhoH4o2N8|d z7b~fCOuV^3N0qxpvIO2oc~54&IHm0D5)xnzZ533HN5`>0t##rDOZS6U8sTr{$F7hE zz9Uy`{qiW|c1Y6MS9X?2dGkxFfN(^L6Y{ii3xQBh!6;hFSfj=})BBE;Joej1Q!&rF z#0}}XlAjyRzSU-Cu1^*m<9=AfAmtYNp%ed}V*CjfHg&C#_q*8cz@K`UW9c+JoLs4p4Lur~6G^Sad-Z8iEI!%Ne`Ks}Ht+x4m=Fg6J!e$YS zXf@TtxMlU|vk67!}TnEpZ|7h0K6=JICXpUcG|x3)uoRT%UC)C+?u;&QcUd zBUuz{e+`I36?+hNM_m-Rt+r0afqIW?S_z|>UxrpOw-Ak$sWo~kEL3E@<-RnMx5;yb zfOCD}f$VvY^E8o_ea*lae7lx*djt6tdT&@m6MxV3{JR5K{2f_3ey1*tD=qi#XhCv0KD zseUcw!{)=`9>n4lznv2okVpm=Z~KD$FSm<8N$3^rMw@)Nie=~tyuih};~7*0XMeAz zjE;>fmuKg^N+dtT1%sJ920fps#nxN9C{#q9@IR%(%-?AY^4-}uM$oslF41&r__nC! zpi}^r^m@wU=X-mf!oO1gbaoDaNWO0Mety83(`obJD}}f9j3fo?t(u)UhGm28FJT2R zKio#uYgCXwktwCK)d;b1}Eh zLf{fIM5M=16#kGmbr4_7N6I0;ZftbVc2Lb&cBD|oMcBH4op)7?*+3Qo^iuP&xyNw5 z$yIJr;`RDkJ3JQvp~Dpig1_)vM^9<6*_>CC8^+CGR*su5=T4Y4&>qfvV0fx?hK*6?Sc9C~m=;bec(0WUl2GWF={Idt?+ov6QBMocK#AnW zY-~+t`zgghN%znZ?)~iu+Zv;7iBKWXFSsF;tA340lP#>0;!ELz416{M_-AsGDh>q~ z@0p=uxOq!|za%hXZCyPmyr2!UCAmfD=5`XMYnwQXvnWMB2oBLEASq=T51;w#`VAfc zhrHd?F#jsTQ0rm%gzSC@WmTCXSG_C=AlmU**fEeH{hQ4FsWH`(ZE#fRvg5{Y^vp2j*l5#YT#5TF^uAd2TFP6k+G{-8q zIoFW~F0NK{6sA>;!~+Zq%!T!rH8ceLP3<}Rgy9(PLKf~?hnr{1n@^}cDIW1Px|H$P zr`q}5y-kMviRjO{pBpiaFefgG5+vrbM%GDW2J(-dni+DN&GU975u|>fyFfjZ@d_gP z)x{YRD`FMY&X$~+f47t{DJUtYN-!y6ia%8yMoT1%f*z-%=hzWeMuWaqn$buN_&`&p z6hc2L*M#qLo7d;*X$8=1s&)HQ+Gf-Ivd;e4iR0UKAGCrNiFNzd3~UYcPC1PVwplw$ z=5z)*xe8b*Zc;UT(YkE4@mAi5AEhXWJj+`1oZH9m6b%N~f38saw%_a-EF$7ur%4-z zf{JIu&`Z}wM2VO{Yg4CEixw}=D8nfI=wEZWRXj1XtG$kMBSLC~;7^j_Uq?3@HWB7x zv7I~DWqiT^R_$>$Z`&T5uFlgN1Fxa(@$5|H`EsZ0tazPQ>hMzkV(U!*^oTiG>^tB}KjFu?5kYh(!X4{w$fL0HP`A@@&}e6lhn3mja~`C&ihGO8#4=f+dpP z3M)wO;z$Ff@cHCF%MkBb&8ucO@^PlZd_9G`oRy^j#W2XuT^oAjinDj_d!Y{{;xfWW zCNJ*&PbY!%k@M8wN9G;m^2BJ%%JeqmJE}_+&b1cg#>X&|b7YIlwA7VyqRK3bt1Pcr zR2Sz9ONe&4ZcR#Or^GUg7*#UVwGs!w-gd0C-OYa-RZG6MXx`+udI$tAq-h?NhNpIe8@(SDN}b-iG&Je+@cfGsd%E*KsYk`aD~$meVwQ>m#D^43^4}>39_YN6T0%? z=3pjj^y}7FR$+1DG%{W3uK!=X-DzACXBq(TrB!#0f>Jkv251U(Ipmm`OfD2@DFlTj zf|x|YQH};B$YCH74#guXgwpy$pjp%kh$1<-O6`w;RzOhXP@;j#wcxSZs3?_KQL+0* zE0Cn!ZTq3~As?Q3ubFw?nNR;bO=b@#t*2i0qmm{G#y95g#EdRqq36=$vdiTjznY|bt66GZ??$qxRyzi zA2{wY!JS`t$*bX9f8${P9%1kUh8s6(Z+T;6c6me%o0WRjsKOY#Kzb$@YiuX0WaKC@ zSFgRbKFTZZxVplLRi1)&R>@?}(#88YSmRtPsE^3}P?J;1J@urJ^~HEkV~iQ0EGI#h z)VAc4ALg`g%XY%@#Ma-X4oivlTVHW#1v2;Z-NB;(O8kCjj(nOwX&MC*vyPx zur^(&?C?s-!1bw}%)+Cj-?~;yzR}!m3(RrnkEoa}17&Jjfc@Dsk%R0{ORVKVCmq25 zTeGgmES306z0I?u#lHRnc7hK7u)Xdb{PNRtc-Ls5#SL;!^L!>>X89s}UGt0Xb`Qe$ zK06Lnl-5~&?+X98YhvEty2dA2SwZ9Z-W1i`Ede%dJ^u+gnISeI zQ#VZH8!@rh%2>{6xWxIBOw4sueY`h&S;5WEPj2oiRbP4HPy$MZ#hxA(?7p?y<19a| zA#GHM)6_fmoF3csxmmC&F6EyeY4Q{s`9TbRP=TWJCZl6zqiR8HKz(?9+Dn>$z`Z7B zm0TVyimR)$#ttZIOPYfjp$-0;=0%cW(SEhhnFU|hT)KMEvL#Q{wqK2`_v_+Wto2p* zBpcC-IjUSnM)LZuZZvM`qvZvOOsw^x(r9Mi;j)=svPByFk}595zpnvz}uT5 zjh9D^6e$#~8TJKyV{uq&lxkP;B(qmVDvdd+ws*Nu^xi2&t+X(4(Yz|9x%f!Ati*(` zA%01ls1>G59BMAyi=mvjc^MhE^}9b2{uAS5dKEgfM%hzcQ?pZ$_NP%{4cTzqGPA$h$UT*DaI?w% zQV)x`L#^<}s)th{LuwV>Ez!Mivdqek&b4qA18yf3vhFAxk5GcTst)|?mWQ{b`=DfA z_v76$Z?DSnef)vyB3ZOAz4hg~CC%)tXBnMac?ZnHg(^+%Fq**kV~4ghPrMjxpU5;? z7JoF&P|pC1^$UDv#+$wNXTOB)z?nbd>`uJFR_-Oa`j=`Fu55{2ap_@ z3~Cy`;d(&K1ykNwxLNIHdT9OMw3gEjIYtewXY(f!8qw*&}qLp%VAoQOD# z;7Djd2lqU*u<`lIj6tiy#zRiRbEFOrlO0#;-aYu;rQR!)wrwVp&s{et_ATE^u<2U6 z_QRo&<#LNf!}s53CuaDw+2Zn6%!u1yesnuKN06LE;8H%w#4#;zyG(_`1spUmAE20<{$c-?j&Mg_)Jr!NE{BmfBYVnl=S03X&mKb>|cjDk&~8-rlr`1E50 zutI|vit3LC5s2DihdOhB7}$9FF$&pW4xo+=+M!gk!5l=0XwVMaU|^AT=7kXw0Jyp_ zhz#VbK@4nigBTSxh*5!%)@uht2vPt30V@qCulC1YGRBqV^6mS7=x@acJFv9%~%`}Imnj^HJ~+8`)Qf>F$h6<+IU{{{cF6LSCn literal 0 HcmV?d00001 diff --git a/public/files/1715363683-tables-label.pdf b/public/files/1715363683-tables-label.pdf new file mode 100644 index 0000000000000000000000000000000000000000..8091080da447e1eda23cad180ecd9ac8c4cf9914 GIT binary patch literal 43697 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 b-~g7K)15kJb0l1WRz&V8PuX!CiyP;O-i9-avBh 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

^Kp?{0~?Kp0oC;94`l{;}05^dX%G)e&$LR%Hg` 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

K|wuC#3t zv~W}%P}sHy`DC8vXo2kU>}nC+D9)YO#7{*v)25mgK*0iq`$WCQ7|=?0JJjJ3#M%D@ zDtlOoh*Nt|gjDwSpkF)A1>$W>bFq_NWBsy~X90gzkGNj89%HyXAnV%+A_$`JoZ=uz zQB57-w{41Yw6`x;8nvXiygRvPr?V+W8QWXU${M}p{KSs-B`5Udb$_n^(T+X#fmpI+ z;o`IBVbe7EW3ybE&PmKK`&nva$qV-7Bo4?wz}3D58;Y~G5P57cXakl5mY}Q2FCu)Ma@$E?fJ22u?(ggwQl_f5;cyMh| zcj`gFea!X<)hw94tU3n@?dO<1Fo3abVIM!zZ#l=D*2GzqOVdR%tMcelFsa?aq>W*S zvtFphLz8pXzc)L0Z3KgLOf1jHjYiKcVnNMJ*8b4N626sTe`d>sYSoDrn@yG7}jJ(uG)ZNzl5&s)&QpmCjgNQ_h`rq4T847mM;=!d(cU!z~&zo(aM z+Kj*}La*U~w=u+S_cD|1bxDidA3vVy90h9YX0nrvXJ^>D+6I#aFPVHT_A^rR@^rf=2ZU){^z$B*n|-be zq5Sk%{Sqde#3|obXwi`yCt?bf7v!@NBrtSGqZ!WKS(Z6c8pj&Ue7Pv2{r&8d@A64~ zqfj0rl@^@Obw`=3 zAON6>DB#Uw5>j%>+CQ1}s_U`u ztLvtZ4%gJ#kub3T7S{MEbWMKH-r3*yVT4|`CtleQxsO%MW0+OtUBMKttwCp^uryl= zCXT3xMXhWUf%5%))cwh z$MNdOGH-iuU?L`m7q==_6R?9nTF2e~G%za0r&Q~J&ODYz*De2kdC|%=mq-~t%F>LUarDVOvFw#2GVzs-Tr`JP{=~!vRVl}1h zenz)xu%VaY(SxqNn~(El@Pr}{FY*aLK@*?Zy@9K~e5Kj7#JhxO+xUJ|aeB1W%YNDz zIBYyu{di$a<=y>^=!mnEV)qi^ZvDN)8uP&P#+bWRU0ml>T+H(9n;2~k#_dQoMpE?- zF-g2`Nb_T7>WWXqV$Bt9&DR!_rI3%GB7ad~%nr~h<5S7So9~z#br%(RIX@~7@;Y8j zXy4E5v^NF9FZ5`yNK9V8-x$=s(>N>AE}}~4{Vs|j!fdV}oL>j4pn*cNXQN(Ic3nnO z7BZV`HQF$z!LG$$11ih6h)t^=a_qSZX6A4(N+_!D$Ou zYkCL-!kIZVwd$rD@a#_=35PVb7DD4-6HU9Ix$3Nm?%lHeYCzf5WkUv5B(zfAGqb#H zrEddAq%(H%IQUhFTEbRfoVFhHmmr&Dhy^qtT2B*vEV1F%pl=_4qU^3ph(~$s*le<2 zKLvo<=gSfJmpZ(A%eF*Tn4MD9>f+10@5`@;kS1tAE%hy*4)s9Sv{v*>CN!%8=JbON z;wzZ=oe%qbd6YU4%MkOeN#)C)`pgp zVoTAI#B(AvVxPmarJ*`@_Qp@#4ilNE-6D za`S{gy+am)&cXlrM=%-bt9KP9Gh&H2IV$Ih{;x?w9HY=hLK?~;FN&8maU2{rT4ow% z^KYW}Jp(;w(ZSK@lxUXDGV$w7r6AWleH$(0U8&cYn9tx<`V%gr#)Fc`mKF8W{nZ1o_?MEK)5KYBEC^8;sw_YgFMekWnNv6A1`inr#s!$iE zzhw2~H*yVy5%NWJEb37PI7Bo^x{O6>XP@k{dT6+1Rh1&naAlPPP6`d5ny$oR(xOIp zPa`j*mGO7gc*_OGXS~E{N{S)bcq4uC9Js+)zM;LRFzjEHIhvb%;sc=e%Gg{?3jSR%tKA7#_l9c9Bn>p^(An zDRee4mPS^>wkOBR$#JW~Z~Hjpy@Xun?Rj@nYckvIp23^ATG@89**^?ZV@jT8HSYHN zvL`L_Y_REdm#HM4KJ~L|V7WVwaT@5Rl(Ps>kOp@~Tv^*34bip51OGJqepI zyNS)$_SR0q7xUhBwdUh}ptK?>D($Z?zRVpxgn(sMbhg1RQFV1|1oN882HF!>gqGp1 zIjsdrgqS@o^>%+F^~BjObT4E5K-v)#{S-RslCOtAgz2G-+Ddo2QeEXHx)FW-0%0`x zV`2SzN>YTDk7$8(7NP#E!Oiawq%hC|MHsYtCNw#@6zOQYx}MRUX1b8n7m$9-Qx{i@ z(2?sWvo~zp8*NNydhh8Jiz<)stBEZxmWvNf-NKBDaFL+GP>4zef>-b>R&rBKLzQG# zJR9oyO}f@vhtPI%KdP^MtkdIY>1s5M5Yq2bol=-I{My$NF6!~)WVEy;%d}-B;K}mpy0QvAIe>|nRk&N-NbR_N; z<|%R~Ld0$V-*v*>7rXYkmudl2GsB+1Iwp77wA0G#-1^IZ3rqYD; z6;{K^mF^ii+32&y1cFJ-jAr%<$FpM#3xi;1)vYa<@!9ZMu?H~qY0(zZza-LDPhx&1 z#)$Rn;n-TxhX4H4NfqSE9exg=wHYwt?W!uu9e~wR?ME_PoiC@+G4kXB9h7eu$P#!S zoE!_5BXoS9&9y&wsc@JH%^~!%9wLzB7ys$Ax z&Y4|U28JS09FeDtnh6AR3P#XTMjO`O7~i%f<*;Ah8;iNu#IH%$6#ZCh^sY29aegr4 z81=y#1SvN$3>^7(72=Pvu&HYWLEgnSd%iSluECbgdp^<|oo| zD~e*{DoW#ofDz5g)V7(qmq{X&@E47jOSSc<6JK_uBQ~=@B#VhIW@oE=@th(G1=B(* z#fVT=Y>6|84`lkhn^|smwd!R|AIRol5qd0xIdS_nw-zEe>d7Kld#gYks@VPTTk4{? zE!8zLb~HO&lS&wk{4#Wk+4*R!%*~M#A;BW6O}EA2ybbP41e~jLcVtgHoX7F3>?``; z!PhG(*H_RF!M6rgw6V8bPruq8tK8FDg%)PXi66NSdS&y@P$r|J*=h!jkFb{UdfAj} zaiJ}RaU+cAszxw|WkapKX}Ph7x!|39szK^=o2VPGwQ(Dm2&$j*`LOwLxcjj<#jj_? z1tbz53pc$%zUP|-pak@?R>O5ZT*VUfINrzkn!_nnL`PqbhP1Y|3#TW?oN{Czq&fYm z97a8_i23Ggn+Q}SozUOK!T@So{ajZzjv@3-t#dTpD!vUGIT#gSIfI_^=;_YRyU_alaw&(Bym~XBlYBkEpAIOwa*{X!t zoI3m1mN6IASE+EgX_*(<_8O`vEKhW!%TOnOYQ2Xm4)-)R3?gs}8X(eREC{{Jnb?ah zPi34WJw|TgshGcO34jGUS`guO;=}!GPh`aK7o~&PzBrUU=5H zz7Smv{IK)$*8{9OsfZZO2eL_&tT|x>k%X27QjFm}uuCYX_Q;WeUdw_B{atG)@1DW2 z&IvX~iG3AHc6~}Ho#3rjHb_FDZK~I}MZG<=w@E!EH0^UZH)efvBHIrsMoRj-wouP6 z2iTSvEenJS{yu?q!CbW~M4D_N#wcDb#cGz3JgqdvZ!$rNn4o zSdZeOUFSCcy+xMizP8o!kY19}dVbRPV{P|5^IrX`QEZ7e{!<8IaCm4#uY6cmCMh}b z2)5v4;I@Vo&vkFj^wu)Lctl@KL>Jy=DPMa`H(co@Phbq@j_J?&fVz*z`~^E8%cq;( zu-EA;USvr*T3s&F@jH8(!i<)u)0)Gj#XQn)V?-QnS1U!)eOBLd@ty1&IV!UseCoQ# z>K8O((q@-6iqg;)Gy>8*jn6M=@W$G@cSk6+IR5cX$ zGsrU){eCl%h@B0z;qKdchfxBI9=L!MB`3zkFU|8jK4b3%J1rFG~i3baLWBu zk7=XLMPB$x#)|vQE`GacAh`B@nbMctM)yDw5yu)$x{%MPcvg(v^ese`NO5#l zH7eC;vEocJOw#whRp%RpV^iDOt2kF8q=tyTBx$}i^ur-zAx>tS*)tvA&-h;}-7aRW zTchJv-CGx5eQ)k@#!{O$Ehfa6oj6@+Vy<_DKHKN=!4K+X~wz)CrMGb52n#H8=ChNdla#EXyS66d?a-#Bd zzSVJ3xXLTFfBx-kz!mL#RB^z1?|!ExMRbINBJpfrWA@o75;7zlsjv8BF8PlV zq+3?giYbm8m|iFsj#`2~yW{A_*^(Kgq$QStPI zSb71IN}9S>d>`1;hLx_f@wdHd(WfTOtL$bs0spxa&4VJ5w;9`l&G6=TnTRqa>Mknb zFu_b4^g=pi4|E!c2ZnDhaM~g-<5gzj)mRFlW)Pr3))Z_)7jE1f0HS)I&Tq;pEH0de z#!H>GzcJ1qiC@8k?cEOU;E=+p|F zSw<>KOX*8K`5U1jhT}8Nv#JW*hL)QZ-o>jWTu!zEF#x%t)}{7+sNb*N584LPq1h!8 zFSxFiuu@r+s%vJ+Ub3*Mx^P~rGrBl#nup+#_*K8>EUz^Bgn=O&C+_YyH7w%nfir(Z zhw;s@q=T!nxZ(DoPbrk&abs4sdpY+=RDr6!&0s|{EQ`2IBcB?QZ>_cnL@$;v5yK2N zPLz^grgRyW*Y)Keb}XoWZ#N^Vli5;c)CH&NSc>-?EYbBmHYf-MT~v67%{s7oQy-!FETt7Pn15oZBYa$@(+( zNDM6$9A{~v+h5Jfjv6YC8gb1d+bY8}t+NQ`3Qgad++IcH6^?(aR+N=#g;>_s5~D3Q zS7&u;spz!G-*zDvuZ090Nwy)>RSiuK;C25B=?gewmXjH^!xfi0nsj(8n`tx~uHxY! zH#){*iGG0{XY04B>XV znD&*vW}l3IKWV?Sf1J(2NtjnV5S`kPDKXPl%)#?ss(j1usuE>_bdoKIYZ=qaq+D_S zqP==FE6Z!cKmVAEPbq7L%asV>b|rpl`^~he92%n)fk``pV#Q-o#n5BdK0qk$P^s&$ zvjglQ{_ef65~y98K_HJ23%fwfZ+^44;cG0`jxw4$-CZ}&D}y)QZ?UY>-2Pq90RBL& ze^Y1w@@D_jpZ#ap4zlO}#o4j2(sMw-763x({x6*!D+_?0lZ6$)1aWqZ5K+g>%m%4X z24G~OXJ>_wEJ99pP6*axWMTi0%I};&<$^DDd zL696fJ0WDo|IYOwVor5D<0u+t(E#X~KbrgopdKf>pKGArr;K#Sd(D`+;4GSW9PSg@aEey>~aP-+C zJw9PH4_VZ;Wv0!VvQ9>@;+OMJ%m&NXfL*$ z((uB<@yS)=Q(?~exVT%cx;12Zc`7$%B`YP@A2w^Oc&#uD+x6d{x(pPDNMW{4+neY% zqqzL2HYD-XZn`~S^~NG7d5cdHQHaANktjpa=>&o<{rFPk;(A+Pkh$C8B3tbBNiVJU zq{k#sA6FG8gg)pOAe%!ZR@ATD&rF!A*Dl0}HG2!i{OoMey3GB5Gk_tRE8uw@g_c!ZHLDD!qW)~v;!9pTQeCJ1aV(})Br-i?Enp!uD-L^1-> z$24Po5*S%f5PlG@=_FS}y|f8#I}JcbuWTN)Y?+oD-7YLB5o8@oGj`hk WF1Doidd;+#6|}~xIw%%v%L_lRx->rk literal 0 HcmV?d00001 diff --git a/public/files/1715363799-tables-label.pdf b/public/files/1715363799-tables-label.pdf new file mode 100644 index 0000000000000000000000000000000000000000..9858bbef35f6561a8238d4b9df516c857fc959bf GIT binary patch literal 42349 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 Oghyb^%9YOU?jFMc literal 0 HcmV?d00001 diff --git a/src/app.controller.ts b/src/app.controller.ts index d0f18e7..5c1df4e 100644 --- a/src/app.controller.ts +++ b/src/app.controller.ts @@ -1,4 +1,10 @@ +import { Quero } from '@core/decorators/quero.decorator'; +import base64url from '@lib/helpers/base64.helper'; +import { config } from '@lib/helpers/config.helper'; import { Controller, Get, Res } from '@nestjs/common'; +import * as fs from 'fs'; +import { lookup } from 'mime-types'; +import * as path from 'path'; @Controller() export class AppController { @@ -11,4 +17,36 @@ export class AppController { favicon(@Res() response): string { return response.noContent(); } + + @Get('/files') + getFiles(@Quero() quero, @Res() response): string { + if (!quero.view) { + return response.status(302).redirect('/error?code=404'); + } + + const file = base64url.decode(quero.view); + + const regexp = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/; + if (regexp.test(file)) { + return response.render('iframe', { name: 'Market Label', url: file }); + } + + if (!fs.existsSync(file)) { + return response.status(302).redirect('/error?code=404'); + } + + const mime = lookup(file); + const uri = file.replace(config.getPublicPath(), ''); + const name = path.basename(uri); + if (['application/pdf', 'text/html'].includes(mime)) { + return response.render('iframe', { name, url: `${config.getAssetURI()}${uri}` }); + } + + return response + .headers({ + 'Content-Type': mime, + 'Content-Disposition': `attachment; filename=${name}`, + }) + .send(fs.readFileSync(file)); + } } diff --git a/src/app/customer/customer.module.ts b/src/app/customer/customer.module.ts index fbed1cc..e3ea7f0 100644 --- a/src/app/customer/customer.module.ts +++ b/src/app/customer/customer.module.ts @@ -2,10 +2,11 @@ import { Module } from '@nestjs/common'; import { CustomerAuthModule } from './auth/auth.module'; import { CustomerController } from './customer.controller'; import { CustomerOrderModule } from './order/order.module'; +import { TableController } from './table.controller'; @Module({ imports: [CustomerAuthModule, CustomerOrderModule], - controllers: [CustomerController], + controllers: [CustomerController, TableController], providers: [], }) export class CustomerModule {} diff --git a/src/app/customer/table.controller.ts b/src/app/customer/table.controller.ts new file mode 100644 index 0000000..747db6b --- /dev/null +++ b/src/app/customer/table.controller.ts @@ -0,0 +1,32 @@ +import { Restaurant, RestaurantStatus } from '@db/entities/owner/restaurant.entity'; +import { Table, TableStatus } from '@db/entities/owner/table.entity'; +import { TableTransformer } from '@db/transformers/table.transformer'; + +import { GenericException } from '@lib/exceptions/generic.exception'; +import { Controller, Get, NotFoundException, Param, Res } from '@nestjs/common'; +import { capitalize, get } from 'lodash'; + +@Controller('tables') +export class TableController { + @Get('/:id') + async checkTable(@Param() params, @Res() response) { + const id = get(params, 'id', null); + const table = await Table.findOneBy({ id }); + + if (!table) { + throw new NotFoundException('Table not found!'); + } + + if (table.status !== TableStatus.Available) { + throw new GenericException(`Can't use Table ${table.number}, because it's ${capitalize(table.status)}`); + } + + const restaurant = await Restaurant.findOneBy({ id: table.restaurant_id }); + + if (restaurant.status !== RestaurantStatus.Active) { + throw new GenericException(`Sorry, restaurant is inactive`); + } + + return response.item(table, TableTransformer); + } +} diff --git a/src/app/owner/restaurant/table/table.controller.ts b/src/app/owner/restaurant/table/table.controller.ts index b55b6fc..51681df 100644 --- a/src/app/owner/restaurant/table/table.controller.ts +++ b/src/app/owner/restaurant/table/table.controller.ts @@ -1,22 +1,34 @@ import { Loc } from '@core/decorators/location.decorator'; import { Rest } from '@core/decorators/restaurant.decorator'; +import { Me } from '@core/decorators/user.decorator'; import { OwnerAuthGuard } from '@core/guards/auth.guard'; import { OwnerGuard } from '@core/guards/owner.guard'; +import { PdfService } from '@core/services/pdf.service'; import { PermAct, PermOwner } from '@core/services/role.service'; +import { UtilService } from '@core/services/util.service'; import { Location } from '@db/entities/owner/location.entity'; +import { Owner } from '@db/entities/owner/owner.entity'; import { Restaurant } from '@db/entities/owner/restaurant.entity'; import { Table, TableStatus } from '@db/entities/owner/table.entity'; import { TableTransformer } from '@db/transformers/table.transformer'; import { ValidationException } from '@lib/exceptions/validation.exception'; +import { config } from '@lib/helpers/config.helper'; +import { time } from '@lib/helpers/time.helper'; +import { writeFile } from '@lib/helpers/utils.helper'; import { Validator } from '@lib/helpers/validator.helper'; +import Logger from '@lib/logger/logger.library'; +import Socket, { PubSubEventType, PubSubPayloadType, PubSubStatus } from '@lib/pubsub/pubsub.lib'; import { Permissions } from '@lib/rbac'; import AppDataSource from '@lib/typeorm/datasource.typeorm'; +import { uuid } from '@lib/uid/uuid.library'; import { BadRequestException, Body, Controller, Get, Param, Post, Put, Res, UseGuards } from '@nestjs/common'; -import { Not } from 'typeorm'; +import { In, Not } from 'typeorm'; @Controller() @UseGuards(OwnerAuthGuard()) export class TableController { + constructor(private pdf: PdfService, private util: UtilService) {} + @Get() @UseGuards(OwnerGuard) @Permissions(`${PermOwner.Table}@${PermAct.R}`) @@ -74,7 +86,7 @@ export class TableController { @Put('/:table_id') @UseGuards(OwnerGuard) - @Permissions(`${PermOwner.Location}@${PermAct.U}`) + @Permissions(`${PermOwner.Table}@${PermAct.U}`) async update(@Rest() rest: Restaurant, @Body() body, @Res() response, @Param() param) { const rules = { number: 'required|unique|safe_text', @@ -107,4 +119,71 @@ export class TableController { return response.item(table, TableTransformer); } + + @Post('/label') + @UseGuards(OwnerGuard) + @Permissions(`${PermOwner.Table}@${PermAct.U}`) + async generateLabel(@Body() body, @Res() response, @Me() me: Owner, @Rest() restaurant: Restaurant) { + const rules = { + table_ids: 'required|array|uid', + }; + const validation = Validator.init(body, rules); + if (validation.fails()) { + throw new ValidationException(validation); + } + + const request_id = uuid(); + const processing = async (): Promise => { + const data = []; + const tables = await Table.find({ where: { id: In(body.table_ids) } }); + const locations = await Location.find({ where: { id: In(tables.map((val) => val.location_id)) } }); + for (const table of tables) { + const url = `${config.getAppURI()}/tables/${table.id}`; + const barcode = await this.util.getQrCode(url, { scale: 2 }); + const location = locations.find((val) => val.id === table.location_id); + data.push({ + name: table.number, + restaurant: restaurant.name, + location: location.name, + barcode: `data:image/png;base64,${barcode.toString('base64')}`, + }); + } + + const pdf = await this.pdf.tableLabel(data); + const dir = `${config.getPublicPath()}/files`; + const filename = `${time().unix()}-tables-label.pdf`; + return writeFile(dir, filename, pdf); + }; + + processing() + .then((path) => { + // send event + Socket.getInstance().event(me.id, { + request_id, + status: PubSubStatus.Success, + type: PubSubEventType.OwnerGetTableLabel, + payload: { + type: PubSubPayloadType.Download, + body: { + mime: 'text/href', + name: `${time().unix()}-tables-label.pdf`, + content: config.getDownloadURI(path), + }, + }, + }); + }) + .catch(async (error) => { + // send event + Socket.getInstance().event(me.id, { + request_id, + status: PubSubStatus.Fail, + type: PubSubEventType.OwnerGetTableLabel, + error: error.message, + }); + + Logger.getInstance().notify(error); + }); + + return response.data({ request_id }); + } } diff --git a/src/core/core.module.ts b/src/core/core.module.ts index edd2821..54b6303 100644 --- a/src/core/core.module.ts +++ b/src/core/core.module.ts @@ -10,9 +10,11 @@ import { DataSource } from 'typeorm'; import { AuthService } from './services/auth.service'; import { AwsService } from './services/aws.service'; import { CustomerService } from './services/customer.service'; +import { PdfService } from './services/pdf.service'; import { RoleService } from './services/role.service'; import { TaskService } from './services/task.service'; import { JwtOwnerStrategy, JwtStrategy } from './services/token.service'; +import { UtilService } from './services/util.service'; @Global() @Module({ @@ -30,6 +32,8 @@ import { JwtOwnerStrategy, JwtStrategy } from './services/token.service'; AuthService, RoleService, CustomerService, + PdfService, + UtilService, JwtOwnerStrategy, { provide: DataSource, @@ -44,6 +48,8 @@ import { JwtOwnerStrategy, JwtStrategy } from './services/token.service'; RoleService, AuthService, CustomerService, + PdfService, + UtilService, JwtStrategy, JwtOwnerStrategy, DataSource, diff --git a/src/core/services/pdf.service.ts b/src/core/services/pdf.service.ts new file mode 100644 index 0000000..c492962 --- /dev/null +++ b/src/core/services/pdf.service.ts @@ -0,0 +1,52 @@ +import { handlebars } from '@lib/handlebars/adapter.library'; +import { config } from '@lib/helpers/config.helper'; +import { Injectable } from '@nestjs/common'; +import HTMLToPDF, { LaunchOptions, PDFOptions } from 'convert-html-to-pdf'; +import * as fs from 'fs'; + +export interface IPdfOption { + browser?: LaunchOptions; + pdf?: PDFOptions; + waitForNetworkIdle?: boolean; +} + +@Injectable() +export class PdfService { + create(template: string, data: any, options?: IPdfOption) { + const html = fs.readFileSync(`${config.getTemplatePath()}/pdf/${template}.hbs`, 'utf8'); + const compiled = handlebars.compile(html)(data); + + const htmlToPdf = new HTMLToPDF(compiled, { + waitForNetworkIdle: options.waitForNetworkIdle || false, + browserOptions: { + args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'], + ...(options.browser || {}), + }, + pdfOptions: { + printBackground: true, + ...(options.pdf || {}), + }, + }); + + return htmlToPdf.convert(); + } + + async tableLabel(data: any) { + return this.create( + 'label-table', + { data }, + { + pdf: { + width: '14.85cm', + height: '21cm', + margin: { + top: 0.5, + bottom: 0.5, + left: 0.5, + right: 0.5, + }, + }, + } + ); + } +} diff --git a/src/core/services/util.service.ts b/src/core/services/util.service.ts new file mode 100644 index 0000000..853132c --- /dev/null +++ b/src/core/services/util.service.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@nestjs/common'; +import * as bwip from 'bwip-js'; +import { ToBufferOptions } from 'bwip-js'; + +export type ICodePayload = Partial; + +@Injectable() +export class UtilService { + getBarCode(text: string, options?: ICodePayload): any { + return bwip.toBuffer({ + text, + bcid: 'code128', + height: 10, + ...(options || {}), + }); + } + + getQrCode(text: string, options: ICodePayload): any { + return bwip.toBuffer({ + text, + bcid: 'qrcode', + scale: 1, + ...(options || {}), + }); + } +} diff --git a/src/database/entities/owner/table.entity.ts b/src/database/entities/owner/table.entity.ts index f8ad372..5c962d2 100644 --- a/src/database/entities/owner/table.entity.ts +++ b/src/database/entities/owner/table.entity.ts @@ -11,7 +11,6 @@ export enum TableStatus { InUse = 'in_use', Reserved = 'reserved', Unvailable = 'unvailable', - Empty = 'empty', } @CoreEntity() @@ -28,7 +27,7 @@ export class Table extends BaseEntity { @JoinColumn() @ManyToOne(() => Restaurant, (restaurant) => restaurant.tables, { onDelete: 'CASCADE' }) - restaurant: Restaurant; + restaurant: Promise; @Exclude() @ForeignColumn() diff --git a/src/database/transformers/table.transformer.ts b/src/database/transformers/table.transformer.ts index 2513ae1..4fa0535 100644 --- a/src/database/transformers/table.transformer.ts +++ b/src/database/transformers/table.transformer.ts @@ -1,10 +1,20 @@ import { Table } from '@db/entities/owner/table.entity'; import { TransformerAbstract } from '@lib/transformer/abstract.transformer'; import { LocationTransformer } from './location.transformer'; +import { RestaurantTransformer } from './restaurant.transformer'; export class TableTransformer extends TransformerAbstract { get availableInclude() { - return ['location']; + return ['restaurant', 'location']; + } + + async includeRestaurant(entity: Table) { + const restaurant = await entity.restaurant; + if (!restaurant) { + return this.null(); + } + + return this.item(restaurant, RestaurantTransformer); } async includeLocation(entity: Table) { diff --git a/src/library/helpers/config.helper.ts b/src/library/helpers/config.helper.ts index 1ab4490..ca6f5e2 100755 --- a/src/library/helpers/config.helper.ts +++ b/src/library/helpers/config.helper.ts @@ -38,6 +38,10 @@ class ConfigService { return path.toString(); } + public getAppURI() { + return `${this.get('APP_URI')}`; + } + public getAssetURI() { return `${this.get('API_URI')}/assets`; } diff --git a/src/library/pubsub/pubsub.lib.ts b/src/library/pubsub/pubsub.lib.ts index 5fe8dec..a1fba9a 100644 --- a/src/library/pubsub/pubsub.lib.ts +++ b/src/library/pubsub/pubsub.lib.ts @@ -9,6 +9,7 @@ export enum PubSubEvent { export enum PubSubEventType { OwnerCreateStock = 'owner_create_stock', + OwnerGetTableLabel = 'owner_get_table_label', } export enum PubSubStatus { diff --git a/templates/error.hbs b/templates/error.hbs new file mode 100644 index 0000000..93a6611 --- /dev/null +++ b/templates/error.hbs @@ -0,0 +1,447 @@ + + + + + + + + + KeepPack Error + + + +

+
+
+ {{ code }} error + {{ message }} +
+ + + +
+ + + +
+ + + +
+
+ + + + \ No newline at end of file diff --git a/templates/iframe.hbs b/templates/iframe.hbs new file mode 100644 index 0000000..8c922d4 --- /dev/null +++ b/templates/iframe.hbs @@ -0,0 +1,26 @@ + + + + + + + + + + {{name}} + + + + + + + + diff --git a/templates/pdf/label-table.hbs b/templates/pdf/label-table.hbs new file mode 100644 index 0000000..b8da7b4 --- /dev/null +++ b/templates/pdf/label-table.hbs @@ -0,0 +1,143 @@ + + + + + + Restaurant QR Code Menu + + + + {{#each data}} +
+
Ordero
+
+ QR Code +
Scan this QR
+
To create order
+
Table {{name}}
+
+ {{restaurant}}
+ Location: {{location}} +
+
+ + + + +
+
+ {{/each}} + + diff --git a/yarn.lock b/yarn.lock index 1392383..743f2ab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1732,6 +1732,13 @@ agent-base@6, agent-base@^6.0.0, agent-base@^6.0.2: dependencies: debug "4" +agent-base@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" + integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== + dependencies: + es6-promisify "^5.0.0" + aggregate-error@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" @@ -1946,6 +1953,11 @@ async-hook-jl@^1.7.6: dependencies: stack-chain "^1.3.7" +async-limiter@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" + integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== + async@^3.2.3: version "3.2.4" resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" @@ -2190,6 +2202,11 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== + buffer-equal-constant-time@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" @@ -2225,6 +2242,11 @@ buffer@^6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" +bwip-js@^3.0.2: + version "3.4.5" + resolved "https://registry.yarnpkg.com/bwip-js/-/bwip-js-3.4.5.tgz#e55a9ce5cd13a17b6f2b1370cda367741764d169" + integrity sha512-qeMBeeHkELC+ZTU3UjUFF0mkIQmhqKzo4B5fYfx98hitsdBMytSuAE9oVz2mE02GQ09u3QmgU/kke0qONumuGQ== + bytes@3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" @@ -2622,6 +2644,16 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== +concat-stream@^1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + concat-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1" @@ -2665,6 +2697,14 @@ content-disposition@^0.5.3: dependencies: safe-buffer "5.2.1" +convert-html-to-pdf@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/convert-html-to-pdf/-/convert-html-to-pdf-1.0.1.tgz#34f8af22cc0fbf1d134921d82c98e5dae16c6b6a" + integrity sha512-xHMmTgrSJwQ54s8pgZhr3nbQSJ59xN9LjE7L++71jViEGCAHy71XTd8C96Ez6czrcNO31Icx+K4pw9i0Df6gnA== + dependencies: + lodash "^4.17.15" + puppeteer "^1.19.0" + convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: version "1.8.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" @@ -2805,7 +2845,7 @@ debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, d dependencies: ms "2.1.2" -debug@^3.2.7: +debug@^3.1.0, debug@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== @@ -3252,6 +3292,18 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +es6-promise@^4.0.3: + version "4.2.8" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" + integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== + +es6-promisify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" + integrity sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ== + dependencies: + es6-promise "^4.0.3" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -3594,6 +3646,16 @@ extract-css@^3.0.0: list-stylesheets "^2.0.0" style-data "^2.0.0" +extract-zip@^1.6.6: + version "1.7.0" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.7.0.tgz#556cc3ae9df7f452c493a0cfb51cc30277940927" + integrity sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA== + dependencies: + concat-stream "^1.6.2" + debug "^2.6.9" + mkdirp "^0.5.4" + yauzl "^2.10.0" + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -3733,6 +3795,13 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== + dependencies: + pend "~1.2.0" + fecha@^4.2.0: version "4.2.3" resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd" @@ -4370,6 +4439,14 @@ https-proxy-agent@5, https-proxy-agent@^5.0.0: agent-base "6" debug "4" +https-proxy-agent@^2.2.1: + version "2.2.4" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" + integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== + dependencies: + agent-base "^4.3.0" + debug "^3.1.0" + human-signals@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" @@ -5767,7 +5844,7 @@ mime@1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mime@2.6.0, mime@^2.4.6: +mime@2.6.0, mime@^2.0.3, mime@^2.4.6: version "2.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== @@ -6153,6 +6230,13 @@ mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== +mkdirp@^0.5.4: + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== + dependencies: + minimist "^1.2.6" + mkdirp@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" @@ -6765,6 +6849,11 @@ peek-readable@^4.1.0: resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-4.1.0.tgz#4ece1111bf5c2ad8867c314c81356847e8a62e72" integrity sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg== +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== + pick-util@^1.1.4: version "1.1.5" resolved "https://registry.yarnpkg.com/pick-util/-/pick-util-1.1.5.tgz#514b11b1a49486d30c805a23125003a360175b6d" @@ -6969,6 +7058,11 @@ process-warning@^2.0.0: resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-2.0.0.tgz#341dbeaac985b90a04ebcd844d50097c7737b2ee" integrity sha512-+MmoAXoUX+VTHAlwns0h+kFUWFs/3FZy+ZuchkgjyOu3oioLAo2LB5aCfKPh2+P9O18i3m43tUEv3YqttSy0Ww== +progress@^2.0.1: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + promise@^7.0.1: version "7.3.1" resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" @@ -7151,6 +7245,20 @@ punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +puppeteer@^1.19.0: + version "1.20.0" + resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-1.20.0.tgz#e3d267786f74e1d87cf2d15acc59177f471bbe38" + integrity sha512-bt48RDBy2eIwZPrkgbcwHtb51mj2nKvHOPMaSH2IsWiv7lOG9k9zhaRzpDZafrk05ajMc3cu+lSQYYOfH2DkVQ== + dependencies: + debug "^4.1.0" + extract-zip "^1.6.6" + https-proxy-agent "^2.2.1" + mime "^2.0.3" + progress "^2.0.1" + proxy-from-env "^1.0.0" + rimraf "^2.6.1" + ws "^6.1.0" + q@2.0.x: version "2.0.3" resolved "https://registry.yarnpkg.com/q/-/q-2.0.3.tgz#75b8db0255a1a5af82f58c3f3aaa1efec7d0d134" @@ -7281,7 +7389,7 @@ readable-stream@2.3.7: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^2.0.6: +readable-stream@^2.0.6, readable-stream@^2.2.2: version "2.3.8" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== @@ -7460,6 +7568,13 @@ rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: dependencies: glob "^7.1.3" +rimraf@^2.6.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + rootpath@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/rootpath/-/rootpath-0.1.2.tgz#5b379a87dca906e9b91d690a599439bef267ea6b" @@ -8958,6 +9073,13 @@ write-file-atomic@^4.0.1: imurmurhash "^0.1.4" signal-exit "^3.0.7" +ws@^6.1.0: + version "6.2.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.2.tgz#dd5cdbd57a9979916097652d78f1cc5faea0c32e" + integrity sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw== + dependencies: + async-limiter "~1.0.0" + ws@~8.2.3: version "8.2.3" resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba" @@ -9084,6 +9206,14 @@ yargs@^17.3.1: y18n "^5.0.5" yargs-parser "^21.0.0" +yauzl@^2.10.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0" + yn@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" From dca8820763229c21cfd30615e9602ffa9d9b016e Mon Sep 17 00:00:00 2001 From: akbarsaputrait Date: Sun, 16 Jun 2024 22:06:06 +0700 Subject: [PATCH 2/7] Improvement on Menus and Order --- Dockerfile | 4 +- package.json | 4 +- src/app.routes.ts | 5 + src/app/customer/customer.module.ts | 6 +- src/app/customer/order/order.controller.ts | 20 +- .../customer/{ => table}/table.controller.ts | 2 +- src/app/customer/table/table.module.ts | 9 + src/app/restaurant/menu.controller.ts | 27 +- src/database/entities/base/basic.ts | 4 +- src/database/entities/core/order.entity.ts | 6 +- .../entities/owner/product-stock.entity.ts | 1 - src/database/interfaces/order.interface.ts | 2 +- .../transformers/product.transformer.ts | 12 +- yarn.lock | 286 ++++++++++++++---- 14 files changed, 300 insertions(+), 88 deletions(-) rename src/app/customer/{ => table}/table.controller.ts (98%) create mode 100644 src/app/customer/table/table.module.ts diff --git a/Dockerfile b/Dockerfile index 543f89b..2abd6a8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:16.13.1-alpine AS BUILD_IMAGE +FROM node:16.14.0-alpine AS BUILD_IMAGE # couchbase sdk requirements RUN apk update && \ @@ -20,7 +20,7 @@ RUN yarn lint # build application RUN yarn build -FROM node:16.13.1-alpine +FROM node:16.14.0-alpine # Python RUN apk update && \ diff --git a/package.json b/package.json index 970a3ba..bd85161 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "libphonenumber-js": "^1.10.12", "lodash": "^4.17.21", "mysql-import": "^5.0.21", - "mysql2": "^2.3.3", + "mysql2": "^3.10.1", "nest-router": "^1.0.9", "nest-winston": "^1.7.0", "node-cache": "^5.1.2", @@ -82,7 +82,7 @@ "simple-encryptor": "^4.0.0", "slug": "^8.2.3", "twilio": "^3.54.1", - "typeorm": "^0.3.7", + "typeorm": "^0.3.20", "ulid": "^2.3.0", "uuid": "^8.3.2", "validatorjs": "^3.22.1", diff --git a/src/app.routes.ts b/src/app.routes.ts index 000b6b9..b788b7b 100644 --- a/src/app.routes.ts +++ b/src/app.routes.ts @@ -2,6 +2,7 @@ import { Routes } from 'nest-router'; import { CustomerAuthModule } from './app/customer/auth/auth.module'; import { CustomerModule } from './app/customer/customer.module'; import { CustomerOrderModule } from './app/customer/order/order.module'; +import { CustomerTableModule } from './app/customer/table/table.module'; import { OwnerAuthModule } from './app/owner/auth/auth.module'; import { OwnerModule } from './app/owner/owner.module'; import { OwnerProfileModule } from './app/owner/profile/profile.module'; @@ -76,6 +77,10 @@ export const routes: Routes = [ path: '/orders', module: CustomerOrderModule, }, + { + path: '/tables', + module: CustomerTableModule, + }, ], }, ]; diff --git a/src/app/customer/customer.module.ts b/src/app/customer/customer.module.ts index e3ea7f0..ed788ce 100644 --- a/src/app/customer/customer.module.ts +++ b/src/app/customer/customer.module.ts @@ -2,11 +2,11 @@ import { Module } from '@nestjs/common'; import { CustomerAuthModule } from './auth/auth.module'; import { CustomerController } from './customer.controller'; import { CustomerOrderModule } from './order/order.module'; -import { TableController } from './table.controller'; +import { CustomerTableModule } from './table/table.module'; @Module({ - imports: [CustomerAuthModule, CustomerOrderModule], - controllers: [CustomerController, TableController], + imports: [CustomerAuthModule, CustomerOrderModule, CustomerTableModule], + controllers: [CustomerController], providers: [], }) export class CustomerModule {} diff --git a/src/app/customer/order/order.controller.ts b/src/app/customer/order/order.controller.ts index 8d83681..c668d3c 100644 --- a/src/app/customer/order/order.controller.ts +++ b/src/app/customer/order/order.controller.ts @@ -17,6 +17,7 @@ import { Validator } from '@lib/helpers/validator.helper'; import AppDataSource from '@lib/typeorm/datasource.typeorm'; import { BadRequestException, Body, Controller, Get, Post, Res, UseGuards } from '@nestjs/common'; import { get } from 'lodash'; +import { IsNull } from 'typeorm'; @Controller() export class OrderController { @@ -40,27 +41,33 @@ export class OrderController { throw new BadRequestException('Sorry, this table is not available right now'); } - await manager.getRepository(Table).update(order.table_id, { status: TableStatus.InUse }); + table.status = TableStatus.InUse; + await manager.getRepository(Table).save(table); order.restaurant_id = newOrder.restaurant_id; - order.location_id = newOrder.location_id; + order.location_id = table.location_id; order.table_id = newOrder.table_id; order.status = newOrder.status; order.note = newOrder.note; order.customer_id = customer?.id || null; + order.discount = null; order.number = ''; // Will be update it later await manager.getRepository(Order).save(order); + const orderedProducts: OrderProduct[] = []; for (const product of newOrder.products) { const variant = await manager.getRepository(ProductVariant).findOneOrFail({ where: { - id: product.id, + product_id: product.id, + variant_id: get(product, 'variant_id', null) || IsNull(), restaurant_id: newOrder.restaurant_id, status: VariantStatus.Available, }, }); + console.log(variant); + const stock = await manager.getRepository(ProductStock).findOneOrFail({ where: { restaurant_id: newOrder.restaurant_id, @@ -96,14 +103,17 @@ export class OrderController { orderProduct.order_id = order.id; orderProduct.product_variant_id = variant.id; orderProduct.qty = product.qty; - orderProduct.price = variant.price; + orderProduct.price = variant.price * product.qty; orderProduct.status = OrderProductStatus.WaitingApproval; await manager.getRepository(OrderProduct).save(orderProduct); + + orderedProducts.push(orderProduct); } // Updare Order Number await manager.getRepository(Order).update(order.id, { number: sequenceNumber(order.uid), + gross_total: orderedProducts.reduce((price, a) => price + a.price, 0), }); }); @@ -116,7 +126,6 @@ export class OrderController { async createOrder(@Res() response, @Body() body) { const rules = { restaurant_id: 'required|uid', - location_id: 'required|uid', table_id: 'required|uid', customer_name: 'required|safe_text', customer_phone: 'phone', @@ -130,7 +139,6 @@ export class OrderController { const newOrder: IOrderDetail = { restaurant_id: get(body, 'restaurant_id', null), - location_id: get(body, 'location_id', null), table_id: get(body, 'table_id', null), status: OrderStatus.WaitingApproval, customer_name: get(body, 'customer_name', 'Guest'), diff --git a/src/app/customer/table.controller.ts b/src/app/customer/table/table.controller.ts similarity index 98% rename from src/app/customer/table.controller.ts rename to src/app/customer/table/table.controller.ts index 747db6b..40a1117 100644 --- a/src/app/customer/table.controller.ts +++ b/src/app/customer/table/table.controller.ts @@ -6,7 +6,7 @@ import { GenericException } from '@lib/exceptions/generic.exception'; import { Controller, Get, NotFoundException, Param, Res } from '@nestjs/common'; import { capitalize, get } from 'lodash'; -@Controller('tables') +@Controller() export class TableController { @Get('/:id') async checkTable(@Param() params, @Res() response) { diff --git a/src/app/customer/table/table.module.ts b/src/app/customer/table/table.module.ts new file mode 100644 index 0000000..dd4e77c --- /dev/null +++ b/src/app/customer/table/table.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { TableController } from './table.controller'; + +@Module({ + imports: [], + controllers: [TableController], + providers: [], +}) +export class CustomerTableModule {} diff --git a/src/app/restaurant/menu.controller.ts b/src/app/restaurant/menu.controller.ts index 02b257a..18a40d2 100644 --- a/src/app/restaurant/menu.controller.ts +++ b/src/app/restaurant/menu.controller.ts @@ -1,5 +1,8 @@ +import { ProductStock } from '@db/entities/owner/product-stock.entity'; +import { ProductVariant } from '@db/entities/owner/product-variant.entity'; import { Product } from '@db/entities/owner/product.entity'; import { Restaurant } from '@db/entities/owner/restaurant.entity'; +import { Table } from '@db/entities/owner/table.entity'; import { ProductTransformer } from '@db/transformers/product.transformer'; import AppDataSource from '@lib/typeorm/datasource.typeorm'; import { Controller, Get, Param, Res } from '@nestjs/common'; @@ -9,12 +12,24 @@ export class MenuController { @Get() async getMenus(@Res() response, @Param() params) { const restaurant = await Restaurant.findOneByOrFail({ id: params.restaurant_id }); - const menus = await AppDataSource.createQueryBuilder(Product, 't1') - .where({ restaurant_id: restaurant.id }) - .search() - .sort() - .getPaged(); - await response.paginate(menus, ProductTransformer); + + let table: Table = null; + + const menus = AppDataSource.createQueryBuilder(Product, 't1') + .leftJoin(ProductVariant, 't2', 't2.product_id = t1.id') + .leftJoin(ProductStock, 't3', 't3.variant_id = t2.id') + .where('t1.restaurant_id = :restId', { restId: restaurant.id }); + + if (params.table_id) { + table = await Table.findOneBy({ id: params.table_id }); + + if (table) { + menus.andWhere('t3.location_id = :locId', { locId: table.location_id }); + } + } + + const results = await menus.search().sort().getPaged(); + await response.paginate(results, ProductTransformer); } @Get(':menu_id') diff --git a/src/database/entities/base/basic.ts b/src/database/entities/base/basic.ts index 91171ad..50f072b 100644 --- a/src/database/entities/base/basic.ts +++ b/src/database/entities/base/basic.ts @@ -9,7 +9,7 @@ import { DeepPartial, FindManyOptions, FindOneOptions, - ObjectID, + ObjectId, RemoveOptions, SaveOptions, } from 'typeorm'; @@ -51,7 +51,7 @@ export class BasicEntity extends Base { this: { new (): T; } & typeof Base, - id?: string | number | Date | ObjectID, + id?: string | number | Date | ObjectId, restaurant?: any ): Promise { const obj = await this.findOne({ where: { id, restaurant_id: restaurant.id } } as any); diff --git a/src/database/entities/core/order.entity.ts b/src/database/entities/core/order.entity.ts index 35f7586..ce21c39 100644 --- a/src/database/entities/core/order.entity.ts +++ b/src/database/entities/core/order.entity.ts @@ -35,13 +35,13 @@ export class Order extends BaseEntity { number: string; @PriceColumn() - gross_total: string; + gross_total: number; @PriceColumn() - discount: string; + discount: number; @PriceColumn() - net_total: string; + net_total: number; @DateTimeColumn() billed_at: Date; diff --git a/src/database/entities/owner/product-stock.entity.ts b/src/database/entities/owner/product-stock.entity.ts index 6491018..d918dc5 100644 --- a/src/database/entities/owner/product-stock.entity.ts +++ b/src/database/entities/owner/product-stock.entity.ts @@ -30,7 +30,6 @@ export class ProductStock extends BaseEntity { @Column({ length: 100, nullable: true }) actor: string; - @Exclude() @ForeignColumn() variant_id: string; diff --git a/src/database/interfaces/order.interface.ts b/src/database/interfaces/order.interface.ts index 9aea965..6c1b6e6 100644 --- a/src/database/interfaces/order.interface.ts +++ b/src/database/interfaces/order.interface.ts @@ -13,7 +13,7 @@ export interface IOrderItems { export interface IOrderDetail { restaurant_id: string; table_id: string; - location_id: string; + location_id?: string; reference?: string; customer_name: string; customer_phone?: string; diff --git a/src/database/transformers/product.transformer.ts b/src/database/transformers/product.transformer.ts index 8af713d..9a1583e 100644 --- a/src/database/transformers/product.transformer.ts +++ b/src/database/transformers/product.transformer.ts @@ -6,10 +6,11 @@ import { In } from 'typeorm'; import { CategoryTransformer } from './category.transformer'; import { MediaTransformer } from './media.transformer'; import { ProductVariantTransformer } from './product-variant.transformer'; +import { RawTransformer } from './raw.transformer'; export class ProductTransformer extends TransformerAbstract { get availableInclude() { - return ['variants', 'categories', 'images']; + return ['variants', 'categories', 'images', 'stocks']; } async includeVariants(entity: Product) { @@ -40,6 +41,15 @@ export class ProductTransformer extends TransformerAbstract { return this.collection(media, MediaTransformer); } + async includeStocks(entity: Product) { + const stocks = await entity.stocks; + if (!stocks) { + return this.null(); + } + + return this.collection(stocks, RawTransformer); + } + transform(entity: Product) { return entity.toJSON(); } diff --git a/yarn.lock b/yarn.lock index 743f2ab..df4ca96 100644 --- a/yarn.lock +++ b/yarn.lock @@ -513,6 +513,18 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz#d9fae00a2d5cb40f92cfe64b47ad749fbc38f917" integrity sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw== +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -960,6 +972,11 @@ consola "^2.15.0" node-fetch "^2.6.1" +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + "@selderee/plugin-htmlparser2@^0.6.0": version "0.6.0" resolved "https://registry.yarnpkg.com/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.6.0.tgz#27e994afd1c2cb647ceb5406a185a5574188069d" @@ -1038,10 +1055,10 @@ resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz#821f8442f4175d8f0467b9daf26e3a18e2d02af2" integrity sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA== -"@sqltools/formatter@^1.2.2": - version "1.2.3" - resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.3.tgz#1185726610acc37317ddab11c3c7f9066966bd20" - integrity sha512-O3uyB/JbkAEMZaP3YqyHH7TMnex7tWyCbCI4EfJdOCoN6HIhqdJBWTM6aCCiWQ/5f5wxjgU735QAIpJbjDvmzg== +"@sqltools/formatter@^1.2.5": + version "1.2.5" + resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.5.tgz#3abc203c79b8c3e90fd6c156a0c62d5403520e12" + integrity sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw== "@streamparser/json@^0.0.6": version "0.0.6" @@ -1835,6 +1852,11 @@ ansi-styles@^6.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.1.0.tgz#87313c102b8118abd57371afab34618bf7350ed3" integrity sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ== +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + any-promise@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" @@ -1853,6 +1875,11 @@ app-root-path@^3.0.0: resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-3.0.0.tgz#210b6f43873227e18a4b810a032283311555d5ad" integrity sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw== +app-root-path@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-3.1.0.tgz#5971a2fc12ba170369a7a1ef018c71e6e47c2e86" + integrity sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA== + append-field@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56" @@ -2515,6 +2542,15 @@ cliui@^7.0.2: strip-ansi "^6.0.0" wrap-ansi "^7.0.0" +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + clone@2.x: version "2.1.2" resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" @@ -2816,16 +2852,16 @@ data-uri-to-buffer@3: resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636" integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og== -date-fns@^2.28.0: - version "2.29.1" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.1.tgz#9667c2615525e552b5135a3116b95b1961456e60" - integrity sha512-dlLD5rKaKxpFdnjrs+5azHDFOPEu4ANy/LTh04A1DTzMM7qoajmKCBc8pkKRFT41CNzw+4gQh79X5C+Jq27HAw== - dayjs@^1.10.6, dayjs@^1.11.4: version "1.11.4" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.4.tgz#3b3c10ca378140d8917e06ebc13a4922af4f433e" integrity sha512-Zj/lPM5hOvQ1Bf7uAvewDaUcsJoI6JmNqmHhHl3nyumwe0XHwt8sWdOVAPACJzCebL8gQCi+K49w7iKWnGwX9g== +dayjs@^1.11.9: + version "1.11.11" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.11.tgz#dfe0e9d54c5f8b68ccf8ca5f72ac603e7e5ed59e" + integrity sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg== + dayjs@^1.8.29: version "1.11.10" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0" @@ -2838,7 +2874,7 @@ debug@2.6.9, debug@^2.6.9: dependencies: ms "2.0.0" -debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: +debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -2940,7 +2976,7 @@ delegates@^1.0.0: resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== -denque@^2.0.1: +denque@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== @@ -3081,11 +3117,16 @@ domutils@^3.0.1: domelementtype "^2.3.0" domhandler "^5.0.1" -dotenv@^16.0.0, dotenv@^16.0.1: +dotenv@^16.0.1: version "16.0.1" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.1.tgz#8f8f9d94876c35dac989876a5d3a82a267fdce1d" integrity sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ== +dotenv@^16.0.3: + version "16.4.5" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" + integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== + duplexify@^4.1.1, duplexify@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.2.tgz#18b4f8d28289132fa0b9573c898d9f903f81c7b0" @@ -3925,6 +3966,14 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" +foreground-child@^3.1.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.2.1.tgz#767004ccf3a5b30df39bed90718bab43fe0a59f7" + integrity sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^4.0.1" + fork-ts-checker-webpack-plugin@7.2.11: version "7.2.11" resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-7.2.11.tgz#aff3febbc11544ba3ad0ae4d5aa4055bd15cd26d" @@ -4170,7 +4219,18 @@ glob@8.0.3, glob@^8.0.1, glob@^8.0.3: minimatch "^5.0.1" once "^1.3.0" -glob@^7.0.0, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.2.0: +glob@^10.3.10: + version "10.4.1" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.1.tgz#0cfb01ab6a6b438177bfe6a58e2576f6efe909c2" + integrity sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + path-scurry "^1.11.1" + +glob@^7.0.0, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -4904,6 +4964,15 @@ iterare@1.2.1: resolved "https://registry.yarnpkg.com/iterare/-/iterare-1.2.1.tgz#139c400ff7363690e33abffa33cbba8920f00042" integrity sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q== +jackspeak@^3.1.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.0.tgz#a75763ff36ad778ede6a156d8ee8b124de445b4a" + integrity sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + jake@^10.8.5: version "10.8.5" resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46" @@ -5683,17 +5752,22 @@ logform@^2.3.2, logform@^2.4.0: safe-stable-stringify "^2.3.1" triple-beam "^1.3.0" -long@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" - integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== +long@^5.2.1: + version "5.2.3" + resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" + integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== lower-case@^1.1.1: version "1.1.4" resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" integrity sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA== -lru-cache@^4.1.3, lru-cache@^4.1.5: +lru-cache@^10.2.0: + version "10.2.2" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.2.tgz#48206bc114c1252940c41b25b41af5b545aca878" + integrity sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ== + +lru-cache@^4.1.5: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== @@ -5715,6 +5789,16 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +lru-cache@^7.14.1: + version "7.18.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" + integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== + +lru-cache@^8.0.0: + version "8.0.5" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-8.0.5.tgz#983fe337f3e176667f8e567cfcce7cb064ea214e" + integrity sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA== + lru_map@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd" @@ -5883,6 +5967,13 @@ minimatch@^5.0.1: dependencies: brace-expansion "^2.0.1" +minimatch@^9.0.4: + version "9.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.4.tgz#8e49c731d1749cbec05050ee5145147b32496a51" + integrity sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw== + dependencies: + brace-expansion "^2.0.1" + minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" @@ -5893,6 +5984,11 @@ minimist@^1.2.3: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" + integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== + mjml-accordion@4.13.0: version "4.13.0" resolved "https://registry.yarnpkg.com/mjml-accordion/-/mjml-accordion-4.13.0.tgz#76ec0b5efe372e129077beaa0b2147c99d76eb7a" @@ -6242,6 +6338,11 @@ mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +mkdirp@^2.1.3: + version "2.1.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-2.1.6.tgz#964fbcb12b2d8c5d6fbc62a963ac95a273e2cc19" + integrity sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A== + moo@^0.5.0, moo@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/moo/-/moo-0.5.1.tgz#7aae7f384b9b09f620b6abf6f74ebbcd1b65dbc4" @@ -6274,17 +6375,17 @@ mysql-import@^5.0.21: dependencies: mysql "^2.18.1" -mysql2@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-2.3.3.tgz#944f3deca4b16629052ff8614fbf89d5552545a0" - integrity sha512-wxJUev6LgMSgACDkb/InIFxDprRa6T95+VEoR+xPvtngtccNH2dGjEB/fVZ8yg1gWv1510c9CvXuJHi5zUm0ZA== +mysql2@^3.10.1: + version "3.10.1" + resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.10.1.tgz#c39b8faf24ef4fd56330ef269122471a22d19198" + integrity sha512-6zo1T3GILsXMCex3YEu7hCz2OXLUarxFsxvFcUHWMpkPtmZLeTTWgRdc1gWyNJiYt6AxITmIf9bZDRy/jAfWew== dependencies: - denque "^2.0.1" + denque "^2.1.0" generate-function "^2.3.1" iconv-lite "^0.6.3" - long "^4.0.0" - lru-cache "^6.0.0" - named-placeholders "^1.1.2" + long "^5.2.1" + lru-cache "^8.0.0" + named-placeholders "^1.1.3" seq-queue "^0.0.5" sqlstring "^2.3.2" @@ -6307,12 +6408,12 @@ mz@^2.4.0: object-assign "^4.0.1" thenify-all "^1.0.0" -named-placeholders@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/named-placeholders/-/named-placeholders-1.1.2.tgz#ceb1fbff50b6b33492b5cf214ccf5e39cef3d0e8" - integrity sha512-wiFWqxoLL3PGVReSZpjLVxyJ1bRqe+KKJVbr4hGs1KWfTZTQyezHFBbuKj9hsizHyGV2ne7EMjHdxEGAybD5SA== +named-placeholders@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/named-placeholders/-/named-placeholders-1.1.3.tgz#df595799a36654da55dda6152ba7a137ad1d9351" + integrity sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w== dependencies: - lru-cache "^4.1.3" + lru-cache "^7.14.1" napi-build-utils@^1.0.1: version "1.0.2" @@ -6824,6 +6925,14 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +path-scurry@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-to-regexp@3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-3.2.0.tgz#fa7877ecbc495c601907562222453c43cc204a5f" @@ -7454,6 +7563,11 @@ reflect-metadata@^0.1.13: resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== +reflect-metadata@^0.2.1: + version "0.2.2" + resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.2.2.tgz#400c845b6cba87a21f2c65c4aeb158f4fa4d9c5b" + integrity sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q== + regenerator-runtime@^0.13.4: version "0.13.9" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" @@ -7847,6 +7961,11 @@ signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + simple-concat@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" @@ -8106,6 +8225,15 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -8124,7 +8252,7 @@ string-width@^1.0.1: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^5.0.0: +string-width@^5.0.0, string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== @@ -8170,6 +8298,13 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" @@ -8592,7 +8727,7 @@ tsconfig-paths@4.0.0: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@2.4.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.1: +tslib@2.4.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.2.0: version "2.4.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== @@ -8607,6 +8742,11 @@ tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslib@^2.5.0: + version "2.6.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" + integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" @@ -8680,28 +8820,26 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== -typeorm@^0.3.7: - version "0.3.7" - resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.3.7.tgz#5776ed5058f0acb75d64723b39ff458d21de64c1" - integrity sha512-MsPJeP6Zuwfe64c++l80+VRqpGEGxf0CkztIEnehQ+CMmQPSHjOnFbFxwBuZ2jiLqZTjLk2ZqQdVF0RmvxNF3Q== +typeorm@^0.3.20: + version "0.3.20" + resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.3.20.tgz#4b61d737c6fed4e9f63006f88d58a5e54816b7ab" + integrity sha512-sJ0T08dV5eoZroaq9uPKBoNcGslHBR4E4y+EBHs//SiGbblGe7IeduP/IH4ddCcj0qp3PHwDwGnuvqEAnKlq/Q== dependencies: - "@sqltools/formatter" "^1.2.2" - app-root-path "^3.0.0" + "@sqltools/formatter" "^1.2.5" + app-root-path "^3.1.0" buffer "^6.0.3" - chalk "^4.1.0" + chalk "^4.1.2" cli-highlight "^2.1.11" - date-fns "^2.28.0" - debug "^4.3.3" - dotenv "^16.0.0" - glob "^7.2.0" - js-yaml "^4.1.0" - mkdirp "^1.0.4" - reflect-metadata "^0.1.13" + dayjs "^1.11.9" + debug "^4.3.4" + dotenv "^16.0.3" + glob "^10.3.10" + mkdirp "^2.1.3" + reflect-metadata "^0.2.1" sha.js "^2.4.11" - tslib "^2.3.1" - uuid "^8.3.2" - xml2js "^0.4.23" - yargs "^17.3.1" + tslib "^2.5.0" + uuid "^9.0.0" + yargs "^17.6.2" typescript@4.7.4, typescript@^4.3.5: version "4.7.4" @@ -8820,6 +8958,11 @@ uuid@8.3.2, uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +uuid@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" @@ -9042,6 +9185,15 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" @@ -9060,6 +9212,15 @@ wrap-ansi@^7.0.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -9093,14 +9254,6 @@ xml2js@0.6.2: sax ">=0.6.0" xmlbuilder "~11.0.0" -xml2js@^0.4.23: - version "0.4.23" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" - integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug== - dependencies: - sax ">=0.6.0" - xmlbuilder "~11.0.0" - xmlbuilder@^13.0.2: version "13.0.2" resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-13.0.2.tgz#02ae33614b6a047d1c32b5389c1fdacb2bce47a7" @@ -9175,7 +9328,7 @@ yargs-parser@^20.2.2: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== -yargs-parser@^21.0.0, yargs-parser@^21.0.1: +yargs-parser@^21.0.0, yargs-parser@^21.0.1, yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== @@ -9206,6 +9359,19 @@ yargs@^17.3.1: y18n "^5.0.5" yargs-parser "^21.0.0" +yargs@^17.6.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + yauzl@^2.10.0: version "2.10.0" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" From 9cbd897cf976543c0270a3adbb55db57f5f75208 Mon Sep 17 00:00:00 2001 From: Akbar Anung Yudha Saputra Date: Wed, 26 Jun 2024 01:07:18 +0700 Subject: [PATCH 3/7] Create docker-build-push.yml --- .github/workflows/docker-build-push.yml | 40 +++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/docker-build-push.yml diff --git a/.github/workflows/docker-build-push.yml b/.github/workflows/docker-build-push.yml new file mode 100644 index 0000000..b9fed8b --- /dev/null +++ b/.github/workflows/docker-build-push.yml @@ -0,0 +1,40 @@ +name: Build and Push to Docker Hub + +on: + push: + branches: + - develop + - master + +jobs: + build-and-push: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Set Docker tag + id: set-tag + run: | + if [[ ${{ github.ref }} == 'refs/heads/develop' ]]; then + echo "DOCKER_TAG=develop" >> $GITHUB_OUTPUT + elif [[ ${{ github.ref }} == 'refs/heads/master' ]]; then + echo "DOCKER_TAG=latest" >> $GITHUB_OUTPUT + fi + + - name: Build and push + uses: docker/build-push-action@v4 + with: + context: . + push: true + tags: akbarsaputrait/ordero-api:${{ steps.set-tag.outputs.DOCKER_TAG }} From aab44970548c125bd7cfba11f9e41b0ac2b0b1ef Mon Sep 17 00:00:00 2001 From: Akbar Anung Yudha Saputra Date: Wed, 26 Jun 2024 01:18:18 +0700 Subject: [PATCH 4/7] Update docker-build-push.yml --- .github/workflows/docker-build-push.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/docker-build-push.yml b/.github/workflows/docker-build-push.yml index b9fed8b..f438821 100644 --- a/.github/workflows/docker-build-push.yml +++ b/.github/workflows/docker-build-push.yml @@ -9,6 +9,7 @@ on: jobs: build-and-push: runs-on: ubuntu-latest + environment: docker steps: - name: Checkout code From 6706610da2e4952d04c7437b397fb81a8b073eac Mon Sep 17 00:00:00 2001 From: Akbar Anung Yudha Saputra Date: Sat, 29 Jun 2024 03:05:18 +0700 Subject: [PATCH 5/7] [FIX 40] Owner - Manage Order (#41) --- .github/workflows/docker-build-push.yml | 6 +- .vscode/settings.json | 10 +- Dockerfile | 10 +- src/app.routes.ts | 10 + src/app/customer/order/detail.controller.ts | 2 +- src/app/customer/order/order.controller.ts | 266 ++++++++++-------- src/app/customer/table/table.controller.ts | 8 +- .../notification/notification.controller.ts | 49 ++++ .../notification/notification.module.ts | 9 + .../restaurant/order/detail.controller.ts | 145 ++++++++++ .../restaurant/order/order.controller.ts | 35 +++ .../owner/restaurant/order/order.module.ts | 10 + src/app/owner/restaurant/restaurant.module.ts | 4 + src/core/services/role.service.ts | 4 + .../entities/core/notification.entity.ts | 49 ++++ .../entities/core/order-product.entity.ts | 1 + src/database/entities/core/order.entity.ts | 21 +- .../entities/owner/product-stock.entity.ts | 1 - .../1718551497693-order-number-index.ts | 13 + .../migrations/1719250498189-notification.ts | 27 ++ .../1719251773021-order_customer.ts | 15 + .../1719334195161-notification_read.ts | 13 + src/database/seeds/dump/initial.sql | 74 ++++- src/database/subscribers/order.controller.ts | 0 .../transformers/notification.transformer.ts | 8 + .../transformers/order-product.transformer.ts | 21 ++ src/database/transformers/order.tranformer.ts | 29 -- .../transformers/order.transformer.ts | 71 +++++ .../transformers/table.transformer.ts | 18 +- src/library/pubsub/pubsub.lib.ts | 4 + 30 files changed, 752 insertions(+), 181 deletions(-) create mode 100644 src/app/owner/restaurant/notification/notification.controller.ts create mode 100644 src/app/owner/restaurant/notification/notification.module.ts create mode 100644 src/app/owner/restaurant/order/detail.controller.ts create mode 100644 src/app/owner/restaurant/order/order.controller.ts create mode 100644 src/app/owner/restaurant/order/order.module.ts create mode 100644 src/database/entities/core/notification.entity.ts create mode 100644 src/database/migrations/1718551497693-order-number-index.ts create mode 100644 src/database/migrations/1719250498189-notification.ts create mode 100644 src/database/migrations/1719251773021-order_customer.ts create mode 100644 src/database/migrations/1719334195161-notification_read.ts delete mode 100644 src/database/subscribers/order.controller.ts create mode 100644 src/database/transformers/notification.transformer.ts create mode 100644 src/database/transformers/order-product.transformer.ts delete mode 100644 src/database/transformers/order.tranformer.ts create mode 100644 src/database/transformers/order.transformer.ts diff --git a/.github/workflows/docker-build-push.yml b/.github/workflows/docker-build-push.yml index f438821..a112418 100644 --- a/.github/workflows/docker-build-push.yml +++ b/.github/workflows/docker-build-push.yml @@ -27,10 +27,10 @@ jobs: - name: Set Docker tag id: set-tag run: | - if [[ ${{ github.ref }} == 'refs/heads/develop' ]]; then - echo "DOCKER_TAG=develop" >> $GITHUB_OUTPUT - elif [[ ${{ github.ref }} == 'refs/heads/master' ]]; then + if [[ ${{ github.ref }} == 'refs/heads/master' ]]; then echo "DOCKER_TAG=latest" >> $GITHUB_OUTPUT + else + echo "DOCKER_TAG=develop" >> $GITHUB_OUTPUT fi - name: Build and push diff --git a/.vscode/settings.json b/.vscode/settings.json index b715707..09c4570 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,8 @@ { - "[yaml]": { - "editor.defaultFormatter": "redhat.vscode-yaml", - "editor.defaultFoldingRangeProvider": "redhat.vscode-yaml" - } + "editor.codeActionsOnSave": { + "source.fixAll": "always", + "source.organizeImports": "always" + }, + "editor.tabSize": 2, + "editor.indentSize": "tabSize" } \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 2abd6a8..76a2621 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:16.14.0-alpine AS BUILD_IMAGE +FROM node:16.14.0-alpine AS build_image # couchbase sdk requirements RUN apk update && \ @@ -34,7 +34,7 @@ RUN apk update && \ # Timezone RUN apk add --no-cache tzdata -ENV TZ UTC +ENV TZ=UTC # Installs latest Chromium (77) package. RUN apk add --no-cache \ @@ -47,13 +47,13 @@ RUN apk add --no-cache \ ttf-freefont # Tell Puppeteer to skip installing Chrome. We'll be using the installed package. -ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true -ENV PUPPETEER_EXECUTABLE_PATH /usr/bin/chromium-browser +ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true +ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser WORKDIR /usr/src/app # copy from build image -COPY --from=BUILD_IMAGE /usr/src/app ./ +COPY --from=build_image /usr/src/app ./ EXPOSE 3000 diff --git a/src/app.routes.ts b/src/app.routes.ts index b788b7b..960f711 100644 --- a/src/app.routes.ts +++ b/src/app.routes.ts @@ -8,6 +8,8 @@ import { OwnerModule } from './app/owner/owner.module'; import { OwnerProfileModule } from './app/owner/profile/profile.module'; import { OwnerCategoryModule } from './app/owner/restaurant/category/category.module'; import { OwnerLocationModule } from './app/owner/restaurant/location/location.module'; +import { OwnerNotificationModule } from './app/owner/restaurant/notification/notification.module'; +import { OwnerOrderModule } from './app/owner/restaurant/order/order.module'; import { OwnerProductModule } from './app/owner/restaurant/product/product.module'; import { OwnerRestaurantModule } from './app/owner/restaurant/restaurant.module'; import { OwnerStaffModule } from './app/owner/restaurant/staff/staff.module'; @@ -57,6 +59,14 @@ export const routes: Routes = [ path: '/:restaurant_id/stocks', module: OwnerStockModule, }, + { + path: '/:restaurant_id/orders', + module: OwnerOrderModule, + }, + { + path: '/:restaurant_id/notifications', + module: OwnerNotificationModule, + }, ], }, ], diff --git a/src/app/customer/order/detail.controller.ts b/src/app/customer/order/detail.controller.ts index 673b7d4..62c3586 100644 --- a/src/app/customer/order/detail.controller.ts +++ b/src/app/customer/order/detail.controller.ts @@ -3,7 +3,7 @@ import { AuthGuard } from '@core/guards/auth.guard'; import { Customer } from '@db/entities/core/customer.entity'; import { Order, OrderStatus } from '@db/entities/core/order.entity'; import { Table, TableStatus } from '@db/entities/owner/table.entity'; -import { OrderTransformer } from '@db/transformers/order.tranformer'; +import { OrderTransformer } from '@db/transformers/order.transformer'; import { ValidationException } from '@lib/exceptions/validation.exception'; import { Validator } from '@lib/helpers/validator.helper'; import AppDataSource from '@lib/typeorm/datasource.typeorm'; diff --git a/src/app/customer/order/order.controller.ts b/src/app/customer/order/order.controller.ts index c668d3c..1a6e2ef 100644 --- a/src/app/customer/order/order.controller.ts +++ b/src/app/customer/order/order.controller.ts @@ -2,6 +2,7 @@ import { Me } from '@core/decorators/user.decorator'; import { AuthGuard } from '@core/guards/auth.guard'; import { CustomerService } from '@core/services/customer.service'; import { Customer, CustomerStatus } from '@db/entities/core/customer.entity'; +import { Notification, NotificationType } from '@db/entities/core/notification.entity'; import { OrderProduct, OrderProductStatus } from '@db/entities/core/order-product.entity'; import { Order, OrderStatus } from '@db/entities/core/order.entity'; import { ProductStock } from '@db/entities/owner/product-stock.entity'; @@ -10,11 +11,13 @@ import { Product } from '@db/entities/owner/product.entity'; import { Table, TableStatus } from '@db/entities/owner/table.entity'; import { Variant, VariantStatus } from '@db/entities/owner/variant.entity'; import { IOrderDetail } from '@db/interfaces/order.interface'; -import { OrderTransformer } from '@db/transformers/order.tranformer'; +import { OrderTransformer } from '@db/transformers/order.transformer'; import { ValidationException } from '@lib/exceptions/validation.exception'; import { sequenceNumber } from '@lib/helpers/utils.helper'; import { Validator } from '@lib/helpers/validator.helper'; +import Socket from '@lib/pubsub/pubsub.lib'; import AppDataSource from '@lib/typeorm/datasource.typeorm'; +import { uuid } from '@lib/uid/uuid.library'; import { BadRequestException, Body, Controller, Get, Post, Res, UseGuards } from '@nestjs/common'; import { get } from 'lodash'; import { IsNull } from 'typeorm'; @@ -24,102 +27,106 @@ export class OrderController { constructor(private custService: CustomerService) {} static async createOrder(newOrder: IOrderDetail, customer: Customer): Promise { - const order = new Order(); - await AppDataSource.transaction(async (manager) => { - const orderProducts = newOrder.products || []; - if (!orderProducts.length) { - throw new BadRequestException(`Order has no product items`); - } - - if (orderProducts.some((row) => row.qty < 1)) { - throw new BadRequestException(`Order has invalid product item qty of 0`); - } - - const table = await manager.getRepository(Table).findOneBy({ id: order.table_id }); - - if (table.status !== TableStatus.Available) { - throw new BadRequestException('Sorry, this table is not available right now'); - } - - table.status = TableStatus.InUse; - await manager.getRepository(Table).save(table); - - order.restaurant_id = newOrder.restaurant_id; - order.location_id = table.location_id; - order.table_id = newOrder.table_id; - order.status = newOrder.status; - order.note = newOrder.note; - order.customer_id = customer?.id || null; - order.discount = null; - order.number = ''; // Will be update it later - - await manager.getRepository(Order).save(order); - - const orderedProducts: OrderProduct[] = []; - for (const product of newOrder.products) { - const variant = await manager.getRepository(ProductVariant).findOneOrFail({ - where: { - product_id: product.id, - variant_id: get(product, 'variant_id', null) || IsNull(), - restaurant_id: newOrder.restaurant_id, - status: VariantStatus.Available, - }, - }); + try { + const order = new Order(); + await AppDataSource.transaction(async (manager) => { + const table = await manager.getRepository(Table).findOneBy({ id: order.table_id, status: TableStatus.Available }); - console.log(variant); + if (!table) { + throw new BadRequestException('Sorry, this table is not available right now'); + } - const stock = await manager.getRepository(ProductStock).findOneOrFail({ - where: { - restaurant_id: newOrder.restaurant_id, - location_id: newOrder.location_id, - variant_id: variant.id, - }, - }); + const orderProducts = newOrder.products || []; + if (!orderProducts.length) { + throw new BadRequestException(`Order has no product items`); + } - if (product.qty < stock.available) { - const product = await manager.getRepository(Product).findOneByOrFail({ id: variant.product_id }); + if (orderProducts.some((row) => row.qty < 1)) { + throw new BadRequestException(`Order has invalid product item qty of 0`); + } - let productName = product.name; + order.restaurant_id = newOrder.restaurant_id; + order.location_id = table.location_id; + order.table_id = newOrder.table_id; + order.status = newOrder.status; + order.note = newOrder.note; + order.customer_id = customer?.id || null; + order.discount = null; + order.customer_name = newOrder.customer_name; + order.customer_phone = newOrder.customer_phone; + order.number = ''; // Will be update it later + + await manager.getRepository(Order).save(order); + + const orderedProducts: OrderProduct[] = []; + for (const product of newOrder.products) { + const variant = await manager.getRepository(ProductVariant).findOneOrFail({ + where: { + product_id: product.id, + variant_id: get(product, 'variant_id', null) || IsNull(), + restaurant_id: newOrder.restaurant_id, + status: VariantStatus.Available, + }, + }); + + const stock = await manager.getRepository(ProductStock).findOneOrFail({ + where: { + restaurant_id: order.restaurant_id, + location_id: order.location_id, + variant_id: variant.id, + }, + }); + + if (product.qty > stock.available) { + const product = await manager.getRepository(Product).findOneByOrFail({ id: variant.product_id }); + + let productName = product.name; + + if (variant.variant_id) { + const vari = await manager.getRepository(Variant).findOneByOrFail({ id: variant.variant_id }); + + productName += ` - ${vari.name}`; + } + + throw new BadRequestException(`Sorry, ${productName} stock is insufficient`); + } - if (variant.variant_id) { - const vari = await manager.getRepository(Variant).findOneByOrFail({ id: variant.variant_id }); + let orderProduct = new OrderProduct(); - productName += ` - ${vari.name}`; + const checkOrderProduct = await manager.getRepository(OrderProduct).findOneBy({ + order_id: order.id, + product_variant_id: variant.id, + }); + if (checkOrderProduct) { + orderProduct = checkOrderProduct; } - throw new BadRequestException(`Sorry, ${productName} stock is insufficient`); - } - - let orderProduct = new OrderProduct(); + orderProduct.order_id = order.id; + orderProduct.product_variant_id = variant.id; + orderProduct.qty = product.qty; + orderProduct.price = variant.price * product.qty; + orderProduct.status = OrderProductStatus.WaitingApproval; + await manager.getRepository(OrderProduct).save(orderProduct); - const checkOrderProduct = await manager.getRepository(OrderProduct).findOneBy({ - order_id: order.id, - product_variant_id: variant.id, - }); - if (checkOrderProduct) { - orderProduct = checkOrderProduct; + orderedProducts.push(orderProduct); } - orderProduct.order_id = order.id; - orderProduct.product_variant_id = variant.id; - orderProduct.qty = product.qty; - orderProduct.price = variant.price * product.qty; - orderProduct.status = OrderProductStatus.WaitingApproval; - await manager.getRepository(OrderProduct).save(orderProduct); - - orderedProducts.push(orderProduct); - } + // Updare Order Number + await manager.getRepository(Order).update(order.id, { + number: sequenceNumber(order.uid), + gross_total: orderedProducts.reduce((price, a) => price + a.price, 0), + }); - // Updare Order Number - await manager.getRepository(Order).update(order.id, { - number: sequenceNumber(order.uid), - gross_total: orderedProducts.reduce((price, a) => price + a.price, 0), + table.status = TableStatus.InUse; + await manager.getRepository(Table).save(table); }); - }); - await order.reload(); + await order.reload(); - return order; + return order; + } catch (error) { + throw error; + } } @Post() @@ -132,50 +139,71 @@ export class OrderController { products: 'required|array', note: 'safe_text', }; - const validation = Validator.init(body, rules); - if (validation.fails()) { - throw new ValidationException(validation); - } - - const newOrder: IOrderDetail = { - restaurant_id: get(body, 'restaurant_id', null), - table_id: get(body, 'table_id', null), - status: OrderStatus.WaitingApproval, - customer_name: get(body, 'customer_name', 'Guest'), - customer_phone: get(body, 'customer_phone', null), - note: get(body, 'note', null), - gross_total: 0, - discount: 0, - fee: 0, - net_total: 0, - products: get(body, 'products', []), - }; - // @TODO: Manage new or existing customer - let customer: Customer = null; - if (body.customer_phone) { - customer = await Customer.findOneBy({ phone: body.customer_phone }); - - if (!customer) { - // @TODO: Create new customer and also verify it! - customer.name = newOrder.customer_name; - customer.phone = newOrder.customer_phone; - const newCustomer = await this.custService.register(customer); - const token = await this.custService.login(newCustomer); - return response.data(token); + try { + const validation = Validator.init(body, rules); + if (validation.fails()) { + throw new ValidationException(validation); } - if (customer.status === CustomerStatus.Verify) { - const token = await this.custService.login(customer); - return response.data(token); + const newOrder: IOrderDetail = { + restaurant_id: get(body, 'restaurant_id', null), + table_id: get(body, 'table_id', null), + status: OrderStatus.WaitingApproval, + customer_name: get(body, 'customer_name', 'Guest'), + customer_phone: get(body, 'customer_phone', null), + note: get(body, 'note', null), + gross_total: 0, + discount: 0, + fee: 0, + net_total: 0, + products: get(body, 'products', []), + }; + + // @TODO: Manage new or existing customer + let customer: Customer = null; + if (body.customer_phone) { + customer = await Customer.findOneBy({ phone: body.customer_phone }); + + if (!customer) { + // @TODO: Create new customer and also verify it! + customer.name = newOrder.customer_name; + customer.phone = newOrder.customer_phone; + const newCustomer = await this.custService.register(customer); + const token = await this.custService.login(newCustomer); + return response.data(token); + } + + if (customer.status === CustomerStatus.Verify) { + const token = await this.custService.login(customer); + return response.data(token); + } } - } - const order = await OrderController.createOrder(newOrder, customer); + const order = await OrderController.createOrder(newOrder, customer); + + const notification = new Notification(); + notification.title = 'New Order'; + notification.content = `Order ${order.number} has been created`; + notification.actor = customer ? customer.name : newOrder.customer_name; + notification.location_id = order.location_id; + notification.restaurant_id = order.restaurant_id; + notification.type = NotificationType.OrderCreated; + notification.order_id = order.id; - // @TODO: Socket to Cashier + await AppDataSource.transaction(async (manager) => { + await manager.getRepository(Notification).save(notification); + }); + + Socket.getInstance().notify(notification.location_id, { + request_id: uuid(), + data: notification, + }); - return response.item(order, OrderTransformer); + return response.item(order, OrderTransformer); + } catch (error) { + throw error; + } } @Get() diff --git a/src/app/customer/table/table.controller.ts b/src/app/customer/table/table.controller.ts index 40a1117..b9af10f 100644 --- a/src/app/customer/table/table.controller.ts +++ b/src/app/customer/table/table.controller.ts @@ -1,10 +1,10 @@ import { Restaurant, RestaurantStatus } from '@db/entities/owner/restaurant.entity'; -import { Table, TableStatus } from '@db/entities/owner/table.entity'; +import { Table } from '@db/entities/owner/table.entity'; import { TableTransformer } from '@db/transformers/table.transformer'; import { GenericException } from '@lib/exceptions/generic.exception'; import { Controller, Get, NotFoundException, Param, Res } from '@nestjs/common'; -import { capitalize, get } from 'lodash'; +import { get } from 'lodash'; @Controller() export class TableController { @@ -17,10 +17,6 @@ export class TableController { throw new NotFoundException('Table not found!'); } - if (table.status !== TableStatus.Available) { - throw new GenericException(`Can't use Table ${table.number}, because it's ${capitalize(table.status)}`); - } - const restaurant = await Restaurant.findOneBy({ id: table.restaurant_id }); if (restaurant.status !== RestaurantStatus.Active) { diff --git a/src/app/owner/restaurant/notification/notification.controller.ts b/src/app/owner/restaurant/notification/notification.controller.ts new file mode 100644 index 0000000..f1bd043 --- /dev/null +++ b/src/app/owner/restaurant/notification/notification.controller.ts @@ -0,0 +1,49 @@ +import { Loc } from '@core/decorators/location.decorator'; +import { Rest } from '@core/decorators/restaurant.decorator'; +import { OwnerAuthGuard } from '@core/guards/auth.guard'; +import { OwnerGuard } from '@core/guards/owner.guard'; +import { PermAct, PermOwner } from '@core/services/role.service'; +import { Notification } from '@db/entities/core/notification.entity'; +import { Location } from '@db/entities/owner/location.entity'; +import { Restaurant } from '@db/entities/owner/restaurant.entity'; +import { NotificationTransformer } from '@db/transformers/notification.transformer'; +import { ValidationException } from '@lib/exceptions/validation.exception'; +import { Validator } from '@lib/helpers/validator.helper'; +import { Permissions } from '@lib/rbac'; +import AppDataSource from '@lib/typeorm/datasource.typeorm'; +import { Body, Controller, Get, Put, Res, UseGuards } from '@nestjs/common'; +import { In } from 'typeorm'; + +@Controller() +@UseGuards(OwnerAuthGuard()) +export class NotificationController { + @Get() + @UseGuards(OwnerGuard) + @Permissions(`${PermOwner.Order}@${PermAct.R}`) // @TODO: Change Permission to Notification + async index(@Rest() rest: Restaurant, @Loc() loc: Location, @Res() response) { + const query = AppDataSource.createQueryBuilder(Notification, 't1').where({ restaurant_id: rest.id }); + + if (loc) { + query.andWhere('t1.location_id = :locId', { locId: loc.id }); + } + + const results = await query.search().sort().getPaged(); + await response.paginate(results, NotificationTransformer); + } + + @Put() + @UseGuards(OwnerGuard) + @Permissions(`${PermOwner.Order}@${PermAct.U}`) + async mark(@Body() body, @Res() response) { + const rules = { + ids: `required|array|uid`, + }; + const validation = Validator.init(body, rules); + if (validation.fails()) { + throw new ValidationException(validation); + } + + await Notification.update({ id: In(body.ids) }, { is_read: true }); + return response.noContent(); + } +} diff --git a/src/app/owner/restaurant/notification/notification.module.ts b/src/app/owner/restaurant/notification/notification.module.ts new file mode 100644 index 0000000..ec3e8ee --- /dev/null +++ b/src/app/owner/restaurant/notification/notification.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { NotificationController } from './notification.controller'; + +@Module({ + imports: [], + controllers: [NotificationController], + providers: [], +}) +export class OwnerNotificationModule {} diff --git a/src/app/owner/restaurant/order/detail.controller.ts b/src/app/owner/restaurant/order/detail.controller.ts new file mode 100644 index 0000000..c304b28 --- /dev/null +++ b/src/app/owner/restaurant/order/detail.controller.ts @@ -0,0 +1,145 @@ +import { Rest } from '@core/decorators/restaurant.decorator'; +import { OwnerAuthGuard } from '@core/guards/auth.guard'; +import { OwnerGuard } from '@core/guards/owner.guard'; +import { PermAct, PermOwner } from '@core/services/role.service'; +import { Notification, NotificationType } from '@db/entities/core/notification.entity'; +import { OrderProduct, OrderProductStatus } from '@db/entities/core/order-product.entity'; +import { Order, OrderStatus } from '@db/entities/core/order.entity'; +import { Table, TableStatus } from '@db/entities/owner/table.entity'; +import { OrderTransformer } from '@db/transformers/order.transformer'; +import { GenericException } from '@lib/exceptions/generic.exception'; +import { ValidationException } from '@lib/exceptions/validation.exception'; +import { time } from '@lib/helpers/time.helper'; +import { Validator } from '@lib/helpers/validator.helper'; +import Socket from '@lib/pubsub/pubsub.lib'; +import { Permissions } from '@lib/rbac'; +import AppDataSource from '@lib/typeorm/datasource.typeorm'; +import { uuid } from '@lib/uid/uuid.library'; +import { Body, Controller, Get, Param, Put, Res, UseGuards } from '@nestjs/common'; + +@Controller(':order_id') +@UseGuards(OwnerAuthGuard()) +export class DetailController { + static async action(order: Order, action: OrderStatus) { + try { + switch (order.status) { + case OrderStatus.WaitingApproval: { + // @TODO: How "REJECTED" flow works? + if (![OrderStatus.Confirmed, OrderStatus.Cancelled].includes(action)) { + throw new GenericException(`Order ${order.number} must be confirmed first.`); + } + + order.status = action; + break; + } + case OrderStatus.Confirmed: { + if (![OrderStatus.Preparing].includes(action)) { + throw new GenericException(`Order ${order.number} can't be ${action}.`); + } + + order.status = OrderStatus.Preparing; + break; + } + case OrderStatus.Preparing: { + if (![OrderStatus.Served].includes(action)) { + throw new GenericException(`Order ${order.number} can't be ${action}.`); + } + + order.status = OrderStatus.Served; + break; + } + case OrderStatus.Served: { + if (![OrderStatus.WaitingPayment].includes(action)) { + throw new GenericException(`Order ${order.number} can't be ${action}.`); + } + + order.status = OrderStatus.WaitingPayment; + break; + } + case OrderStatus.WaitingPayment: { + if (![OrderStatus.Completed].includes(action)) { + throw new GenericException(`Order ${order.number} can't be ${action}.`); + } + + order.status = OrderStatus.Completed; + order.billed_at = time().toDate(); + break; + } + case OrderStatus.Completed: + break; + case OrderStatus.Cancelled: { + if (![OrderStatus.WaitingApproval].includes(action)) { + throw new GenericException(`Order ${order.number} can't be ${action}.`); + } + + order.status = OrderStatus.Cancelled; + break; + } + } + + const notification = new Notification(); + notification.title = 'Order Updated'; + notification.content = JSON.stringify(order); + notification.actor = 'System'; + notification.location_id = order.location_id; + notification.restaurant_id = order.restaurant_id; + notification.type = NotificationType.OrderUpdate; + notification.order_id = order.id; + + await AppDataSource.transaction(async (manager) => { + await manager.getRepository(Order).save(order); + + if ([OrderStatus.Cancelled].includes(order.status)) { + await manager.getRepository(OrderProduct).update({ order_id: order.id }, { status: OrderProductStatus.Cancelled }); + } + + if ([OrderStatus.Completed, OrderStatus.Cancelled].includes(order.status)) { + const table = await manager.getRepository(Table).findOneBy({ id: order.table_id }); + if (!table) { + throw new Error(`Table not found with ID: ${order.table_id}`); + } + table.status = TableStatus.Available; + await manager.getRepository(Table).save(table); + } + + await manager.getRepository(Notification).save(notification); + }); + + Socket.getInstance().notify(notification.order_id, { + request_id: uuid(), + data: notification, + }); + } catch (error) { + throw error; + } + } + + @Get() + @UseGuards(OwnerGuard) + @Permissions(`${PermOwner.Order}@${PermAct.R}`) + async show(@Rest() rest, @Res() response, @Param() param) { + const order = await Order.findOneByOrFail({ restaurant_id: rest.id, id: param.order_id }); + await response.item(order, OrderTransformer); + } + + @Put() + @UseGuards(OwnerGuard) + @Permissions(`${PermOwner.Order}@${PermAct.U}`) + async action(@Body() body, @Param() param, @Res() response) { + const rules = { + action: `required|in:${Object.values(OrderStatus) + .filter((val) => val !== 'waiting_approval') + .join(',')}`, + }; + const validation = Validator.init(body, rules); + if (validation.fails()) { + throw new ValidationException(validation); + } + + const order = await Order.findOneByOrFail({ id: param.order_id }); + await DetailController.action(order, body.action); + await order.reload(); + + return response.item(order, OrderTransformer); + } +} diff --git a/src/app/owner/restaurant/order/order.controller.ts b/src/app/owner/restaurant/order/order.controller.ts new file mode 100644 index 0000000..471508a --- /dev/null +++ b/src/app/owner/restaurant/order/order.controller.ts @@ -0,0 +1,35 @@ +import { Loc } from '@core/decorators/location.decorator'; +import { Quero } from '@core/decorators/quero.decorator'; +import { Rest } from '@core/decorators/restaurant.decorator'; +import { OwnerAuthGuard } from '@core/guards/auth.guard'; +import { OwnerGuard } from '@core/guards/owner.guard'; +import { PermAct, PermOwner } from '@core/services/role.service'; +import { Order } from '@db/entities/core/order.entity'; +import { Location } from '@db/entities/owner/location.entity'; +import { Restaurant } from '@db/entities/owner/restaurant.entity'; +import { OrderTransformer } from '@db/transformers/order.transformer'; +import { Permissions } from '@lib/rbac'; +import AppDataSource from '@lib/typeorm/datasource.typeorm'; +import { Controller, Get, Res, UseGuards } from '@nestjs/common'; + +@Controller() +@UseGuards(OwnerAuthGuard()) +export class OrderController { + @Get() + @UseGuards(OwnerGuard) + @Permissions(`${PermOwner.Order}@${PermAct.R}`) + async index(@Rest() rest: Restaurant, @Loc() loc: Location, @Res() response, @Quero() quero) { + const query = AppDataSource.createQueryBuilder(Order, 't1').where({ restaurant_id: rest.id }); + + if (loc) { + query.andWhere('t1.location_id = :locId', { locId: loc.id }); + } + + if (quero.status) { + query.andWhere('t1.status = :status', { status: quero.status }); + } + + const results = await query.search().sort().getPaged(); + await response.paginate(results, OrderTransformer); + } +} diff --git a/src/app/owner/restaurant/order/order.module.ts b/src/app/owner/restaurant/order/order.module.ts new file mode 100644 index 0000000..f91c7fd --- /dev/null +++ b/src/app/owner/restaurant/order/order.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { DetailController } from './detail.controller'; +import { OrderController } from './order.controller'; + +@Module({ + imports: [], + controllers: [OrderController, DetailController], + providers: [], +}) +export class OwnerOrderModule {} diff --git a/src/app/owner/restaurant/restaurant.module.ts b/src/app/owner/restaurant/restaurant.module.ts index d1f2ab6..40aca99 100644 --- a/src/app/owner/restaurant/restaurant.module.ts +++ b/src/app/owner/restaurant/restaurant.module.ts @@ -1,6 +1,8 @@ import { Module } from '@nestjs/common'; import { OwnerCategoryModule } from './category/category.module'; import { OwnerLocationModule } from './location/location.module'; +import { OwnerNotificationModule } from './notification/notification.module'; +import { OwnerOrderModule } from './order/order.module'; import { OwnerProductModule } from './product/product.module'; import { RestaurantController } from './restaurant.controller'; import { OwnerStaffModule } from './staff/staff.module'; @@ -17,6 +19,8 @@ import { OwnerVariantModule } from './variant/variant.module'; OwnerVariantModule, OwnerProductModule, OwnerStockModule, + OwnerOrderModule, + OwnerNotificationModule, ], controllers: [RestaurantController], providers: [], diff --git a/src/core/services/role.service.ts b/src/core/services/role.service.ts index 405ecc2..40c89d7 100644 --- a/src/core/services/role.service.ts +++ b/src/core/services/role.service.ts @@ -21,6 +21,8 @@ export enum PermOwner { Variant = 'variant', Product = 'product', Stock = 'stock', + Order = 'order', + Notification = 'notification', } export const DefaultPerms = [PermOwner.Profile, PermOwner.Restaurant]; @@ -45,6 +47,8 @@ export class RoleService implements IDynamicStorageRbac { [PermOwner.Variant]: [PermAct.R, PermAct.C, PermAct.U, PermAct.D], [PermOwner.Product]: [PermAct.R, PermAct.C, PermAct.U, PermAct.D], [PermOwner.Stock]: [PermAct.R, PermAct.C, PermAct.U, PermAct.D], + [PermOwner.Order]: [PermAct.R, PermAct.C, PermAct.U, PermAct.D], + [PermOwner.Notification]: [PermAct.R, PermAct.C, PermAct.U, PermAct.D], }; return { diff --git a/src/database/entities/core/notification.entity.ts b/src/database/entities/core/notification.entity.ts new file mode 100644 index 0000000..01d906f --- /dev/null +++ b/src/database/entities/core/notification.entity.ts @@ -0,0 +1,49 @@ +import { BooleanColumn, Column, CoreEntity, ForeignColumn } from '@lib/typeorm/decorators'; +import { ManyToOne } from 'typeorm'; +import { BaseEntity } from '../base/base'; +import { Location } from '../owner/location.entity'; +import { Restaurant } from '../owner/restaurant.entity'; +import { Order } from './order.entity'; + +export enum NotificationType { + OrderCreated = 'order_created', + OrderUpdate = 'order_update', +} + +@CoreEntity() +export class Notification extends BaseEntity { + public static sortable = ['created_at']; + + @Column({ length: 100 }) + type: NotificationType; + + @Column() + title: string; + + @Column({ type: 'text' }) + content: string; + + @Column({ length: 100 }) + actor: string; + + @BooleanColumn({ default: 0 }) + is_read: boolean; + + @ForeignColumn() + location_id: string; + + @ManyToOne(() => Location, { onDelete: 'CASCADE' }) + location: Location; + + @ForeignColumn() + restaurant_id: string; + + @ManyToOne(() => Restaurant, { onDelete: 'CASCADE' }) + restaurant: Restaurant; + + @ForeignColumn() + order_id: string; + + @ManyToOne(() => Order, { onDelete: 'CASCADE' }) + order: Order; +} diff --git a/src/database/entities/core/order-product.entity.ts b/src/database/entities/core/order-product.entity.ts index f92b4f6..c749fcb 100644 --- a/src/database/entities/core/order-product.entity.ts +++ b/src/database/entities/core/order-product.entity.ts @@ -26,6 +26,7 @@ export class OrderProduct extends BaseEntity { @ForeignColumn() product_variant_id: string; + @Exclude() @JoinColumn() @ManyToOne(() => ProductVariant, (pv) => pv.order_products, { onDelete: 'CASCADE' }) product_variant: Promise; diff --git a/src/database/entities/core/order.entity.ts b/src/database/entities/core/order.entity.ts index ce21c39..a4286b6 100644 --- a/src/database/entities/core/order.entity.ts +++ b/src/database/entities/core/order.entity.ts @@ -8,7 +8,7 @@ import { StatusColumn, } from '@lib/typeorm/decorators'; import { Exclude } from 'class-transformer'; -import { Column, Generated, JoinColumn, ManyToOne, OneToMany } from 'typeorm'; +import { Brackets, Column, Generated, Index, JoinColumn, ManyToOne, OneToMany, SelectQueryBuilder } from 'typeorm'; import { Location } from '../owner/location.entity'; import { Restaurant } from '../owner/restaurant.entity'; import { Table } from '../owner/table.entity'; @@ -20,17 +20,22 @@ export enum OrderStatus { Confirmed = 'confirmed', Preparing = 'preparing', Served = 'served', + WaitingPayment = 'waiting_payment', Completed = 'completed', Cancelled = 'cancelled', } @CoreEntity({ autoIncrement: 202410000001 }) export class Order extends BaseEntity { + public static sortable = ['number', 'status', 'created_at']; + public static searchable = ['search']; + @Exclude() @Generated('increment') @NotNullColumn({ type: 'bigint', unique: true }) uid: number; + @Index() @Column({ length: 50 }) number: string; @@ -52,6 +57,12 @@ export class Order extends BaseEntity { @Column({ nullable: true }) note: string; + @Column() + customer_name: string; + + @Column({ nullable: true }) + customer_phone: string; + @Exclude() @ForeignColumn() customer_id: string; @@ -87,4 +98,12 @@ export class Order extends BaseEntity { @Exclude() @OneToMany(() => OrderProduct, (op) => op.order) order_products: Promise; + + static onFilterSearch(value: string, builder: SelectQueryBuilder) { + builder.nextWhere( + new Brackets((qb) => { + qb.where('t1.number LIKE :query', { query: `%${value}%` }); + }) + ); + } } diff --git a/src/database/entities/owner/product-stock.entity.ts b/src/database/entities/owner/product-stock.entity.ts index d918dc5..aaac6a3 100644 --- a/src/database/entities/owner/product-stock.entity.ts +++ b/src/database/entities/owner/product-stock.entity.ts @@ -45,7 +45,6 @@ export class ProductStock extends BaseEntity { @ManyToOne(() => Product, (variant) => variant.stocks) product: Promise; - @Exclude() @ForeignColumn() location_id: string; diff --git a/src/database/migrations/1718551497693-order-number-index.ts b/src/database/migrations/1718551497693-order-number-index.ts new file mode 100644 index 0000000..4cfbac0 --- /dev/null +++ b/src/database/migrations/1718551497693-order-number-index.ts @@ -0,0 +1,13 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class OrderNumberIndex1718551497693 implements MigrationInterface { + name = 'OrderNumberIndex1718551497693'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE INDEX \`IDX_2bf21e468cc540c1ac7645da26\` ON \`order\` (\`number\`)`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX \`IDX_2bf21e468cc540c1ac7645da26\` ON \`order\``); + } +} diff --git a/src/database/migrations/1719250498189-notification.ts b/src/database/migrations/1719250498189-notification.ts new file mode 100644 index 0000000..f4eb84d --- /dev/null +++ b/src/database/migrations/1719250498189-notification.ts @@ -0,0 +1,27 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class Notification1719250498189 implements MigrationInterface { + name = 'Notification1719250498189'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE \`notification\` (\`id\` varchar(26) NOT NULL, \`created_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`type\` varchar(100) NULL, \`title\` varchar(255) NULL, \`content\` text NULL, \`actor\` varchar(100) NULL, \`location_id\` varchar(26) NULL, \`restaurant_id\` varchar(26) NULL, \`order_id\` varchar(26) NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB` + ); + await queryRunner.query( + `ALTER TABLE \`notification\` ADD CONSTRAINT \`FK_9f0f4d05e60d2d94135eda86649\` FOREIGN KEY (\`location_id\`) REFERENCES \`location\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION` + ); + await queryRunner.query( + `ALTER TABLE \`notification\` ADD CONSTRAINT \`FK_eea2206a8461ccbcfc11f1c45a5\` FOREIGN KEY (\`restaurant_id\`) REFERENCES \`restaurant\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION` + ); + await queryRunner.query( + `ALTER TABLE \`notification\` ADD CONSTRAINT \`FK_3ea5cd8a1de9cbf90c86dd0582c\` FOREIGN KEY (\`order_id\`) REFERENCES \`order\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION` + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`notification\` DROP FOREIGN KEY \`FK_3ea5cd8a1de9cbf90c86dd0582c\``); + await queryRunner.query(`ALTER TABLE \`notification\` DROP FOREIGN KEY \`FK_eea2206a8461ccbcfc11f1c45a5\``); + await queryRunner.query(`ALTER TABLE \`notification\` DROP FOREIGN KEY \`FK_9f0f4d05e60d2d94135eda86649\``); + await queryRunner.query(`DROP TABLE \`notification\``); + } +} diff --git a/src/database/migrations/1719251773021-order_customer.ts b/src/database/migrations/1719251773021-order_customer.ts new file mode 100644 index 0000000..2e9ed01 --- /dev/null +++ b/src/database/migrations/1719251773021-order_customer.ts @@ -0,0 +1,15 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class OrderCustomer1719251773021 implements MigrationInterface { + name = 'OrderCustomer1719251773021'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`order\` ADD \`customer_name\` varchar(255) NOT NULL`); + await queryRunner.query(`ALTER TABLE \`order\` ADD \`customer_phone\` varchar(255) NULL`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`order\` DROP COLUMN \`customer_phone\``); + await queryRunner.query(`ALTER TABLE \`order\` DROP COLUMN \`customer_name\``); + } +} diff --git a/src/database/migrations/1719334195161-notification_read.ts b/src/database/migrations/1719334195161-notification_read.ts new file mode 100644 index 0000000..2e81336 --- /dev/null +++ b/src/database/migrations/1719334195161-notification_read.ts @@ -0,0 +1,13 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class NotificationRead1719334195161 implements MigrationInterface { + name = 'NotificationRead1719334195161'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`notification\` ADD \`is_read\` tinyint NULL DEFAULT '0'`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`notification\` DROP COLUMN \`is_read\``); + } +} diff --git a/src/database/seeds/dump/initial.sql b/src/database/seeds/dump/initial.sql index 3e912de..b01aab1 100644 --- a/src/database/seeds/dump/initial.sql +++ b/src/database/seeds/dump/initial.sql @@ -1,23 +1,75 @@ - -SET FOREIGN_KEY_CHECKS=0; +SET + FOREIGN_KEY_CHECKS = 0; -- -- Dumping data for table `role` -- - -INSERT INTO `role` (`id`, `slug`, `permissions`, `created_at`) VALUES -('01EP4ZDWCZCHVFA1ZHV13YGRJZ', 'owner', '[\"restaurant@read\", \"restaurant@create\", \"restaurant@update\", \"restaurant@delete\"]', '2023-01-06 16:00:00.501148'); +INSERT INTO + `role` (`id`, `slug`, `permissions`, `created_at`) +VALUES + ( + '01EP4ZDWCZCHVFA1ZHV13YGRJZ', + 'owner', + '[\"role@read\",\"role@create\",\"role@update\",\"role@delete\",\"restaurant@read\",\"restaurant@create\",\"restaurant@update\",\"restaurant@delete\",\"profile@read\",\"profile@create\",\"profile@update\",\"profile@delete\",\"staff@read\",\"staff@create\",\"staff@update\",\"staff@delete\",\"location@read\",\"location@create\",\"location@update\",\"location@delete\",\"table@read\",\"table@create\",\"table@update\",\"table@delete\",\"category@read\",\"category@create\",\"category@update\",\"category@delete\",\"variant@read\",\"variant@create\",\"variant@update\",\"variant@delete\",\"product@read\",\"product@create\",\"product@update\",\"product@delete\",\"stock@read\",\"stock@create\",\"stock@update\",\"stock@delete\",\"order@read\",\"order@create\",\"order@update\",\"order@delete\"]', + '2023-01-06 16:00:00.501148' + ); -- -- Dumping data for table `owner` -- - -INSERT INTO `owner` (`id`, `name`, `email`, `phone`, `password`, `verification_code`, `status`, `verified_at`, `last_login_at`, `created_at`, `restaurant_id`) VALUES -('01F2KFTXZNS01CJQCGNPJKXA1N', 'Yudha', 'owner@yuppey.com', '+6281931006841', '$2a$10$9/R3pR5rP1gnnG0n06pbEORY39fXfuJ2.eJkdqvoi5oDScm1gcHRi', NULL, 'verify', NULL, NULL, '2023-01-06 16:00:00.501148', '01F2KFTXX9W2Y630FKYSSC9NSQ'); +INSERT INTO + `owner` ( + `id`, + `name`, + `email`, + `phone`, + `password`, + `verification_code`, + `status`, + `verified_at`, + `last_login_at`, + `created_at`, + `restaurant_id` + ) +VALUES + ( + '01F2KFTXZNS01CJQCGNPJKXA1N', + 'Yudha', + 'owner@yuppey.com', + '+6281931006841', + '$2a$10$9/R3pR5rP1gnnG0n06pbEORY39fXfuJ2.eJkdqvoi5oDScm1gcHRi', + NULL, + 'verify', + NULL, + NULL, + '2023-01-06 16:00:00.501148', + '01F2KFTXX9W2Y630FKYSSC9NSQ' + ); -- -- Dumping data for table `restaurant` -- - -INSERT INTO `restaurant` (`id`, `name`, `slug`, `email`, `phone`, `website`, `status`, `created_at`, `owner_id`) VALUES -('01F2KFTXX9W2Y630FKYSSC9NSQ', 'Yuppey', 'yuppey', NULL, NULL, NULL, 'active', '2023-01-06 16:00:00.501148', '01F2KFTXZNS01CJQCGNPJKXA1N'); +INSERT INTO + `restaurant` ( + `id`, + `name`, + `slug`, + `email`, + `phone`, + `website`, + `status`, + `created_at`, + `owner_id` + ) +VALUES + ( + '01F2KFTXX9W2Y630FKYSSC9NSQ', + 'Yuppey', + 'yuppey', + NULL, + NULL, + NULL, + 'active', + '2023-01-06 16:00:00.501148', + '01F2KFTXZNS01CJQCGNPJKXA1N' + ); \ No newline at end of file diff --git a/src/database/subscribers/order.controller.ts b/src/database/subscribers/order.controller.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/database/transformers/notification.transformer.ts b/src/database/transformers/notification.transformer.ts new file mode 100644 index 0000000..40bd672 --- /dev/null +++ b/src/database/transformers/notification.transformer.ts @@ -0,0 +1,8 @@ +import { Notification } from '@db/entities/core/notification.entity'; +import { TransformerAbstract } from '@lib/transformer/abstract.transformer'; + +export class NotificationTransformer extends TransformerAbstract { + transform(entity: Notification) { + return entity.toJSON(); + } +} diff --git a/src/database/transformers/order-product.transformer.ts b/src/database/transformers/order-product.transformer.ts new file mode 100644 index 0000000..dcd0c7b --- /dev/null +++ b/src/database/transformers/order-product.transformer.ts @@ -0,0 +1,21 @@ +import { OrderProduct } from '@db/entities/core/order-product.entity'; +import { TransformerAbstract } from '@lib/transformer/abstract.transformer'; +import { ProductVariantTransformer } from './product-variant.transformer'; + +export class OrderProductTransformer extends TransformerAbstract { + get availableInclude() { + return ['item']; + } + + async includeItem(entity: OrderProduct) { + const product = await entity.product_variant; + if (!product) { + return this.null(); + } + return this.item(product, ProductVariantTransformer); + } + + transform(entity: OrderProduct) { + return entity.toJSON(); + } +} diff --git a/src/database/transformers/order.tranformer.ts b/src/database/transformers/order.tranformer.ts deleted file mode 100644 index 6b4f083..0000000 --- a/src/database/transformers/order.tranformer.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Order } from '@db/entities/core/order.entity'; -import { ProductVariant } from '@db/entities/owner/product-variant.entity'; -import { TransformerAbstract } from '@lib/transformer/abstract.transformer'; -import { In } from 'typeorm'; -import { ProductVariantTransformer } from './product-variant.transformer'; - -export class OrderTransformer extends TransformerAbstract { - get availableInclude() { - return ['items']; - } - - async includeItems(entity: Order) { - const orderProducts = await entity.order_products; - if (!orderProducts) { - return this.null(); - } - - let items: ProductVariant[] = []; - if (orderProducts.filter((val) => val.product_variant_id).length > 0) { - items = await ProductVariant.findBy({ id: In(orderProducts.map((val) => val.product_variant_id)) }); - } - - return this.collection(items, ProductVariantTransformer); - } - - transform(entity: Order) { - return entity.toJSON(); - } -} diff --git a/src/database/transformers/order.transformer.ts b/src/database/transformers/order.transformer.ts new file mode 100644 index 0000000..8eb199d --- /dev/null +++ b/src/database/transformers/order.transformer.ts @@ -0,0 +1,71 @@ +import { Media } from '@db/entities/core/media.entity'; +import { OrderProduct, OrderProductStatus } from '@db/entities/core/order-product.entity'; +import { Order } from '@db/entities/core/order.entity'; +import { Product } from '@db/entities/owner/product.entity'; +import { Variant } from '@db/entities/owner/variant.entity'; +import { TransformerAbstract } from '@lib/transformer/abstract.transformer'; +import { RawTransformer } from './raw.transformer'; + +export class OrderTransformer extends TransformerAbstract { + get availableInclude() { + return ['items', 'table', 'stats', 'location']; + } + + async includeItems(entity: Order) { + const orderProducts = await entity.order_products; + if (!orderProducts) { + return this.null(); + } + + const items: { + id: string; + qty: number; + price: number; + status: OrderProductStatus; + product: Product; + images: Media[]; + variant: Variant; + }[] = []; + if (orderProducts.length > 0) { + for (const orderProduct of orderProducts) { + const productVar = await orderProduct.product_variant; + const product = await productVar.product; + const variant = await productVar.variant; + const images = await Media.findBy({ product_id: product.id }); + + items.push({ + id: orderProduct.id, + qty: orderProduct.qty, + price: orderProduct.price, + status: orderProduct.status, + product, + images, + variant, + }); + } + } + + return this.collection(items, RawTransformer); + } + + async includeTable(entity: Order) { + return await entity.table; + } + + async includeStats(entity: Order) { + const data = { total_items: 0, total_preparing_items: 0 }; + + data.total_items = await OrderProduct.countBy({ order_id: entity.id }); + data.total_preparing_items = await OrderProduct.countBy({ order_id: entity.id, status: OrderProductStatus.Preparing }); + + return this.item(data, RawTransformer); + } + + async includeLocation(entity: Order) { + return await entity.location; + } + + transform(entity: Order) { + return entity.toJSON(); + } +} diff --git a/src/database/transformers/table.transformer.ts b/src/database/transformers/table.transformer.ts index 4fa0535..1102ed2 100644 --- a/src/database/transformers/table.transformer.ts +++ b/src/database/transformers/table.transformer.ts @@ -1,11 +1,14 @@ +import { Order, OrderStatus } from '@db/entities/core/order.entity'; import { Table } from '@db/entities/owner/table.entity'; import { TransformerAbstract } from '@lib/transformer/abstract.transformer'; +import { In, Not } from 'typeorm'; import { LocationTransformer } from './location.transformer'; +import { OrderTransformer } from './order.transformer'; import { RestaurantTransformer } from './restaurant.transformer'; export class TableTransformer extends TransformerAbstract { get availableInclude() { - return ['restaurant', 'location']; + return ['restaurant', 'location', 'order']; } async includeRestaurant(entity: Table) { @@ -26,6 +29,19 @@ export class TableTransformer extends TransformerAbstract { return this.item(location, LocationTransformer); } + async includeOrder(entity: Table) { + const order = await Order.findOneBy({ + table_id: entity.id, + status: Not(In([OrderStatus.Completed, OrderStatus.Cancelled])), + }); + + if (!order) { + return this.null(); + } + + return this.item(order, OrderTransformer); + } + transform(entity: Table) { return entity.toJSON(); } diff --git a/src/library/pubsub/pubsub.lib.ts b/src/library/pubsub/pubsub.lib.ts index a1fba9a..0ab9e16 100644 --- a/src/library/pubsub/pubsub.lib.ts +++ b/src/library/pubsub/pubsub.lib.ts @@ -1,3 +1,4 @@ +import { Notification } from '@db/entities/core/notification.entity'; import { config } from '@lib/helpers/config.helper'; import { Server } from 'socket.io'; import { SocketIOInstance } from './socketio.pubsub'; @@ -10,6 +11,8 @@ export enum PubSubEvent { export enum PubSubEventType { OwnerCreateStock = 'owner_create_stock', OwnerGetTableLabel = 'owner_get_table_label', + // Customer + CustomerCreateOrder = 'customer_create_order', } export enum PubSubStatus { @@ -22,6 +25,7 @@ export enum PubSubStatus { export enum PubSubPayloadType { Dialog = 'dialog', Download = 'download', + Notification = 'notification', } interface IPubSubEventData { From aca2db33dfa47d8be2e650239556547ca13b745f Mon Sep 17 00:00:00 2001 From: akbarsaputrait Date: Mon, 17 Jun 2024 00:04:07 +0700 Subject: [PATCH 6/7] [FIX 40] Owner - Manage Order --- .../restaurant/order/detail.controller.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/app/owner/restaurant/order/detail.controller.ts b/src/app/owner/restaurant/order/detail.controller.ts index c304b28..dbf7c28 100644 --- a/src/app/owner/restaurant/order/detail.controller.ts +++ b/src/app/owner/restaurant/order/detail.controller.ts @@ -5,6 +5,7 @@ import { PermAct, PermOwner } from '@core/services/role.service'; import { Notification, NotificationType } from '@db/entities/core/notification.entity'; import { OrderProduct, OrderProductStatus } from '@db/entities/core/order-product.entity'; import { Order, OrderStatus } from '@db/entities/core/order.entity'; +import { ProductStock } from '@db/entities/owner/product-stock.entity'; import { Table, TableStatus } from '@db/entities/owner/table.entity'; import { OrderTransformer } from '@db/transformers/order.transformer'; import { GenericException } from '@lib/exceptions/generic.exception'; @@ -16,6 +17,7 @@ import { Permissions } from '@lib/rbac'; import AppDataSource from '@lib/typeorm/datasource.typeorm'; import { uuid } from '@lib/uid/uuid.library'; import { Body, Controller, Get, Param, Put, Res, UseGuards } from '@nestjs/common'; +import { In } from 'typeorm'; @Controller(':order_id') @UseGuards(OwnerAuthGuard()) @@ -29,6 +31,8 @@ export class DetailController { throw new GenericException(`Order ${order.number} must be confirmed first.`); } + // @TODO: Able to cancel/decline Product and Recalculate Gross Total + order.status = action; break; } @@ -37,6 +41,8 @@ export class DetailController { throw new GenericException(`Order ${order.number} can't be ${action}.`); } + // @TODO: Able to cancel/decline Product and Recalculate Gross Total + order.status = OrderStatus.Preparing; break; } @@ -66,6 +72,25 @@ export class DetailController { break; } case OrderStatus.Completed: + // @TODO: Decrease stock + const orderProducts = await OrderProduct.findBy({ order_id: order.id }); + const productStocks = await ProductStock.findBy({ + variant_id: In(orderProducts.map((val) => val.product_variant_id)), + }); + + for (const stock of productStocks) { + const orderProduct = orderProducts.find((val) => val.product_variant_id === stock.variant_id); + if (orderProduct) { + stock.onhand -= orderProduct.qty; + stock.allocated -= orderProduct.qty; + stock.actor = ``; // @TODO: Staff LogName + stock.last_action = `Order ${order.number}`; + await AppDataSource.transaction(async (manager) => { + await manager.getRepository(ProductStock).save(stock); + }); + } + } + break; case OrderStatus.Cancelled: { if (![OrderStatus.WaitingApproval].includes(action)) { From 844b7030edc4858bd65737097e0855459c43b955 Mon Sep 17 00:00:00 2001 From: Akbar Anung Yudha Saputra Date: Wed, 10 Jul 2024 01:16:57 +0700 Subject: [PATCH 7/7] [FIX 42] Staff - Manage Order (#44) --- package.json | 2 +- src/app.module.ts | 2 + src/app.routes.ts | 57 +++- .../notification/notification.controller.ts | 4 +- .../restaurant/order/detail.controller.ts | 10 +- .../restaurant/product/category.controller.ts | 51 ++++ .../restaurant/product/detail.controller.ts | 55 +--- .../restaurant/product/product.controller.ts | 77 +----- .../restaurant/product/product.module.ts | 3 +- .../restaurant/product/variant.controller.ts | 58 +--- src/app/staff/auth/auth.controller.ts | 181 +++++++++++++ src/app/staff/auth/auth.module.ts | 7 + src/app/staff/profile/profile.controller.ts | 73 +++++ src/app/staff/profile/profile.module.ts | 7 + .../category/category.controller.ts | 91 +++++++ .../restaurant/category/category.module.ts | 9 + .../notification/notification.controller.ts | 49 ++++ .../notification/notification.module.ts | 9 + .../restaurant/order/detail.controller.ts | 172 ++++++++++++ .../restaurant/order/order.controller.ts | 35 +++ .../staff/restaurant/order/order.module.ts | 10 + .../restaurant/product/category.controller.ts | 51 ++++ .../restaurant/product/detail.controller.ts | 116 ++++++++ .../restaurant/product/product.controller.ts | 58 ++++ .../restaurant/product/product.module.ts | 12 + .../restaurant/product/variant.controller.ts | 59 +++++ .../staff/restaurant/restaurant.controller.ts | 18 ++ src/app/staff/restaurant/restaurant.module.ts | 11 + .../restaurant/stock/stock.controller.ts | 250 ++++++++++++++++++ .../staff/restaurant/stock/stock.module.ts | 9 + .../restaurant/table/table.controller.ts | 189 +++++++++++++ .../staff/restaurant/table/table.module.ts | 9 + .../restaurant/variant/group.controller.ts | 100 +++++++ .../restaurant/variant/variant.controller.ts | 108 ++++++++ .../restaurant/variant/variant.module.ts | 10 + src/app/staff/staff.module.ts | 25 ++ src/config/database.config.ts | 1 + src/core/core.module.ts | 38 ++- src/core/guards/auth.guard.ts | 32 ++- src/core/guards/staff.guard.ts | 90 +++++++ src/core/services/jwt.service.ts | 27 -- src/core/services/product.service.ts | 179 +++++++++++++ src/core/services/role.service.ts | 53 +++- src/core/services/token.service.ts | 22 ++ .../entities/owner/restaurant.entity.ts | 2 +- src/database/entities/staff/user.entity.ts | 6 +- .../transformers/staff.transformer.ts | 26 +- src/library/pubsub/pubsub.lib.ts | 5 + yarn.lock | 8 +- 49 files changed, 2203 insertions(+), 273 deletions(-) create mode 100644 src/app/owner/restaurant/product/category.controller.ts create mode 100644 src/app/staff/auth/auth.controller.ts create mode 100644 src/app/staff/auth/auth.module.ts create mode 100644 src/app/staff/profile/profile.controller.ts create mode 100644 src/app/staff/profile/profile.module.ts create mode 100644 src/app/staff/restaurant/category/category.controller.ts create mode 100644 src/app/staff/restaurant/category/category.module.ts create mode 100644 src/app/staff/restaurant/notification/notification.controller.ts create mode 100644 src/app/staff/restaurant/notification/notification.module.ts create mode 100644 src/app/staff/restaurant/order/detail.controller.ts create mode 100644 src/app/staff/restaurant/order/order.controller.ts create mode 100644 src/app/staff/restaurant/order/order.module.ts create mode 100644 src/app/staff/restaurant/product/category.controller.ts create mode 100644 src/app/staff/restaurant/product/detail.controller.ts create mode 100644 src/app/staff/restaurant/product/product.controller.ts create mode 100644 src/app/staff/restaurant/product/product.module.ts create mode 100644 src/app/staff/restaurant/product/variant.controller.ts create mode 100644 src/app/staff/restaurant/restaurant.controller.ts create mode 100644 src/app/staff/restaurant/restaurant.module.ts create mode 100644 src/app/staff/restaurant/stock/stock.controller.ts create mode 100644 src/app/staff/restaurant/stock/stock.module.ts create mode 100644 src/app/staff/restaurant/table/table.controller.ts create mode 100644 src/app/staff/restaurant/table/table.module.ts create mode 100644 src/app/staff/restaurant/variant/group.controller.ts create mode 100644 src/app/staff/restaurant/variant/variant.controller.ts create mode 100644 src/app/staff/restaurant/variant/variant.module.ts create mode 100644 src/app/staff/staff.module.ts create mode 100644 src/core/guards/staff.guard.ts delete mode 100644 src/core/services/jwt.service.ts create mode 100644 src/core/services/product.service.ts diff --git a/package.json b/package.json index bd85161..61c058f 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "libphonenumber-js": "^1.10.12", "lodash": "^4.17.21", "mysql-import": "^5.0.21", - "mysql2": "^3.10.1", + "mysql2": "^3.10.2", "nest-router": "^1.0.9", "nest-winston": "^1.7.0", "node-cache": "^5.1.2", diff --git a/src/app.module.ts b/src/app.module.ts index 8bf9e16..6ec292e 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -10,6 +10,7 @@ import { routes } from './app.routes'; import { CustomerModule } from './app/customer/customer.module'; import { OwnerModule } from './app/owner/owner.module'; import { RestaurantModule } from './app/restaurant/restaurant.module'; +import { StaffModule } from './app/staff/staff.module'; @Module({ imports: [ @@ -20,6 +21,7 @@ import { RestaurantModule } from './app/restaurant/restaurant.module'; OwnerModule, RestaurantModule, CustomerModule, + StaffModule, ], controllers: [AppController], providers: [SocketioGateway], diff --git a/src/app.routes.ts b/src/app.routes.ts index 960f711..99d2bd2 100644 --- a/src/app.routes.ts +++ b/src/app.routes.ts @@ -1,4 +1,4 @@ -import { Routes } from 'nest-router'; +import type { Routes } from 'nest-router'; import { CustomerAuthModule } from './app/customer/auth/auth.module'; import { CustomerModule } from './app/customer/customer.module'; import { CustomerOrderModule } from './app/customer/order/order.module'; @@ -17,6 +17,17 @@ import { OwnerStockModule } from './app/owner/restaurant/stock/stock.module'; import { OwnerTableModule } from './app/owner/restaurant/table/table.module'; import { OwnerVariantModule } from './app/owner/restaurant/variant/variant.module'; import { RestaurantModule } from './app/restaurant/restaurant.module'; +import { StaffAuthModule } from './app/staff/auth/auth.module'; +import { StaffProfileModule } from './app/staff/profile/profile.module'; +import { StaffCategoryModule } from './app/staff/restaurant/category/category.module'; +import { StafffNotificationModule } from './app/staff/restaurant/notification/notification.module'; +import { StaffOrderModule } from './app/staff/restaurant/order/order.module'; +import { StaffProductModule } from './app/staff/restaurant/product/product.module'; +import { StaffRestaurantModule } from './app/staff/restaurant/restaurant.module'; +import { StaffStockModule } from './app/staff/restaurant/stock/stock.module'; +import { StaffTableModule } from './app/staff/restaurant/table/table.module'; +import { StaffVariantModule } from './app/staff/restaurant/variant/variant.module'; +import { StaffModule } from './app/staff/staff.module'; export const routes: Routes = [ // { path: '/auth', module: AuthModule }, @@ -71,6 +82,50 @@ export const routes: Routes = [ }, ], }, + + { + path: '/staff', + module: StaffModule, + children: [ + { path: '/auth', module: StaffAuthModule }, + { path: '/me', module: StaffProfileModule }, + { + path: '/restaurants', + module: StaffRestaurantModule, + children: [ + { + path: '/:restaurant_id/orders', + module: StaffOrderModule, + }, + { + path: '/:restaurant_id/stocks', + module: StaffStockModule, + }, + { + path: '/:restaurant_id/tables', + module: StaffTableModule, + }, + { + path: '/:restaurant_id/categories', + module: StaffCategoryModule, + }, + { + path: '/:restaurant_id/variants', + module: StaffVariantModule, + }, + { + path: '/:restaurant_id/products', + module: StaffProductModule, + }, + { + path: '/:restaurant_id/notifications', + module: StafffNotificationModule, + }, + ], + }, + ], + }, + { path: '/restaurants', module: RestaurantModule, diff --git a/src/app/owner/restaurant/notification/notification.controller.ts b/src/app/owner/restaurant/notification/notification.controller.ts index f1bd043..e23d0d2 100644 --- a/src/app/owner/restaurant/notification/notification.controller.ts +++ b/src/app/owner/restaurant/notification/notification.controller.ts @@ -19,7 +19,7 @@ import { In } from 'typeorm'; export class NotificationController { @Get() @UseGuards(OwnerGuard) - @Permissions(`${PermOwner.Order}@${PermAct.R}`) // @TODO: Change Permission to Notification + @Permissions(`${PermOwner.Notification}@${PermAct.R}`) async index(@Rest() rest: Restaurant, @Loc() loc: Location, @Res() response) { const query = AppDataSource.createQueryBuilder(Notification, 't1').where({ restaurant_id: rest.id }); @@ -33,7 +33,7 @@ export class NotificationController { @Put() @UseGuards(OwnerGuard) - @Permissions(`${PermOwner.Order}@${PermAct.U}`) + @Permissions(`${PermOwner.Notification}@${PermAct.U}`) async mark(@Body() body, @Res() response) { const rules = { ids: `required|array|uid`, diff --git a/src/app/owner/restaurant/order/detail.controller.ts b/src/app/owner/restaurant/order/detail.controller.ts index dbf7c28..79b4a41 100644 --- a/src/app/owner/restaurant/order/detail.controller.ts +++ b/src/app/owner/restaurant/order/detail.controller.ts @@ -1,10 +1,12 @@ import { Rest } from '@core/decorators/restaurant.decorator'; +import { Me } from '@core/decorators/user.decorator'; import { OwnerAuthGuard } from '@core/guards/auth.guard'; import { OwnerGuard } from '@core/guards/owner.guard'; import { PermAct, PermOwner } from '@core/services/role.service'; import { Notification, NotificationType } from '@db/entities/core/notification.entity'; import { OrderProduct, OrderProductStatus } from '@db/entities/core/order-product.entity'; import { Order, OrderStatus } from '@db/entities/core/order.entity'; +import { Owner } from '@db/entities/owner/owner.entity'; import { ProductStock } from '@db/entities/owner/product-stock.entity'; import { Table, TableStatus } from '@db/entities/owner/table.entity'; import { OrderTransformer } from '@db/transformers/order.transformer'; @@ -22,7 +24,7 @@ import { In } from 'typeorm'; @Controller(':order_id') @UseGuards(OwnerAuthGuard()) export class DetailController { - static async action(order: Order, action: OrderStatus) { + static async action(order: Order, action: OrderStatus, actor: Owner) { try { switch (order.status) { case OrderStatus.WaitingApproval: { @@ -83,7 +85,7 @@ export class DetailController { if (orderProduct) { stock.onhand -= orderProduct.qty; stock.allocated -= orderProduct.qty; - stock.actor = ``; // @TODO: Staff LogName + stock.actor = actor ? actor.logName : 'System'; stock.last_action = `Order ${order.number}`; await AppDataSource.transaction(async (manager) => { await manager.getRepository(ProductStock).save(stock); @@ -150,7 +152,7 @@ export class DetailController { @Put() @UseGuards(OwnerGuard) @Permissions(`${PermOwner.Order}@${PermAct.U}`) - async action(@Body() body, @Param() param, @Res() response) { + async action(@Body() body, @Param() param, @Res() response, @Me() me: Owner) { const rules = { action: `required|in:${Object.values(OrderStatus) .filter((val) => val !== 'waiting_approval') @@ -162,7 +164,7 @@ export class DetailController { } const order = await Order.findOneByOrFail({ id: param.order_id }); - await DetailController.action(order, body.action); + await DetailController.action(order, body.action, me); await order.reload(); return response.item(order, OrderTransformer); diff --git a/src/app/owner/restaurant/product/category.controller.ts b/src/app/owner/restaurant/product/category.controller.ts new file mode 100644 index 0000000..99a6340 --- /dev/null +++ b/src/app/owner/restaurant/product/category.controller.ts @@ -0,0 +1,51 @@ +import { Rest } from '@core/decorators/restaurant.decorator'; +import { OwnerAuthGuard } from '@core/guards/auth.guard'; +import { OwnerGuard } from '@core/guards/owner.guard'; +import { ProductService } from '@core/services/product.service'; +import { PermAct, PermOwner } from '@core/services/role.service'; +import { ProductCategory } from '@db/entities/owner/product-category.entity'; +import { Product } from '@db/entities/owner/product.entity'; +import { ProductTransformer } from '@db/transformers/product.transformer'; +import { ValidationException } from '@lib/exceptions/validation.exception'; +import { Validator } from '@lib/helpers/validator.helper'; +import { Permissions } from '@lib/rbac'; +import { BadRequestException, Body, Controller, Delete, Param, Post, Res, UseGuards } from '@nestjs/common'; + +@Controller(':product_id') +@UseGuards(OwnerAuthGuard()) +export class CategoryController { + constructor(private productService: ProductService) {} + + @Post('/categories') + @UseGuards(OwnerGuard) + @Permissions(`${PermOwner.Product}@${PermAct.U}`) + async addCategory(@Body() body, @Res() response, @Param() param, @Rest() rest) { + const rules = { + category_ids: 'required|array|uid', + }; + const validation = Validator.init(body, rules); + if (validation.fails()) { + throw new ValidationException(validation); + } + + const product = await this.productService.assignCategories(rest, param.product_id, body.category_ids); + + await response.item(product, ProductTransformer); + } + + @Delete('/categories/:category_id') + @UseGuards(OwnerGuard) + @Permissions(`${PermOwner.Product}@${PermAct.D}`) + async deleteCategory(@Param() param, @Res() response, @Rest() rest) { + if (!param.category_id) { + throw new BadRequestException(); + } + + const product = await Product.findOrFail({ where: { id: param.product_id, restaurant_id: rest.id } }); + const category = await ProductCategory.findOneByOrFail({ id: param.category_id, product_id: product.id }); + + await category.remove(); + + return response.noContent(); + } +} diff --git a/src/app/owner/restaurant/product/detail.controller.ts b/src/app/owner/restaurant/product/detail.controller.ts index fd43d72..a5c5d6d 100644 --- a/src/app/owner/restaurant/product/detail.controller.ts +++ b/src/app/owner/restaurant/product/detail.controller.ts @@ -5,8 +5,6 @@ import { OwnerGuard } from '@core/guards/owner.guard'; import { AwsService } from '@core/services/aws.service'; import { PermAct, PermOwner } from '@core/services/role.service'; import { Media } from '@db/entities/core/media.entity'; -import { Category } from '@db/entities/owner/category.entity'; -import { ProductCategory } from '@db/entities/owner/product-category.entity'; import { ProductStock } from '@db/entities/owner/product-stock.entity'; import { Product } from '@db/entities/owner/product.entity'; import { ProductTransformer } from '@db/transformers/product.transformer'; @@ -15,7 +13,7 @@ import { ValidationException } from '@lib/exceptions/validation.exception'; import { Validator } from '@lib/helpers/validator.helper'; import { Permissions } from '@lib/rbac'; import { BadRequestException, Body, Controller, Delete, Get, Param, Post, Put, Req, Res, UseGuards } from '@nestjs/common'; -import { In, Not } from 'typeorm'; +import { Not } from 'typeorm'; @Controller(':product_id') @UseGuards(OwnerAuthGuard()) @@ -96,57 +94,6 @@ export class DetailController { return response.noContent(); } - @Post('/categories') - @UseGuards(OwnerGuard) - @Permissions(`${PermOwner.Product}@${PermAct.U}`) - async addCategory(@Body() body, @Res() response, @Param() param, @Rest() rest) { - const rules = { - category_ids: 'required|array|uid', - }; - const validation = Validator.init(body, rules); - if (validation.fails()) { - throw new ValidationException(validation); - } - - const product = await Product.findOrFail({ where: { id: param.product_id, restaurant_id: rest.id } }); - const prodCategories: ProductCategory[] = []; - - const categories = await Category.findBy({ id: In(body.category_ids) }); - for (const category of categories) { - const isExist = await ProductCategory.exists({ where: { product_id: product.id, category_id: category.id } }); - if (isExist) { - continue; - } - - const pcat = new ProductCategory(); - pcat.product_id = product.id; - pcat.category_id = category.id; - prodCategories.push(pcat); - } - - if (prodCategories.length > 0) { - await ProductCategory.save(prodCategories); - } - - await response.item(product, ProductTransformer); - } - - @Delete('/categories/:category_id') - @UseGuards(OwnerGuard) - @Permissions(`${PermOwner.Product}@${PermAct.D}`) - async deleteCategory(@Param() param, @Res() response, @Rest() rest) { - if (!param.category_id) { - throw new BadRequestException(); - } - - const product = await Product.findOrFail({ where: { id: param.product_id, restaurant_id: rest.id } }); - const category = await ProductCategory.findOneByOrFail({ id: param.category_id, product_id: product.id }); - - await category.remove(); - - return response.noContent(); - } - @Get('/stocks') @UseGuards(OwnerGuard) @Permissions(`${PermOwner.Product}@${PermAct.R}`) diff --git a/src/app/owner/restaurant/product/product.controller.ts b/src/app/owner/restaurant/product/product.controller.ts index 0f9250b..a0e8c30 100644 --- a/src/app/owner/restaurant/product/product.controller.ts +++ b/src/app/owner/restaurant/product/product.controller.ts @@ -1,23 +1,21 @@ import { Rest } from '@core/decorators/restaurant.decorator'; import { OwnerAuthGuard } from '@core/guards/auth.guard'; import { OwnerGuard } from '@core/guards/owner.guard'; +import { ProductService } from '@core/services/product.service'; import { PermAct, PermOwner } from '@core/services/role.service'; -import { Category } from '@db/entities/owner/category.entity'; -import { ProductCategory } from '@db/entities/owner/product-category.entity'; -import { ProductVariant } from '@db/entities/owner/product-variant.entity'; import { Product, ProductStatus } from '@db/entities/owner/product.entity'; -import { Variant, VariantStatus } from '@db/entities/owner/variant.entity'; import { ProductTransformer } from '@db/transformers/product.transformer'; import { ValidationException } from '@lib/exceptions/validation.exception'; import { Validator } from '@lib/helpers/validator.helper'; import { Permissions } from '@lib/rbac'; import AppDataSource from '@lib/typeorm/datasource.typeorm'; -import { BadRequestException, Body, Controller, Get, Post, Res, UseGuards } from '@nestjs/common'; -import { In } from 'typeorm'; +import { Body, Controller, Get, Post, Res, UseGuards } from '@nestjs/common'; @Controller() @UseGuards(OwnerAuthGuard()) export class ProductController { + constructor(private productService: ProductService) {} + @Get() @UseGuards(OwnerGuard) @Permissions(`${PermOwner.Product}@${PermAct.R}`) @@ -48,72 +46,7 @@ export class ProductController { throw new ValidationException(validation); } - const productExist = await Product.exists({ where: { sku: body.sku, restaurant_id: rest.id } }); - if (productExist) { - throw new BadRequestException('Product SKU has already existed.'); - } - - const prod = new Product(); - prod.sku = body.sku; - prod.name = body.name; - prod.description = body.description; - prod.price = body.price; - prod.status = body.status; - prod.restaurant_id = rest.id; - - let categories: Category[] = []; - let variants: Variant[] = []; - - if (body.category_ids.length > 0) { - categories = await Category.find({ where: { id: In(body.category_ids), restaurant_id: rest.id } }); - } - - if (body.variant_ids.length > 0) { - variants = await Variant.find({ where: { id: In(body.variant_ids), restaurant_id: rest.id } }); - } - - await AppDataSource.transaction(async (manager) => { - // Save Product - await manager.getRepository(Product).save(prod); - - // Save Category - if (categories.length > 0) { - const pcats: ProductCategory[] = []; - for (const category of categories) { - const pcat = new ProductCategory(); - pcat.product_id = prod.id; - pcat.category_id = category.id; - pcats.push(pcat); - } - - await manager.getRepository(ProductCategory).save(pcats); - } - - const pvars: ProductVariant[] = []; - - // Create default variant for single product - const pvar = new ProductVariant(); - pvar.product_id = prod.id; - pvar.price = prod.price; - pvar.status = VariantStatus.Available; - pvar.restaurant_id = rest.id; - pvars.push(pvar); - - // Save Variants - if (variants.length > 0) { - for (const variant of variants) { - const vari = new ProductVariant(); - vari.status = variant.status; - vari.product_id = prod.id; - vari.variant_id = variant.id; - vari.price = variant.price; - vari.restaurant_id = rest.id; - pvars.push(vari); - } - } - - await manager.getRepository(ProductVariant).save(pvars); - }); + const prod = await this.productService.create(rest, body); return response.item(prod, ProductTransformer); } diff --git a/src/app/owner/restaurant/product/product.module.ts b/src/app/owner/restaurant/product/product.module.ts index 29be23a..3ab3a5f 100644 --- a/src/app/owner/restaurant/product/product.module.ts +++ b/src/app/owner/restaurant/product/product.module.ts @@ -1,11 +1,12 @@ import { Module } from '@nestjs/common'; +import { CategoryController } from './category.controller'; import { DetailController } from './detail.controller'; import { ProductController } from './product.controller'; import { VariantController } from './variant.controller'; @Module({ imports: [], - controllers: [ProductController, DetailController, VariantController], + controllers: [ProductController, DetailController, CategoryController, VariantController], providers: [], }) export class OwnerProductModule {} diff --git a/src/app/owner/restaurant/product/variant.controller.ts b/src/app/owner/restaurant/product/variant.controller.ts index b53c313..911fec6 100644 --- a/src/app/owner/restaurant/product/variant.controller.ts +++ b/src/app/owner/restaurant/product/variant.controller.ts @@ -1,53 +1,26 @@ import { Rest } from '@core/decorators/restaurant.decorator'; import { OwnerAuthGuard } from '@core/guards/auth.guard'; import { OwnerGuard } from '@core/guards/owner.guard'; +import { ProductService } from '@core/services/product.service'; import { PermAct, PermOwner } from '@core/services/role.service'; import { ProductVariant } from '@db/entities/owner/product-variant.entity'; import { Product } from '@db/entities/owner/product.entity'; -import { VariantGroup } from '@db/entities/owner/variant-group.entity'; -import { Variant } from '@db/entities/owner/variant.entity'; import { ProductTransformer } from '@db/transformers/product.transformer'; import { ValidationException } from '@lib/exceptions/validation.exception'; import { Validator } from '@lib/helpers/validator.helper'; import { Permissions } from '@lib/rbac'; import { BadRequestException, Body, Controller, Delete, Get, Param, Post, Res, UseGuards } from '@nestjs/common'; -import { In } from 'typeorm'; @Controller(':product_id/variants') @UseGuards(OwnerAuthGuard()) export class VariantController { + constructor(private productService: ProductService) {} + @Get() @UseGuards(OwnerGuard) @Permissions(`${PermOwner.Product}@${PermAct.R}`) async getVariants(@Rest() rest, @Res() response, @Param() param) { - const product = await Product.findOrFail({ where: { id: param.product_id, restaurant_id: rest.id } }); - const productVariants = await ProductVariant.findBy({ product_id: product.id }); - const variants = await Variant.find({ - where: { id: In(productVariants.map((val) => val.variant_id)) }, - order: { price: 'ASC' }, - }); - const groups = await VariantGroup.find({ - where: { id: In(variants.map((val) => val.group_id)) }, - order: { name: 'ASC' }, - }); - - const result: any[] = []; - - for (const group of groups) { - const payload = { - ...group, - variants: [], - }; - - for (const variant of variants) { - if (variant.group_id === group.id) { - payload.variants.push(variant); - } - } - - result.push(payload); - } - + const result = await this.productService.getVariants(param.product_id, rest); return response.data(result); } @@ -63,28 +36,7 @@ export class VariantController { throw new ValidationException(validation); } - const product = await Product.findOrFail({ where: { id: param.product_id, restaurant_id: rest.id } }); - const prodVariants: ProductVariant[] = []; - - const variants = await Variant.findBy({ id: In(body.variant_ids) }); - for (const variant of variants) { - const isExist = await ProductVariant.exists({ where: { product_id: product.id, variant_id: variant.id } }); - if (isExist) { - continue; - } - - const pcat = new ProductVariant(); - pcat.product_id = product.id; - pcat.variant_id = variant.id; - pcat.status = variant.status; - pcat.price = variant.price; - pcat.restaurant_id = rest.id; - prodVariants.push(pcat); - } - - if (prodVariants.length > 0) { - await ProductVariant.save(prodVariants); - } + const product = await this.productService.assignVariant(rest, param.product_id, body.variant_ids); await response.item(product, ProductTransformer); } diff --git a/src/app/staff/auth/auth.controller.ts b/src/app/staff/auth/auth.controller.ts new file mode 100644 index 0000000..dc8d8d1 --- /dev/null +++ b/src/app/staff/auth/auth.controller.ts @@ -0,0 +1,181 @@ +import { jwt } from '@config/jwt.config'; +import { StaffGuard } from '@core/guards/staff.guard'; +import { PermAct, PermStaff } from '@core/services/role.service'; +import { StaffBlacklist } from '@db/entities/staff/blacklist.entity'; +import { StaffSession } from '@db/entities/staff/session.entity'; +import { StaffUser } from '@db/entities/staff/user.entity'; +import { ValidationException } from '@lib/exceptions/validation.exception'; +import { config } from '@lib/helpers/config.helper'; +import { hash, hashAreEqual } from '@lib/helpers/encrypt.helper'; +import { time } from '@lib/helpers/time.helper'; +import { Validator } from '@lib/helpers/validator.helper'; +import { Permissions } from '@lib/rbac'; +import { uuid } from '@lib/uid/uuid.library'; +import { + BadRequestException, + Body, + Controller, + Delete, + Post, + Req, + Res, + UnauthorizedException, + UseGuards, +} from '@nestjs/common'; +import * as JWT from 'jsonwebtoken'; +import { ExtractJwt } from 'passport-jwt'; + +@Controller() +export class StaffAuthController { + // constructor(private mail: MailerService) {} + + static token(request: any): any { + try { + const token = ExtractJwt.fromAuthHeaderAsBearerToken()(request); + const decoded = JWT.decode(token); + return { token, decoded }; + } catch (err) { + return null; + } + } + + @Post('/login') + async postLogin(@Body() body, @Res() response) { + const rules = { + username: 'required', + password: 'required', + }; + const validation = Validator.init({ ...body }, rules); + if (validation.fails()) { + throw new ValidationException(validation); + } + + const user = await StaffUser.findOne({ where: [{ email: body.username }, { phone: body.username }] }); + + if (!user) { + throw new UnauthorizedException('Email or phone number are not found'); + } + + if (!(await hashAreEqual(user.password, body.password))) { + throw new UnauthorizedException('Password is incorrect'); + } + + if (!user.isActive) { + throw new UnauthorizedException('Your account was inactive by system'); + } + + const tokenId = uuid(); + + const payload = { + email: user.email, + sub: user.id, + }; + const singOptions = { + jwtid: tokenId, + issuer: config.get('API_URI'), + audience: config.get('APP_URI'), //sha256(`${authSession.ip_address}_${authSession.user_agent}`), + ...jwt.signOptions, + }; + + return response.data({ + access_token: JWT.sign(payload, jwt.secret, singOptions), + }); + } + + @Delete('/logout') + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Profile}@${PermAct.D}`) + async deleteLogout(@Req() request, @Res() response) { + const token = StaffAuthController.token(request); + if (token) { + const authSession = await StaffSession.findOne({ + where: { + staff_user_id: token.decoded.sub, + token_id: token.decoded.jti, + }, + }); + + // if exists then set to logged out + if (authSession) { + authSession.token_deleted = true; + authSession.logged_out_at = time().toDate(); + await authSession.save(); + } else { + // if not, then blacklist token + await StaffBlacklist.store(token.token); + } + } + + return response.noContent(); + } + + @Post('/forgot-password') + async postForgotPass(@Body() { email }, @Res() response) { + const rules = { + email: 'required|email', + }; + const validation = Validator.init({ email }, rules); + if (validation.fails()) { + throw new ValidationException(validation); + } + + const staff: StaffUser = await StaffUser.findOne({ where: { email } }); + if (staff && staff.id) { + staff.reset_token = uuid(); + staff.reset_token_expires = time().add(24, 'hour').toDate(); + await staff.save(); + + // this.mail + // .sendMail({ + // to: staff.email, + // subject: 'Set up a new password', + // template: 'change-password', + // context: { + // name: staff.name, + // link: `${config.get('STAFF_URI')}/reset-password/${staff.reset_token}`, + // }, + // }) + // .then(() => null) + // .catch((error) => Logger.getInstance().notify(error)); + } + + return response.noContent(); + } + + @Post('/change-password') + async postChangePass(@Body() body, @Res() response) { + const rules = { + token: 'required', + password: 'required|confirmed|min:6', + }; + const validation = Validator.init(body, rules); + if (validation.fails()) { + throw new ValidationException(validation); + } + + const staff: StaffUser = await StaffUser.findOrFail({ where: { reset_token: body.token } }); + + if (time().toDate() > staff.reset_token_expires) { + throw new BadRequestException('This reset token has expired'); + } + + staff.password = await hash(body.password); + staff.reset_token = null; + staff.reset_token_expires = null; + await staff.save(); + + // await this.mail + // .sendMail({ + // to: staff.email, + // subject: 'Changed password', + // template: 'changed-password', + // context: { + // name: staff.name, + // }, + // }) + // .then(() => null) + // .catch((error) => Logger.getInstance().notify(error)); + + return response.noContent(); + } +} diff --git a/src/app/staff/auth/auth.module.ts b/src/app/staff/auth/auth.module.ts new file mode 100644 index 0000000..a5d0956 --- /dev/null +++ b/src/app/staff/auth/auth.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { StaffAuthController } from './auth.controller'; + +@Module({ + controllers: [StaffAuthController], +}) +export class StaffAuthModule {} diff --git a/src/app/staff/profile/profile.controller.ts b/src/app/staff/profile/profile.controller.ts new file mode 100644 index 0000000..b1ca5f5 --- /dev/null +++ b/src/app/staff/profile/profile.controller.ts @@ -0,0 +1,73 @@ +import { Me } from '@core/decorators/user.decorator'; +import { StaffAuthGuard } from '@core/guards/auth.guard'; +import { StaffGuard } from '@core/guards/staff.guard'; +import { AuthService } from '@core/services/auth.service'; +import { AwsService } from '@core/services/aws.service'; +import { PermAct, PermStaff } from '@core/services/role.service'; +import { Media } from '@db/entities/core/media.entity'; +import { StaffUser } from '@db/entities/staff/user.entity'; +import { StaffTransformer } from '@db/transformers/staff.transformer'; +import { ValidationException } from '@lib/exceptions/validation.exception'; +import { Validator } from '@lib/helpers/validator.helper'; +import { Permissions } from '@lib/rbac'; +import { BadRequestException, Body, Controller, Delete, Get, Param, Post, Put, Req, Res, UseGuards } from '@nestjs/common'; +import { isEmpty } from 'lodash'; + +@Controller() +@UseGuards(StaffAuthGuard()) +export class StaffProfileController { + constructor(private auth: AuthService, private aws: AwsService) {} + + @Get() + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Profile}@${PermAct.R}`) + async me(@Me() me: StaffUser, @Res() response) { + console.log(me); + return response.item(me, StaffTransformer); + } + + @Put() + @Permissions(`${PermStaff.Profile}@${PermAct.U}`) + async update(@Param() param, @Body() body, @Res() response, @Me() me: StaffUser) { + const rules = { + name: 'required|safe_text', + phone: 'required|phone', + }; + const validation = Validator.init(body, rules); + if (validation.fails()) { + throw new ValidationException(validation); + } + + me.name = body.name; + me.phone = body.phone; + await me.save(); + + await response.item(me, StaffTransformer); + } + + @Post('/avatar') + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Profile}@${PermAct.C}`) + async uploadAvatar(@Req() request, @Res() response, @Me() me: StaffUser) { + const file = await this.aws.uploadFile(request, response, 'image', { dynamicPath: `staff/${me.id}/avatar` }); + if (!file || isEmpty(file)) { + throw new BadRequestException('Unable to upload image'); + } + + if (await me.image) { + await this.aws.removeFile(await me.image); + } + + await Media.build(me, file); + + await response.item(me, StaffTransformer); + } + + @Delete('/avatar') + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Profile}@${PermAct.D}`) + async deleteAvatar(@Res() response, @Me() me: StaffUser) { + await Media.delete({ staff_user_id: me.id }); + return response.noContent(); + } +} diff --git a/src/app/staff/profile/profile.module.ts b/src/app/staff/profile/profile.module.ts new file mode 100644 index 0000000..492fb0d --- /dev/null +++ b/src/app/staff/profile/profile.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { StaffProfileController } from './profile.controller'; + +@Module({ + controllers: [StaffProfileController], +}) +export class StaffProfileModule {} diff --git a/src/app/staff/restaurant/category/category.controller.ts b/src/app/staff/restaurant/category/category.controller.ts new file mode 100644 index 0000000..d8a9e5f --- /dev/null +++ b/src/app/staff/restaurant/category/category.controller.ts @@ -0,0 +1,91 @@ +import { Rest } from '@core/decorators/restaurant.decorator'; +import { StaffAuthGuard } from '@core/guards/auth.guard'; +import { StaffGuard } from '@core/guards/staff.guard'; +import { PermAct, PermStaff } from '@core/services/role.service'; +import { Category } from '@db/entities/owner/category.entity'; +import { CategoryTransformer } from '@db/transformers/category.transformer'; +import { ValidationException } from '@lib/exceptions/validation.exception'; +import { Validator } from '@lib/helpers/validator.helper'; +import { Permissions } from '@lib/rbac'; +import AppDataSource from '@lib/typeorm/datasource.typeorm'; +import { BadRequestException, Body, Controller, Get, Param, Post, Put, Res, UseGuards } from '@nestjs/common'; +import { Not } from 'typeorm'; + +@Controller() +@UseGuards(StaffAuthGuard()) +export class CategoryController { + @Get() + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Category}@${PermAct.R}`) + async index(@Rest() rest, @Res() response) { + const categories = await AppDataSource.createQueryBuilder(Category, 't1') + .where({ restaurant_id: rest.id }) + .search() + .sort() + .getPaged(); + await response.paginate(categories, CategoryTransformer); + } + + @Get('/:category_id') + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Category}@${PermAct.R}`) + async show(@Rest() rest, @Res() response, @Param() param) { + const category = await Category.findOneByOrFail({ restaurant_id: rest.id, id: param.category_id }); + await response.item(category, CategoryTransformer); + } + + @Post() + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Category}@${PermAct.C}`) + async create(@Rest() rest, @Body() body, @Res() response) { + const rules = { + name: 'required|unique|safe_text', + }; + const validation = Validator.init(body, rules); + if (validation.fails()) { + throw new ValidationException(validation); + } + + const categoryExist = await Category.exists({ where: { name: body.name, restaurant_id: rest.id } }); + if (categoryExist) { + throw new BadRequestException('Category has already existed.'); + } + + const categ = new Category(); + categ.name = body.name; + categ.restaurant_id = rest.id; + await categ.save(); + + return response.item(categ, CategoryTransformer); + } + + @Put('/:category_id') + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Category}@${PermAct.U}`) + async update(@Rest() rest, @Body() body, @Res() response, @Param() param) { + const rules = { + name: 'required|unique|safe_text', + }; + const validation = Validator.init(body, rules); + if (validation.fails()) { + throw new ValidationException(validation); + } + + if (!param.category_id) { + throw new BadRequestException(); + } + + const categoryExist = await Category.exists({ + where: { name: body.name, restaurant_id: rest.id, id: Not(param.category_id) }, + }); + if (categoryExist) { + throw new BadRequestException('Category has already existed.'); + } + + const categ = await Category.findOneByOrFail({ id: param.category_id }); + categ.name = body.name; + await categ.save(); + + return response.item(categ, CategoryTransformer); + } +} diff --git a/src/app/staff/restaurant/category/category.module.ts b/src/app/staff/restaurant/category/category.module.ts new file mode 100644 index 0000000..2fbd496 --- /dev/null +++ b/src/app/staff/restaurant/category/category.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { CategoryController } from './category.controller'; + +@Module({ + imports: [], + controllers: [CategoryController], + providers: [], +}) +export class StaffCategoryModule {} diff --git a/src/app/staff/restaurant/notification/notification.controller.ts b/src/app/staff/restaurant/notification/notification.controller.ts new file mode 100644 index 0000000..49d9ae9 --- /dev/null +++ b/src/app/staff/restaurant/notification/notification.controller.ts @@ -0,0 +1,49 @@ +import { Loc } from '@core/decorators/location.decorator'; +import { Rest } from '@core/decorators/restaurant.decorator'; +import { StaffAuthGuard } from '@core/guards/auth.guard'; +import { StaffGuard } from '@core/guards/staff.guard'; +import { PermAct, PermStaff } from '@core/services/role.service'; +import { Notification } from '@db/entities/core/notification.entity'; +import { Location } from '@db/entities/owner/location.entity'; +import { Restaurant } from '@db/entities/owner/restaurant.entity'; +import { NotificationTransformer } from '@db/transformers/notification.transformer'; +import { ValidationException } from '@lib/exceptions/validation.exception'; +import { Validator } from '@lib/helpers/validator.helper'; +import { Permissions } from '@lib/rbac'; +import AppDataSource from '@lib/typeorm/datasource.typeorm'; +import { Body, Controller, Get, Put, Res, UseGuards } from '@nestjs/common'; +import { In } from 'typeorm'; + +@Controller() +@UseGuards(StaffAuthGuard()) +export class NotificationController { + @Get() + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Notification}@${PermAct.R}`) + async index(@Rest() rest: Restaurant, @Loc() loc: Location, @Res() response) { + const query = AppDataSource.createQueryBuilder(Notification, 't1').where({ restaurant_id: rest.id }); + + if (loc) { + query.andWhere('t1.location_id = :locId', { locId: loc.id }); + } + + const results = await query.search().sort().getPaged(); + await response.paginate(results, NotificationTransformer); + } + + @Put() + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Notification}@${PermAct.U}`) + async mark(@Body() body, @Res() response) { + const rules = { + ids: `required|array|uid`, + }; + const validation = Validator.init(body, rules); + if (validation.fails()) { + throw new ValidationException(validation); + } + + await Notification.update({ id: In(body.ids) }, { is_read: true }); + return response.noContent(); + } +} diff --git a/src/app/staff/restaurant/notification/notification.module.ts b/src/app/staff/restaurant/notification/notification.module.ts new file mode 100644 index 0000000..864f8e3 --- /dev/null +++ b/src/app/staff/restaurant/notification/notification.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { NotificationController } from './notification.controller'; + +@Module({ + imports: [], + controllers: [NotificationController], + providers: [], +}) +export class StafffNotificationModule {} diff --git a/src/app/staff/restaurant/order/detail.controller.ts b/src/app/staff/restaurant/order/detail.controller.ts new file mode 100644 index 0000000..bf58277 --- /dev/null +++ b/src/app/staff/restaurant/order/detail.controller.ts @@ -0,0 +1,172 @@ +import { Rest } from '@core/decorators/restaurant.decorator'; +import { Me } from '@core/decorators/user.decorator'; +import { StaffAuthGuard } from '@core/guards/auth.guard'; +import { StaffGuard } from '@core/guards/staff.guard'; +import { PermAct, PermStaff } from '@core/services/role.service'; +import { Notification, NotificationType } from '@db/entities/core/notification.entity'; +import { OrderProduct, OrderProductStatus } from '@db/entities/core/order-product.entity'; +import { Order, OrderStatus } from '@db/entities/core/order.entity'; +import { ProductStock } from '@db/entities/owner/product-stock.entity'; +import { Table, TableStatus } from '@db/entities/owner/table.entity'; +import { StaffUser } from '@db/entities/staff/user.entity'; +import { OrderTransformer } from '@db/transformers/order.transformer'; +import { GenericException } from '@lib/exceptions/generic.exception'; +import { ValidationException } from '@lib/exceptions/validation.exception'; +import { time } from '@lib/helpers/time.helper'; +import { Validator } from '@lib/helpers/validator.helper'; +import Socket from '@lib/pubsub/pubsub.lib'; +import { Permissions } from '@lib/rbac'; +import AppDataSource from '@lib/typeorm/datasource.typeorm'; +import { uuid } from '@lib/uid/uuid.library'; +import { Body, Controller, Get, Param, Put, Res, UseGuards } from '@nestjs/common'; +import { In } from 'typeorm'; + +@Controller(':order_id') +@UseGuards(StaffAuthGuard()) +export class DetailController { + static async action(order: Order, action: OrderStatus, actor: StaffUser) { + try { + switch (order.status) { + case OrderStatus.WaitingApproval: { + // @TODO: How "REJECTED" flow works? + if (![OrderStatus.Confirmed, OrderStatus.Cancelled].includes(action)) { + throw new GenericException(`Order ${order.number} must be confirmed first.`); + } + + // @TODO: Able to cancel/decline Product and Recalculate Gross Total + + order.status = action; + break; + } + case OrderStatus.Confirmed: { + if (![OrderStatus.Preparing].includes(action)) { + throw new GenericException(`Order ${order.number} can't be ${action}.`); + } + + // @TODO: Able to cancel/decline Product and Recalculate Gross Total + + order.status = OrderStatus.Preparing; + break; + } + case OrderStatus.Preparing: { + if (![OrderStatus.Served].includes(action)) { + throw new GenericException(`Order ${order.number} can't be ${action}.`); + } + + order.status = OrderStatus.Served; + break; + } + case OrderStatus.Served: { + if (![OrderStatus.WaitingPayment].includes(action)) { + throw new GenericException(`Order ${order.number} can't be ${action}.`); + } + + order.status = OrderStatus.WaitingPayment; + break; + } + case OrderStatus.WaitingPayment: { + if (![OrderStatus.Completed].includes(action)) { + throw new GenericException(`Order ${order.number} can't be ${action}.`); + } + + order.status = OrderStatus.Completed; + order.billed_at = time().toDate(); + break; + } + case OrderStatus.Completed: + // @TODO: Decrease stock + const orderProducts = await OrderProduct.findBy({ order_id: order.id }); + const productStocks = await ProductStock.findBy({ + variant_id: In(orderProducts.map((val) => val.product_variant_id)), + }); + + for (const stock of productStocks) { + const orderProduct = orderProducts.find((val) => val.product_variant_id === stock.variant_id); + if (orderProduct) { + stock.onhand -= orderProduct.qty; + stock.allocated -= orderProduct.qty; + stock.actor = actor ? actor.logName : 'System'; + stock.last_action = `Order ${order.number}`; + await AppDataSource.transaction(async (manager) => { + await manager.getRepository(ProductStock).save(stock); + }); + } + } + + break; + case OrderStatus.Cancelled: { + if (![OrderStatus.WaitingApproval].includes(action)) { + throw new GenericException(`Order ${order.number} can't be ${action}.`); + } + + order.status = OrderStatus.Cancelled; + break; + } + } + + const notification = new Notification(); + notification.title = 'Order Updated'; + notification.content = JSON.stringify(order); + notification.actor = 'System'; + notification.location_id = order.location_id; + notification.restaurant_id = order.restaurant_id; + notification.type = NotificationType.OrderUpdate; + notification.order_id = order.id; + + await AppDataSource.transaction(async (manager) => { + await manager.getRepository(Order).save(order); + + if ([OrderStatus.Cancelled].includes(order.status)) { + await manager.getRepository(OrderProduct).update({ order_id: order.id }, { status: OrderProductStatus.Cancelled }); + } + + if ([OrderStatus.Completed, OrderStatus.Cancelled].includes(order.status)) { + const table = await manager.getRepository(Table).findOneBy({ id: order.table_id }); + if (!table) { + throw new Error(`Table not found with ID: ${order.table_id}`); + } + table.status = TableStatus.Available; + await manager.getRepository(Table).save(table); + } + + await manager.getRepository(Notification).save(notification); + }); + + Socket.getInstance().notify(notification.order_id, { + request_id: uuid(), + data: notification, + }); + } catch (error) { + throw error; + } + } + + @Get() + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Order}@${PermAct.R}`) + async show(@Rest() rest, @Res() response, @Param() param) { + const order = await Order.findOneByOrFail({ restaurant_id: rest.id, id: param.order_id }); + await response.item(order, OrderTransformer); + } + + @Put() + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Order}@${PermAct.U}`) + async action(@Body() body, @Param() param, @Res() response, @Me() me: StaffUser) { + const rules = { + action: `required|in:${Object.values(OrderStatus) + .filter((val) => val !== 'waiting_approval') + .join(',')}`, + }; + const validation = Validator.init(body, rules); + if (validation.fails()) { + throw new ValidationException(validation); + } + + const order = await Order.findOneByOrFail({ id: param.order_id }); + await DetailController.action(order, body.action, me); + await order.reload(); + + return response.item(order, OrderTransformer); + } +} diff --git a/src/app/staff/restaurant/order/order.controller.ts b/src/app/staff/restaurant/order/order.controller.ts new file mode 100644 index 0000000..d554b80 --- /dev/null +++ b/src/app/staff/restaurant/order/order.controller.ts @@ -0,0 +1,35 @@ +import { Loc } from '@core/decorators/location.decorator'; +import { Quero } from '@core/decorators/quero.decorator'; +import { Rest } from '@core/decorators/restaurant.decorator'; +import { StaffAuthGuard } from '@core/guards/auth.guard'; +import { StaffGuard } from '@core/guards/staff.guard'; +import { PermAct, PermStaff } from '@core/services/role.service'; +import { Order } from '@db/entities/core/order.entity'; +import { Location } from '@db/entities/owner/location.entity'; +import { Restaurant } from '@db/entities/owner/restaurant.entity'; +import { OrderTransformer } from '@db/transformers/order.transformer'; +import { Permissions } from '@lib/rbac'; +import AppDataSource from '@lib/typeorm/datasource.typeorm'; +import { Controller, Get, Res, UseGuards } from '@nestjs/common'; + +@Controller() +@UseGuards(StaffAuthGuard()) +export class OrderController { + @Get() + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Order}@${PermAct.R}`) + async index(@Rest() rest: Restaurant, @Loc() loc: Location, @Res() response, @Quero() quero) { + const query = AppDataSource.createQueryBuilder(Order, 't1').where({ restaurant_id: rest.id }); + + if (loc) { + query.andWhere('t1.location_id = :locId', { locId: loc.id }); + } + + if (quero.status) { + query.andWhere('t1.status = :status', { status: quero.status }); + } + + const results = await query.search().sort().getPaged(); + await response.paginate(results, OrderTransformer); + } +} diff --git a/src/app/staff/restaurant/order/order.module.ts b/src/app/staff/restaurant/order/order.module.ts new file mode 100644 index 0000000..19a7312 --- /dev/null +++ b/src/app/staff/restaurant/order/order.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { DetailController } from './detail.controller'; +import { OrderController } from './order.controller'; + +@Module({ + imports: [], + controllers: [OrderController, DetailController], + providers: [], +}) +export class StaffOrderModule {} diff --git a/src/app/staff/restaurant/product/category.controller.ts b/src/app/staff/restaurant/product/category.controller.ts new file mode 100644 index 0000000..1132480 --- /dev/null +++ b/src/app/staff/restaurant/product/category.controller.ts @@ -0,0 +1,51 @@ +import { Rest } from '@core/decorators/restaurant.decorator'; +import { StaffAuthGuard } from '@core/guards/auth.guard'; +import { StaffGuard } from '@core/guards/staff.guard'; +import { ProductService } from '@core/services/product.service'; +import { PermAct, PermStaff } from '@core/services/role.service'; +import { ProductCategory } from '@db/entities/owner/product-category.entity'; +import { Product } from '@db/entities/owner/product.entity'; +import { ProductTransformer } from '@db/transformers/product.transformer'; +import { ValidationException } from '@lib/exceptions/validation.exception'; +import { Validator } from '@lib/helpers/validator.helper'; +import { Permissions } from '@lib/rbac'; +import { BadRequestException, Body, Controller, Delete, Param, Post, Res, UseGuards } from '@nestjs/common'; + +@Controller(':product_id') +@UseGuards(StaffAuthGuard()) +export class CategoryController { + constructor(private productService: ProductService) {} + + @Post('/categories') + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Product}@${PermAct.U}`) + async addCategory(@Body() body, @Res() response, @Param() param, @Rest() rest) { + const rules = { + category_ids: 'required|array|uid', + }; + const validation = Validator.init(body, rules); + if (validation.fails()) { + throw new ValidationException(validation); + } + + const product = await this.productService.assignCategories(rest, param.product_id, body.category_ids); + + await response.item(product, ProductTransformer); + } + + @Delete('/categories/:category_id') + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Product}@${PermAct.D}`) + async deleteCategory(@Param() param, @Res() response, @Rest() rest) { + if (!param.category_id) { + throw new BadRequestException(); + } + + const product = await Product.findOrFail({ where: { id: param.product_id, restaurant_id: rest.id } }); + const category = await ProductCategory.findOneByOrFail({ id: param.category_id, product_id: product.id }); + + await category.remove(); + + return response.noContent(); + } +} diff --git a/src/app/staff/restaurant/product/detail.controller.ts b/src/app/staff/restaurant/product/detail.controller.ts new file mode 100644 index 0000000..2219daf --- /dev/null +++ b/src/app/staff/restaurant/product/detail.controller.ts @@ -0,0 +1,116 @@ +import { Quero } from '@core/decorators/quero.decorator'; +import { Rest } from '@core/decorators/restaurant.decorator'; +import { StaffAuthGuard } from '@core/guards/auth.guard'; +import { StaffGuard } from '@core/guards/staff.guard'; +import { AwsService } from '@core/services/aws.service'; +import { PermAct, PermStaff } from '@core/services/role.service'; +import { Media } from '@db/entities/core/media.entity'; +import { ProductStock } from '@db/entities/owner/product-stock.entity'; +import { Product } from '@db/entities/owner/product.entity'; +import { ProductTransformer } from '@db/transformers/product.transformer'; +import { StockTransformer } from '@db/transformers/stock.transformer'; +import { ValidationException } from '@lib/exceptions/validation.exception'; +import { Validator } from '@lib/helpers/validator.helper'; +import { Permissions } from '@lib/rbac'; +import { BadRequestException, Body, Controller, Delete, Get, Param, Post, Put, Req, Res, UseGuards } from '@nestjs/common'; +import { Not } from 'typeorm'; + +@Controller(':product_id') +@UseGuards(StaffAuthGuard()) +export class DetailController { + constructor(private aws: AwsService) {} + + @Get() + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Product}@${PermAct.R}`) + async show(@Rest() rest, @Res() response, @Param() param) { + const product = await Product.findOneByOrFail({ restaurant_id: rest.id, id: param.product_id }); + await response.item(product, ProductTransformer); + } + + @Put() + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Product}@${PermAct.C}`) + async update(@Rest() rest, @Body() body, @Res() response, @Param() param) { + const rules = { + sku: 'required|sku', + name: 'required|string', + description: 'string', + price: 'required|numeric|min:0', + }; + const validation = Validator.init(body, rules); + if (validation.fails()) { + throw new ValidationException(validation); + } + + if (!param.product_id) { + throw new BadRequestException(); + } + + const productExist = await Product.exists({ + where: { sku: body.sku, restaurant_id: rest.id, id: Not(param.product_id) }, + }); + if (productExist) { + throw new BadRequestException('Product has already existed.'); + } + + const product = await Product.findOneByOrFail({ id: param.product_id }); + product.sku = body.sku; + product.name = body.name; + product.description = body.decription; + product.price = body.price; + await product.save(); + + return response.item(product, ProductTransformer); + } + + @Post('/images') + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Product}@${PermAct.U}`) + async uploadImage(@Param() param, @Req() request, @Res() response, @Rest() rest) { + const product = await Product.findOrFail({ where: { id: param.product_id, restaurant_id: rest.id } }); + if ((await Media.total(product)) >= 5) { + throw new BadRequestException('Maximum total image allowed is 5'); + } + + const file = await this.aws.uploadFile(request, response, 'image', { + dynamicPath: `restaurants/${rest.id}/products`, + }); + + await Media.add(product, file); + + await response.item(product, ProductTransformer); + } + + @Delete('/images/:image_id') + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Product}@${PermAct.D}`) + async deleteImage(@Param() param, @Res() response, @Rest() rest) { + const product = await Product.findOrFail({ where: { id: param.product_id, restaurant_id: rest.id } }); + const media = await Media.findOrFail({ where: { id: param.image_id, product_id: product.id } }); + + await this.aws.removeFile(media); + + return response.noContent(); + } + + @Get('/stocks') + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Product}@${PermAct.R}`) + async getStocks(@Param() param, @Res() response, @Rest() rest, @Quero() quero) { + if (!param.product_id) { + throw new BadRequestException(); + } + + const product = await Product.findOrFail({ where: { id: param.product_id, restaurant_id: rest.id } }); + const where = { product_id: product.id }; + + if (quero.location_id) { + Object.assign(where, { ...where, location_id: quero.location_id }); + } + + const stocks = await ProductStock.findBy(where); + + return response.collection(stocks, StockTransformer); + } +} diff --git a/src/app/staff/restaurant/product/product.controller.ts b/src/app/staff/restaurant/product/product.controller.ts new file mode 100644 index 0000000..e246bbe --- /dev/null +++ b/src/app/staff/restaurant/product/product.controller.ts @@ -0,0 +1,58 @@ +import { Rest } from '@core/decorators/restaurant.decorator'; +import { StaffAuthGuard } from '@core/guards/auth.guard'; +import { StaffGuard } from '@core/guards/staff.guard'; +import { ProductService } from '@core/services/product.service'; +import { PermAct, PermStaff } from '@core/services/role.service'; +import { Product, ProductStatus } from '@db/entities/owner/product.entity'; +import { ProductTransformer } from '@db/transformers/product.transformer'; +import { ValidationException } from '@lib/exceptions/validation.exception'; +import { Validator } from '@lib/helpers/validator.helper'; +import { Permissions } from '@lib/rbac'; +import AppDataSource from '@lib/typeorm/datasource.typeorm'; +import { BadRequestException, Body, Controller, Get, Post, Res, UseGuards } from '@nestjs/common'; + +@Controller() +@UseGuards(StaffAuthGuard()) +export class ProductController { + constructor(private productService: ProductService) {} + + @Get() + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Product}@${PermAct.R}`) + async index(@Rest() rest, @Res() response) { + const products = await AppDataSource.createQueryBuilder(Product, 't1') + .where({ restaurant_id: rest.id }) + .search() + .sort() + .getPaged(); + await response.paginate(products, ProductTransformer); + } + + @Post() + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Product}@${PermAct.C}`) + async create(@Rest() rest, @Body() body, @Res() response) { + const rules = { + sku: 'required|sku', + name: 'required|string', + description: 'string', + price: 'required|numeric|min:0', + status: `required|in:${Object.values(ProductStatus).join(',')}`, + category_ids: 'array|unique|uid', + variant_ids: 'array|unique|uid', + }; + const validation = Validator.init(body, rules); + if (validation.fails()) { + throw new ValidationException(validation); + } + + const productExist = await Product.exists({ where: { sku: body.sku, restaurant_id: rest.id } }); + if (productExist) { + throw new BadRequestException('Product SKU has already existed.'); + } + + const prod = await this.productService.create(rest, body); + + return response.item(prod, ProductTransformer); + } +} diff --git a/src/app/staff/restaurant/product/product.module.ts b/src/app/staff/restaurant/product/product.module.ts new file mode 100644 index 0000000..ed9ce99 --- /dev/null +++ b/src/app/staff/restaurant/product/product.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { CategoryController } from './category.controller'; +import { DetailController } from './detail.controller'; +import { ProductController } from './product.controller'; +import { VariantController } from './variant.controller'; + +@Module({ + imports: [], + controllers: [ProductController, DetailController, CategoryController, VariantController], + providers: [], +}) +export class StaffProductModule {} diff --git a/src/app/staff/restaurant/product/variant.controller.ts b/src/app/staff/restaurant/product/variant.controller.ts new file mode 100644 index 0000000..6e1a2a8 --- /dev/null +++ b/src/app/staff/restaurant/product/variant.controller.ts @@ -0,0 +1,59 @@ +import { Rest } from '@core/decorators/restaurant.decorator'; +import { StaffAuthGuard } from '@core/guards/auth.guard'; +import { StaffGuard } from '@core/guards/staff.guard'; +import { ProductService } from '@core/services/product.service'; +import { PermAct, PermStaff } from '@core/services/role.service'; +import { ProductVariant } from '@db/entities/owner/product-variant.entity'; +import { Product } from '@db/entities/owner/product.entity'; +import { ProductTransformer } from '@db/transformers/product.transformer'; +import { ValidationException } from '@lib/exceptions/validation.exception'; +import { Validator } from '@lib/helpers/validator.helper'; +import { Permissions } from '@lib/rbac'; +import { BadRequestException, Body, Controller, Delete, Get, Param, Post, Res, UseGuards } from '@nestjs/common'; + +@Controller(':product_id/variants') +@UseGuards(StaffAuthGuard()) +export class VariantController { + constructor(private productService: ProductService) {} + + @Get() + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Product}@${PermAct.R}`) + async getVariants(@Rest() rest, @Res() response, @Param() param) { + const result = await this.productService.getVariants(param.product_id, rest); + return response.data(result); + } + + @Post() + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Product}@${PermAct.U}`) + async addVariant(@Body() body, @Res() response, @Param() param, @Rest() rest) { + const rules = { + variant_ids: 'required|array', + }; + const validation = Validator.init(body, rules); + if (validation.fails()) { + throw new ValidationException(validation); + } + + const product = await this.productService.assignVariant(rest, param.product_id, body.variant_ids); + + await response.item(product, ProductTransformer); + } + + @Delete('/:variant_id') + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Product}@${PermAct.D}`) + async deleteVariant(@Param() param, @Res() response, @Rest() rest) { + if (!param.variant_id) { + throw new BadRequestException(); + } + + const product = await Product.findOrFail({ where: { id: param.product_id, restaurant_id: rest.id } }); + const variant = await ProductVariant.findOneByOrFail({ id: param.variant_id, product_id: product.id }); + + await variant.remove(); + + return response.noContent(); + } +} diff --git a/src/app/staff/restaurant/restaurant.controller.ts b/src/app/staff/restaurant/restaurant.controller.ts new file mode 100644 index 0000000..4bb895f --- /dev/null +++ b/src/app/staff/restaurant/restaurant.controller.ts @@ -0,0 +1,18 @@ +import { Rest } from '@core/decorators/restaurant.decorator'; +import { StaffAuthGuard } from '@core/guards/auth.guard'; +import { StaffGuard } from '@core/guards/staff.guard'; +import { PermAct, PermStaff } from '@core/services/role.service'; +import { RestaurantTransformer } from '@db/transformers/restaurant.transformer'; +import { Permissions } from '@lib/rbac'; +import { Controller, Get, Res, UseGuards } from '@nestjs/common'; + +@Controller() +@UseGuards(StaffAuthGuard()) +export class StaffRestaurantController { + @Get() + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Restaurant}@${PermAct.R}`) + async index(@Rest() rest, @Res() response) { + return response.item(rest, RestaurantTransformer); + } +} diff --git a/src/app/staff/restaurant/restaurant.module.ts b/src/app/staff/restaurant/restaurant.module.ts new file mode 100644 index 0000000..98767fb --- /dev/null +++ b/src/app/staff/restaurant/restaurant.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { StafffNotificationModule } from './notification/notification.module'; +import { StaffOrderModule } from './order/order.module'; +import { StaffRestaurantController } from './restaurant.controller'; + +@Module({ + imports: [StaffOrderModule, StafffNotificationModule], + controllers: [StaffRestaurantController], + providers: [], +}) +export class StaffRestaurantModule {} diff --git a/src/app/staff/restaurant/stock/stock.controller.ts b/src/app/staff/restaurant/stock/stock.controller.ts new file mode 100644 index 0000000..ac37403 --- /dev/null +++ b/src/app/staff/restaurant/stock/stock.controller.ts @@ -0,0 +1,250 @@ +import { Loc } from '@core/decorators/location.decorator'; +import { Rest } from '@core/decorators/restaurant.decorator'; +import { Me } from '@core/decorators/user.decorator'; +import { StaffAuthGuard } from '@core/guards/auth.guard'; +import { StaffGuard } from '@core/guards/staff.guard'; +import { PermAct, PermStaff } from '@core/services/role.service'; +import { Location } from '@db/entities/owner/location.entity'; +import { ProductStock } from '@db/entities/owner/product-stock.entity'; +import { ProductVariant } from '@db/entities/owner/product-variant.entity'; +import { Product, ProductStatus } from '@db/entities/owner/product.entity'; +import { VariantStatus } from '@db/entities/owner/variant.entity'; +import { StaffUser } from '@db/entities/staff/user.entity'; +import { StockTransformer } from '@db/transformers/stock.transformer'; +import { ValidationException } from '@lib/exceptions/validation.exception'; +import { Validator } from '@lib/helpers/validator.helper'; +import Socket, { PubSubEventType, PubSubPayloadType, PubSubStatus } from '@lib/pubsub/pubsub.lib'; +import { Permissions } from '@lib/rbac'; +import AppDataSource from '@lib/typeorm/datasource.typeorm'; +import { uuid } from '@lib/uid/uuid.library'; +import { + BadRequestException, + Body, + Controller, + Get, + NotFoundException, + Param, + Post, + Put, + Res, + UseGuards, +} from '@nestjs/common'; +import { get } from 'lodash'; +import { In, IsNull } from 'typeorm'; + +@Controller() +@UseGuards(StaffAuthGuard()) +export class StockController { + @Get() + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Stock}@${PermAct.R}`) + async index(@Rest() rest, @Res() response, @Loc() loc: Location) { + const query = AppDataSource.createQueryBuilder(ProductStock, 't1').where({ + restaurant_id: rest.id, + }); + + if (loc) { + query.andWhere({ location_id: loc.id }); + } + + const stocks = await query.search().sort().getPaged(); + await response.paginate(stocks, StockTransformer); + } + + @Get('/:stock_id') + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Stock}@${PermAct.R}`) + async show(@Rest() rest, @Res() response, @Param() param) { + const stock = await ProductStock.findOneByOrFail({ + restaurant_id: rest.id, + id: param.stock_id, + }); + await response.item(stock, StockTransformer); + } + + @Post() + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Stock}@${PermAct.C}`) + async create(@Rest() rest, @Body() body, @Res() response, @Me() me: StaffUser) { + const rules = { + products: 'required|array', + location_ids: 'required|array|uid', + }; + const validation = Validator.init(body, rules); + if (validation.fails()) { + throw new ValidationException(validation); + } + + const request_id = uuid(); + + const progress = async () => { + const stats = { success: [], fails: [] }; + + try { + const items = await Product.findBy({ + id: In(body.products.map((val) => val.id)), + restaurant_id: rest.id, + }); + + if (!items.length) { + throw new BadRequestException(`There is no products found`); + } + + const locations = await Location.findBy({ + id: In(body.location_ids), + restaurant_id: rest.id, + }); + + if (!locations.length) { + throw new BadRequestException(`There is no locations found`); + } + + const stocks: ProductStock[] = []; + + for (const item of body.products) { + const product = items.find((val) => val.id === item.id); + + const where = { product_id: product.id }; + + if (item.variant_id) { + Object.assign(where, { ...where, variant_id: item.variant_id }); + } + + const productVariant = await ProductVariant.findOneBy(where); + + if (!productVariant) { + throw new NotFoundException(`Can't found ${product.sku} match with ID Variant ${item.variant_id}`); + } + + for (const location of locations) { + try { + const isExist = await ProductStock.exists({ + where: { + location_id: location.id, + product_id: product.id, + variant_id: productVariant.id, + }, + }); + + const productFullName = await productVariant.getFullName(); + + if (product.status == ProductStatus.Discontinued) { + throw new BadRequestException(`Can't add initial stock, ${productFullName} status is discontinued`); + } + + if (isExist) { + throw new BadRequestException(`Product ${productFullName} already exist at ${location.name}`); + } + + // Count Product Variants (to check if it has parent, so it should be skipped) + const countVariant = await ProductVariant.count({ + where: { product_id: product.id }, + }); + if (countVariant > 1) { + const check = await ProductVariant.exists({ + where: { id: productVariant.id, variant_id: IsNull() }, + }); + if (check) { + // Skip if it's a Parent + throw new BadRequestException( + `Product ${productFullName} are skipped because it has variants. Please choose the variant.` + ); + } + } + + const quantity = body.products.find((val) => val.id === product.id); + + const action = `Initial Stock: ${productFullName} at ${location.name}`; + const productStock = new ProductStock(); + productStock.product_id = productVariant.product_id; + productStock.variant_id = productVariant.id; + productStock.location_id = location.id; + productStock.onhand = get(quantity, 'qty', 0); + productStock.restaurant_id = rest.id; + productStock.last_action = action; + productStock.actor = me.logName; + stocks.push(productStock); + + stats.success.push(`Product ${productFullName} has been added to ${location.name}`); + } catch (error) { + stats.fails.push(error.message); + } + } + } + + if (stocks.length > 0) { + await ProductStock.save(stocks); + } + } catch (error) { + stats.fails.push(error.message); + } + + return stats; + }; + + progress() + .then(({ success, fails }) => { + const payload = [...fails, ...success]; + + Socket.getInstance().event(me.id, { + request_id, + status: fails.length > 0 || !success.length ? PubSubStatus.Warning : PubSubStatus.Success, + type: PubSubEventType.StaffCreateStock, + payload: { + type: PubSubPayloadType.Dialog, + body: payload.length > 50 ? [...payload.slice(0, 50), 'and more...'] : payload, + }, + }); + }) + .catch((error) => { + Socket.getInstance() + .event(me.id, { + request_id, + status: PubSubStatus.Fail, + type: PubSubEventType.StaffCreateStock, + error: error.message, + }) + .catch((error) => console.log(error)); + }); + + return response.data({ request_id }); + } + + @Put('/:stock_id') + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Stock}@${PermAct.U}`) + async update(@Rest() rest, @Body() body, @Res() response, @Param() param) { + const rules = { + onhand: 'required|numeric|min:0', + }; + const validation = Validator.init(body, rules); + if (validation.fails()) { + throw new ValidationException(validation); + } + + const productStock = await ProductStock.findOneByOrFail({ + restaurant_id: rest.id, + id: param.stock_id, + }); + const product = await productStock.product; + const variant = await productStock.variant; + + // @TODO: How about changing parent stock that affected to their variants stock + + if (variant === null) { + // Set all product variants to unavailable when parent are 0 + if (Number(body.onhand) <= 0) { + await ProductStock.update({ product_id: product.id }, { onhand: 0 }); + await ProductVariant.update({ product_id: product.id }, { status: VariantStatus.Unvailable }); + + product.status = ProductStatus.Unvailable; + await product.save(); + } + } else { + productStock.onhand = Number(body.onhand); + await productStock.save(); + } + + await response.item(productStock, StockTransformer); + } +} diff --git a/src/app/staff/restaurant/stock/stock.module.ts b/src/app/staff/restaurant/stock/stock.module.ts new file mode 100644 index 0000000..5cfe886 --- /dev/null +++ b/src/app/staff/restaurant/stock/stock.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { StockController } from './stock.controller'; + +@Module({ + imports: [], + controllers: [StockController], + providers: [], +}) +export class StaffStockModule {} diff --git a/src/app/staff/restaurant/table/table.controller.ts b/src/app/staff/restaurant/table/table.controller.ts new file mode 100644 index 0000000..15c0d99 --- /dev/null +++ b/src/app/staff/restaurant/table/table.controller.ts @@ -0,0 +1,189 @@ +import { Loc } from '@core/decorators/location.decorator'; +import { Rest } from '@core/decorators/restaurant.decorator'; +import { Me } from '@core/decorators/user.decorator'; +import { StaffAuthGuard } from '@core/guards/auth.guard'; +import { StaffGuard } from '@core/guards/staff.guard'; +import { PdfService } from '@core/services/pdf.service'; +import { PermAct, PermStaff } from '@core/services/role.service'; +import { UtilService } from '@core/services/util.service'; +import { Location } from '@db/entities/owner/location.entity'; +import { Restaurant } from '@db/entities/owner/restaurant.entity'; +import { Table, TableStatus } from '@db/entities/owner/table.entity'; +import { StaffUser } from '@db/entities/staff/user.entity'; +import { TableTransformer } from '@db/transformers/table.transformer'; +import { ValidationException } from '@lib/exceptions/validation.exception'; +import { config } from '@lib/helpers/config.helper'; +import { time } from '@lib/helpers/time.helper'; +import { writeFile } from '@lib/helpers/utils.helper'; +import { Validator } from '@lib/helpers/validator.helper'; +import Logger from '@lib/logger/logger.library'; +import Socket, { PubSubEventType, PubSubPayloadType, PubSubStatus } from '@lib/pubsub/pubsub.lib'; +import { Permissions } from '@lib/rbac'; +import AppDataSource from '@lib/typeorm/datasource.typeorm'; +import { uuid } from '@lib/uid/uuid.library'; +import { BadRequestException, Body, Controller, Get, Param, Post, Put, Res, UseGuards } from '@nestjs/common'; +import { In, Not } from 'typeorm'; + +@Controller() +@UseGuards(StaffAuthGuard()) +export class TableController { + constructor(private pdf: PdfService, private util: UtilService) {} + + @Get() + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Table}@${PermAct.R}`) + async index(@Rest() rest, @Res() response, @Loc() loc: Location) { + const tables = AppDataSource.createQueryBuilder(Table, 't1'); + tables.where({ restaurant_id: rest.id }); + + if (loc && loc.id) { + tables.andWhere({ location_id: loc.id }); + } + + const data = await tables.search().sort().getPaged(); + + await response.paginate(data, TableTransformer); + } + + @Get('/:table_id') + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Table}@${PermAct.R}`) + async show(@Rest() rest, @Res() response, @Param() param) { + const table = await Table.findOneByOrFail({ restaurant_id: rest.id, id: param.table_id }); + await response.item(table, TableTransformer); + } + + @Post() + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Table}@${PermAct.C}`) + async create(@Rest() rest: Restaurant, @Body() body, @Res() response) { + const rules = { + number: 'required|unique|safe_text', + location_id: 'required', + status: `required|in:${Object.values(TableStatus).join(',')}`, + }; + const validation = Validator.init(body, rules); + if (validation.fails()) { + throw new ValidationException(validation); + } + + const loc = await Location.findOneByOrFail({ id: body.location_id }); + + const tableExist = await Table.exists({ where: { number: body.number, restaurant_id: rest.id, location_id: loc.id } }); + if (tableExist) { + throw new BadRequestException('Table has already existed.'); + } + + const table = new Table(); + table.number = body.number; + table.status = body.status; + table.location_id = loc.id; + table.restaurant_id = rest.id; + await table.save(); + + return response.item(table, TableTransformer); + } + + @Put('/:table_id') + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Table}@${PermAct.U}`) + async update(@Rest() rest: Restaurant, @Body() body, @Res() response, @Param() param) { + const rules = { + number: 'required|unique|safe_text', + location_id: 'required', + status: `required|in:${Object.values(TableStatus).join(',')}`, + }; + const validation = Validator.init(body, rules); + if (validation.fails()) { + throw new ValidationException(validation); + } + + if (!param.table_id) { + throw new BadRequestException(); + } + + const table = await Table.findOneByOrFail({ id: param.table_id }); + + const loc = await Location.findOneByOrFail({ id: table.location_id }); + + const tableExist = await Table.exists({ + where: { number: body.number, restaurant_id: rest.id, location_id: loc.id, id: Not(table.id) }, + }); + if (tableExist) { + throw new BadRequestException('Table has already existed.'); + } + + table.number = body.number; + table.status = body.status; + await table.save(); + + return response.item(table, TableTransformer); + } + + @Post('/label') + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Table}@${PermAct.U}`) + async generateLabel(@Body() body, @Res() response, @Me() me: StaffUser, @Rest() restaurant: Restaurant) { + const rules = { + table_ids: 'required|array|uid', + }; + const validation = Validator.init(body, rules); + if (validation.fails()) { + throw new ValidationException(validation); + } + + const request_id = uuid(); + const processing = async (): Promise => { + const data = []; + const tables = await Table.find({ where: { id: In(body.table_ids) } }); + const locations = await Location.find({ where: { id: In(tables.map((val) => val.location_id)) } }); + for (const table of tables) { + const url = `${config.getAppURI()}/tables/${table.id}`; + const barcode = await this.util.getQrCode(url, { scale: 2 }); + const location = locations.find((val) => val.id === table.location_id); + data.push({ + name: table.number, + restaurant: restaurant.name, + location: location.name, + barcode: `data:image/png;base64,${barcode.toString('base64')}`, + }); + } + + const pdf = await this.pdf.tableLabel(data); + const dir = `${config.getPublicPath()}/files`; + const filename = `${time().unix()}-tables-label.pdf`; + return writeFile(dir, filename, pdf); + }; + + processing() + .then((path) => { + // send event + Socket.getInstance().event(me.id, { + request_id, + status: PubSubStatus.Success, + type: PubSubEventType.StaffGetTableLabel, + payload: { + type: PubSubPayloadType.Download, + body: { + mime: 'text/href', + name: `${time().unix()}-tables-label.pdf`, + content: config.getDownloadURI(path), + }, + }, + }); + }) + .catch(async (error) => { + // send event + Socket.getInstance().event(me.id, { + request_id, + status: PubSubStatus.Fail, + type: PubSubEventType.StaffGetTableLabel, + error: error.message, + }); + + Logger.getInstance().notify(error); + }); + + return response.data({ request_id }); + } +} diff --git a/src/app/staff/restaurant/table/table.module.ts b/src/app/staff/restaurant/table/table.module.ts new file mode 100644 index 0000000..6a06566 --- /dev/null +++ b/src/app/staff/restaurant/table/table.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { TableController } from './table.controller'; + +@Module({ + imports: [], + controllers: [TableController], + providers: [], +}) +export class StaffTableModule {} diff --git a/src/app/staff/restaurant/variant/group.controller.ts b/src/app/staff/restaurant/variant/group.controller.ts new file mode 100644 index 0000000..2319508 --- /dev/null +++ b/src/app/staff/restaurant/variant/group.controller.ts @@ -0,0 +1,100 @@ +import { Rest } from '@core/decorators/restaurant.decorator'; +import { StaffAuthGuard } from '@core/guards/auth.guard'; +import { StaffGuard } from '@core/guards/staff.guard'; +import { PermAct, PermStaff } from '@core/services/role.service'; +import { VariantGroup, VariantGroupType } from '@db/entities/owner/variant-group.entity'; +import { VariantGroupTransformer } from '@db/transformers/variant-group.transformer'; +import { ValidationException } from '@lib/exceptions/validation.exception'; +import { isTrue } from '@lib/helpers/utils.helper'; +import { Validator } from '@lib/helpers/validator.helper'; +import { Permissions } from '@lib/rbac'; +import AppDataSource from '@lib/typeorm/datasource.typeorm'; +import { BadRequestException, Body, Controller, Get, Param, Post, Put, Res, UseGuards } from '@nestjs/common'; +import { Not } from 'typeorm'; + +@Controller('groups') +@UseGuards(StaffAuthGuard()) +export class VariantGroupController { + @Get() + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Variant}@${PermAct.R}`) + async index(@Rest() rest, @Res() response) { + const groups = await AppDataSource.createQueryBuilder(VariantGroup, 't1') + .where({ restaurant_id: rest.id }) + .search() + .sort() + .getPaged(); + await response.paginate(groups, VariantGroupTransformer); + } + + @Get('/:group_id') + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Variant}@${PermAct.R}`) + async show(@Rest() rest, @Res() response, @Param() param) { + const group = await VariantGroup.findOneByOrFail({ restaurant_id: rest.id, id: param.group_id }); + await response.item(group, VariantGroupTransformer); + } + + @Post() + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Variant}@${PermAct.C}`) + async create(@Rest() rest, @Body() body, @Res() response) { + const rules = { + name: 'required|unique|safe_text', + type: `required|in:${Object.values(VariantGroupType).join(',')}`, + required: 'required|boolean', + }; + const validation = Validator.init(body, rules); + if (validation.fails()) { + throw new ValidationException(validation); + } + + const groupExist = await VariantGroup.exists({ where: { name: body.name, restaurant_id: rest.id } }); + if (groupExist) { + throw new BadRequestException('Variant Group has already existed.'); + } + + const group = new VariantGroup(); + group.name = body.name; + group.type = body.type; + group.required = isTrue(body.required); + group.restaurant_id = rest.id; + await group.save(); + + return response.item(group, VariantGroupTransformer); + } + + @Put('/:group_id') + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Variant}@${PermAct.U}`) + async update(@Rest() rest, @Body() body, @Res() response, @Param() param) { + const rules = { + name: 'required|unique|safe_text', + type: `required|in:${Object.values(VariantGroupType).join(',')}`, + required: 'required|boolean', + }; + const validation = Validator.init(body, rules); + if (validation.fails()) { + throw new ValidationException(validation); + } + + if (!param.group_id) { + throw new BadRequestException(); + } + + const groupExist = await VariantGroup.exists({ + where: { name: body.name, restaurant_id: rest.id, id: Not(param.group_id) }, + }); + if (groupExist) { + throw new BadRequestException('Variant Group has already existed.'); + } + + const group = await VariantGroup.findOneByOrFail({ id: param.group_id }); + group.name = body.name; + group.type = body.type; + group.required = isTrue(body.required); + await group.save(); + + return response.item(group, VariantGroupTransformer); + } +} diff --git a/src/app/staff/restaurant/variant/variant.controller.ts b/src/app/staff/restaurant/variant/variant.controller.ts new file mode 100644 index 0000000..ffb32cc --- /dev/null +++ b/src/app/staff/restaurant/variant/variant.controller.ts @@ -0,0 +1,108 @@ +import { Rest } from '@core/decorators/restaurant.decorator'; +import { StaffAuthGuard } from '@core/guards/auth.guard'; +import { StaffGuard } from '@core/guards/staff.guard'; +import { PermAct, PermStaff } from '@core/services/role.service'; +import { VariantGroup } from '@db/entities/owner/variant-group.entity'; +import { Variant, VariantStatus } from '@db/entities/owner/variant.entity'; +import { VariantTransformer } from '@db/transformers/variant.transformer'; +import { ValidationException } from '@lib/exceptions/validation.exception'; +import { Validator } from '@lib/helpers/validator.helper'; +import { Permissions } from '@lib/rbac'; +import AppDataSource from '@lib/typeorm/datasource.typeorm'; +import { BadRequestException, Body, Controller, Get, Param, Post, Put, Res, UseGuards } from '@nestjs/common'; +import { Not } from 'typeorm'; + +@Controller() +@UseGuards(StaffAuthGuard()) +export class VariantController { + @Get() + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Variant}@${PermAct.R}`) + async index(@Rest() rest, @Res() response) { + const variants = await AppDataSource.createQueryBuilder(Variant, 't1') + .where({ restaurant_id: rest.id }) + .search() + .sort() + .getPaged(); + await response.paginate(variants, VariantTransformer); + } + + @Get('/:variant_id') + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Variant}@${PermAct.R}`) + async show(@Rest() rest, @Res() response, @Param() param) { + const variant = await Variant.findOneByOrFail({ restaurant_id: rest.id, id: param.variant_id }); + await response.item(variant, VariantTransformer); + } + + @Post() + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Variant}@${PermAct.C}`) + async create(@Rest() rest, @Body() body, @Res() response) { + const rules = { + name: 'required|unique|safe_text', + price: 'required|numeric|min:0', + status: `required|in:${Object.values(VariantStatus).join(',')}`, + group_id: 'required|uid', + }; + const validation = Validator.init(body, rules); + if (validation.fails()) { + throw new ValidationException(validation); + } + + const variantExist = await Variant.exists({ where: { name: body.name, restaurant_id: rest.id } }); + if (variantExist) { + throw new BadRequestException('Variant has already existed.'); + } + + const group = await VariantGroup.findOneByOrFail({ id: body.group_id }); + + const variant = new Variant(); + variant.name = body.name; + variant.price = body.price; + variant.status = body.status; + variant.restaurant_id = rest.id; + variant.group_id = group.id; + await variant.save(); + + return response.item(variant, VariantTransformer); + } + + @Put('/:variant_id') + @UseGuards(StaffGuard) + @Permissions(`${PermStaff.Variant}@${PermAct.U}`) + async update(@Rest() rest, @Body() body, @Res() response, @Param() param) { + const rules = { + name: 'required|unique|safe_text', + price: 'required|numeric|min:0', + status: `required|in:${Object.values(VariantStatus).join(',')}`, + group_id: 'required|uid', + }; + const validation = Validator.init(body, rules); + if (validation.fails()) { + throw new ValidationException(validation); + } + + if (!param.variant_id) { + throw new BadRequestException(); + } + + const variantExist = await Variant.exists({ + where: { name: body.name, restaurant_id: rest.id, id: Not(param.variant_id) }, + }); + if (variantExist) { + throw new BadRequestException('Variant has already existed.'); + } + + const group = await VariantGroup.findOneByOrFail({ id: body.group_id }); + + const variant = await Variant.findOneByOrFail({ id: param.variant_id }); + variant.name = body.name; + variant.price = body.price; + variant.status = body.status; + variant.group_id = group.id; + await variant.save(); + + return response.item(variant, VariantTransformer); + } +} diff --git a/src/app/staff/restaurant/variant/variant.module.ts b/src/app/staff/restaurant/variant/variant.module.ts new file mode 100644 index 0000000..56f0098 --- /dev/null +++ b/src/app/staff/restaurant/variant/variant.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { VariantGroupController } from './group.controller'; +import { VariantController } from './variant.controller'; + +@Module({ + imports: [], + controllers: [VariantController, VariantGroupController], + providers: [], +}) +export class StaffVariantModule {} diff --git a/src/app/staff/staff.module.ts b/src/app/staff/staff.module.ts new file mode 100644 index 0000000..127a2cb --- /dev/null +++ b/src/app/staff/staff.module.ts @@ -0,0 +1,25 @@ +import { Module } from '@nestjs/common'; +import { StaffAuthModule } from './auth/auth.module'; +import { StaffProfileModule } from './profile/profile.module'; +import { StaffCategoryModule } from './restaurant/category/category.module'; +import { StaffProductModule } from './restaurant/product/product.module'; +import { StaffRestaurantModule } from './restaurant/restaurant.module'; +import { StaffStockModule } from './restaurant/stock/stock.module'; +import { StaffTableModule } from './restaurant/table/table.module'; +import { StaffVariantModule } from './restaurant/variant/variant.module'; + +@Module({ + imports: [ + StaffAuthModule, + StaffProfileModule, + StaffRestaurantModule, + StaffStockModule, + StaffTableModule, + StaffCategoryModule, + StaffVariantModule, + StaffProductModule, + ], + controllers: [], + providers: [], +}) +export class StaffModule {} diff --git a/src/config/database.config.ts b/src/config/database.config.ts index 5237533..349db69 100644 --- a/src/config/database.config.ts +++ b/src/config/database.config.ts @@ -71,6 +71,7 @@ const development: TypeOrmModuleOptions = { username: config.get('DATABASE_USER'), password: config.get('DATABASE_PASSWORD'), database: config.get('DATABASE_NAME'), + synchronize: true, }; export const database: TypeOrmModuleOptions = config.isDevelopment() ? development : production; diff --git a/src/core/core.module.ts b/src/core/core.module.ts index 54b6303..3ab957a 100644 --- a/src/core/core.module.ts +++ b/src/core/core.module.ts @@ -11,11 +11,23 @@ import { AuthService } from './services/auth.service'; import { AwsService } from './services/aws.service'; import { CustomerService } from './services/customer.service'; import { PdfService } from './services/pdf.service'; +import { ProductService } from './services/product.service'; import { RoleService } from './services/role.service'; import { TaskService } from './services/task.service'; -import { JwtOwnerStrategy, JwtStrategy } from './services/token.service'; +import { JwtOwnerStrategy, JwtStaffStrategy, JwtStrategy } from './services/token.service'; import { UtilService } from './services/util.service'; +const services = [ + AwsService, + TaskService, + RoleService, + AuthService, + CustomerService, + ProductService, + PdfService, + UtilService, +]; + @Global() @Module({ imports: [ @@ -26,15 +38,10 @@ import { UtilService } from './services/util.service'; MulterExtendedModule.register(storage.aws), ], providers: [ - AwsService, - TaskService, + ...services, JwtStrategy, - AuthService, - RoleService, - CustomerService, - PdfService, - UtilService, JwtOwnerStrategy, + JwtStaffStrategy, { provide: DataSource, useFactory: async () => { @@ -42,19 +49,6 @@ import { UtilService } from './services/util.service'; }, }, ], - exports: [ - AwsService, - TaskService, - RoleService, - AuthService, - CustomerService, - PdfService, - UtilService, - JwtStrategy, - JwtOwnerStrategy, - DataSource, - RBAcModule, - MulterExtendedModule, - ], + exports: [...services, JwtStrategy, JwtOwnerStrategy, JwtStaffStrategy, DataSource, RBAcModule, MulterExtendedModule], }) export class CoreModule {} diff --git a/src/core/guards/auth.guard.ts b/src/core/guards/auth.guard.ts index ef7167e..d4b8ac2 100644 --- a/src/core/guards/auth.guard.ts +++ b/src/core/guards/auth.guard.ts @@ -97,16 +97,6 @@ function createAuthGuard(type: string | string[] = 'jwt'): Type { export const AuthGuard: (type?: string | string[]) => Type = memoize(createAuthGuard); -function createOwnerAuthGuard(type: string | string[] = 'jwt-owner'): Type { - class MixinAuthGuard extends BaseAuthGuard implements CanActivate { - protected type: string | string[] = type; - } - - return mixin(MixinAuthGuard); -} - -export const OwnerAuthGuard: (type?: string | string[]) => Type = memoize(createOwnerAuthGuard); - function createBasicAuthGuard(type: string | string[] = 'jwt'): Type { class MixinAuthGuard extends BaseAuthGuard implements CanActivate { protected type: string | string[] = type; @@ -135,3 +125,25 @@ function createApiGuard(type: string | string[] = 'bearer'): Type { } export const ApiGuard: (type?: string | string[]) => Type = memoize(createApiGuard); + +// Owner Guard +function createOwnerAuthGuard(type: string | string[] = 'jwt-owner'): Type { + class MixinAuthGuard extends BaseAuthGuard implements CanActivate { + protected type: string | string[] = type; + } + + return mixin(MixinAuthGuard); +} + +export const OwnerAuthGuard: (type?: string | string[]) => Type = memoize(createOwnerAuthGuard); + +// Staff Guard +function createStaffAuthGuard(type: string | string[] = 'jwt-staff'): Type { + class MixinAuthGuard extends BaseAuthGuard implements CanActivate { + protected type: string | string[] = type; + } + + return mixin(MixinAuthGuard); +} + +export const StaffAuthGuard: (type?: string | string[]) => Type = memoize(createStaffAuthGuard); diff --git a/src/core/guards/staff.guard.ts b/src/core/guards/staff.guard.ts new file mode 100644 index 0000000..d4331f6 --- /dev/null +++ b/src/core/guards/staff.guard.ts @@ -0,0 +1,90 @@ +import { Location } from '@db/entities/owner/location.entity'; +import { Restaurant } from '@db/entities/owner/restaurant.entity'; +import { StaffRole } from '@db/entities/staff/role.entity'; +import { StaffUser } from '@db/entities/staff/user.entity'; +import { GuardException } from '@lib/exceptions/guard.exception'; +import { TokenException } from '@lib/exceptions/token.exception'; +import { ParamsFilter, RBAC_REQUEST_FILTER, RbacService } from '@lib/rbac'; +import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { get } from 'lodash'; + +const validateRestaurant = async (request: any) => { + console.log(request); + const user: StaffUser = request.user; + if (!user) { + throw new TokenException('Getting user was failed'); + } + + const restaurantId = + get(request, 'params.restaurant_id') || get(request, 'headers.x-restaurant-id') || get(user, 'restaurant_id'); + if (!restaurantId) { + throw new GuardException('Restaurant is not found'); + } + + const restaurant = await Restaurant.findOneBy({ id: restaurantId }); + if (!restaurant) { + throw new TokenException('Getting restaurant was failed'); + } + + const roles = await StaffRole.findBy({ slug: user.role_slug }); + const role = roles.find((row) => row.slug !== 'owner'); + if (!role) { + throw new GuardException('Getting role was failed'); + } + + let location: Location = null; + if (user.location_id) { + location = await Location.findOneBy({ id: user.location_id }); + } + + return { user, restaurant, roles, role, location }; +}; + +export const setupPermission = async (request) => { + const { restaurant, role, roles, location } = await validateRestaurant(request); + + // assign additional data to request object + Object.assign(request, { + current: { + restaurant: restaurant, + role, + location, + }, + }); + + // filter based on available modules on plan + const grants = roles.reduce((acc, cur) => { + acc[cur.slug] = cur.permissions || []; + return acc; + }, {}); + + // assign grants to request helper + (request as any).requestContext.set('grants', grants); + + const filter = new ParamsFilter(); + filter.setParam(RBAC_REQUEST_FILTER, { ...request }); + + return { restaurant, role, roles, location, filter }; +}; + +@Injectable() +export class StaffGuard implements CanActivate { + constructor(private readonly reflector: Reflector, private readonly rbacService: RbacService) {} + + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + + const permissions = this.reflector.get('Permissions', context.getHandler()); + if (!permissions) { + throw new GuardException('Bad permission'); + } + + const { role, filter } = await setupPermission(request); + if (!(await this.rbacService.getRole(role.slug, filter)).can(...permissions)) { + throw new GuardException('Forbidden resource'); + } + + return true; + } +} diff --git a/src/core/services/jwt.service.ts b/src/core/services/jwt.service.ts deleted file mode 100644 index bd02fc6..0000000 --- a/src/core/services/jwt.service.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { jwt } from '@config/jwt.config'; -import { Owner } from '@db/entities/owner/owner.entity'; -import { TokenException } from '@lib/exceptions/token.exception'; -import { Injectable } from '@nestjs/common'; -import { PassportStrategy } from '@nestjs/passport'; -import { ExtractJwt, Strategy, VerifiedCallback } from 'passport-jwt'; - -@Injectable() -export class JwtOwnerStrategy extends PassportStrategy(Strategy, 'jwt-owner') { - constructor() { - super({ - jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - ignoreExpiration: false, - secretOrKey: jwt.secret, - }); - } - - // exposing payload - async validate(payload: any, done: VerifiedCallback) { - const user = await Owner.findOne(payload.sub); - if (!user) { - return done(new TokenException('Invalid token supplied'), false); - } - - return done(null, user, payload.iat); - } -} diff --git a/src/core/services/product.service.ts b/src/core/services/product.service.ts new file mode 100644 index 0000000..ed5f5ca --- /dev/null +++ b/src/core/services/product.service.ts @@ -0,0 +1,179 @@ +import { Category } from '@db/entities/owner/category.entity'; +import { ProductCategory } from '@db/entities/owner/product-category.entity'; +import { ProductVariant } from '@db/entities/owner/product-variant.entity'; +import { Product } from '@db/entities/owner/product.entity'; +import { Restaurant } from '@db/entities/owner/restaurant.entity'; +import { VariantGroup } from '@db/entities/owner/variant-group.entity'; +import { Variant, VariantStatus } from '@db/entities/owner/variant.entity'; +import AppDataSource from '@lib/typeorm/datasource.typeorm'; +import { BadRequestException, Injectable } from '@nestjs/common'; +import { In } from 'typeorm'; + +@Injectable() +export class ProductService { + async create(rest: Restaurant, body: any): Promise { + try { + const productExist = await Product.exists({ where: { sku: body.sku, restaurant_id: rest.id } }); + if (productExist) { + throw new BadRequestException('Product SKU has already existed.'); + } + + const prod = new Product(); + prod.sku = body.sku; + prod.name = body.name; + prod.description = body.description; + prod.price = body.price; + prod.status = body.status; + prod.restaurant_id = rest.id; + + let categories: Category[] = []; + let variants: Variant[] = []; + + if (body.category_ids.length > 0) { + categories = await Category.find({ where: { id: In(body.category_ids), restaurant_id: rest.id } }); + } + + if (body.variant_ids.length > 0) { + variants = await Variant.find({ where: { id: In(body.variant_ids), restaurant_id: rest.id } }); + } + + await AppDataSource.transaction(async (manager) => { + // Save Product + await manager.getRepository(Product).save(prod); + + // Save Category + if (categories.length > 0) { + const pcats: ProductCategory[] = []; + for (const category of categories) { + const pcat = new ProductCategory(); + pcat.product_id = prod.id; + pcat.category_id = category.id; + pcats.push(pcat); + } + + await manager.getRepository(ProductCategory).save(pcats); + } + + const pvars: ProductVariant[] = []; + + // Create default variant for single product + const pvar = new ProductVariant(); + pvar.product_id = prod.id; + pvar.price = prod.price; + pvar.status = VariantStatus.Available; + pvar.restaurant_id = rest.id; + pvars.push(pvar); + + // Save Variants + if (variants.length > 0) { + for (const variant of variants) { + const vari = new ProductVariant(); + vari.status = variant.status; + vari.product_id = prod.id; + vari.variant_id = variant.id; + vari.price = variant.price; + vari.restaurant_id = rest.id; + pvars.push(vari); + } + } + + await manager.getRepository(ProductVariant).save(pvars); + + // @TODO: Create Product History + }); + + return prod; + } catch (error) { + throw error; + } + } + + async assignCategories(rest: Restaurant, id: string, category_ids: string[]): Promise { + try { + const product = await Product.findOrFail({ where: { id, restaurant_id: rest.id } }); + const prodCategories: ProductCategory[] = []; + + const categories = await Category.findBy({ id: In(category_ids) }); + for (const category of categories) { + const isExist = await ProductCategory.exists({ where: { product_id: product.id, category_id: category.id } }); + if (isExist) { + continue; + } + + const pcat = new ProductCategory(); + pcat.product_id = product.id; + pcat.category_id = category.id; + prodCategories.push(pcat); + } + + if (prodCategories.length > 0) { + await ProductCategory.save(prodCategories); + } + + return product; + } catch (error) { + throw error; + } + } + + async assignVariant(rest: Restaurant, id: string, variant_ids: string[]): Promise { + try { + const product = await Product.findOrFail({ where: { id, restaurant_id: rest.id } }); + const prodVariants: ProductVariant[] = []; + + const variants = await Variant.findBy({ id: In(variant_ids) }); + for (const variant of variants) { + const isExist = await ProductVariant.exists({ where: { product_id: product.id, variant_id: variant.id } }); + if (isExist) { + continue; + } + + const pcat = new ProductVariant(); + pcat.product_id = product.id; + pcat.variant_id = variant.id; + pcat.status = variant.status; + pcat.price = variant.price; + pcat.restaurant_id = rest.id; + prodVariants.push(pcat); + } + + if (prodVariants.length > 0) { + await ProductVariant.save(prodVariants); + } + return product; + } catch (error) { + throw error; + } + } + + async getVariants(id: string, rest: Restaurant): Promise { + const product = await Product.findOrFail({ where: { id, restaurant_id: rest.id } }); + const productVariants = await ProductVariant.findBy({ product_id: product.id }); + const variants = await Variant.find({ + where: { id: In(productVariants.map((val) => val.variant_id)) }, + order: { price: 'ASC' }, + }); + const groups = await VariantGroup.find({ + where: { id: In(variants.map((val) => val.group_id)) }, + order: { name: 'ASC' }, + }); + + const result: any[] = []; + + for (const group of groups) { + const payload = { + ...group, + variants: [], + }; + + for (const variant of variants) { + if (variant.group_id === group.id) { + payload.variants.push(variant); + } + } + + result.push(payload); + } + return result; + } +} diff --git a/src/core/services/role.service.ts b/src/core/services/role.service.ts index 40c89d7..e23f7da 100644 --- a/src/core/services/role.service.ts +++ b/src/core/services/role.service.ts @@ -11,21 +11,35 @@ export enum PermAct { } export enum PermOwner { - Profile = 'profile', - Restaurant = 'restaurant', - Staff = 'staff', - Role = 'role', - Location = 'location', - Table = 'table', - Category = 'category', - Variant = 'variant', - Product = 'product', - Stock = 'stock', - Order = 'order', - Notification = 'notification', + Profile = 'owner_profile', + Restaurant = 'owner_restaurant', + Staff = 'owner_staff', + Role = 'owner_role', + Location = 'owner_location', + Table = 'owner_table', + Category = 'owner_category', + Variant = 'owner_variant', + Product = 'owner_product', + Stock = 'owner_stock', + Order = 'owner_order', + Notification = 'owner_notification', } -export const DefaultPerms = [PermOwner.Profile, PermOwner.Restaurant]; +export enum PermStaff { + Profile = 'staff_profile', + Restaurant = 'staff_restaurant', + Role = 'staff_role', + Location = 'staff_location', + Table = 'staff_table', + Category = 'staff_category', + Variant = 'staff_variant', + Product = 'staff_product', + Stock = 'staff_stock', + Order = 'staff_order', + Notification = 'staff_notification', +} + +export const DefaultPerms = [PermOwner.Profile, PermOwner.Restaurant, PermStaff.Profile]; @Injectable() export class RoleService implements IDynamicStorageRbac { @@ -49,6 +63,19 @@ export class RoleService implements IDynamicStorageRbac { [PermOwner.Stock]: [PermAct.R, PermAct.C, PermAct.U, PermAct.D], [PermOwner.Order]: [PermAct.R, PermAct.C, PermAct.U, PermAct.D], [PermOwner.Notification]: [PermAct.R, PermAct.C, PermAct.U, PermAct.D], + + [PermStaff.Profile]: [PermAct.R, PermAct.C, PermAct.U, PermAct.D], + [PermStaff.Restaurant]: [PermAct.R, PermAct.C, PermAct.U, PermAct.D], + // [PermStaff.Staff]: [PermAct.R, PermAct.C, PermAct.U, PermAct.D], + [PermStaff.Role]: [PermAct.R, PermAct.C, PermAct.U, PermAct.D], + [PermStaff.Location]: [PermAct.R, PermAct.C, PermAct.U, PermAct.D], + [PermStaff.Table]: [PermAct.R, PermAct.C, PermAct.U, PermAct.D], + [PermStaff.Category]: [PermAct.R, PermAct.C, PermAct.U, PermAct.D], + [PermStaff.Variant]: [PermAct.R, PermAct.C, PermAct.U, PermAct.D], + [PermStaff.Product]: [PermAct.R, PermAct.C, PermAct.U, PermAct.D], + [PermStaff.Stock]: [PermAct.R, PermAct.C, PermAct.U, PermAct.D], + [PermStaff.Order]: [PermAct.R, PermAct.C, PermAct.U, PermAct.D], + [PermStaff.Notification]: [PermAct.R, PermAct.C, PermAct.U, PermAct.D], }; return { diff --git a/src/core/services/token.service.ts b/src/core/services/token.service.ts index 958ece2..8b4f097 100644 --- a/src/core/services/token.service.ts +++ b/src/core/services/token.service.ts @@ -1,6 +1,7 @@ import { jwt } from '@config/jwt.config'; import { Customer } from '@db/entities/core/customer.entity'; import { Owner } from '@db/entities/owner/owner.entity'; +import { StaffUser } from '@db/entities/staff/user.entity'; import { TokenException } from '@lib/exceptions/token.exception'; import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; @@ -47,3 +48,24 @@ export class JwtOwnerStrategy extends PassportStrategy(Strategy, 'jwt-owner') { return done(null, user, payload.iat); } } + +@Injectable() +export class JwtStaffStrategy extends PassportStrategy(Strategy, 'jwt-staff') { + constructor() { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + ignoreExpiration: false, + secretOrKey: jwt.secret, + }); + } + + // exposing payload + async validate(payload: any, done: VerifiedCallback) { + const user = await StaffUser.findOneBy({ id: payload.sub }); + if (!user) { + return done(new TokenException('Invalid token supplied'), false); + } + + return done(null, user, payload.iat); + } +} diff --git a/src/database/entities/owner/restaurant.entity.ts b/src/database/entities/owner/restaurant.entity.ts index 00491f3..ec41ac9 100644 --- a/src/database/entities/owner/restaurant.entity.ts +++ b/src/database/entities/owner/restaurant.entity.ts @@ -26,7 +26,7 @@ export class Restaurant extends BaseEntity { @PhoneColumn() phone: string; - @Column({ type: 'longtext' }) + @Column({ type: 'longtext', nullable: true }) description: string; @Column() diff --git a/src/database/entities/staff/user.entity.ts b/src/database/entities/staff/user.entity.ts index 0fe8d60..72c8a80 100644 --- a/src/database/entities/staff/user.entity.ts +++ b/src/database/entities/staff/user.entity.ts @@ -57,7 +57,7 @@ export class StaffUser extends BaseEntity { last_login_at: Date; @OneToOne(() => Media, (media) => media.staff_user, { eager: true }) - media: Media; + image: Media; @Exclude() @ForeignColumn() @@ -85,6 +85,10 @@ export class StaffUser extends BaseEntity { return this.status === StaffUserStatus.Blocked; } + get isActive() { + return [StaffUserStatus.Active].includes(this.status); + } + get logName() { return `${this.name} <${this.email}>`; } diff --git a/src/database/transformers/staff.transformer.ts b/src/database/transformers/staff.transformer.ts index 1406fbf..1501145 100644 --- a/src/database/transformers/staff.transformer.ts +++ b/src/database/transformers/staff.transformer.ts @@ -1,13 +1,15 @@ import { Media } from '@db/entities/core/media.entity'; +import { StaffRole } from '@db/entities/staff/role.entity'; import { StaffUser } from '@db/entities/staff/user.entity'; import { encrypt } from '@lib/helpers/encrypt.helper'; import { RequestHelper } from '@lib/helpers/request.helper'; import { TransformerAbstract } from '@lib/transformer/abstract.transformer'; import { LocationTransformer } from './location.transformer'; +import { RestaurantTransformer } from './restaurant.transformer'; export class StaffTransformer extends TransformerAbstract { get availableInclude() { - return ['location', 'role']; + return ['restaurant', 'location', 'role']; } get defaultInclude() { @@ -16,17 +18,17 @@ export class StaffTransformer extends TransformerAbstract { transform(entity: StaffUser) { // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { media, ...rest } = entity.toJSON(); + const { image, ...rest } = entity.toJSON(); return { ...rest, - avatar: Media.getImage(entity.media), + avatar: Media.getImage(entity.image), }; } async transformWithPermission(entity: StaffUser) { // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { media, ...rest } = entity.toJSON(); + const { image, ...rest } = entity.toJSON(); const grants = RequestHelper.getPermissionGrants(); const role = await entity.role; @@ -44,10 +46,19 @@ export class StaffTransformer extends TransformerAbstract { name: location.name, } : null, - avatar: Media.getImage(entity.media), + avatar: Media.getImage(entity.image), }; } + async includeRestaurant(entity: StaffUser) { + const restaurant = await entity.restaurant; + if (!restaurant) { + return this.null(); + } + + return this.item(restaurant, RestaurantTransformer); + } + async includeLocation(entity: StaffUser) { const location = await entity.location; if (!location) { @@ -58,11 +69,12 @@ export class StaffTransformer extends TransformerAbstract { } async includeRole(entity: StaffUser) { - const role = await entity.role; + const role = await StaffRole.findOneBy({ slug: entity.role_slug }); if (!role) { return this.null(); } - return { id: role.id, name: role.slug }; + const grants = RequestHelper.getPermissionGrants(); + return { id: role.id, name: role.slug, permissions: encrypt(grants[role.slug]) }; } } diff --git a/src/library/pubsub/pubsub.lib.ts b/src/library/pubsub/pubsub.lib.ts index 0ab9e16..db00c30 100644 --- a/src/library/pubsub/pubsub.lib.ts +++ b/src/library/pubsub/pubsub.lib.ts @@ -11,6 +11,11 @@ export enum PubSubEvent { export enum PubSubEventType { OwnerCreateStock = 'owner_create_stock', OwnerGetTableLabel = 'owner_get_table_label', + + // Staff + StaffCreateStock = 'staff_create_stock', + StaffGetTableLabel = 'staff_get_table_label', + // Customer CustomerCreateOrder = 'customer_create_order', } diff --git a/yarn.lock b/yarn.lock index df4ca96..a5dce72 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6375,10 +6375,10 @@ mysql-import@^5.0.21: dependencies: mysql "^2.18.1" -mysql2@^3.10.1: - version "3.10.1" - resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.10.1.tgz#c39b8faf24ef4fd56330ef269122471a22d19198" - integrity sha512-6zo1T3GILsXMCex3YEu7hCz2OXLUarxFsxvFcUHWMpkPtmZLeTTWgRdc1gWyNJiYt6AxITmIf9bZDRy/jAfWew== +mysql2@^3.10.2: + version "3.10.2" + resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.10.2.tgz#37297d5d75d2958e37adc9cd4db865354832d7a4" + integrity sha512-KCXPEvAkO0RcHPr362O5N8tFY2fXvbjfkPvRY/wGumh4EOemo9Hm5FjQZqv/pCmrnuxGu5OxnSENG0gTXqKMgQ== dependencies: denque "^2.1.0" generate-function "^2.3.1"