@posx/core 5.5.586 → 5.5.588

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CLAUDE.md +23 -23
  2. package/LICENSE +21 -21
  3. package/README.md +85 -85
  4. package/build/index.d.ts +80 -1
  5. package/build/index.js +4 -4
  6. package/dev/.stfolder/syncthing-folder-9a95b7.txt +5 -0
  7. package/dev/98894488.xlsx +0 -0
  8. package/dev/HappyThaiSembawang.csv +336 -0
  9. package/dev/KB/create-new-model.md +34 -0
  10. package/dev/KB/markdown-lint.md +14 -0
  11. package/dev/KB/object-clone-pitfalls.md +52 -0
  12. package/dev/KB/readmefirst.md +8 -0
  13. package/dev/KB/stock-deduction-logic.md +61 -0
  14. package/dev/Merchants/HappyThaiSembawang.csv +400 -0
  15. package/dev/Merchants/HappyThaiSembawang.xlsx +0 -0
  16. package/dev/Merchants/charen_thai/category.csv +20 -0
  17. package/dev/Merchants/charen_thai/charen_thai_xpos.csv +1021 -0
  18. package/dev/Merchants/charen_thai/convert.cjs +194 -0
  19. package/dev/Merchants/charen_thai/item.csv +183 -0
  20. package/dev/Merchants/charen_thai/modifier.csv +664 -0
  21. package/dev/Product_Import_Template.xlsx +0 -0
  22. package/dev/XPOS Invoice Module.pdf +232 -0
  23. package/dev/convert_menu.cjs +134 -0
  24. package/dev/convert_menu.py +127 -0
  25. package/dev/data/invoice.json +1 -0
  26. package/dev/escpos/receipt.bin +0 -0
  27. package/dev/escpos/receipt.hex +1 -0
  28. package/dev/escpos/receipt.json +1 -0
  29. package/dev/escpos-cli-usage.md +103 -0
  30. package/dev/export/xpos_menu.csv +1021 -0
  31. package/dev/export/xpos_menu_bilingual.csv +1021 -0
  32. package/dev/export/xpos_retail_sample.csv +38 -0
  33. package/dev/harbor-harness-deployment.md +78 -0
  34. package/dev/incidents/2026-04-01-reprint-timeout.md +33 -0
  35. package/dev/incidents/2026-05-06-searchable-field-design-pitfall.md +37 -0
  36. package/dev/nginx-harbor-harness.conf +84 -0
  37. package/dev/px-cli.md +97 -0
  38. package/dev/test-logs/2026-02.md +5 -0
  39. package/dev/tmp/xpos_product_import(1).csv +338 -0
  40. package/dev/tmp/xpos_product_import_fixed.csv +338 -0
  41. package/dev//344/272/247/345/223/201/345/257/274/345/205/245/346/250/241/346/235/277.xlsx +0 -0
  42. package/jest.config.cjs +36 -36
  43. package/jest.setup.cjs +91 -91
  44. package/package.json +1 -1
  45. package/package.publish.json +121 -121
  46. package/tsdown.config.ts +21 -21
  47. package/vite.config.ts +86 -86
  48. package/AGENTS.md +0 -24
  49. package/memo/technical-docs/01_ARCHITECTURE.md +0 -147
  50. package/memo/technical-docs/02_CORE_BUSINESS.md +0 -292
  51. package/memo/technical-docs/03_UI_COMPONENTS.md +0 -59
  52. package/memo/technical-docs/04_VIEWS.md +0 -82
  53. package/memo/technical-docs/05_DATA_LAYER.md +0 -375
  54. package/memo/technical-docs/06_CROSS_PLATFORM.md +0 -246
  55. package/memo/technical-docs/07_SIMILARITY_INDEX.md +0 -195
  56. package/memo/technical-docs/CHECKPOINT.md +0 -46
  57. package/memo/technical-docs/PROJECT_OVERVIEW.md +0 -122
  58. package/memo/technical-docs/TECHNICAL_DOCS_PLAN.md +0 -77
@@ -0,0 +1,232 @@
1
+ %PDF-1.4
2
+ %���� ReportLab Generated PDF document (opensource)
3
+ 1 0 obj
4
+ <<
5
+ /F1 2 0 R /F2 3 0 R /F3 11 0 R
6
+ >>
7
+ endobj
8
+ 2 0 obj
9
+ <<
10
+ /BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
11
+ >>
12
+ endobj
13
+ 3 0 obj
14
+ <<
15
+ /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
16
+ >>
17
+ endobj
18
+ 4 0 obj
19
+ <<
20
+ /Contents 17 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 16 0 R /Resources <<
21
+ /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
22
+ >> /Rotate 0 /Trans <<
23
+
24
+ >>
25
+ /Type /Page
26
+ >>
27
+ endobj
28
+ 5 0 obj
29
+ <<
30
+ /Contents 18 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 16 0 R /Resources <<
31
+ /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
32
+ >> /Rotate 0 /Trans <<
33
+
34
+ >>
35
+ /Type /Page
36
+ >>
37
+ endobj
38
+ 6 0 obj
39
+ <<
40
+ /Contents 19 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 16 0 R /Resources <<
41
+ /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
42
+ >> /Rotate 0 /Trans <<
43
+
44
+ >>
45
+ /Type /Page
46
+ >>
47
+ endobj
48
+ 7 0 obj
49
+ <<
50
+ /Contents 20 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 16 0 R /Resources <<
51
+ /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
52
+ >> /Rotate 0 /Trans <<
53
+
54
+ >>
55
+ /Type /Page
56
+ >>
57
+ endobj
58
+ 8 0 obj
59
+ <<
60
+ /Contents 21 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 16 0 R /Resources <<
61
+ /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
62
+ >> /Rotate 0 /Trans <<
63
+
64
+ >>
65
+ /Type /Page
66
+ >>
67
+ endobj
68
+ 9 0 obj
69
+ <<
70
+ /Contents 22 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 16 0 R /Resources <<
71
+ /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
72
+ >> /Rotate 0 /Trans <<
73
+
74
+ >>
75
+ /Type /Page
76
+ >>
77
+ endobj
78
+ 10 0 obj
79
+ <<
80
+ /Contents 23 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 16 0 R /Resources <<
81
+ /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
82
+ >> /Rotate 0 /Trans <<
83
+
84
+ >>
85
+ /Type /Page
86
+ >>
87
+ endobj
88
+ 11 0 obj
89
+ <<
90
+ /BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
91
+ >>
92
+ endobj
93
+ 12 0 obj
94
+ <<
95
+ /Contents 24 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 16 0 R /Resources <<
96
+ /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
97
+ >> /Rotate 0 /Trans <<
98
+
99
+ >>
100
+ /Type /Page
101
+ >>
102
+ endobj
103
+ 13 0 obj
104
+ <<
105
+ /Contents 25 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 16 0 R /Resources <<
106
+ /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
107
+ >> /Rotate 0 /Trans <<
108
+
109
+ >>
110
+ /Type /Page
111
+ >>
112
+ endobj
113
+ 14 0 obj
114
+ <<
115
+ /PageMode /UseNone /Pages 16 0 R /Type /Catalog
116
+ >>
117
+ endobj
118
+ 15 0 obj
119
+ <<
120
+ /Author (\(anonymous\)) /CreationDate (D:20260123152929+08'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260123152929+08'00') /Producer (ReportLab PDF Library - \(opensource\))
121
+ /Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
122
+ >>
123
+ endobj
124
+ 16 0 obj
125
+ <<
126
+ /Count 9 /Kids [ 4 0 R 5 0 R 6 0 R 7 0 R 8 0 R 9 0 R 10 0 R 12 0 R 13 0 R ] /Type /Pages
127
+ >>
128
+ endobj
129
+ 17 0 obj
130
+ <<
131
+ /Filter [ /ASCII85Decode /FlateDecode ] /Length 337
132
+ >>
133
+ stream
134
+ Gat=d>t`'h'Sc)J/)JGu1aZAR-/SIB6uc^&]Idt?'AKgq(&%)Zl_J=Za+YcaO>hsX3CcE_P3n\"^no*)Xb9[CYE52gp]oT![V#*rkJSR1Na(g#<Nu9Q12bN/CW<34/s\KP<fES9[:BE'*BPHM:1+7%G@77j==neLN2><B(qn.b(:YipJ;`(pf4E[sTr=0$<>HtODt'Z\"r1Z3s/'F&?Vb1HO+"rQVjg&S1R]($jXL^a/@4J%+9F]f6<S01fP]u9dmG.Ae\LDs$giD6T=4^oM)I?%;k9KCffb+*6X1IGpt4:bq=/qXGG,;l?c^N4T+k!dXXg+),8f&\3I-G-~>endstream
135
+ endobj
136
+ 18 0 obj
137
+ <<
138
+ /Filter [ /ASCII85Decode /FlateDecode ] /Length 391
139
+ >>
140
+ stream
141
+ Gat>R>t`'h'F*LmregGJ"_3aF,eJ1o(i8s^n@EdUZ`[#7(T77ZdnZe.U.>QCdNn;!BhgZt5BoDZ%</:dEQu<'oFa8t.J+#^*PFr?H`'i).;)84PX"+C=KHNd1GCFNWB\HlRd`_>>t$r:*rmD5pi$8:\o8h(kS%5aeHEM&fG[9"D,6j4T[,[kcB#^oQbVIg?T;Z9T(+\VI1:OX1Yh>qh,X)2"QZ=S5VPmVc;l7MWc/.(G!YdD,t"ZmLRM4t0QknC_okB[[.jLSf_4q7T'F2?!k5;f;!TSL]WaL)75KZ'B?N0Pc\5L6k#D24C>0/;_>TKP9[$^.D;cs[#&DA5HUN$!da;fji6f"!AShYk1(3L_`V_C>Che!4BU?h2dChOA&Ua]XYjO=~>endstream
142
+ endobj
143
+ 19 0 obj
144
+ <<
145
+ /Filter [ /ASCII85Decode /FlateDecode ] /Length 1290
146
+ >>
147
+ stream
148
+ Gatm:D/\/e&H;*)ESmc3W@H?%?M85O[\6o\UlAC`=GD?gkZEfi3YZ/Os*i!)fhuS..52%5ki:J^O7%7:+qW[$b\peir05%0?O.2Mfb-j>LQHq;T<m5"N&]'AJN+p(P,&Cfi.`=!&WIUb':OYhkgOKGjtXr4)1!NS!-D59Esa2?c7.;H4lXB0L<E<5\$$tP$J?@-pBe#8A<b@EOFc)0%R;7sJFiq2h]V]PR&1I5D8FCrAIF45]:(Yc$JspN]]UlF'o*=E7[,og=qk8]@4fHe93'uSj;$Q+1f+7rAqQsIm\&CDMUs7Qr'<)<M?_]2.g-d97pML[#$E2KFa\B7mtF0m'iRaKCEG<rQO_\o<0H@(/[VV-bTpt-Q>?4HYbhEO.8_<pcDkUa2/ChHDb%$<X$lpmaI-*]Cfe:6&^Z.7B4V*1!f@=kpleR9aP!MUn6"1s+4)uBL`uCPhi6X&(AP(%;-nAKs'[""Wba@-(kWTEd3r$RAb2QN;09@c1b?2u%DKgc4E[$P;XJ;);!VR1!_d(6/@gEK`iRVcj5>KJ&\`a-@f&o@PW4!`XQ::mk)^G!</7!j)$YFK@O1p08q[!;6[?cfK0Mc?/%G7939fhr.40spEP>nBV9[6geY(A<QOHq!?Xe=tXlT<i[n:-!3aCW@5&sn(a!i0\1e3EHI:9>uD72]^3JFqeCWna$:1*rX<km>-&DCtcNThW\!M<4+'7;.C4JdCW+F.QQns9R<k:WW1hA5)EH(.$JRIRn\A8^*[L%LI$%/qC2&Wh:<PO;`2rjEakBqRu#WdE!,\)t`=BFt%GO-c`7Y''.;lGJ7P:YHL?gIf,]/1+IZ,s6TXes!b:k+>BDeDWtOY]8EpYV"B4?=A!6]u'2hkPF)rINh>-8F]O>DMjZW43u"tIdUDX1gPNjN-#5'69N!Leu,HS]iY^!Mb[2jC9E#(-F>?LVuOJp;U<9o/aq9&>4^kXY&S&Y^_`\o\]XkJlP\*/!om(cnd1UWX7'R@;>]g3VJ&#C+/NL0&Bj%2\FnX1^mQXOi\\'iZo\ef-s+'k4%%D0^RV#S\?tZ5_q**\r3pbM(Ne2q0lmCW!c!du*n,=,SL3RmE&:7SVc1d])FkdJf:su#fWs(U&=cFl0t*\]PAHs0ObqA'Yt+s5H><A4pXbh*`5*Wh!(Vd7'59_[Lqr1\BDqt@q]Sh+k,;>rg_ab`AALeL%!:k(\!D??3%;$(dU8+K*-BZd6$g>_WF0;Ik[c^]X:,oYCu3(R#:Oq\*^c]:&H+_qU]HKS,Ia_&kEEGap)g]#%o!~>endstream
149
+ endobj
150
+ 20 0 obj
151
+ <<
152
+ /Filter [ /ASCII85Decode /FlateDecode ] /Length 1877
153
+ >>
154
+ stream
155
+ Gb!SlgN)%,&:O:Sm$jQ)_[ms09A^XHmAN`kE`".o9h.QJONj.:'G??pJ"O]>'I=EWdlqn2[UJ*%pCQ+cS.u&H5=GIq[fETInFCc)KYh1#,kOE*]Re[mMf0EM+QEUaOEa?[-Fa-o4?n[949LYk1rP@p%0I6]:u>TP2:=KM;d2(RJL&Z'V:,!9i5*)&cAS4/S"a@G(MjItq3mT<j_A\!jN<g%cX?8X(=+:tp\5V(,4P[[?S*62TUGjESZeV['6p)0G>Lo`KlgV,?-O9gM$>`feU638!ZGCJJQEu^&7sKX']MmeK2>nC6.B%d?W=M=c:$fS;GDr`"irV2KCb,-P/O+$_LTK2r2CQMSte=AZ-,G3QgnJVZBrRQ(8cV3UG+%i2t;NbFrNtoZsd@'+K[$E,Dklr(XXVa$]cBCBTF4`T<dW%5L@2PHH^iT>^ZH/")G4j^5Tl+/k2809VmjETo?g2&FU4X0U<<#ordF=<f?oB:`eOc0_9VW@_ZE6j![Vbg>RKj9jSVt;=NiNI[G=7d9Pb29#n!@=?CdG=<hQS/giH-r)=Eg>"mC&->'M3=:H3UX3Bsor,FOVbZ?[e8n+)b`!l>J@KuXZD-5%4/00<m5.8&4.$5e?.(SPDgjD9me8-[TJHU#-h3C[$b/[Rj)6u9&CU0IiP)BralW]k;=h=d[@]O[a@^VDn6,#9YJSu7=7[;q0/i-kT;cSW"h^l5fCYF([Q45.HjV-NHKtW1Hg./gZ%'%JU<nuPgb>D#c$!o_e-P:tmj(o-EY0R4;b$_e67"30+]J*oqg]*_JfF9IX+ZH-jE`_:4js4SiB94o?bWl3OY'bBWc?)I!WoL?u=+Z@3FAXd>BdO%_"GJ]Afg]@(=;O]W>J/h5pr(:)cZ7"o:ta:bF7G17%<2Iio$`Vr#t?ri[sY=#gB^P110CTPDU<rVqY*3,G%T3-j1lq4WXJSWM]2@X"XXfg2Tdi*Tis9GX>;BpU1p8Nc:aI'/oCmhq2QV`qEVM);QhN>[]YWkq/F,b3E95lrX<fOBusLa];uc''+DY'pSp,e::E*!G>hR>FQNb&EB!0!P[TdhjsiFE-8k(\q"+0q94EC:,_,qrX4#KFBggms*uEcB*]cm(<RO9q9),>sI5C$sB)Dai?9R*n26<4b;)_-/0;QW\IB$rAW1-M_>rh1^;1"a7*-JT]od32b/@F?qW13HADgq.,[Ed4W=);0]]r.9@2!b)CekL,AV/p*o?n6cr=LunTrj4'_4FN*5A67W[[RHnihfW\-lI<0lGk$?MR:'eag(MSZC=d"7^P'/!1NMjMfn0!3Ha\,'b,N4YW^KleO#:EL^WL[$D1bg>X\=t^J&<auZ_kR<l#)L)i[8h`KY"b&dq3<oHT>f4LA"B2V($a=mF0G=8[C6^n"b@MQof#J-ENcF:BPA[c-g-8qXa(f8V;*<-VC3,0j'3k,b^QH\:<<u=ckgq7;-!3k.Drr1P<(%PIeB;*bn;i2tsG<L4r@N0Z]<,kP+H^@lp*b8l=_u4`\!EJmN$]36Z=JcKHZ6(PB*F`bE(%b)N4bA8mR'edMuXVQFX5op'5>fm!,VfU?Z_[M6K,5;)Pe&k9,NS#7QN`rrJQB$bFpUR2q[8+ji'.pu:f,A5[[lI##o4$Q4u/)LQ@:o_(p%)JX`4!gEg`d%Ui2GM%<CRHQuW;;e\n!&fje?$ku]H;p^rKoP[U\-t&#mV&%<F$%0e_[a[fC3PT=nS`FT_.,!=B0h[9]Y%I4elPcH47d?@(T'][(0!Q%Jn]F=]&e3P/2=gB"5gb`@c3G+1Jm(m68b;m"sQ^%=:%EB\<(s070gi%p_=q//-aZ=?8%"43Tr@+7/p_8R4)-Jl[s/B;L'/Y=2#g26n`9lGCLNj^7dG~>endstream
156
+ endobj
157
+ 21 0 obj
158
+ <<
159
+ /Filter [ /ASCII85Decode /FlateDecode ] /Length 1590
160
+ >>
161
+ stream
162
+ Gatm;>Beg[%"@rS^g!>3<CMbUope0]CY!&E?#F!F;"*a!&oBGR`?h@Vrq?LDb*MEc\^X-iOTL+7L^sId!]E51s%RNa?RP@BG65^6aUAH_-qqqEA2=!>,).lZ+eZa\_^NGD\7"!`mfX\6`/,m[ZlJ?D2ZPQSTes&UJ>.&umJmQYE5thMa.<Id<LNai^FeGScJUKOn:Sp^(aaG\H)$U2ij3J%#s&:CH=9mGe#:0jf_R.Y]#fPT>fhlb&D'pM`t_D0/6bE1hUSop^l*P?<2E^5_h&Wghi]@sVi%NY6DBI(erXm#k\b[d$U]]D=)9h41L"bTdsqjOLMCltiuJ17m2tA[a_JqDQ&!<K?*qBTKBM2li3pu;l8nDEItGN'%8'Q[=nQI7MY8m-<YRM7N<%l4***cRK=1W[^Vdf4%B2Lk3pmuD2GiAs3Y(_s&!Vjh(SY.i;6;lSkcbZkSmgFE</8R](_"k`-544^'ebITD`==g":\I%FdS.K#1UCF:]%7LN3X)/R.o?HfWNkc$!(Hl<@(T]ZUt8R1>HYSYXarhNfKq*[lQ,k53+*/=Od-u'E&[a[S%i@)&0"5HWf6,,#0VTM)*-D^_#n>aO^La1?C=;KH*7[!Nm+)Vg.R3rsZ]AGC2<C)`-1nmHd_"ngFG)7TLA)KETM[-7L..6p?1&:F"&8LGgAt*N/[p_6p/nL1G4"ljZUo\@3"?VkN'>964>5mY26.U7pO96R-cQ"fh<#TL4*$<,Y.:goiu7GK:-PQid5KV0/Lf"fC<Y;\h%G<K?0]H9>.<4o6,Ig4-2/]9[5269U\u_"`hfa76#PEs^$WVf-^,R$t0m\V)uD3\b`rAMkKt?#O-eB9GuMZ+T[=!Jt3([>>W&BmODr[c:hIBXN>\I/7DpH?3PIbN'CN&n[s?e>I@dJh47X!d27#4g21oYNLt#-CXo710uYM2bURXnb;^b2$86HW]B-N)B9B2f3j'Hcu'HaTRKi1F;T&`7Sc?Gk[T]Qb74U"M`Qr8R(KT_8'2D<6(BH&/!pd]E,g>T\q+kUh]5Cq\d<P@<Jp%F9&#_O]+ib$(>(5Fm21RibqroZpm`.?ei!jGZ%Sk[[/4O@=.m!G5h)_eo'^.IB83a5cBBF=mT-7VOu=dm=(eL4:<2S`S5kJF!<m5sRYc@oikqDirbDrcQ;Sn`[+.L-omG.krV+jjf;`sE9F8AK0gqhJP?77pjhJ"IH*']j13b\aXlbj46W+<Y&=N`PT:3YVM6L&]*];#fX[.&=&<!fH[^GBk)?_>3Gnu<S:quVefY`R)!Y^g!DV]iFdM=ElDe%JX*@mrk**dk!:9\qO9m%G72ocC<QK:;*"JuZ9lVd?81_=@k#QD',!FjNOE?K4,2@f)5%pRKUL4q"NrQ+aFHFd+hM6jd+/0LP?ErGK0i7hG\q2uKkapcb`k$l6U7^Y3)Y`rWSB0DQ`a,]cN&*u@R2&Nd)L&r.(>Lp[o[WT8^MNYS[;r+(Gpe+K0nP8Fbmb8UE?&/U^fJ7VNX0Go:rR3Keh"_qRhGfm8[Ck%D]WB:XO`"??S*kCk)bRTAhrWUa#;A"R&q1gnqkpgcrpeh0D*!(^r==JJ@-%~>endstream
163
+ endobj
164
+ 22 0 obj
165
+ <<
166
+ /Filter [ /ASCII85Decode /FlateDecode ] /Length 944
167
+ >>
168
+ stream
169
+ GauHJ9lldX&A@Zcp?6%a6k\Ct->bg21c%ne[$P#OaK?/.*"^6%^V23$2R[+OdnE76@TIEDAbWkA$3UU9mn!_&5(U%5$C&A;#1U%dR!a\8'C4&?%5O`fmfXd:/?m#G$IPt^eYAL93J&4m>_g-#)FD8KG&#]-^cXOWS:*>h3/>G/=Go])2P/E2iG'$$<(Fip=O>\)ird1)1Dp(T,!CM!4\SUp+OhVfk\L/C7D])o.8So?U^AiKo:gn9alEI5a17"kO?&+74NAb,A!,W=]Y(S36Z5&H=!,tHXE):<__E"p7>46B]Ds6d)6Y/`OR.8XFFM-nhQk%LG[UlFBqGPM-B?HoVsr`)AUEoOB9,;iEk[&C2!@VJPFgAqZ8RiQ"W-o0=92pL#@jO3>Pm]r!FFAf_[%N?+#t>sfK*Wg\]o5]);'ru].#.6;m*:Gfd36PeccP+JOdH!i)XHReWOF!DKDL:cJXT#AQA6%>&8uA'%<c8<\-u-.#V?+ONh$c3^HD%^S)]TL3i;]jRmCJO]`cgES$Z#nY5#u=k'*.nM2Q3;DY_54^L%-<LbS5<l<?_4G-Na;OFV[otp:Lj;elS1DfZ?C.K7I8*mc+V[:-"YoiZf^aNh)kpiUkGJ$Zhk8/7"+\CQe-Ou(CR9#0m=rGfno)A<nTJ#IB"splT+&GEp%OjcK#4/kU4L]7<s/B2Ek\F_`_Z!]IOjV>Il?Z:;%J3-/Go@JJ/.G\S<.%6K8/ZKuY6"GW_"#_LIB(?55YQ:4Gf!6s:N+n&J^@-NDNmd*q%M*EF1jWiI9^Bu\MBYsSsJ(rXckTDnoQ&G3FpZ,M<2UE>TPr*l&o+''f483(U*uumk^.2,k[4Lp_3+8j2M%Xj<:,hjfAi*?g5hK<4@IVfstXtRr0q9G1uW=]q_iq9p^iCBXC'7>;.s_BPf/GA&F)2GN^q+e"X\T$Ylf)2Z~>endstream
170
+ endobj
171
+ 23 0 obj
172
+ <<
173
+ /Filter [ /ASCII85Decode /FlateDecode ] /Length 835
174
+ >>
175
+ stream
176
+ Gatm:>Aoub(k'`6pthH5.`5KmoPr\67G?Hj7I.iH9eMgO/(gigUs6[3LS69T`=F4\MLod0pmgs/&Ff35p:"]8hW=8#,68EB?3,nq#kCd\+eLkl83\k$+c2#AVHJd4Yf$(]H'#u&OW]R$U)-3@#np.9$*H`%!*lP?`<.5s]6Fmo92?33(;d;@U4u;AG>^S'[="E/rY+i]/bBj,?GlZ^/,2(uK*Yoa0Z->m0UYE;EPP(fDeT_Xi*)C>_51V,JdZjq`WsT!mJ%>Bk\s?chr8b4>Su%*TOl5;0G:nJ8R=*L\R6?$5oWrh!7u14SHGuD<YufI%@B"2C-u(HEtXDQ#pfo)P)eE/h)A"E-ZkH0eDtMl`/>6Lma5i.'Pf1!-jaX1G!gP#G2^C5a't[Ym>L$O.hlcCM2L3Z07]fthW'n/_p'5PWIWB*D67huM:;!"50K*j*(-\)8#f#X:$NUbI`BasUc';j%?l;nUtQ@1:rQh[PifP'\)YsfG0AC<os#HIj),T<:7!aa[kL!SEl0'2?R7@r\0O0`[M7G;%X!_ji?'UgB3_3J/ET6''uQ+]<.6jhiZKK\[<=#?-0*CV7D/r;g!guD^akM(YIL5_oO[nfF^M=I#4ZpBJ!UGC[2d\8_NO8=&W:&ECnrggG&S`:dk9i<99+,lYj/0L%574^TQQ50J<5"=Lm=/h17l:.<jO\fh'KJ!QJMEKqu6![Eb7l\Kuq50m6TFV!(]3llqQbG-6>tL;E"9tSiXH8mmAh"eV-DL";V2T[\:cse]n7P%$.f18*b$\P1VZdThSLND)3iEkq;po@QVcT.!,niM?:E.^V2a:ndFWUCdA~>endstream
177
+ endobj
178
+ 24 0 obj
179
+ <<
180
+ /Filter [ /ASCII85Decode /FlateDecode ] /Length 1617
181
+ >>
182
+ stream
183
+ Gat%"D/Yn7&H88.0sYn!Y,XJto9sT7e)\ri[W/I`<Ya?(g>3$9#F6Y'WW2p/(N9?nk>%=Q1=*Tq1W.Kh!>@[cs,Bp,[o<(^L]F\f3Wih12GDHTrH.LWO49Vi&"ZK(h[b+A5eo(%fCBn8L_\QUBNXt]9K^DZHg$Hj"/)\/SQe@,jkR/+l[@Z@G9U<>9JSTL*X%N@]RQYJniKNp_"<m!rt.J$$Du:H?tu>VOOOjs*E/l2,<CSFqHa`KoF^ep$O/DL,iXr!F5R3uL`/1aB(P!XncB/dQ]Wh?<6mu?OqeU=.d:`)=Xl1[WDkK`Ou0O?*<EjX9T"3Tc<3I[Md`$3--ITI`PJ\c/^Ia9ptJfoAg1$M6jHdMVctss/8aBI3D[Fh.0mH(s"n`h!CeAr7gC_7?K"=lZhf&*kC9(p')V=Bb&4;9_pReY2T?1VK<AI^Q:sk[T!=Ai?*KDiQA'oFdlpgh8g.o#-uf[!K?\J+`ShpZo-+fY-C*b4m@4`GBG-"..4fCj7ILA6BCQ"i#3fQUBiGjGUjYo'I>2Zs;P>ngno1-i:N]t"3$dIP1HE45J"M3BG^=Z88US(3UR(b[n"'@hXQ?nG)OF#2H?!n+*&!W0r!29&j2^E_cc&>F!gbGI)^Cc*jDFEunal\'g!([G?!N8NS'0,B2i<gZ]=t-,.XoBK-tZo1BRqNr\\0Y1-FmCS]r^>ZW!D^'Hj^C%eVh_iVUkq(FW5\e<5a1(=g!\XI7mR:hQ:Xq])T$3BT^\A(ZCf0l9)EK*23UtmT9[S>V89A'(AUraJO*"=)K!*%G]Ao&i&,N2Xfhr.NGC8X,!k:DhaN$]%a<S6:;H)"X6f[WH.;Idb@0D_pQO`FEeBiq@p4_Q7eGh),6!j=p<(kOEMHmBKg&^)=2*X$P"C$[<%IO2P=14X6,r1s5EeK_I;$jb+72ZMaMBQ>aOoL;9/7ZnEGq#g)=t@5/gfP8QR@AB3-26ECa*P,KmHaZ"]7nIqm[B[X[61L1=Fp]-A$A;TrH(=^.sflRgTA0t(RoooA9(KWH?)iuR$dCu"F?,f%ZA9K^r#aGq9Zl](5^4.a5:>H.R-m6Wel/;Xa>oZMo"\2cu9\-dXn\.c:?"3&<%?bCJkap^;a'+;hT<BL'm\l54>%a58KIMHT#T&nT*i31GL#^hnNU[M>KrJihWrdWcOmOiE+*C0;RbYgO9\]\Bc>30tu"u&m`f[o5=.k2@;ZF4ZYY.E:Dg4.MGAD7j(\8/M8m[^.H)Te:6q_k2:><=6B-^?K99"p_NNFl>\,$N_0Np'p>[H/:PmKucF5mgF3n^a%ZSr&O7EE=d%qf'A/]6p+4]8LTE6\]m:)BWa-MA71H]O=jmbDM\.TUm&hjD:kiY.(BSENDlYVk^3AMMF1Hnl,dmfr7&1RHUsb.?b,ocpAPHb\#$-m:7$Lg<(CI<bo>uo,MPqFH\kl"N3uXH*F:&#iQ]Wg!lAP<!lQHMZM?6//5Yu]d[+@Z;\*s"+Gad)8<!Q/)Wk2RV%+B!gg^#j#t;2a7UiRoghnT$!_8FM))C`;7JWZ=(.(6BLK\Cb\KOic^$Q@.N(E,17VU7$m-B*cD`k+?cr_X+Bq@=St>a[R&cCNha[H>Iph_P#:H`H~>endstream
184
+ endobj
185
+ 25 0 obj
186
+ <<
187
+ /Filter [ /ASCII85Decode /FlateDecode ] /Length 554
188
+ >>
189
+ stream
190
+ GasIdgMWN8'R]'ojtVipC)\kh[;&mEC8G6]MkZ#XM4?-,!H5":q]96#g"kd7"1I?NLV_3n?o7ZLC^8m.N=VXA\3Sl&k@otQ)_UF:+lSQ_GUg$tTk%0'<2F9_;Y=P29h$<A3V#l%4]e"-qXYu78l=XaBU3qcBiZdc`1EUC'E4+4TuU0A32((D>:j/MF@tKJ.`i;ai+umm4^YLqe2TUeDYHZ96[aadr+#nrreog6S)*bT[MbWU]B@kA>.HjEgML."09)jtFmI7r$St!3dFi4=2`9tc6-h&QM#>oh?ougV4jC/JNLMQ(?M50ahkM1h!R,48ZON4BLYbdnSSdh&ng500:2dMgh=Y7I2-9?C;h7gCpWnZ,d?urbBMD"3RQO<(1Nkklnj3.FpK`r^OqgT?1+5Ro:o"r3,fd)A8Qt'@OrT3"UiD%E"S`[VaNlBX<f!j`(&qc@7KZ,<4[t8:(bGg/$UlH$'uOc$&7j>/IZE@4ANVgcooq#6[AQbmOrq%M8V\g<_4Kh^FL28n]QD-G`Rr$#pgi.H5_2$W:i?BV5?_^m0E~>endstream
191
+ endobj
192
+ xref
193
+ 0 26
194
+ 0000000000 65535 f
195
+ 0000000061 00000 n
196
+ 0000000113 00000 n
197
+ 0000000220 00000 n
198
+ 0000000332 00000 n
199
+ 0000000537 00000 n
200
+ 0000000742 00000 n
201
+ 0000000947 00000 n
202
+ 0000001152 00000 n
203
+ 0000001357 00000 n
204
+ 0000001562 00000 n
205
+ 0000001768 00000 n
206
+ 0000001874 00000 n
207
+ 0000002080 00000 n
208
+ 0000002286 00000 n
209
+ 0000002356 00000 n
210
+ 0000002637 00000 n
211
+ 0000002748 00000 n
212
+ 0000003176 00000 n
213
+ 0000003658 00000 n
214
+ 0000005040 00000 n
215
+ 0000007009 00000 n
216
+ 0000008691 00000 n
217
+ 0000009726 00000 n
218
+ 0000010652 00000 n
219
+ 0000012361 00000 n
220
+ trailer
221
+ <<
222
+ /ID
223
+ [<c03ca4c5a887abb2e75db5d4579bfba7><c03ca4c5a887abb2e75db5d4579bfba7>]
224
+ % ReportLab generated PDF document -- digest (opensource)
225
+
226
+ /Info 15 0 R
227
+ /Root 14 0 R
228
+ /Size 26
229
+ >>
230
+ startxref
231
+ 13006
232
+ %%EOF
@@ -0,0 +1,134 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const root = path.join(__dirname, '..');
5
+
6
+ function parseTSV(content) {
7
+ const lines = content.trim().replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
8
+ const headers = lines[0].split('\t').map(h => h.replace(/^"|"$/g, ''));
9
+ return lines.slice(1).filter(l => l.trim()).map(line => {
10
+ const values = line.split('\t').map(v => (v === '\\N' || v === 'null' || v === '') ? null : v.replace(/^"|"$/g, ''));
11
+ const obj = {};
12
+ headers.forEach((h, i) => obj[h] = values[i]);
13
+ return obj;
14
+ });
15
+ }
16
+
17
+ const itemData = parseTSV(fs.readFileSync(path.join(root, 'item.csv'), 'utf8'));
18
+ const modifierData = parseTSV(fs.readFileSync(path.join(root, 'modifier.csv'), 'utf8'));
19
+
20
+ const items = itemData.filter(i => i.is_delete !== '1' && i.visible !== '0');
21
+ const modifiers = modifierData.filter(m => m.is_delete !== '1');
22
+
23
+ // Build modifier map: item_id -> group_name -> modifiers[]
24
+ const modifierMap = new Map();
25
+ for (const mod of modifiers) {
26
+ if (!modifierMap.has(mod.item_id)) modifierMap.set(mod.item_id, new Map());
27
+ const groupName = mod.group_name || 'Options';
28
+ const groupMap = modifierMap.get(mod.item_id);
29
+ if (!groupMap.has(groupName)) groupMap.set(groupName, []);
30
+ groupMap.get(groupName).push(mod);
31
+ }
32
+
33
+ // Derive category names from first item in each category
34
+ const categoryNames = new Map();
35
+ for (const item of items) {
36
+ const cid = item.category_id;
37
+ if (cid && !categoryNames.has(cid)) categoryNames.set(cid, `Category ${cid}`);
38
+ }
39
+
40
+ // Override with heuristic names by scanning item names
41
+ const catKeywords = {
42
+ fish: /fish|garoupa|seabass|stingray|fishcake|fish cake/i,
43
+ squid: /squid/i,
44
+ chicken: /chicken/i,
45
+ prawn: /prawn/i,
46
+ pork: /pork|pork neck|satay|skewer/i,
47
+ vegetable: /vegetable|veggie/i,
48
+ soup: /soup|tomyam|tom yam/i,
49
+ rice: /rice|fried rice|hor fun/i,
50
+ salad: /salad/i,
51
+ drinks: /coke|fanta|sprite|drink|coffee|tea|juice|water|mineral|beer|wine/i,
52
+ omelette: /omelette|omellet|egg/i,
53
+ seafood: /seafood/i,
54
+ dessert: /cake|ice cream|dessert|pudding|waffle/i,
55
+ };
56
+
57
+ const catItemsMap = new Map();
58
+ for (const item of items) {
59
+ const cid = item.category_id;
60
+ if (!cid) continue;
61
+ if (!catItemsMap.has(cid)) catItemsMap.set(cid, []);
62
+ catItemsMap.get(cid).push(item);
63
+ }
64
+
65
+ for (const [cid, catItems] of catItemsMap.entries()) {
66
+ const counts = {};
67
+ for (const item of catItems) {
68
+ for (const [label, re] of Object.entries(catKeywords)) {
69
+ if (re.test(item.name)) counts[label] = (counts[label] || 0) + 1;
70
+ }
71
+ }
72
+ const top = Object.entries(counts).sort((a, b) => b[1] - a[1])[0];
73
+ if (top && top[1] >= Math.ceil(catItems.length * 0.3))
74
+ categoryNames.set(cid, top[0].charAt(0).toUpperCase() + top[0].slice(1));
75
+ }
76
+
77
+ // Collect sorted category ids
78
+ const sortedCatIds = Array.from(catItemsMap.keys()).sort((a, b) => parseInt(a) - parseInt(b));
79
+
80
+ function escapeCSV(val) {
81
+ if (val === null || val === undefined) return '';
82
+ const str = String(val);
83
+ return (str.includes(',') || str.includes('"') || str.includes('\n')) ? `"${str.replace(/"/g, '""')}"` : str;
84
+ }
85
+
86
+ const headers = ['Type','Name','SKU','Price','Stock','Is Internal','Min Order','Max Order','Image URL','Thumbnail URL','Sequence','Item UID','Print On Receipt','Discountable','Open Editor Panel','Is Delisted','Name Translations','Prices'];
87
+
88
+ function makeRow(obj) {
89
+ return [
90
+ obj.type || '', obj.name || '', obj.sku || '', obj.price ?? '', obj.stock ?? '',
91
+ obj.is_internal ?? '', obj.min_order ?? '', obj.max_order ?? '',
92
+ obj.image_url || '', obj.thumbnail_url || '', obj.sequence ?? '',
93
+ obj.item_uid || '', obj.print_on_receipt ?? '', obj.discountable ?? '',
94
+ obj.open_editor_panel ?? '', obj.is_delisted ?? '', obj.name_translations || '', obj.prices || ''
95
+ ].map(escapeCSV).join(',');
96
+ }
97
+
98
+ const rows = [headers.join(',')];
99
+
100
+ for (const cid of sortedCatIds) {
101
+ const catItems = catItemsMap.get(cid).sort((a, b) => (parseInt(a.sequence) || 0) - (parseInt(b.sequence) || 0));
102
+ rows.push(makeRow({ type: 'category', name: categoryNames.get(cid), is_internal: 'false', sequence: cid, is_delisted: 'false' }));
103
+
104
+ for (const item of catItems) {
105
+ rows.push(makeRow({
106
+ type: 'product',
107
+ name: item.name,
108
+ sku: item.codename || '',
109
+ price: item.price || '0',
110
+ is_internal: item.internality === '1' ? 'true' : 'false',
111
+ sequence: item.sequence || '0',
112
+ image_url: item.image_cdn || '',
113
+ thumbnail_url: item.image_thumbnail_cdn || '',
114
+ print_on_receipt: item.is_print === '1' ? 'true' : 'false',
115
+ discountable: item.discountable === '1' ? 'true' : 'false',
116
+ is_delisted: 'false'
117
+ }));
118
+
119
+ const itemMods = modifierMap.get(item.id);
120
+ if (!itemMods) continue;
121
+ const sortedGroups = Array.from(itemMods.entries()).sort((a, b) => parseInt(a[1][0]?.group || 0) - parseInt(b[1][0]?.group || 0));
122
+ for (const [groupName, mods] of sortedGroups) {
123
+ const quota = parseInt(mods[0].group_quota) || 0;
124
+ rows.push(makeRow({ type: 'modifier_category', name: groupName, min_order: '0', max_order: String(quota), sequence: mods[0].group || '0' }));
125
+ for (const mod of mods.sort((a, b) => parseInt(a.id) - parseInt(b.id)))
126
+ rows.push(makeRow({ type: 'modifier', name: mod.name, price: mod.price || '0', is_internal: 'false', print_on_receipt: mod.is_print === '1' ? 'true' : 'false' }));
127
+ }
128
+ }
129
+ }
130
+
131
+ const outPath = path.join(__dirname, 'export', 'xpos_menu.csv');
132
+ fs.writeFileSync(outPath, '\uFEFF' + rows.join('\n'), 'utf8');
133
+ console.log(`Generated ${rows.length - 1} rows to ${outPath}`);
134
+ console.log(`Categories: ${sortedCatIds.length}, Items: ${items.length}, Modifiers: ${modifiers.length}`);
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env python3
2
+ import csv, io, os, re
3
+ from collections import defaultdict
4
+
5
+ ROOT = os.path.join(os.path.dirname(__file__), '..')
6
+ OUT = os.path.join(os.path.dirname(__file__), 'export', 'xpos_menu.csv')
7
+
8
+ def read_tsv(path):
9
+ rows = []
10
+ with open(path, encoding='utf-8') as f:
11
+ reader = csv.DictReader(f, delimiter='\t')
12
+ for row in reader:
13
+ rows.append({k: (None if v in ('\\N', 'null', '') else v) for k, v in row.items()})
14
+ return rows
15
+
16
+ def escape_csv(val):
17
+ if val is None: return ''
18
+ s = str(val)
19
+ return f'"{s.replace(chr(34), chr(34)*2)}"' if any(c in s for c in ',"\n') else s
20
+
21
+ HEADERS = ['Type','Name','SKU','Price','Stock','Is Internal','Min Order','Max Order',
22
+ 'Image URL','Thumbnail URL','Sequence','Item UID','Print On Receipt',
23
+ 'Discountable','Open Editor Panel','Is Delisted','Name Translations','Prices']
24
+
25
+ def make_row(**kw):
26
+ return ','.join(escape_csv(kw.get(k.lower().replace(' ','_').replace('-','_'), '')) for k in HEADERS)
27
+
28
+ def make_row_raw(type='',name='',sku='',price='',stock='',is_internal='',min_order='',max_order='',
29
+ image_url='',thumbnail_url='',sequence='',item_uid='',print_on_receipt='',
30
+ discountable='',open_editor_panel='',is_delisted='',name_translations='',prices=''):
31
+ vals = [type,name,sku,price,stock,is_internal,min_order,max_order,
32
+ image_url,thumbnail_url,sequence,item_uid,print_on_receipt,
33
+ discountable,open_editor_panel,is_delisted,name_translations,prices]
34
+ return ','.join(escape_csv(v) for v in vals)
35
+
36
+ items = [r for r in read_tsv(os.path.join(ROOT,'item.csv')) if r['is_delete'] != '1']
37
+ modifiers = [r for r in read_tsv(os.path.join(ROOT,'modifier.csv')) if r['is_delete'] != '1']
38
+
39
+ # modifier map: item_id -> group_name -> [mods]
40
+ mod_map = defaultdict(lambda: defaultdict(list))
41
+ for m in modifiers:
42
+ mod_map[m['item_id']][m['group_name'] or 'Options'].append(m)
43
+
44
+ # group items by category
45
+ cat_items = defaultdict(list)
46
+ for it in items:
47
+ if it.get('category_id'):
48
+ cat_items[it['category_id']].append(it)
49
+
50
+ # derive category names via keyword heuristics
51
+ KEYWORDS = [
52
+ ('Fish', re.compile(r'fish|garoupa|seabass|stingray|fishcake', re.I)),
53
+ ('Squid', re.compile(r'squid', re.I)),
54
+ ('Chicken', re.compile(r'chicken', re.I)),
55
+ ('Prawn', re.compile(r'prawn', re.I)),
56
+ ('Beef', re.compile(r'beef', re.I)),
57
+ ('Pork', re.compile(r'\bpork\b|satay|skewer', re.I)),
58
+ ('Vegetable', re.compile(r'vegetable|veggie|brinjal|broccoli|asparagus|kailan|cabbage|kang kong|petai|bean sprout', re.I)),
59
+ ('Soup', re.compile(r'soup|tomyam|tom yam', re.I)),
60
+ ('Rice', re.compile(r'\brice\b|fried rice|hor fun', re.I)),
61
+ ('Salad', re.compile(r'salad', re.I)),
62
+ ('Drinks', re.compile(r'coke|fanta|sprite|drink|coffee|tea|juice|water|beer|wine', re.I)),
63
+ ('Omelette', re.compile(r'omelette|omellet', re.I)),
64
+ ('Seafood', re.compile(r'seafood', re.I)),
65
+ ('Dessert', re.compile(r'cake|ice cream|dessert|pudding|waffle|red ruby|tapioca', re.I)),
66
+ ('Set', re.compile(r'^set [a-z]$', re.I)),
67
+ ('Extras', re.compile(r'plain rice|spring roll|vermicelli|open item|tofu', re.I)),
68
+ ]
69
+
70
+ def infer_name(cid, cat_its):
71
+ counts = defaultdict(int)
72
+ for it in cat_its:
73
+ name = it.get('name') or ''
74
+ for label, rx in KEYWORDS:
75
+ if rx.search(name): counts[label] += 1
76
+ if counts:
77
+ top, cnt = max(counts.items(), key=lambda x: x[1])
78
+ if cnt >= max(1, len(cat_its) * 0.3):
79
+ if top == 'Set':
80
+ # Find the specific set letter from item names
81
+ for it in cat_its:
82
+ m = re.match(r'^set ([a-z])$', (it.get('name') or '').strip(), re.I)
83
+ if m: return f'Set {m.group(1).upper()}'
84
+ return top
85
+ return f'Category {cid}'
86
+
87
+ rows = [','.join(HEADERS)]
88
+
89
+ for cid in sorted(cat_items.keys(), key=lambda x: int(x)):
90
+ cat_its = sorted(cat_items[cid], key=lambda x: int(x.get('sequence') or 0))
91
+ cat_name = infer_name(cid, cat_its)
92
+ rows.append(make_row_raw(type='category', name=cat_name, is_internal='false',
93
+ sequence=cid, is_delisted='false'))
94
+
95
+ for it in cat_its:
96
+ rows.append(make_row_raw(
97
+ type='product', name=it['name'] or '', sku=it.get('codename') or '',
98
+ price=it.get('price') or '0',
99
+ is_internal='true' if it.get('internality') == '1' else 'false',
100
+ sequence=it.get('sequence') or '0',
101
+ image_url=it.get('image_cdn') or '', thumbnail_url=it.get('image_thumbnail_cdn') or '',
102
+ print_on_receipt='true' if it.get('is_print') == '1' else 'false',
103
+ discountable='true' if it.get('discountable') == '1' else 'false',
104
+ is_delisted='false'
105
+ ))
106
+
107
+ item_mods = mod_map.get(it['id'])
108
+ if not item_mods: continue
109
+ sorted_groups = sorted(item_mods.items(), key=lambda kv: int(kv[1][0].get('group') or 0))
110
+ for group_name, mods in sorted_groups:
111
+ quota = int(mods[0].get('group_quota') or 0)
112
+ rows.append(make_row_raw(type='modifier_category', name=group_name,
113
+ min_order='0', max_order=str(quota),
114
+ sequence=mods[0].get('group') or '0'))
115
+ for mod in sorted(mods, key=lambda m: int(m['id'])):
116
+ rows.append(make_row_raw(
117
+ type='modifier', name=mod['name'] or '', price=mod.get('price') or '0',
118
+ is_internal='false',
119
+ print_on_receipt='true' if mod.get('is_print') == '1' else 'false'
120
+ ))
121
+
122
+ os.makedirs(os.path.dirname(OUT), exist_ok=True)
123
+ with open(OUT, 'w', encoding='utf-8-sig', newline='\n') as f:
124
+ f.write('\n'.join(rows))
125
+
126
+ print(f'Generated {len(rows)-1} rows -> {OUT}')
127
+ print(f'Categories: {len(cat_items)}, Items: {len(items)}, Modifiers: {len(modifiers)}')
@@ -0,0 +1 @@
1
+ {"uid":"inv_iWcYcdv1YNoZC0aGwQxj5","updated_at":"2026-02-14T12:32:28.321Z","created_at":"2026-02-14T12:32:38.731Z","created_at_timestamp":1771072358731,"ref_id":"01260214000001601","call_num":"601","table_uid":"sit_2hPwnc9KSpi39Sn3sFlOi","table_name":"Grab","type":"dine_in","pax":0,"adult_pax":0,"child_pax":0,"baby_pax":0,"subtotal":70000,"point_subtotal":0,"service_chargeable_subtotal":70000,"discountable_subtotal":70000,"service_charge":0,"delivery_id":0,"delivery_charge":0,"delivery_type":"","delivery_by":"","use_customer_point":false,"add_point":0,"minus_point":0,"rewarded_point_rate":0,"rewarded_point":0,"use_customer_balance":false,"add_credit":0,"minus_credit":0,"rewarded_credit_rate":0,"rewarded_credit":0,"discounts":[],"discount_amount":0,"tax":5185.19,"rounding":0,"tip":0,"grand_total":70000,"customer_id":0,"remark":"","sales_user_uid":"","status":"paid","paid_at":"2026-02-14T12:32:38.712Z","voided_at":"1970-01-01T00:00:00.000Z","humanized_paid_at":"14/02/2026 19:32:38","humanized_voided_at":"","humanized_created_at":"14/02/2026 19:32:38","order_id":0,"to_address":{"address1":"","address2":"","lat":"","lng":"","postcode":"","note":"","is_default":false,"name":"","salute":"","phone_number":""},"payment_source_method":"","payment_source_last4":"","payment_source_charge_ref":"","is_sales_exclusive":false,"etc":{},"lines":[{"uid":"iln_qLuhso1UqVWH9ox6JsfGU","updated_at":"2026-02-14T12:32:32.978Z","created_at":"2026-02-14T12:32:32.978Z","created_at_timestamp":1771072352978,"item_uid":"itm_4iQMHedTPbCp3mFokLSvx","price":70000,"unit_of_measure":"quantity","quantity":1,"remark":"","invoice_uid":"","discount_calc_type":"flat","discount_amount":0,"discount_percent":0,"subtotal":70000,"subtotal_before_discount":70000,"item":{"uid":"itm_4iQMHedTPbCp3mFokLSvx","updated_at":"2026-02-13T12:29:18.955Z","created_at":"2025-11-17T07:27:37.134Z","created_at_timestamp":1763364457134,"name":"Grab - H\u1ED3ng Tr\u00E0 S\u1EEFa","name_translations":{"en":"Grab - Black Milk Tea"},"description":"(olong green tea, roasted olong, (avocado,condensed milk,Tapioca white pearl )","description_translations":{},"meta":{},"etc":{},"assets":{},"config":{"online_order_display_large_image":false},"is_internal":false,"color":"","icon":"","sequence":170011,"image_url":"https://app.posx.ai/images/93da00e8-5cda-4cbf-be37-0b483b8a99cc.jpg","image_file_path":"","image_thumbnail_url":"https://app.posx.ai/images/93da00e8-5cda-4cbf-be37-0b483b8a99cc.thumbnail.jpg","image_thumbnail_file_path":"","sku":"","type":"by_quantity","barcode":"","category_uid":"cat_nDSPXlpMaHVMyBubKEpO2","price":70000,"original_price":0,"prices":[],"stock":-34,"print_on_receipt":true,"price_adjustable":false,"is_sold_out":false,"codename":"","recipe":"CP003-B01-W01","add_credit":0,"subcategory_uid":"","unit":0,"sold":0,"open_editor":true,"minus_point":0,"cost":0,"discountable":true,"printing_sequence":0,"recommended":false,"likes":0,"dislikes":0,"is_tax_exempt":false,"is_service_charge_exempt":false,"is_point_reward_exempt":false,"is_credit_reward_exempt":false,"is_delisted":false,"kitchen_printers_uids":["prt_UBY9G95mCuHgtg34tnD7H"],"order_printers_uids":[],"label_printers_uids":["prt_B1R1W19J1LRa14nraxiz2"],"open_editor_panel":true,"open_price_editor":true,"combo_groups":[],"id_in_server":9375,"_timestamp":1770985758771696,"modifier_categories":[{"min_order":1,"max_order":1,"sequence":4,"name":"Sugar Level","uid":"imc_5TG1isS3rDcW-TtE9mPvP"},{"min_order":1,"max_order":1,"sequence":3,"name":"Ice","uid":"imc_MA0C7z9B2GHEAplgfMSFc"},{"min_order":1,"max_order":1,"sequence":6,"name":"Topping one","uid":"imc_vQfQFta5UdBpn9KzyItRr"},{"min_order":0,"max_order":0,"sequence":7,"name":"Topping","uid":"imc_UH7jvReaP3k2tyuMUWAm_"}],"modifiers":[{"uid":"iod_JK30rn-ELHCiYyqGy-qi8","updated_at":"2025-11-29T12:05:26.892Z","created_at":"2025-11-29T12:05:26.892Z","created_at_timestamp":1764417926892,"name":"0%","name_translations":{},"description_translations":{},"meta":{},"etc":{},"assets":{},"config":{"online_order_display_large_image":false},"is_internal":false,"color":"","icon":"","sequence":4001,"image_url":"","image_file_path":"","image_thumbnail_url":"","image_thumbnail_file_path":"","visibility":[],"sku":"","type":"modifier","barcode":"","category_uid":"imc_5TG1isS3rDcW-TtE9mPvP","price":0,"original_price":0,"prices":[],"stock":1000,"print_on_receipt":true,"price_adjustable":false,"is_sold_out":false,"recipe":"T05"},{"uid":"iod_2SBZM4Yg0XmqsS3kC9JX1","updated_at":"2025-11-29T12:05:41.197Z","created_at":"2025-11-29T12:05:41.197Z","created_at_timestamp":1764417941197,"name":"30%","name_translations":{},"description_translations":{},"meta":{},"etc":{},"assets":{},"config":{"online_order_display_large_image":false},"is_internal":false,"color":"","icon":"","sequence":4002,"image_url":"","image_file_path":"","image_thumbnail_url":"","image_thumbnail_file_path":"","visibility":[],"sku":"","type":"modifier","barcode":"","category_uid":"imc_5TG1isS3rDcW-TtE9mPvP","price":0,"original_price":0,"prices":[],"stock":999,"print_on_receipt":true,"price_adjustable":false,"is_sold_out":false,"recipe":"T04"},{"uid":"iod_dilkDd6CXmVxDEtoc_cyl","updated_at":"2025-11-29T12:05:51.459Z","created_at":"2025-11-29T12:05:51.459Z","created_at_timestamp":1764417951459,"name":"50%","name_translations":{},"description_translations":{},"meta":{},"etc":{},"assets":{},"config":{"online_order_display_large_image":false},"is_internal":false,"color":"","icon":"","sequence":4003,"image_url":"","image_file_path":"","image_thumbnail_url":"","image_thumbnail_file_path":"","visibility":[],"sku":"","type":"modifier","barcode":"","category_uid":"imc_5TG1isS3rDcW-TtE9mPvP","price":0,"original_price":0,"prices":[],"stock":994,"print_on_receipt":true,"price_adjustable":false,"is_sold_out":false,"recipe":"T03"},{"uid":"iod_wI5ynNWhF2-e2l8wHMYo7","updated_at":"2025-11-29T12:05:59.937Z","created_at":"2025-11-29T12:05:59.937Z","created_at_timestamp":1764417959937,"name":"70%","name_translations":{},"description_translations":{},"meta":{},"etc":{},"assets":{},"config":{"online_order_display_large_image":false},"is_internal":false,"color":"","icon":"","sequence":4004,"image_url":"","image_file_path":"","image_thumbnail_url":"","image_thumbnail_file_path":"","visibility":[],"sku":"","type":"modifier","barcode":"","category_uid":"imc_5TG1isS3rDcW-TtE9mPvP","price":0,"original_price":0,"prices":[],"stock":999,"print_on_receipt":true,"price_adjustable":false,"is_sold_out":false,"recipe":"T02"},{"uid":"iod_UfyyYSUwqyfa5Fek9guGA","updated_at":"2025-11-29T12:06:08.590Z","created_at":"2025-11-29T12:06:08.590Z","created_at_timestamp":1764417968590,"name":"100%","name_translations":{},"description_translations":{},"meta":{},"etc":{},"assets":{},"config":{"online_order_display_large_image":false},"is_internal":false,"color":"","icon":"","sequence":4005,"image_url":"","image_file_path":"","image_thumbnail_url":"","image_thumbnail_file_path":"","visibility":[],"sku":"","type":"modifier","barcode":"","category_uid":"imc_5TG1isS3rDcW-TtE9mPvP","price":0,"original_price":0,"prices":[],"stock":978,"print_on_receipt":true,"price_adjustable":false,"is_sold_out":false,"recipe":"T01"},{"uid":"iod_Wt1BN3gK_ck1HrVeUAbDd","updated_at":"2025-11-30T07:56:42.422Z","created_at":"2025-11-30T07:56:42.422Z","created_at_timestamp":1764489402422,"name":"0%","name_translations":{},"description_translations":{},"meta":{},"etc":{},"assets":{},"config":{"online_order_display_large_image":false},"is_internal":false,"color":"","icon":"","sequence":3001,"image_url":"","image_file_path":"","image_thumbnail_url":"","image_thumbnail_file_path":"","visibility":[],"sku":"","type":"modifier","barcode":"","category_uid":"imc_MA0C7z9B2GHEAplgfMSFc","price":0,"original_price":0,"prices":[],"stock":1000,"print_on_receipt":true,"price_adjustable":false,"is_sold_out":false,"recipe":""},{"uid":"iod_mMLNAEzOKtY2epTp4gVYN","updated_at":"2025-11-30T07:56:57.428Z","created_at":"2025-11-30T07:56:57.428Z","created_at_timestamp":1764489417428,"name":"50%","name_translations":{},"description_translations":{},"meta":{},"etc":{},"assets":{},"config":{"online_order_display_large_image":false},"is_internal":false,"color":"","icon":"","sequence":3002,"image_url":"","image_file_path":"","image_thumbnail_url":"","image_thumbnail_file_path":"","visibility":[],"sku":"","type":"modifier","barcode":"","category_uid":"imc_MA0C7z9B2GHEAplgfMSFc","price":0,"original_price":0,"prices":[],"stock":997,"print_on_receipt":true,"price_adjustable":false,"is_sold_out":false,"recipe":""},{"uid":"iod_ZahQa5c3NVufqLt-J_YTw","updated_at":"2025-11-30T07:57:06.578Z","created_at":"2025-11-30T07:57:06.578Z","created_at_timestamp":1764489426578,"name":"70%","name_translations":{},"description_translations":{},"meta":{},"etc":{},"assets":{},"config":{"online_order_display_large_image":false},"is_internal":false,"color":"","icon":"","sequence":3003,"image_url":"","image_file_path":"","image_thumbnail_url":"","image_thumbnail_file_path":"","visibility":[],"sku":"","type":"modifier","barcode":"","category_uid":"imc_MA0C7z9B2GHEAplgfMSFc","price":0,"original_price":0,"prices":[],"stock":998,"print_on_receipt":true,"price_adjustable":false,"is_sold_out":false,"recipe":""},{"uid":"iod_gJcG8TTG20hD9BQLWCQGZ","updated_at":"2025-11-30T07:57:15.527Z","created_at":"2025-11-30T07:57:15.527Z","created_at_timestamp":1764489435527,"name":"100%","name_translations":{},"description_translations":{},"meta":{},"etc":{},"assets":{},"config":{"online_order_display_large_image":false},"is_internal":false,"color":"","icon":"","sequence":3004,"image_url":"","image_file_path":"","image_thumbnail_url":"","image_thumbnail_file_path":"","visibility":[],"sku":"","type":"modifier","barcode":"","category_uid":"imc_MA0C7z9B2GHEAplgfMSFc","price":0,"original_price":0,"prices":[],"stock":975,"print_on_receipt":true,"price_adjustable":false,"is_sold_out":false,"recipe":""},{"uid":"iod_gj9FzI7-W64UmxsFr5IBr","updated_at":"2025-12-01T02:43:49.893Z","created_at":"2025-12-01T02:43:49.893Z","created_at_timestamp":1764557029893,"name":"Kem Mu\u1ED1i","name_translations":{},"description_translations":{},"meta":{},"etc":{},"assets":{},"config":{"online_order_display_large_image":false},"is_internal":false,"color":"","icon":"","sequence":6001,"image_url":"","image_file_path":"","image_thumbnail_url":"","image_thumbnail_file_path":"","visibility":[],"sku":"","type":"modifier","barcode":"","category_uid":"imc_vQfQFta5UdBpn9KzyItRr","price":0,"original_price":0,"prices":[],"stock":1000,"print_on_receipt":true,"price_adjustable":false,"is_sold_out":false,"recipe":""},{"uid":"iod_qJbUtYkEmbxWCVP05oOpa","updated_at":"2025-12-01T02:44:36.158Z","created_at":"2025-12-01T02:44:36.158Z","created_at_timestamp":1764557076158,"name":"Kem Tiramisu","name_translations":{},"description_translations":{},"meta":{},"etc":{},"assets":{},"config":{"online_order_display_large_image":false},"is_internal":false,"color":"","icon":"","sequence":6002,"image_url":"","image_file_path":"","image_thumbnail_url":"","image_thumbnail_file_path":"","visibility":[],"sku":"","type":"modifier","barcode":"","category_uid":"imc_vQfQFta5UdBpn9KzyItRr","price":0,"original_price":0,"prices":[],"stock":998,"print_on_receipt":true,"price_adjustable":false,"is_sold_out":false,"recipe":""},{"uid":"iod_NC_3a3Jx4Ss67wgQ7gxku","updated_at":"2025-12-01T02:44:43.988Z","created_at":"2025-12-01T02:44:43.988Z","created_at_timestamp":1764557083988,"name":"Kem Matcha","name_translations":{},"description_translations":{},"meta":{},"etc":{},"assets":{},"config":{"online_order_display_large_image":false},"is_internal":false,"color":"","icon":"","sequence":6003,"image_url":"","image_file_path":"","image_thumbnail_url":"","image_thumbnail_file_path":"","visibility":[],"sku":"","type":"modifier","barcode":"","category_uid":"imc_vQfQFta5UdBpn9KzyItRr","price":0,"original_price":0,"prices":[],"stock":998,"print_on_receipt":true,"price_adjustable":false,"is_sold_out":false,"recipe":""},{"uid":"iod_ys_x93jxoF45IxkQdn1V5","updated_at":"2025-12-01T02:44:52.823Z","created_at":"2025-12-01T02:44:52.823Z","created_at_timestamp":1764557092823,"name":"Kem Khoai M\u00F4n","name_translations":{},"description_translations":{},"meta":{},"etc":{},"assets":{},"config":{"online_order_display_large_image":false},"is_internal":false,"color":"","icon":"","sequence":6004,"image_url":"","image_file_path":"","image_thumbnail_url":"","image_thumbnail_file_path":"","visibility":[],"sku":"","type":"modifier","barcode":"","category_uid":"imc_vQfQFta5UdBpn9KzyItRr","price":0,"original_price":0,"prices":[],"stock":1000,"print_on_receipt":true,"price_adjustable":false,"is_sold_out":false,"recipe":""},{"uid":"iod_X8sPo6TuUmiyWKUMIt2En","updated_at":"2025-12-01T02:39:12.571Z","created_at":"2025-12-01T02:39:12.571Z","created_at_timestamp":1764556752571,"name":"H\u1EA1t Sen","name_translations":{},"description_translations":{},"meta":{},"etc":{},"assets":{},"config":{"online_order_display_large_image":false},"is_internal":false,"color":"","icon":"","sequence":7001,"image_url":"","image_file_path":"","image_thumbnail_url":"","image_thumbnail_file_path":"","visibility":[],"sku":"","type":"modifier","barcode":"","category_uid":"imc_UH7jvReaP3k2tyuMUWAm_","price":20000,"original_price":0,"prices":[],"stock":1000,"print_on_receipt":true,"price_adjustable":false,"is_sold_out":false,"recipe":""},{"uid":"iod_NP_ELMackBiRKt4zUJuYm","updated_at":"2025-12-01T02:39:22.344Z","created_at":"2025-12-01T02:39:22.344Z","created_at_timestamp":1764556762344,"name":"Tr\u00E2n Ch\u00E2u Tr\u1EAFng","name_translations":{},"description_translations":{},"meta":{},"etc":{},"assets":{},"config":{"online_order_display_large_image":false},"is_internal":false,"color":"","icon":"","sequence":7002,"image_url":"","image_file_path":"","image_thumbnail_url":"","image_thumbnail_file_path":"","visibility":[],"sku":"","type":"modifier","barcode":"","category_uid":"imc_UH7jvReaP3k2tyuMUWAm_","price":10000,"original_price":0,"prices":[],"stock":1000,"print_on_receipt":true,"price_adjustable":false,"is_sold_out":false,"recipe":""},{"uid":"iod_Pv_E-aBfwnFUDobeVZQrj","updated_at":"2025-12-01T02:39:29.101Z","created_at":"2025-12-01T02:39:29.101Z","created_at_timestamp":1764556769101,"name":"Tr\u00E2n Ch\u00E2u \u0110\u01B0\u1EDDng \u0110en","name_translations":{},"description_translations":{},"meta":{},"etc":{},"assets":{},"config":{"online_order_display_large_image":false},"is_internal":false,"color":"","icon":"","sequence":7003,"image_url":"","image_file_path":"","image_thumbnail_url":"","image_thumbnail_file_path":"","visibility":[],"sku":"","type":"modifier","barcode":"","category_uid":"imc_UH7jvReaP3k2tyuMUWAm_","price":10000,"original_price":0,"prices":[],"stock":1000,"print_on_receipt":true,"price_adjustable":false,"is_sold_out":false,"recipe":""},{"uid":"iod_srhIckzDJzrX_ngNlzWEi","updated_at":"2025-12-01T02:39:38.616Z","created_at":"2025-12-01T02:39:38.616Z","created_at_timestamp":1764556778617,"name":"Th\u1EA1ch Chanh V\u00E0ng","name_translations":{},"description_translations":{},"meta":{},"etc":{},"assets":{},"config":{"online_order_display_large_image":false},"is_internal":false,"color":"","icon":"","sequence":7004,"image_url":"","image_file_path":"","image_thumbnail_url":"","image_thumbnail_file_path":"","visibility":[],"sku":"","type":"modifier","barcode":"","category_uid":"imc_UH7jvReaP3k2tyuMUWAm_","price":10000,"original_price":0,"prices":[],"stock":999,"print_on_receipt":true,"price_adjustable":false,"is_sold_out":false,"recipe":""},{"uid":"iod_tAxh_GII-A9nxPYQQ7pXH","updated_at":"2025-12-01T02:39:46.145Z","created_at":"2025-12-01T02:39:46.145Z","created_at_timestamp":1764556786145,"name":"Th\u1EA1ch Nha \u0110am","name_translations":{},"description_translations":{},"meta":{},"etc":{},"assets":{},"config":{"online_order_display_large_image":false},"is_internal":false,"color":"","icon":"","sequence":7005,"image_url":"","image_file_path":"","image_thumbnail_url":"","image_thumbnail_file_path":"","visibility":[],"sku":"","type":"modifier","barcode":"","category_uid":"imc_UH7jvReaP3k2tyuMUWAm_","price":10000,"original_price":0,"prices":[],"stock":1000,"print_on_receipt":true,"price_adjustable":false,"is_sold_out":false,"recipe":""},{"uid":"iod_DfzW0yeeEDGjfLPDyMDP9","updated_at":"2025-12-01T02:39:52.931Z","created_at":"2025-12-01T02:39:52.931Z","created_at_timestamp":1764556792931,"name":"B\u00E1nh L\u1ECDt","name_translations":{},"description_translations":{},"meta":{},"etc":{},"assets":{},"config":{"online_order_display_large_image":false},"is_internal":false,"color":"","icon":"","sequence":7006,"image_url":"","image_file_path":"","image_thumbnail_url":"","image_thumbnail_file_path":"","visibility":[],"sku":"","type":"modifier","barcode":"","category_uid":"imc_UH7jvReaP3k2tyuMUWAm_","price":10000,"original_price":0,"prices":[],"stock":1000,"print_on_receipt":true,"price_adjustable":false,"is_sold_out":false,"recipe":""},{"uid":"iod_dSHn36b5-UNatGPAXTA7-","updated_at":"2025-12-01T02:40:01.345Z","created_at":"2025-12-01T02:40:01.345Z","created_at_timestamp":1764556801345,"name":"Kh\u00FAc B\u1EA1ch","name_translations":{},"description_translations":{},"meta":{},"etc":{},"assets":{},"config":{"online_order_display_large_image":false},"is_internal":false,"color":"","icon":"","sequence":7007,"image_url":"","image_file_path":"","image_thumbnail_url":"","image_thumbnail_file_path":"","visibility":[],"sku":"","type":"modifier","barcode":"","category_uid":"imc_UH7jvReaP3k2tyuMUWAm_","price":15000,"original_price":0,"prices":[],"stock":999,"print_on_receipt":true,"price_adjustable":false,"is_sold_out":false,"recipe":""},{"uid":"iod_JwZio9Uv3BpOwQeDUgJt1","updated_at":"2025-12-01T02:40:11.646Z","created_at":"2025-12-01T02:40:11.646Z","created_at_timestamp":1764556811646,"name":"Nh\u00E3n Ng\u00E2m","name_translations":{},"description_translations":{},"meta":{},"etc":{},"assets":{},"config":{"online_order_display_large_image":false},"is_internal":false,"color":"","icon":"","sequence":7008,"image_url":"","image_file_path":"","image_thumbnail_url":"","image_thumbnail_file_path":"","visibility":[],"sku":"","type":"modifier","barcode":"","category_uid":"imc_UH7jvReaP3k2tyuMUWAm_","price":20000,"original_price":0,"prices":[],"stock":1000,"print_on_receipt":true,"price_adjustable":false,"is_sold_out":false,"recipe":""},{"uid":"iod_mmjGhQEIMoIT_v2Aopqre","updated_at":"2025-12-01T02:40:22.943Z","created_at":"2025-12-01T02:40:22.943Z","created_at_timestamp":1764556822943,"name":"\u0110\u00E0o Mi\u1EBFng","name_translations":{},"description_translations":{},"meta":{},"etc":{},"assets":{},"config":{"online_order_display_large_image":false},"is_internal":false,"color":"","icon":"","sequence":7009,"image_url":"","image_file_path":"","image_thumbnail_url":"","image_thumbnail_file_path":"","visibility":[],"sku":"","type":"modifier","barcode":"","category_uid":"imc_UH7jvReaP3k2tyuMUWAm_","price":20000,"original_price":0,"prices":[],"stock":1000,"print_on_receipt":true,"price_adjustable":false,"is_sold_out":false,"recipe":""}],"print_template_uid":""},"point":0,"point_subtotal":0,"up_size":false,"is_take_out":false,"is_duplicate":false,"is_combo":false,"is_combo_item":false,"combo_group_uid":"","combo_line_uid":"","modifiers":[{"item_uid":"iod_gJcG8TTG20hD9BQLWCQGZ","item":{"uid":"iod_gJcG8TTG20hD9BQLWCQGZ","updated_at":"2025-11-30T07:57:15.527Z","created_at":"2025-11-30T07:57:15.527Z","created_at_timestamp":1764489435527,"name":"100%","name_translations":{},"description_translations":{},"meta":{},"etc":{},"assets":{},"config":{"online_order_display_large_image":false},"is_internal":false,"color":"","icon":"","sequence":3004,"image_url":"","image_file_path":"","image_thumbnail_url":"","image_thumbnail_file_path":"","visibility":[],"sku":"","type":"modifier","barcode":"","category_uid":"imc_MA0C7z9B2GHEAplgfMSFc","price":0,"original_price":0,"prices":[],"stock":975,"print_on_receipt":true,"price_adjustable":false,"is_sold_out":false,"recipe":""},"price":0,"unit_of_measure":"quantity","quantity":1,"sent":0,"is_cancel_printed":false,"selected":false,"sales_user_uid":""},{"item_uid":"iod_UfyyYSUwqyfa5Fek9guGA","item":{"uid":"iod_UfyyYSUwqyfa5Fek9guGA","updated_at":"2025-11-29T12:06:08.590Z","created_at":"2025-11-29T12:06:08.590Z","created_at_timestamp":1764417968590,"name":"100%","name_translations":{},"description_translations":{},"meta":{},"etc":{},"assets":{},"config":{"online_order_display_large_image":false},"is_internal":false,"color":"","icon":"","sequence":4005,"image_url":"","image_file_path":"","image_thumbnail_url":"","image_thumbnail_file_path":"","visibility":[],"sku":"","type":"modifier","barcode":"","category_uid":"imc_5TG1isS3rDcW-TtE9mPvP","price":0,"original_price":0,"prices":[],"stock":978,"print_on_receipt":true,"price_adjustable":false,"is_sold_out":false,"recipe":"T01"},"price":0,"unit_of_measure":"quantity","quantity":1,"sent":0,"is_cancel_printed":false,"selected":false,"sales_user_uid":""}],"duplicated_from_uid":"","sent":1,"is_cancel_printed":false,"selected":false,"sales_user_uid":"","meta":{}}],"deleted_lines":[],"payment_has_cash":false,"payments":[{"uid":"pay_rqbYCVJ0tM9YqEtBBGGFg","updated_at":"2026-02-14T12:32:38.646Z","created_at":"2026-02-14T12:32:38.646Z","created_at_timestamp":1771072358646,"invoice_uid":"inv_iWcYcdv1YNoZC0aGwQxj5","tender_amount":70000,"change_amount":0,"payment_method_uid":"pmd_w5xPnDgpTeU2qufEmt9qz","payment_method_code":"ZALO","payment_method_name":"Zalo Pay","payment_method":{"uid":"pmd_w5xPnDgpTeU2qufEmt9qz","updated_at":"2026-01-24T15:20:40.450Z","created_at":"2025-11-25T06:02:37.244Z","created_at_timestamp":1764050557244,"deleted_at":null,"name":"Zalo Pay","name_translations":{},"sequence":0,"payment_currency_id":0,"currency_code":"","currency_symbol":"$","payment_method_code":"ZALO","codename":"","name_abbrev":"","enable_drawer":false,"enable_receipt":true,"image_url":"https://revopos-cdn.sgp1.digitaloceanspaces.com/4d49a465-47bb-44ca-8cdc-59f58c40e0cf.png","is_cash":false,"is_integrated":true,"is_disabled":false,"type":0,"api_id":"","api_key":"","is_sales_exclusive":false,"id_in_server":6742,"_timestamp":1768964152742600,"custom_image_url":"","enable_credit_validation":false,"enable_customer_validation":false,"is_cancellable":false},"payment_method_history":{"uid":"pmd_w5xPnDgpTeU2qufEmt9qz","updated_at":"2026-01-24T15:20:40.450Z","created_at":"2025-11-25T06:02:37.244Z","created_at_timestamp":1764050557244,"deleted_at":null,"name":"Zalo Pay","name_translations":{},"sequence":0,"payment_currency_id":0,"currency_code":"","currency_symbol":"$","payment_method_code":"ZALO","codename":"","name_abbrev":"","enable_drawer":false,"enable_receipt":true,"image_url":"https://revopos-cdn.sgp1.digitaloceanspaces.com/4d49a465-47bb-44ca-8cdc-59f58c40e0cf.png","is_cash":false,"is_integrated":true,"is_disabled":false,"type":0,"api_id":"","api_key":"","is_sales_exclusive":false,"id_in_server":6742,"_timestamp":1768964152742600,"custom_image_url":"","enable_credit_validation":false,"enable_customer_validation":false,"is_cancellable":false,"changed_at":"2026-02-14T14:34:38.555Z"}}],"table_switch_id":0,"employee_uid":"emp_ux2a3RLr1KZDYuHz_B7AP","employee_name":"Hamster","receipt_print_override":false,"customer":{"id":0,"ref_id":"","image_url":"","stripe_token":"","stripe_customer_id":"","username":"","normalized_username":"","company":"","ic":"","display_name":"","full_name":"","birth_date":"","gender":"","address":"","postcode":"","note":"","language":"","time_zone":"","created_at":"2026-02-14T12:32:28.322Z","updated_at":"2026-02-14T12:32:28.322Z","license_ids":"","security_stamp":"","phone_number":"","license_id":0,"credit":0,"point":0,"transaction_total":0,"transaction_time":0,"user_group_id":0,"user_group_name":"","error":""},"is_duplicate":false,"kitchen_print_override":false,"otp":"","activities":[],"receipt_print_job_uid":"pjb_6AY0c1t9DYoqZfiglWruH","kitchen_print_job_uids":[["pjb_6lIIH_bLvO2QJJ701v4Hx"]],"label_print_job_uids":[["pjb_TEy4QwnyPiP_ryRMpMNNB"]],"charges":[],"is_charge_triggered":false,"meta":{"total_item_quantity":1},"price_lookup_key":"","id_in_server":252475,"_timestamp":1771079680743354}
Binary file
@@ -0,0 +1 @@
1
+ 1B401B45001B34001B2D000A1B45011B61011D2111204861707079205468616920466F6F64202D20566963746F727920380A1D21001B45001B34001B2D000A1B45011B610138204A616C616E204C6567756E6469202330312D313020566963746F727920380A1B45001B34001B2D000A1B45011B610154656C3A2036373134363638320A1B45001B34001B2D000A1B45011B610044696E652D496E0A1B45001B34001B2D000A1B45011B6100232030313236303231303030303030313437320A1B45001B34001B2D000A1B45011B61001D21115461626C653A31350A1D21001B45001B34001B2D000A1B45011B61001D21114F52444552204E4F2E3A203437320A1D21001B45001B34001B2D000A1B45011B61005365727665642042793A61646D696E0A1B45001B34001B2D000A1B45011B6100446174653A323032362D4665622D31302031333A34313A35340A1B45001B34001B2D000A1B45001B34001B2D002D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D0A1B45014954454D20202020202020202020202020202020202020202020202051545920202020414D542020202020202020200A1B45001B34001B2D002D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D0A1B45011B61004D332E204761726C696320467269656420506F726B20202020202020783120202020202431332E39302020202020200A1B45011B61005445352E20436869636B656E204F6D656C65747465202020202020207831202020202024372E3930202020202020200A1B45011B61004E44322E20506861642073656520457720284B7761792054202020207831202020202024392E3930202020202020200A656F772920536561666F6F6420202020202020202020202020202020202020202020202020202020202020202020200A1B45011B610041342E205468616920507261776E2043616B65202834205020202020783120202020202431332E39302020202020200A69656365732920202020202020202020202020202020202020202020202020202020202020202020202020202020200A1B45011B6100445231312E2054686169204D696C6B205465612020202020202020207831202020202024342E3530202020202020200A1B45011B61004452372E204C656D6F6E6772617373204472696E6B202020202020207831202020202024332E3930202020202020200A1B45001B34001B2D001B61002D2020436F6C642020202020202020202020202020202020202020207B7B7D7D2020202020202020202020202020200A1B45001B34001B2D000A1B45001B34001B2D002D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D0A1B45011B6100535542544F54414C202020202020202020202020202020202020202020202020202020202435342E30302020202020200A1B450131302520534552564943452043484152474520202020202020202020202020202020202024352E3430202020202020200A1B4501544F54414C202020202020202020202020202020202020202020202020202020202020202435392E34302020202020200A1B45001B34001B2D002D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D0A1B45015041594D454E542042593A466975752020202020202020202020202020202020202020202435392E34302020202020200A1B45001B34001B2D002D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D0A1B45015041594D454E5420545950452020202020202020202020204D41535445524341524420202020202020202020202020200A1B45015245462020202020202020202020202020202020202020203334383732363039343320202020202020202020202020200A1B450143415244204E554D202020202020202020202020202020202A2A2A2A202A2A2A2A202A2A2A2A203130343020202020200A1B4501435553544F4D45522054454C2020202020202020202020202020202020202020202020202B36352A2A2A2A34383436200A1B450143617368204261636B20202020202020202020202020202020202020202020202020202024312E3738202020202020200A1B45014372656469742042616C616E63652020202020202020202020202020202020202020202024312E3738202020202020200A1B4501506F696E742042616C616E636520202020202020202020202020202020202020202020207074202020202020202020200A1B45001B34001B2D002D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D0A1B45011B610138204A616C616E204C6567756E6469202330312D313020566963746F727920380A1B45001B34001B2D000A0A0A0A0A1B45001B34001B2D000A1B45001B34001B2D000A1B45001B34001B2D000A1B45001B34001B2D001D5600
@@ -0,0 +1 @@
1
+ {"uid": "pjb_I8RJcR0Hm6iMsYSzmIDRd", "updated_at": "2026-02-10T05:41:54.209Z", "created_at": "2026-02-10T05:41:54.356Z", "created_at_timestamp": 1770702114356, "deleted_at": null, "name_translations": {}, "printer_name": "Receipt Printer", "driver_printer_name": "Receipt Printer", "printer_ip": "0.0.0.0", "printer_uid": "prt_eJ5yITaByzBnAXSz1IvhF", "printer_type": "receipt_printer", "protocol": "ESC/POS", "connection_type": "WINDOWS_DRIVER", "data": {"codepage": "cp936", "items": [{"type": "newline", "repeat": 1}, {"type": "text", "align": "center", "style": "bold", "item": {"size": "custom", "width": 1, "height": 1, "text": " Happy Thai Food - Victory 8"}}, {"type": "newline", "repeat": 1}, {"type": "text", "align": "center", "style": "bold", "item": {"text": "8 Jalan Legundi #01-10 Victory 8"}}, {"type": "newline", "repeat": 1}, {"type": "text", "align": "center", "style": "bold", "item": {"text": "Tel: 67146682"}}, {"type": "newline", "repeat": 1}, {"type": "text", "align": "left", "style": "bold", "item": {"text": "Dine-In"}}, {"type": "newline", "repeat": 1}, {"type": "text", "align": "left", "style": "bold", "item": {"text": "# 01260210000001472"}}, {"type": "newline", "repeat": 1}, {"type": "text", "align": "left", "style": "bold", "item": {"size": "custom", "width": 1, "height": 1, "text": "Table:15"}}, {"type": "newline", "repeat": 1}, {"type": "text", "align": "left", "style": "bold", "item": {"size": "custom", "width": 1, "height": 1, "text": "ORDER NO.: 472"}}, {"type": "newline", "repeat": 1}, {"type": "text", "align": "left", "style": "bold", "item": {"text": "Served By:admin"}}, {"type": "newline", "repeat": 1}, {"type": "text", "align": "left", "style": "bold", "item": {"text": "Date:2026-Feb-10 13:41:54"}}, {"type": "newline", "repeat": 1}, {"type": "line"}, {"type": "table", "style": "bold", "table": {"items": [{"text": "ITEM", "column_width": 50}, {"text": "", "column_width": 10}, {"text": "QTY", "column_width": 15}, {"text": "AMT", "column_width": 25}]}}, {"type": "line"}, {"type": "table", "align": "left", "style": "bold", "table": {"items": [{"text": "M3. Garlic Fried Pork ", "column_width": 50}, {"text": "", "column_width": 10}, {"text": "x1", "column_width": 15}, {"text": "$13.90", "column_width": 25}]}}, {"type": "table", "align": "left", "style": "bold", "table": {"items": [{"text": "TE5. Chicken Omelette ", "column_width": 50}, {"text": "", "column_width": 10}, {"text": "x1", "column_width": 15}, {"text": "$7.90", "column_width": 25}]}}, {"type": "table", "align": "left", "style": "bold", "table": {"items": [{"text": "ND2. Phad see Ew (Kway Teow) Seafood ", "column_width": 50}, {"text": "", "column_width": 10}, {"text": "x1", "column_width": 15}, {"text": "$9.90", "column_width": 25}]}}, {"type": "table", "align": "left", "style": "bold", "table": {"items": [{"text": "A4. Thai Prawn Cake (4 Pieces) ", "column_width": 50}, {"text": "", "column_width": 10}, {"text": "x1", "column_width": 15}, {"text": "$13.90", "column_width": 25}]}}, {"type": "table", "align": "left", "style": "bold", "table": {"items": [{"text": "DR11. Thai Milk Tea ", "column_width": 50}, {"text": "", "column_width": 10}, {"text": "x1", "column_width": 15}, {"text": "$4.50", "column_width": 25}]}}, {"type": "table", "align": "left", "style": "bold", "table": {"items": [{"text": "DR7. Lemongrass Drink ", "column_width": 50}, {"text": "", "column_width": 10}, {"text": "x1", "column_width": 15}, {"text": "$3.90", "column_width": 25}]}}, {"type": "table", "align": "left", "table": {"items": [{"text": "- Cold ", "column_width": 50}, {"text": "", "column_width": 10}, {"text": "{{}}", "column_width": 15}, {"text": "", "column_width": 25}]}}, {"type": "newline", "repeat": 1}, {"type": "line"}, {"codepage": "cp437", "type": "table", "align": "left", "style": "bold", "table": {"items": [{"text": "SUBTOTAL", "column_width": 50}, {"text": "", "column_width": 25}, {"text": "$54.00", "column_width": 25}]}}, {"codepage": "cp437", "type": "table", "style": "bold", "table": {"items": [{"text": "10% SERVICE CHARGE", "column_width": 50}, {"text": "", "column_width": 25}, {"text": "$5.40", "column_width": 25}]}}, {"codepage": "cp437", "type": "table", "style": "bold", "size": "custom", "width": 1, "height": 1, "table": {"items": [{"text": "TOTAL", "column_width": 50}, {"text": "", "column_width": 25}, {"text": "$59.40", "column_width": 25}]}}, {"type": "line"}, {"codepage": "cp437", "type": "table", "style": "bold", "table": {"items": [{"text": "PAYMENT BY:Fiuu", "column_width": 50}, {"text": "", "column_width": 25}, {"text": "$59.40", "column_width": 25}]}}, {"type": "line"}, {"codepage": "cp437", "type": "table", "style": "bold", "table": {"items": [{"text": "PAYMENT TYPE", "column_width": 50}, {"text": "MASTERCARD", "column_width": 50}]}}, {"codepage": "cp437", "type": "table", "style": "bold", "table": {"items": [{"text": "REF", "column_width": 50}, {"text": "3487260943", "column_width": 50}]}}, {"codepage": "cp437", "type": "table", "style": "bold", "table": {"items": [{"text": "CARD NUM", "column_width": 50}, {"text": "**** **** **** 1040", "column_width": 50}]}}, {"type": "table", "style": "bold", "table": {"items": [{"text": "CUSTOMER TEL", "column_width": 50}, {"text": "", "column_width": 25}, {"text": "\u002B65****4846", "column_width": 25}]}}, {"type": "table", "style": "bold", "table": {"items": [{"text": "Cash Back", "column_width": 50}, {"text": "", "column_width": 25}, {"text": "$1.78", "column_width": 25}]}}, {"type": "table", "style": "bold", "table": {"items": [{"text": "Credit Balance", "column_width": 50}, {"text": "", "column_width": 25}, {"text": "$1.78", "column_width": 25}]}}, {"type": "table", "style": "bold", "table": {"items": [{"text": "Point Balance", "column_width": 50}, {"text": "", "column_width": 25}, {"text": "pt", "column_width": 25}]}}, {"type": "line"}, {"type": "text", "align": "center", "style": "bold", "item": {"text": "8 Jalan Legundi #01-10 Victory 8"}}, {"type": "newline", "repeat": 5}, {"type": "newline", "repeat": 1}, {"type": "newline", "repeat": 1}, {"type": "newline", "repeat": 1}, {"type": "cut"}]}, "status": "printed", "retries": 0, "commands": "", "save_only": false, "print_times": 1, "printed": 1, "device_uid_to_print": "", "designated_table_section_uids": [], "job_id": 0, "print_sequence": 0, "meta": {}, "id_in_server": 1044494, "_timestamp": 1770702115370025}