@user231243/remove-debugger 0.0.10 → 0.0.11

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.
@@ -1,1390 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="vi">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>FnB POS — Business Logic Toàn Diện</title>
7
- <style>
8
- :root {
9
- --blue: #2563eb; --blue-dark: #1e3a8a; --blue-light: #dbeafe; --blue-xlight: #eff6ff;
10
- --green: #16a34a; --green-light: #dcfce7; --green-xlight: #f0fdf4;
11
- --red: #dc2626; --red-light: #fee2e2; --red-xlight: #fff1f2;
12
- --yellow: #d97706; --yellow-light: #fef3c7; --yellow-xlight: #fffbeb;
13
- --purple: #7c3aed; --purple-light: #ede9fe; --purple-xlight: #faf5ff;
14
- --teal: #0d9488; --teal-light: #ccfbf1; --teal-xlight: #f0fdfa;
15
- --orange: #ea580c; --orange-light: #ffedd5;
16
- --gray: #6b7280; --gray-light: #f3f4f6; --dark: #111827;
17
- --shadow: 0 4px 24px rgba(0,0,0,0.09);
18
- }
19
- *{box-sizing:border-box;margin:0;padding:0}
20
- html{scroll-behavior:smooth}
21
- body{font-family:'Segoe UI',system-ui,sans-serif;background:#f0f4ff;color:var(--dark);line-height:1.7}
22
-
23
- /* ── HEADER ── */
24
- header{background:linear-gradient(135deg,#0f172a 0%,#1e3a8a 50%,#7c3aed 100%);color:white;padding:56px 32px 44px;text-align:center}
25
- header h1{font-size:2.4rem;font-weight:900;letter-spacing:-1px}
26
- header p{margin-top:12px;font-size:1.1rem;opacity:.8}
27
- .badge-row{margin-top:20px;display:flex;gap:8px;justify-content:center;flex-wrap:wrap}
28
- .badge{background:rgba(255,255,255,.15);border:1px solid rgba(255,255,255,.3);border-radius:999px;padding:4px 14px;font-size:.8rem;font-weight:600}
29
-
30
- /* ── LAYOUT ── */
31
- .container{max-width:1200px;margin:0 auto;padding:40px 20px 100px}
32
-
33
- /* ── NAV ── */
34
- .toc{background:white;border-radius:16px;padding:24px 28px;margin-bottom:40px;box-shadow:var(--shadow)}
35
- .toc h2{font-size:.78rem;text-transform:uppercase;letter-spacing:1.5px;color:var(--gray);margin-bottom:14px}
36
- .toc-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:6px}
37
- .toc a{display:flex;align-items:center;gap:8px;padding:8px 12px;border-radius:8px;text-decoration:none;font-size:.88rem;font-weight:600;color:var(--blue);transition:.15s}
38
- .toc a:hover{background:var(--blue-xlight)}
39
- .toc a .n{background:var(--blue);color:white;width:22px;height:22px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:.72rem;flex-shrink:0}
40
-
41
- /* ── SECTION ── */
42
- .section{margin-bottom:72px}
43
- .section-header{display:flex;align-items:center;gap:14px;margin-bottom:28px;padding-bottom:12px;border-bottom:3px solid}
44
- .section-header .icon{width:48px;height:48px;border-radius:14px;display:flex;align-items:center;justify-content:center;font-size:1.5rem;flex-shrink:0}
45
- .section-header h2{font-size:1.5rem;font-weight:900}
46
-
47
- /* ── CARD ── */
48
- .card{background:white;border-radius:16px;padding:28px 32px;box-shadow:var(--shadow);margin-bottom:20px}
49
- .card-title{font-size:1rem;font-weight:800;margin-bottom:16px;color:var(--dark)}
50
- .card-subtitle{font-size:.85rem;color:var(--gray);margin-bottom:16px}
51
-
52
- /* ── GRID ── */
53
- .g2{display:grid;grid-template-columns:1fr 1fr;gap:20px}
54
- .g3{display:grid;grid-template-columns:repeat(3,1fr);gap:16px}
55
- .g4{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}
56
- @media(max-width:800px){.g2,.g3,.g4{grid-template-columns:1fr}}
57
-
58
- /* ── STATE MACHINE ── */
59
- .states{display:flex;flex-wrap:wrap;gap:12px;margin:16px 0}
60
- .state-pill{border-radius:10px;padding:10px 16px;font-size:.85rem;font-weight:700;text-align:center;flex:1;min-width:120px}
61
- .state-arrow{display:flex;align-items:center;justify-content:center;font-size:1.2rem;color:var(--gray);flex-shrink:0;padding:0 4px}
62
-
63
- /* ── FLOW ── */
64
- .flow{display:flex;flex-direction:column;gap:0;position:relative}
65
- .flow-step{display:flex;align-items:flex-start;gap:14px;position:relative;padding-bottom:24px}
66
- .flow-step:last-child{padding-bottom:0}
67
- .flow-step::before{content:'';position:absolute;left:17px;top:36px;width:2px;height:calc(100% - 8px);background:var(--blue-light)}
68
- .flow-step:last-child::before{display:none}
69
- .flow-num{width:36px;height:36px;border-radius:50%;background:var(--blue);color:white;font-weight:800;font-size:.9rem;display:flex;align-items:center;justify-content:center;flex-shrink:0;position:relative;z-index:1}
70
- .flow-body{flex:1;padding-top:5px}
71
- .flow-body h4{font-size:.95rem;font-weight:700;margin-bottom:3px}
72
- .flow-body p{font-size:.87rem;color:#374151}
73
- .hint{display:inline-block;margin-top:5px;font-size:.75rem;color:var(--gray);font-family:'Courier New',monospace;background:var(--gray-light);border-radius:5px;padding:2px 8px}
74
-
75
- /* ── FORMULA ── */
76
- .formula{background:linear-gradient(135deg,var(--blue-dark),var(--blue));color:white;border-radius:14px;padding:22px 26px;margin:14px 0;font-family:'Courier New',monospace;font-size:.9rem;line-height:2.2}
77
- .formula .f-label{font-family:'Segoe UI',sans-serif;font-size:.75rem;text-transform:uppercase;letter-spacing:1px;opacity:.65;margin-bottom:8px}
78
- .plus{color:#86efac}.minus{color:#fca5a5}.eq{color:#fde68a;font-weight:800}.result{color:#fde68a;font-weight:800;font-size:1.05rem}
79
-
80
- /* ── TABLE ── */
81
- .tbl{width:100%;border-collapse:collapse;font-size:.875rem}
82
- .tbl th{background:var(--blue-dark);color:white;padding:10px 14px;text-align:left;font-size:.8rem;font-weight:700}
83
- .tbl th:first-child{border-radius:8px 0 0 0}.tbl th:last-child{border-radius:0 8px 0 0}
84
- .tbl td{padding:10px 14px;border-bottom:1px solid var(--gray-light);vertical-align:top}
85
- .tbl tr:hover td{background:#fafafa}
86
- .tbl tr:last-child td{border-bottom:none}
87
-
88
- /* ── TAGS ── */
89
- .tag{display:inline-block;border-radius:6px;padding:2px 8px;font-size:.73rem;font-weight:700}
90
- .tag-blue{background:var(--blue-light);color:#1d4ed8}
91
- .tag-green{background:var(--green-light);color:#15803d}
92
- .tag-red{background:var(--red-light);color:#b91c1c}
93
- .tag-yellow{background:var(--yellow-light);color:#92400e}
94
- .tag-purple{background:var(--purple-light);color:#5b21b6}
95
- .tag-teal{background:var(--teal-light);color:#0f766e}
96
- .tag-orange{background:var(--orange-light);color:#9a3412}
97
- .tag-gray{background:var(--gray-light);color:#374151}
98
-
99
- /* ── CALLOUT ── */
100
- .callout{border-left:4px solid;border-radius:0 10px 10px 0;padding:14px 18px;margin:14px 0;font-size:.88rem}
101
- .callout strong{display:block;margin-bottom:4px}
102
- .c-info{border-color:var(--blue);background:var(--blue-xlight)}
103
- .c-warn{border-color:var(--yellow);background:var(--yellow-xlight)}
104
- .c-success{border-color:var(--green);background:var(--green-xlight)}
105
- .c-danger{border-color:var(--red);background:var(--red-xlight)}
106
- .c-purple{border-color:var(--purple);background:var(--purple-xlight)}
107
-
108
- /* ── DECISION TREE ── */
109
- .dtree{padding:8px 0}
110
- .dt-root{background:var(--blue);color:white;border-radius:10px;padding:12px 18px;font-weight:700;text-align:center;margin-bottom:14px}
111
- .dt-branches{display:flex;gap:14px;flex-wrap:wrap}
112
- .dt-branch{flex:1;min-width:180px;border-radius:10px;padding:14px 16px;border:2px solid;font-size:.86rem}
113
- .dt-pos{border-color:var(--green);background:var(--green-xlight)}
114
- .dt-neg{border-color:var(--red);background:var(--red-xlight)}
115
- .dt-zero{border-color:var(--yellow);background:var(--yellow-xlight)}
116
- .dt-label{font-weight:800;font-size:.75rem;text-transform:uppercase;margin-bottom:8px}
117
- .dt-pos .dt-label{color:var(--green)}.dt-neg .dt-label{color:var(--red)}.dt-zero .dt-label{color:var(--yellow)}
118
- .dt-branch ul{padding-left:16px;margin-top:6px}
119
- .dt-branch li{margin-bottom:4px}
120
- .dt-invar{margin-top:8px;padding:8px;border-radius:7px;font-size:.78rem;font-weight:600}
121
- .dt-pos .dt-invar{background:rgba(22,163,74,.1)}.dt-neg .dt-invar{background:rgba(220,38,38,.1)}.dt-zero .dt-invar{background:rgba(217,119,6,.1)}
122
-
123
- /* ── STATUS CARD ── */
124
- .status-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:12px}
125
- .status-card{border-radius:12px;padding:14px;text-align:center;border:2px solid}
126
- .status-card .s-icon{font-size:1.6rem;margin-bottom:6px}
127
- .status-card .s-name{font-weight:800;font-size:.9rem}
128
- .status-card .s-desc{font-size:.75rem;color:var(--gray);margin-top:4px}
129
- .s-available{border-color:var(--green);background:var(--green-xlight)}
130
- .s-reserved{border-color:var(--blue);background:var(--blue-xlight)}
131
- .s-occupied{border-color:var(--orange);background:var(--orange-light)}
132
- .s-inactive{border-color:var(--gray);background:var(--gray-light)}
133
- .s-inprogress{border-color:var(--blue);background:var(--blue-xlight)}
134
- .s-done{border-color:var(--green);background:var(--green-xlight)}
135
- .s-cancelled{border-color:var(--red);background:var(--red-xlight)}
136
- .s-waiting{border-color:var(--yellow);background:var(--yellow-xlight)}
137
-
138
- /* ── DISH STATE PIPELINE ── */
139
- .pipeline{display:flex;align-items:center;gap:6px;flex-wrap:wrap;padding:16px 0}
140
- .pipe-step{border-radius:8px;padding:8px 14px;font-size:.8rem;font-weight:700;text-align:center;border:2px solid}
141
- .pipe-arrow{color:var(--gray);font-size:1.1rem}
142
-
143
- /* ── PROMO CARD ── */
144
- .promo-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:14px}
145
- .promo-card{border-radius:12px;padding:16px;border:2px solid}
146
- .promo-icon{font-size:1.6rem;margin-bottom:8px}
147
- .promo-name{font-weight:800;font-size:.88rem;margin-bottom:4px}
148
- .promo-desc{font-size:.78rem;color:var(--gray)}
149
-
150
- /* ── TIMELINE ── */
151
- .timeline{position:relative;padding-left:30px}
152
- .timeline::before{content:'';position:absolute;left:8px;top:0;bottom:0;width:2px;background:linear-gradient(to bottom,var(--blue),var(--purple))}
153
- .tl-item{position:relative;margin-bottom:22px}
154
- .tl-dot{position:absolute;left:-27px;top:4px;width:16px;height:16px;border-radius:50%;border:3px solid white;background:var(--blue);box-shadow:0 0 0 2px var(--blue)}
155
- .tl-item h4{font-weight:700;font-size:.95rem}
156
- .tl-item p{font-size:.87rem;color:#374151;margin-top:3px}
157
- .tl-hint{display:inline-block;font-size:.72rem;background:var(--blue-xlight);color:var(--blue);border-radius:6px;padding:2px 8px;font-weight:700;margin-top:4px}
158
-
159
- /* ── METHOD CARDS ── */
160
- .method-card{border-radius:12px;padding:18px;border:2px solid;text-align:center}
161
- .method-icon{font-size:2rem;margin-bottom:8px}
162
- .method-name{font-weight:800;font-size:.9rem}
163
- .method-sub{font-size:.78rem;color:var(--gray);margin-top:4px}
164
- .m-cash{border-color:var(--green);background:var(--green-xlight)}
165
- .m-bank{border-color:var(--blue);background:var(--blue-xlight)}
166
- .m-card{border-color:var(--purple);background:var(--purple-xlight)}
167
- .m-debt{border-color:var(--yellow);background:var(--yellow-xlight)}
168
- .m-split{border-color:var(--teal);background:var(--teal-xlight)}
169
-
170
- /* ── MISC ── */
171
- code{background:var(--gray-light);border-radius:5px;padding:2px 7px;font-size:.82rem;font-family:'Courier New',monospace;color:var(--purple)}
172
- .mt-12{margin-top:12px}.mt-20{margin-top:20px}
173
- .center{text-align:center}
174
-
175
- /* ── BIG DIAGRAM ── */
176
- .arch-wrap{overflow-x:auto;padding:12px 0}
177
- .arch-wrap svg{min-width:900px}
178
-
179
- /* ── FOOTER ── */
180
- .footer-card{background:linear-gradient(135deg,var(--blue-dark),var(--purple));color:white;border-radius:20px;padding:40px;text-align:center;margin-top:40px}
181
- .footer-card h3{font-size:1.3rem;font-weight:900;margin-bottom:10px}
182
- .footer-card p{font-size:.9rem;opacity:.85;max-width:700px;margin:0 auto}
183
- .footer-card .meta{margin-top:16px;font-size:.75rem;opacity:.55}
184
- </style>
185
- </head>
186
- <body>
187
-
188
- <header>
189
- <h1>🍽️ FnB POS — Business Logic Toàn Diện</h1>
190
- <p>9 agent nghiên cứu song song · Tổng hợp từ mã nguồn thực tế · Dành cho người muốn hiểu sâu hệ thống</p>
191
- <div class="badge-row">
192
- <span class="badge">📋 Đơn hàng</span>
193
- <span class="badge">🪑 Bàn &amp; Khu vực</span>
194
- <span class="badge">🎁 Khuyến mãi</span>
195
- <span class="badge">👤 Khách hàng &amp; Công nợ</span>
196
- <span class="badge">🍳 Bếp &amp; In ấn</span>
197
- <span class="badge">💰 Thanh toán</span>
198
- </div>
199
- </header>
200
-
201
- <div class="container">
202
-
203
- <!-- TOC -->
204
- <nav class="toc">
205
- <h2>Mục lục</h2>
206
- <div class="toc-grid">
207
- <a href="#s1"><span class="n">1</span> Kiến trúc tổng quan hệ thống</a>
208
- <a href="#s2"><span class="n">2</span> Bàn &amp; Khu vực (Table &amp; Area)</a>
209
- <a href="#s3"><span class="n">3</span> Vòng đời Đơn hàng (Service Turn)</a>
210
- <a href="#s4"><span class="n">4</span> Máy trạng thái Món ăn</a>
211
- <a href="#s5"><span class="n">5</span> Gửi bếp &amp; Theo dõi món</a>
212
- <a href="#s6"><span class="n">6</span> Khuyến mãi (CTKM Engine)</a>
213
- <a href="#s7"><span class="n">7</span> Khách hàng, Cọc &amp; Công nợ</a>
214
- <a href="#s8"><span class="n">8</span> Mở &amp; Tính tiền thanh toán</a>
215
- <a href="#s9"><span class="n">9</span> Phương thức thanh toán</a>
216
- <a href="#s10"><span class="n">10</span> Phân bổ tiền thừa/thiếu</a>
217
- <a href="#s11"><span class="n">11</span> Tiền cọc dư (Deposit Excess)</a>
218
- <a href="#s12"><span class="n">12</span> Xác nhận &amp; Lưu thanh toán</a>
219
- <a href="#s13"><span class="n">13</span> In ấn &amp; Hóa đơn</a>
220
- <a href="#s14"><span class="n">14</span> QR Payment &amp; Hóa đơn điện tử</a>
221
- </div>
222
- </nav>
223
-
224
- <!-- ══════════════════════════════════════════
225
- SECTION 1: KIẾN TRÚC TỔNG QUAN
226
- ══════════════════════════════════════════ -->
227
- <section class="section" id="s1">
228
- <div class="section-header" style="border-color:var(--blue)">
229
- <div class="icon" style="background:var(--blue-light)">🗺️</div>
230
- <div><h2 style="color:var(--blue)">1. Kiến trúc tổng quan hệ thống</h2></div>
231
- </div>
232
-
233
- <div class="card">
234
- <div class="card-title">Sơ đồ tổng thể — Các thực thể và quan hệ chính</div>
235
- <div class="arch-wrap">
236
- <svg viewBox="0 0 950 340" xmlns="http://www.w3.org/2000/svg" style="height:340px">
237
- <defs>
238
- <marker id="a" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto">
239
- <path d="M0,0 L0,6 L8,3 z" fill="#6b7280"/>
240
- </marker>
241
- </defs>
242
- <!-- DArea -->
243
- <rect x="10" y="130" width="130" height="60" rx="10" fill="#dbeafe" stroke="#2563eb" stroke-width="2"/>
244
- <text x="75" y="155" text-anchor="middle" fill="#1e3a8a" font-size="12" font-weight="bold">DArea</text>
245
- <text x="75" y="172" text-anchor="middle" fill="#374151" font-size="10">Khu vực / Zone</text>
246
- <!-- DTable -->
247
- <rect x="180" y="80" width="130" height="60" rx="10" fill="#dbeafe" stroke="#2563eb" stroke-width="2"/>
248
- <text x="245" y="105" text-anchor="middle" fill="#1e3a8a" font-size="12" font-weight="bold">DTable</text>
249
- <text x="245" y="122" text-anchor="middle" fill="#374151" font-size="10">Bàn ăn</text>
250
- <!-- DReservation -->
251
- <rect x="180" y="180" width="130" height="60" rx="10" fill="#fef3c7" stroke="#d97706" stroke-width="2"/>
252
- <text x="245" y="205" text-anchor="middle" fill="#92400e" font-size="12" font-weight="bold">DReservation</text>
253
- <text x="245" y="222" text-anchor="middle" fill="#374151" font-size="10">Đặt bàn / Booking</text>
254
- <!-- DServiceTurn -->
255
- <rect x="380" y="120" width="150" height="70" rx="10" fill="#dcfce7" stroke="#16a34a" stroke-width="2"/>
256
- <text x="455" y="148" text-anchor="middle" fill="#14532d" font-size="12" font-weight="bold">DServiceTurn</text>
257
- <text x="455" y="165" text-anchor="middle" fill="#374151" font-size="10">Lượt phục vụ</text>
258
- <text x="455" y="180" text-anchor="middle" fill="#374151" font-size="9">Status · PaymentStatus</text>
259
- <!-- DOrder -->
260
- <rect x="580" y="60" width="130" height="60" rx="10" fill="#ede9fe" stroke="#7c3aed" stroke-width="2"/>
261
- <text x="645" y="85" text-anchor="middle" fill="#4c1d95" font-size="12" font-weight="bold">DOrder</text>
262
- <text x="645" y="102" text-anchor="middle" fill="#374151" font-size="10">Vòng order (1..N)</text>
263
- <!-- DOrderDetail -->
264
- <rect x="760" y="60" width="140" height="60" rx="10" fill="#ede9fe" stroke="#7c3aed" stroke-width="2"/>
265
- <text x="830" y="85" text-anchor="middle" fill="#4c1d95" font-size="12" font-weight="bold">DOrderDetail</text>
266
- <text x="830" y="102" text-anchor="middle" fill="#374151" font-size="10">Dòng món ăn</text>
267
- <!-- DKhachHang -->
268
- <rect x="580" y="200" width="130" height="60" rx="10" fill="#fef3c7" stroke="#d97706" stroke-width="2"/>
269
- <text x="645" y="225" text-anchor="middle" fill="#92400e" font-size="12" font-weight="bold">DKhachHang</text>
270
- <text x="645" y="242" text-anchor="middle" fill="#374151" font-size="10">Khách hàng</text>
271
- <!-- DBADeposit -->
272
- <rect x="760" y="200" width="140" height="60" rx="10" fill="#ccfbf1" stroke="#0d9488" stroke-width="2"/>
273
- <text x="830" y="225" text-anchor="middle" fill="#134e4a" font-size="12" font-weight="bold">DBADeposit</text>
274
- <text x="830" y="242" text-anchor="middle" fill="#374151" font-size="10">Phiếu cọc</text>
275
- <!-- ThanhToanDonHang -->
276
- <rect x="580" y="290" width="170" height="40" rx="8" fill="#fee2e2" stroke="#dc2626" stroke-width="2"/>
277
- <text x="665" y="310" text-anchor="middle" fill="#7f1d1d" font-size="11" font-weight="bold">ThanhToanDonHang</text>
278
- <text x="665" y="323" text-anchor="middle" fill="#374151" font-size="9">Dòng thanh toán</text>
279
- <!-- SPromotion -->
280
- <rect x="10" y="260" width="130" height="60" rx="10" fill="#ffedd5" stroke="#ea580c" stroke-width="2"/>
281
- <text x="75" y="285" text-anchor="middle" fill="#7c2d12" font-size="12" font-weight="bold">SPromotion</text>
282
- <text x="75" y="302" text-anchor="middle" fill="#374151" font-size="10">CTKM</text>
283
- <!-- arrows -->
284
- <line x1="140" y1="160" x2="178" y2="130" stroke="#6b7280" stroke-width="1.5" marker-end="url(#a)"/>
285
- <line x1="140" y1="165" x2="178" y2="200" stroke="#6b7280" stroke-width="1.5" marker-end="url(#a)"/>
286
- <line x1="245" y1="80" x2="380" y2="145" stroke="#6b7280" stroke-width="1.5" marker-end="url(#a)"/>
287
- <line x1="310" y1="210" x2="378" y2="165" stroke="#6b7280" stroke-width="1.5" marker-end="url(#a)"/>
288
- <line x1="530" y1="145" x2="578" y2="100" stroke="#6b7280" stroke-width="1.5" marker-end="url(#a)"/>
289
- <line x1="710" y1="90" x2="758" y2="90" stroke="#6b7280" stroke-width="1.5" marker-end="url(#a)"/>
290
- <line x1="530" y1="165" x2="578" y2="220" stroke="#6b7280" stroke-width="1.5" marker-end="url(#a)"/>
291
- <line x1="530" y1="175" x2="578" y2="305" stroke="#6b7280" stroke-width="1.5" stroke-dasharray="5,3" marker-end="url(#a)"/>
292
- <line x1="710" y1="230" x2="758" y2="230" stroke="#6b7280" stroke-width="1.5" marker-end="url(#a)"/>
293
- <line x1="140" y1="285" x2="378" y2="195" stroke="#ea580c" stroke-width="1.5" stroke-dasharray="5,3" marker-end="url(#a)"/>
294
- <!-- labels on arrows -->
295
- <text x="200" y="145" fill="#6b7280" font-size="9">1:N</text>
296
- <text x="310" y="145" fill="#6b7280" font-size="9">link</text>
297
- <text x="550" y="125" fill="#6b7280" font-size="9">1:N rounds</text>
298
- <text x="720" y="82" fill="#6b7280" font-size="9">1:N</text>
299
- <text x="550" y="200" fill="#6b7280" font-size="9">optional</text>
300
- <text x="200" y="255" fill="#ea580c" font-size="9">applied</text>
301
- </svg>
302
- </div>
303
-
304
- <div class="g3 mt-20">
305
- <div class="callout c-info" style="margin:0">
306
- <strong>🔑 ServiceTurn là trung tâm</strong>
307
- Mọi thứ quay quanh <code>DServiceTurn</code> — bàn, khách, món, cọc, thanh toán đều gắn vào đây.
308
- </div>
309
- <div class="callout c-success" style="margin:0">
310
- <strong>📦 DOrder = một vòng order</strong>
311
- Một ServiceTurn có thể có nhiều DOrder (nhiều lần gọi món). Không giới hạn số vòng.
312
- </div>
313
- <div class="callout c-purple" style="margin:0">
314
- <strong>🎁 CTKM áp dụng tại order detail</strong>
315
- Khuyến mãi ảnh hưởng tới từng DOrderDetail và tạo ra DOrderDetail loại GiftDishes.
316
- </div>
317
- </div>
318
- </div>
319
- </section>
320
-
321
- <!-- ══════════════════════════════════════════
322
- SECTION 2: BÀN & KHU VỰC
323
- ══════════════════════════════════════════ -->
324
- <section class="section" id="s2">
325
- <div class="section-header" style="border-color:var(--teal)">
326
- <div class="icon" style="background:var(--teal-light)">🪑</div>
327
- <div><h2 style="color:var(--teal)">2. Bàn &amp; Khu vực (Table &amp; Area)</h2></div>
328
- </div>
329
-
330
- <div class="g2">
331
- <div class="card">
332
- <div class="card-title">4 Trạng thái của bàn</div>
333
- <div class="status-grid">
334
- <div class="status-card s-available">
335
- <div class="s-icon">🟢</div>
336
- <div class="s-name">Trống</div>
337
- <div class="s-desc">Available (0) — Sẵn sàng tiếp khách</div>
338
- </div>
339
- <div class="status-card s-reserved">
340
- <div class="s-icon">📅</div>
341
- <div class="s-name">Đặt trước</div>
342
- <div class="s-desc">Reserved (1) — Có booking xác nhận</div>
343
- </div>
344
- <div class="status-card s-occupied">
345
- <div class="s-icon">🔴</div>
346
- <div class="s-name">Đang dùng</div>
347
- <div class="s-desc">Occupied (2) — Có ServiceTurn đang mở</div>
348
- </div>
349
- <div class="status-card s-inactive">
350
- <div class="s-icon">⚫</div>
351
- <div class="s-name">Ngừng</div>
352
- <div class="s-desc">Inactive (3) — Bàn không phục vụ</div>
353
- </div>
354
- </div>
355
- <div class="callout c-info mt-12">
356
- <strong>💡 Trạng thái bàn là COMPUTED — không lưu trong DB</strong>
357
- <code>GetTables()</code> tính real-time: Occupied = có ServiceTurn <code>InProgress</code>; Reserved = có Reservation <code>TableReserved</code>. Không có field "TableStatus" trong DTable.
358
- </div>
359
- <span class="hint">DTableLayoutAppService.cs:190-246</span>
360
- </div>
361
-
362
- <div class="card">
363
- <div class="card-title">Khu vực (Area/Zone)</div>
364
- <div style="font-size:.9rem">
365
- <div style="background:var(--gray-light);border-radius:10px;padding:14px;margin-bottom:12px">
366
- <div style="font-weight:700;margin-bottom:8px">DArea có 2 loại:</div>
367
- <div style="display:flex;gap:12px;flex-wrap:wrap">
368
- <div style="flex:1;min-width:140px;background:white;border-radius:8px;padding:10px;border-left:3px solid var(--teal)">
369
- <div style="font-weight:700;color:var(--teal)">🍽️ Customer</div>
370
- <div style="font-size:.8rem;margin-top:4px">Khu vực phòng ăn — nơi khách ngồi</div>
371
- </div>
372
- <div style="flex:1;min-width:140px;background:white;border-radius:8px;padding:10px;border-left:3px solid var(--orange)">
373
- <div style="font-weight:700;color:var(--orange)">🍳 Kitchen</div>
374
- <div style="font-size:.8rem;margin-top:4px">Khu vực bếp — nhận phiếu order</div>
375
- </div>
376
- </div>
377
- </div>
378
- <div style="font-size:.85rem;color:#374151">
379
- Quan hệ: <strong>Một khu vực → nhiều bàn</strong> (<code>DTable.AreaId</code> FK). Một đơn hàng có thể <strong>dùng nhiều bàn</strong> (<code>DServiceTurnTable</code> — junction table many-to-many).
380
- </div>
381
- </div>
382
- <span class="hint mt-12">DArea.cs · AreaConfigurationEnum.cs:43-61</span>
383
- </div>
384
- </div>
385
-
386
- <div class="g2">
387
- <div class="card">
388
- <div class="card-title">🔀 Gộp bàn (Merge Tables)</div>
389
- <div class="flow">
390
- <div class="flow-step">
391
- <div class="flow-num">1</div>
392
- <div class="flow-body">
393
- <h4>Nhân viên mở "Gộp bàn"</h4>
394
- <p><code>GetForViewMerge(areaId)</code> — lấy danh sách ServiceTurn trong cùng khu vực đang InProgress</p>
395
- </div>
396
- </div>
397
- <div class="flow-step">
398
- <div class="flow-num">2</div>
399
- <div class="flow-body">
400
- <h4>Chọn các đơn và bàn đích</h4>
401
- <p>Nhân viên tick chọn các ServiceTurn nguồn và xác nhận bàn đích</p>
402
- </div>
403
- </div>
404
- <div class="flow-step">
405
- <div class="flow-num">3</div>
406
- <div class="flow-body">
407
- <h4>Backend gộp: Merge()</h4>
408
- <p>① Chuyển tất cả DOrder từ ServiceTurn nguồn sang đích<br>② Chuyển phiếu cọc<br>③ Cập nhật danh sách bàn<br>④ Xóa mềm ServiceTurn nguồn</p>
409
- <span class="hint">DServiceTurnsManager.cs:1595-1660</span>
410
- </div>
411
- </div>
412
- </div>
413
- </div>
414
-
415
- <div class="card">
416
- <div class="card-title">📅 Đặt bàn (Reservation)</div>
417
- <table class="tbl">
418
- <tr><th>Field</th><th>Ý nghĩa</th></tr>
419
- <tr><td><code>Quantity</code></td><td>Số khách</td></tr>
420
- <tr><td><code>BookingDate + BookingTime</code></td><td>Thời điểm đặt</td></tr>
421
- <tr><td><code>EndTime</code></td><td>Kết thúc slot</td></tr>
422
- <tr><td><code>DepositAmount</code></td><td>Tiền cọc đặt bàn</td></tr>
423
- <tr><td><code>Status</code></td><td>TableReserved(0) / TableAccepted(1) / Cancelled(2)</td></tr>
424
- </table>
425
- <div class="callout c-info mt-12">
426
- <strong>Reset bàn về Trống</strong>
427
- Tự động: khi ServiceTurn → Done/Cancelled hoặc Reservation → Cancelled. Không cần cập nhật field riêng.
428
- </div>
429
- </div>
430
- </div>
431
- </section>
432
-
433
- <!-- ══════════════════════════════════════════
434
- SECTION 3: VÒNG ĐỜI ĐƠN HÀNG
435
- ══════════════════════════════════════════ -->
436
- <section class="section" id="s3">
437
- <div class="section-header" style="border-color:var(--green)">
438
- <div class="icon" style="background:var(--green-light)">📋</div>
439
- <div><h2 style="color:var(--green)">3. Vòng đời Đơn hàng (Service Turn)</h2></div>
440
- </div>
441
-
442
- <div class="card">
443
- <div class="card-title">3 máy trạng thái độc lập hoạt động song song</div>
444
- <div class="g3">
445
- <div style="border:2px solid var(--blue);border-radius:12px;padding:16px">
446
- <div style="font-weight:800;color:var(--blue);margin-bottom:10px">① ServiceTurn.Status</div>
447
- <div class="states" style="flex-direction:column;gap:8px">
448
- <div class="state-pill s-waiting" style="border-radius:8px">WaitingInProgress — QR order chờ nhân viên duyệt</div>
449
- <div style="text-align:center;color:var(--gray)">↓ Nhân viên xác nhận</div>
450
- <div class="state-pill s-inprogress" style="border-radius:8px">InProgress — Đang phục vụ</div>
451
- <div style="text-align:center;color:var(--gray)">↓ Thanh toán xong</div>
452
- <div class="state-pill s-done" style="border-radius:8px">Done — Đã hoàn thành</div>
453
- <div style="text-align:center;color:var(--gray)">⎇ Hủy</div>
454
- <div class="state-pill s-cancelled" style="border-radius:8px">Cancelled — Đã hủy</div>
455
- </div>
456
- <span class="hint" style="margin-top:10px">_OrderContextFactory.js:104</span>
457
- </div>
458
-
459
- <div style="border:2px solid var(--yellow);border-radius:12px;padding:16px">
460
- <div style="font-weight:800;color:var(--yellow);margin-bottom:10px">② ServiceTurn.PaymentStatus</div>
461
- <div class="states" style="flex-direction:column;gap:8px">
462
- <div class="state-pill" style="background:#fef3c7;border:2px solid #d97706;border-radius:8px">Unpaid (0) — Chưa thanh toán</div>
463
- <div style="text-align:center;color:var(--gray)">↓ Mở modal thanh toán</div>
464
- <div class="state-pill" style="background:#dbeafe;border:2px solid #2563eb;border-radius:8px">PaymentRequested (1) — Đang xử lý</div>
465
- <div style="text-align:center;color:var(--gray)">↓ Xác nhận thanh toán</div>
466
- <div class="state-pill" style="background:#dcfce7;border:2px solid #16a34a;border-radius:8px">Paid (2) — Đã thanh toán</div>
467
- </div>
468
- <span class="hint" style="margin-top:10px">ServiceTurnEnum.cs:39-49</span>
469
- </div>
470
-
471
- <div style="border:2px solid var(--purple);border-radius:12px;padding:16px">
472
- <div style="font-weight:800;color:var(--purple);margin-bottom:10px">③ DOrder.Status (mỗi vòng)</div>
473
- <div class="states" style="flex-direction:column;gap:8px">
474
- <div class="state-pill" style="background:#fef3c7;border:2px solid #d97706;border-radius:8px">WaitingConfirm — Chưa gửi bếp</div>
475
- <div style="text-align:center;color:var(--gray)">↓ Gửi bếp</div>
476
- <div class="state-pill" style="background:#dcfce7;border:2px solid #16a34a;border-radius:8px">Confirmed — Đã gửi bếp</div>
477
- <div style="text-align:center;color:var(--gray)">⎇ Từ chối</div>
478
- <div class="state-pill" style="background:#fee2e2;border:2px solid #dc2626;border-radius:8px">Rejected — Bị từ chối</div>
479
- </div>
480
- <span class="hint" style="margin-top:10px">DOrder.cs:42</span>
481
- </div>
482
- </div>
483
- </div>
484
-
485
- <div class="g2">
486
- <div class="card">
487
- <div class="card-title">Vòng order (DOrder rounds)</div>
488
- <div style="font-size:.9rem">
489
- <p>Một ServiceTurn có thể có <strong>N vòng gọi món</strong>. Mỗi lần khách gọi thêm món là một DOrder mới.</p>
490
- <div style="background:var(--gray-light);border-radius:10px;padding:14px;margin-top:12px">
491
- <div style="font-weight:700;margin-bottom:8px">Ví dụ:</div>
492
- <div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
493
- <div style="background:#dbeafe;border-radius:8px;padding:8px 12px;font-size:.82rem;text-align:center">
494
- <div style="font-weight:700">Vòng 1</div>
495
- <div style="color:var(--gray)">Bò lúc lắc × 2</div>
496
- </div>
497
- <div>→</div>
498
- <div style="background:#dcfce7;border-radius:8px;padding:8px 12px;font-size:.82rem;text-align:center">
499
- <div style="font-weight:700">Vòng 2</div>
500
- <div style="color:var(--gray)">Hải sản × 1</div>
501
- </div>
502
- <div>→</div>
503
- <div style="background:#ede9fe;border-radius:8px;padding:8px 12px;font-size:.82rem;text-align:center">
504
- <div style="font-weight:700">Vòng 3</div>
505
- <div style="color:var(--gray)">Bia × 4</div>
506
- </div>
507
- </div>
508
- <div style="margin-top:10px;font-size:.82rem;color:var(--gray)">→ Cùng 1 ServiceTurn, 3 DOrder khác nhau, tổng hợp khi thanh toán</div>
509
- </div>
510
- <div class="callout c-info mt-12">
511
- <strong>Không giới hạn số vòng</strong>
512
- Mỗi lần nhấn "Tiếp theo" tạo DOrder mới. Không có validation giới hạn.
513
- </div>
514
- </div>
515
- </div>
516
-
517
- <div class="card">
518
- <div class="card-title">Khóa đơn hàng — Khi nào không chỉnh sửa được?</div>
519
- <table class="tbl">
520
- <tr><th>Điều kiện</th><th>Bị khóa gì</th></tr>
521
- <tr>
522
- <td><span class="tag tag-green">PaymentStatus = Paid</span></td>
523
- <td>Toàn bộ đơn: hiển thị read-only, không chỉnh sửa được</td>
524
- </tr>
525
- <tr>
526
- <td><span class="tag tag-yellow">Detail Status ∉ {WaitingConfirm, Confirmed}</span></td>
527
- <td>Từng món: không đổi số lượng, không hủy</td>
528
- </tr>
529
- <tr>
530
- <td>Sau khi gửi bếp</td>
531
- <td>Món đã gửi → WaitingProcessing → khóa edit</td>
532
- </tr>
533
- </table>
534
- <span class="hint mt-12">_CreateNewOrderModal.js:273,956 · Commons.js:1572</span>
535
- </div>
536
- </div>
537
- </section>
538
-
539
- <!-- ══════════════════════════════════════════
540
- SECTION 4: MÁY TRẠNG THÁI MÓN ĂN
541
- ══════════════════════════════════════════ -->
542
- <section class="section" id="s4">
543
- <div class="section-header" style="border-color:var(--purple)">
544
- <div class="icon" style="background:var(--purple-light)">🥘</div>
545
- <div><h2 style="color:var(--purple)">4. Máy trạng thái Món ăn (OrderDetail Status)</h2></div>
546
- </div>
547
-
548
- <div class="card">
549
- <div class="card-title">9 trạng thái của một dòng món — QrOrderDetailStatus</div>
550
- <div class="pipeline">
551
- <div class="pipe-step" style="border-color:#d97706;background:#fffbeb;color:#92400e">
552
- <div style="font-weight:800">0</div>WaitingConfirm
553
- </div>
554
- <div class="pipe-arrow">→</div>
555
- <div class="pipe-step" style="border-color:#2563eb;background:#eff6ff;color:#1d4ed8">
556
- <div style="font-weight:800">1</div>Confirmed
557
- </div>
558
- <div class="pipe-arrow">→</div>
559
- <div class="pipe-step" style="border-color:#7c3aed;background:#faf5ff;color:#5b21b6">
560
- <div style="font-weight:800">2</div>WaitingProcessing
561
- </div>
562
- <div class="pipe-arrow">→</div>
563
- <div class="pipe-step" style="border-color:#0d9488;background:#f0fdfa;color:#0f766e">
564
- <div style="font-weight:800">3</div>Processing
565
- </div>
566
- <div class="pipe-arrow">→</div>
567
- <div class="pipe-step" style="border-color:#ea580c;background:#ffedd5;color:#9a3412">
568
- <div style="font-weight:800">4</div>ReadyToServe
569
- </div>
570
- <div class="pipe-arrow">→</div>
571
- <div class="pipe-step" style="border-color:#16a34a;background:#f0fdf4;color:#15803d">
572
- <div style="font-weight:800">5</div>Served
573
- </div>
574
- </div>
575
- <div style="display:flex;gap:12px;flex-wrap:wrap;margin-top:8px">
576
- <div class="pipe-step" style="border-color:#dc2626;background:#fff1f2;color:#b91c1c">
577
- <div style="font-weight:800">8</div>Cancel
578
- </div>
579
- <div class="pipe-step" style="border-color:#6b7280;background:#f3f4f6;color:#374151">
580
- <div style="font-weight:800">6</div>Cooking
581
- </div>
582
- <div class="pipe-step" style="border-color:#6b7280;background:#f3f4f6;color:#374151">
583
- <div style="font-weight:800">7</div>Cooked
584
- </div>
585
- </div>
586
-
587
- <div class="g2 mt-20">
588
- <div class="callout c-warn" style="margin:0">
589
- <strong>🔑 Gate chỉnh sửa</strong>
590
- Chỉ khi status là <code>WaitingConfirm (0)</code> hoặc <code>Confirmed (1)</code> → nhân viên mới có thể đổi số lượng hoặc hủy. Từ <code>WaitingProcessing (2)</code> trở đi → khóa.
591
- </div>
592
- <div class="callout c-info" style="margin:0">
593
- <strong>📍 Gửi bếp thay đổi trạng thái</strong>
594
- WaitingConfirm → WaitingProcessing (khi gửi bếp). <em>Không phải tất cả 9 state đều dùng trong FnBOrderStaff</em> — một số (Cooking, Cooked) dành cho màn hình bếp (KDS).
595
- </div>
596
- </div>
597
- <span class="hint mt-12">TrangThaiEnum.cs:437-465</span>
598
- </div>
599
- </section>
600
-
601
- <!-- ══════════════════════════════════════════
602
- SECTION 5: GỬI BẾP
603
- ══════════════════════════════════════════ -->
604
- <section class="section" id="s5">
605
- <div class="section-header" style="border-color:var(--orange)">
606
- <div class="icon" style="background:var(--orange-light)">🍳</div>
607
- <div><h2 style="color:var(--orange)">5. Gửi bếp &amp; Theo dõi món</h2></div>
608
- </div>
609
-
610
- <div class="g2">
611
- <div class="card">
612
- <div class="card-title">Luồng Gửi bếp</div>
613
- <div class="flow">
614
- <div class="flow-step">
615
- <div class="flow-num">1</div>
616
- <div class="flow-body">
617
- <h4>Nhân viên bấm "Gửi bếp"</h4>
618
- <p><code>$sendToKitchenBtn</code> → <code>sendOrderToKitchen()</code></p>
619
- <span class="hint">_CreateNewOrderModal.js:38,1455</span>
620
- </div>
621
- </div>
622
- <div class="flow-step">
623
- <div class="flow-num">2</div>
624
- <div class="flow-body">
625
- <h4>Lọc món gửi</h4>
626
- <p><code>getSendToKitchenPrintDetails()</code> — chỉ gửi status WaitingConfirm hoặc Confirmed. Bỏ qua Cancel và DiscountOrder.</p>
627
- <span class="hint">_CreateNewOrderModal.js:1480-1489</span>
628
- </div>
629
- </div>
630
- <div class="flow-step">
631
- <div class="flow-num">3</div>
632
- <div class="flow-body">
633
- <h4>In phiếu bếp</h4>
634
- <p><code>printKitchenOrderSlip()</code> — in danh sách món, ghi chú, kèm sub-details (nguyên liệu, món phụ). In qua máy in nhiệt 80mm.</p>
635
- <span class="hint">Commons.js:1779-1888</span>
636
- </div>
637
- </div>
638
- <div class="flow-step">
639
- <div class="flow-num">4</div>
640
- <div class="flow-body">
641
- <h4>API gửi &amp; cập nhật DB</h4>
642
- <p>Status detail: WaitingConfirm/Confirmed → <code>WaitingProcessing</code>. DOrder.Status → Confirmed.</p>
643
- <span class="hint">DServiceTurnsManager.cs:1006</span>
644
- </div>
645
- </div>
646
- </div>
647
- </div>
648
-
649
- <div class="card">
650
- <div class="card-title">Theo dõi và xác nhận phục vụ</div>
651
- <div style="font-size:.9rem">
652
- <p style="margin-bottom:14px">Sau khi bếp làm xong, nhân viên xác nhận đã mang ra bàn qua modal <code>_DishServedStatusModal</code>:</p>
653
- <div style="background:var(--gray-light);border-radius:10px;padding:14px">
654
- <div style="font-weight:700;margin-bottom:8px">Màn hình xác nhận phục vụ:</div>
655
- <ul style="padding-left:18px;font-size:.85rem;display:flex;flex-direction:column;gap:6px">
656
- <li>Hiển thị danh sách món với <code>servedQuantity vs quantity</code></li>
657
- <li>Nhân viên tick từng món đã mang ra</li>
658
- <li>Bấm "Xác nhận phục vụ" → <code>confirmServiceTurn()</code></li>
659
- <li>ServedQty > 0 → Status → <code>Served</code></li>
660
- </ul>
661
- </div>
662
- <span class="hint mt-12">_DishServedStatusModal.js:76-96</span>
663
-
664
- <div class="callout c-warn mt-12">
665
- <strong>🚫 Hủy món sau khi gửi bếp</strong>
666
- Bếp không nhận phiếu hủy tự động. Cancel chỉ cập nhật trạng thái trong DB — màn hình bếp (KDS) poll để ẩn/deprioritize. Không in phiếu hủy.
667
- </div>
668
- </div>
669
- </div>
670
- </div>
671
- </section>
672
-
673
- <!-- ══════════════════════════════════════════
674
- SECTION 6: CTKM ENGINE
675
- ══════════════════════════════════════════ -->
676
- <section class="section" id="s6">
677
- <div class="section-header" style="border-color:var(--orange)">
678
- <div class="icon" style="background:var(--orange-light)">🎁</div>
679
- <div><h2 style="color:var(--orange)">6. Khuyến mãi (CTKM Engine)</h2></div>
680
- </div>
681
-
682
- <div class="card">
683
- <div class="card-title">Các loại khuyến mãi (PromotionMethodType)</div>
684
- <div class="promo-grid">
685
- <div class="promo-card" style="border-color:var(--blue);background:var(--blue-xlight)">
686
- <div class="promo-icon">💸</div>
687
- <div class="promo-name">Giảm giá đơn hàng</div>
688
- <div class="promo-desc">DiscountOrder (0) — Trừ % hoặc tiền cố định vào tổng bill</div>
689
- </div>
690
- <div class="promo-card" style="border-color:var(--green);background:var(--green-xlight)">
691
- <div class="promo-icon">🎁</div>
692
- <div class="promo-name">Tặng sản phẩm</div>
693
- <div class="promo-desc">BonusProduct (1) — Thêm món miễn phí vào đơn</div>
694
- </div>
695
- <div class="promo-card" style="border-color:var(--teal);background:var(--teal-xlight)">
696
- <div class="promo-icon">🏷️</div>
697
- <div class="promo-name">Giảm giá sản phẩm</div>
698
- <div class="promo-desc">DiscountProduct (2) — Giảm giá từng món</div>
699
- </div>
700
- <div class="promo-card" style="border-color:var(--purple);background:var(--purple-xlight)">
701
- <div class="promo-icon">🛍️</div>
702
- <div class="promo-name">Mua A tặng B</div>
703
- <div class="promo-desc">BuyProductBonusProduct (6) — Mua món X được tặng món Y</div>
704
- </div>
705
- <div class="promo-card" style="border-color:var(--yellow);background:var(--yellow-xlight)">
706
- <div class="promo-icon">🎫</div>
707
- <div class="promo-name">Tặng phiếu quà</div>
708
- <div class="promo-desc">BonusGiftCertificate (4) — Tặng voucher</div>
709
- </div>
710
- <div class="promo-card" style="border-color:var(--orange);background:var(--orange-light)">
711
- <div class="promo-icon">📊</div>
712
- <div class="promo-name">Giá theo số lượng</div>
713
- <div class="promo-desc">DiscountByQuantity (9) — Giá thay đổi theo SL mua</div>
714
- </div>
715
- </div>
716
- </div>
717
-
718
- <div class="g2">
719
- <div class="card">
720
- <div class="card-title">Auto-apply — Tự động áp dụng</div>
721
- <div class="flow">
722
- <div class="flow-step">
723
- <div class="flow-num" style="background:var(--orange)">1</div>
724
- <div class="flow-body">
725
- <h4>Gọi <code>getPromotions()</code></h4>
726
- <p>Server trả về danh sách CTKM đủ điều kiện (filter theo TongTienTu — ngưỡng tối thiểu). Flag <code>tuDongApDungKhuyenMai</code> đánh dấu loại tự động.</p>
727
- </div>
728
- </div>
729
- <div class="flow-step">
730
- <div class="flow-num" style="background:var(--orange)">2</div>
731
- <div class="flow-body">
732
- <h4>Kiểm tra isCombine (Áp dụng gộp)</h4>
733
- <p>Nếu tenant BẬT combine → auto-apply TẤT CẢ CTKM đủ điều kiện. Nếu TẮT → chỉ áp dụng 1 (cao nhất theo TongTienTu).</p>
734
- <span class="hint">_ClassPromotionManager.js:356</span>
735
- </div>
736
- </div>
737
- <div class="flow-step">
738
- <div class="flow-num" style="background:var(--orange)">3</div>
739
- <div class="flow-body">
740
- <h4>Các gate chặn auto-apply</h4>
741
- <p>① Đã có manual CTKM → không override<br>② Flag <code>disableAutoApplyPromotions</code> trên món → bỏ qua<br>③ <code>suppressGiftlessAutoApply</code> khi reload → không re-apply</p>
742
- </div>
743
- </div>
744
- </div>
745
- </div>
746
-
747
- <div class="card">
748
- <div class="card-title">Combine Rule — Áp dụng gộp CTKM</div>
749
- <div style="background:var(--gray-light);border-radius:10px;padding:14px;margin-bottom:14px;font-size:.88rem">
750
- <div style="font-weight:700;margin-bottom:8px">Cài đặt: <code>BH.NV.ApDungGopCTKM</code> per-tenant</div>
751
- <div style="display:flex;gap:12px">
752
- <div style="flex:1;background:var(--green-xlight);border:2px solid var(--green);border-radius:8px;padding:10px">
753
- <div style="font-weight:800;color:var(--green)">BẬT (true)</div>
754
- <ul style="padding-left:14px;font-size:.82rem;margin-top:6px">
755
- <li>Nhiều CTKM cùng áp dụng</li>
756
- <li>Auto-apply tất cả đủ điều kiện</li>
757
- <li>Không cảnh báo khi chọn thêm</li>
758
- </ul>
759
- </div>
760
- <div style="flex:1;background:var(--red-xlight);border:2px solid var(--red);border-radius:8px;padding:10px">
761
- <div style="font-weight:800;color:var(--red)">TẮT (false)</div>
762
- <ul style="padding-left:14px;font-size:.82rem;margin-top:6px">
763
- <li>Chỉ 1 CTKM tại một thời điểm</li>
764
- <li>Auto-apply chọn cao nhất</li>
765
- <li>Cảnh báo khi chọn thêm</li>
766
- <li>Block save nếu &gt;1 CTKM</li>
767
- </ul>
768
- </div>
769
- </div>
770
- </div>
771
- <div class="callout c-warn" style="margin:0">
772
- <strong>3 lớp enforce Combine Rule</strong>
773
- FE picker cảnh báo (L328) → FE payment block (L581) → Server validator (OrderValidator.cs:266). Cả 3 lớp phải pass.
774
- </div>
775
- </div>
776
- </div>
777
-
778
- <div class="card">
779
- <div class="card-title">Món tặng (Gift Dish) — Logic tạo và quản lý</div>
780
- <div class="g3">
781
- <div>
782
- <div style="font-weight:700;margin-bottom:10px;color:var(--green)">🎁 Tạo gift dish</div>
783
- <ul style="font-size:.86rem;padding-left:16px;display:flex;flex-direction:column;gap:5px">
784
- <li><code>foodType = GiftDishes</code> — phân biệt với món thường</li>
785
- <li>Lưu vào <code>subDetails[]</code> của món chính</li>
786
- <li>Giữ nguyên metadata: sPromotionId, conditionPromotionId, isAutoApplied</li>
787
- </ul>
788
- <span class="hint mt-12">_ClassPromotionManager.js:918</span>
789
- </div>
790
- <div>
791
- <div style="font-weight:700;margin-bottom:10px;color:var(--yellow)">⚖️ DuSoLuong — Shared quota</div>
792
- <ul style="font-size:.86rem;padding-left:16px;display:flex;flex-direction:column;gap:5px">
793
- <li>Khi LoaiGiamGiaTangTheo = DuSoLuong → tất cả sản phẩm quà chia pool chung</li>
794
- <li>Ví dụ: "Mua 2 tặng 1" — pool = 1 món, tặng bất kỳ trong danh sách</li>
795
- <li>Reconciliation: tách valid (còn quota) vs excess (vượt quota)</li>
796
- </ul>
797
- <span class="hint mt-12">_ClassPromotionManager.js:947-960</span>
798
- </div>
799
- <div>
800
- <div style="font-weight:700;margin-bottom:10px;color:var(--red)">🔄 Khi xóa/thay thế</div>
801
- <ul style="font-size:.86rem;padding-left:16px;display:flex;flex-direction:column;gap:5px">
802
- <li><code>removeExistedCartPromotions()</code> — xóa gift cũ khỏi OrderCart khi confirm thay thế</li>
803
- <li>Excess → convert thành món thường (tính tiền bình thường)</li>
804
- <li>Opt-out: flag <code>disableAutoApplyPromotions</code> ngăn auto re-apply</li>
805
- </ul>
806
- <span class="hint mt-12">_ClassPromotionManager.js:379-413,943-1006</span>
807
- </div>
808
- </div>
809
- </div>
810
- </section>
811
-
812
- <!-- ══════════════════════════════════════════
813
- SECTION 7: KHÁCH HÀNG & CÔNG NỢ
814
- ══════════════════════════════════════════ -->
815
- <section class="section" id="s7">
816
- <div class="section-header" style="border-color:var(--yellow)">
817
- <div class="icon" style="background:var(--yellow-light)">👤</div>
818
- <div><h2 style="color:var(--yellow)">7. Khách hàng, Cọc &amp; Công nợ</h2></div>
819
- </div>
820
-
821
- <div class="g2">
822
- <div class="card">
823
- <div class="card-title">Khách hàng và đơn hàng</div>
824
- <table class="tbl">
825
- <tr><th>Khía cạnh</th><th>Chi tiết</th></tr>
826
- <tr>
827
- <td>Liên kết</td>
828
- <td><code>DServiceTurn.KhachHangId</code> — nullable, TÙYCHỌN</td>
829
- </tr>
830
- <tr>
831
- <td>Bắt buộc?</td>
832
- <td><span class="tag tag-red">KHÔNG</span> — có thể thanh toán không cần chọn khách</td>
833
- </tr>
834
- <tr>
835
- <td>Hiển thị khi TT</td>
836
- <td>Tên + Số điện thoại — KHÔNG hiện số dư công nợ</td>
837
- </tr>
838
- <tr>
839
- <td>Nhóm khách</td>
840
- <td><code>NhomKhachHangId</code> — giảm giá theo nhóm (không phải điểm tích lũy)</td>
841
- </tr>
842
- </table>
843
- <div class="callout c-warn mt-12">
844
- <strong>⚠️ Không tìm thấy loyalty points trong lớp OrderStaff</strong>
845
- Chỉ có nhóm khách hàng → giảm giá theo nhóm. Lớp OrderStaff không implement loyalty points hay reward tự động — có thể tồn tại ở module khác (CRM/loyalty), không thể xác nhận từ code FnB.
846
- </div>
847
- </div>
848
-
849
- <div class="card">
850
- <div class="card-title">Tiền cọc (Deposit) — Cơ chế hoạt động</div>
851
- <div class="flow">
852
- <div class="flow-step">
853
- <div class="flow-num" style="background:var(--teal)">1</div>
854
- <div class="flow-body">
855
- <h4>Thu tiền cọc trước</h4>
856
- <p>Lưu vào bảng <code>DBADeposit</code> (extend SoQuyTienGuiBaseEntity) — phiếu thu riêng biệt với đơn hàng</p>
857
- </div>
858
- </div>
859
- <div class="flow-step">
860
- <div class="flow-num" style="background:var(--teal)">2</div>
861
- <div class="flow-body">
862
- <h4>Cộng nhiều phiếu cọc</h4>
863
- <p><code>receipts.Sum(a => a.Amount)</code> — một đơn có thể có nhiều phiếu cọc, tất cả được cộng lại</p>
864
- <span class="hint">DServiceTurnsManager.cs:1165</span>
865
- </div>
866
- </div>
867
- <div class="flow-step">
868
- <div class="flow-num" style="background:var(--teal)">3</div>
869
- <div class="flow-body">
870
- <h4>Trừ tại thanh toán</h4>
871
- <p><code>depositAmount</code> được trừ khỏi <code>totalAmountPayable</code> trong công thức tính tiền</p>
872
- <span class="hint">_BillOrderModal.js:678</span>
873
- </div>
874
- </div>
875
- </div>
876
- </div>
877
- </div>
878
-
879
- <div class="card">
880
- <div class="card-title">Công nợ (Debt) — Cơ chế tích lũy và thanh toán</div>
881
- <div class="g3">
882
- <div>
883
- <div style="font-weight:700;color:var(--red);margin-bottom:10px">Tích lũy công nợ</div>
884
- <ul style="font-size:.86rem;padding-left:16px;display:flex;flex-direction:column;gap:5px">
885
- <li>Khi khách không trả đủ → phần thiếu vào <code>ChargeToAccount</code></li>
886
- <li>Lưu vào <code>DServiceTurn.ChargeToAccount</code> cho từng lần</li>
887
- <li>Tổng lũy kế lưu tại <code>DKhachHang.CongNoHienTai</code></li>
888
- </ul>
889
- <span class="hint mt-12">OrdersAppService.cs:193-198</span>
890
- </div>
891
- <div>
892
- <div style="font-weight:700;color:var(--yellow);margin-bottom:10px">Thanh toán công nợ</div>
893
- <ul style="font-size:.86rem;padding-left:16px;display:flex;flex-direction:column;gap:5px">
894
- <li><code>DoiTruCongNo</code> — offset công nợ khi khách thanh toán lần sau</li>
895
- <li>Có thể dùng tiền thừa của đơn hiện tại để bù công nợ cũ</li>
896
- <li>Cả 2 đường: "tính vào công nợ" (thêm nợ) và "dùng tín dụng" (trừ nợ)</li>
897
- </ul>
898
- <span class="hint mt-12">OrderPaymentManager.cs:1583</span>
899
- </div>
900
- <div>
901
- <div style="font-weight:700;color:var(--blue);margin-bottom:10px">Tại màn hình TT</div>
902
- <ul style="font-size:.86rem;padding-left:16px;display:flex;flex-direction:column;gap:5px">
903
- <li>Công nợ KHÔNG hiện số dư tồn trên modal</li>
904
- <li>Nhân viên không thấy "khách đang nợ X đồng"</li>
905
- <li>ChargeToAccount chỉ là field phân bổ chênh lệch lần này</li>
906
- </ul>
907
- <div class="callout c-warn" style="margin-top:10px;font-size:.83rem">
908
- Đây là điểm thiếu UX — nhân viên không biết tổng nợ khách hàng khi thanh toán.
909
- </div>
910
- </div>
911
- </div>
912
- </div>
913
- </section>
914
-
915
- <!-- ══════════════════════════════════════════
916
- SECTION 8: MỞ & TÍNH TIỀN
917
- ══════════════════════════════════════════ -->
918
- <section class="section" id="s8">
919
- <div class="section-header" style="border-color:var(--blue)">
920
- <div class="icon" style="background:var(--blue-light)">🧮</div>
921
- <div><h2 style="color:var(--blue)">8. Mở &amp; Tính tiền thanh toán</h2></div>
922
- </div>
923
-
924
- <div class="g2">
925
- <div class="card">
926
- <div class="card-title">Luồng khởi động modal thanh toán</div>
927
- <div class="flow">
928
- <div class="flow-step">
929
- <div class="flow-num">1</div>
930
- <div class="flow-body">
931
- <h4>Nhấn nút Thanh toán</h4>
932
- <p><code>.btn-payment</code> → <code>openPaymentModal()</code> — kiểm tra món hợp lệ trước</p>
933
- <span class="hint">Index.js:267,456</span>
934
- </div>
935
- </div>
936
- <div class="flow-step">
937
- <div class="flow-num">2</div>
938
- <div class="flow-body">
939
- <h4>Load 3 API song song (Promise.all)</h4>
940
- <p>① <code>getByIdFnB()</code> — toàn bộ đơn hàng<br>② <code>getPaymentMethodComboboxItem()</code> — danh sách PT thanh toán<br>③ <code>getByKeys()</code> — cài đặt thuế, phụ thu</p>
941
- <span class="hint">_BillOrderModal.js:70-83</span>
942
- </div>
943
- </div>
944
- <div class="flow-step">
945
- <div class="flow-num">3</div>
946
- <div class="flow-body">
947
- <h4>Tính toán &amp; Hiển thị</h4>
948
- <p><code>calculateTotalAmountPayable()</code> → <code>updateDifferenceMoney()</code> → gắn validator → gắn events</p>
949
- </div>
950
- </div>
951
- </div>
952
- </div>
953
-
954
- <div class="card">
955
- <div class="card-title">Công thức tính tiền cần trả</div>
956
- <div class="formula">
957
- <div class="f-label">calculateTotalAmountPayable() — _BillOrderModal.js:672</div>
958
- <span class="plus">totalAmount</span> &nbsp;← Tổng giá tất cả món<br>
959
- <span class="minus">− totalDiscount</span> &nbsp;← Trừ CTKM<br>
960
- <span class="minus">− costOfSaleTotalAmount</span> &nbsp;← Trừ giảm giá bán<br>
961
- <span class="plus">+ VAT</span> &nbsp;← Cộng thuế<br>
962
- <span class="minus">− depositAmount</span> &nbsp;← Trừ tiền cọc đã đặt<br>
963
- <span class="plus">+ otherCostTotalAmount</span> &nbsp;← Phụ thu khác<br>
964
- <span class="eq">= totalAmountPayable</span> &nbsp;← Tiền cần thu
965
- </div>
966
- <div class="callout c-warn">
967
- <strong>FE lưu raw (có thể âm) — server clamp về 0</strong><br>
968
- <code>dataForm.totalAmountPayable</code> = giá trị thô (âm khi cọc &gt; hóa đơn).<br>
969
- <code>dataForm.totalPayment = totalAmountPayable &gt; 0 ? totalAmountPayable : 0</code> — hiển thị clamped.<br>
970
- FE gửi <code>SoTienCanTra = 0</code> khi deposit-excess. Server cũng clamp về 0 trước khi so sánh → khớp.
971
- </div>
972
- </div>
973
- </div>
974
- </section>
975
-
976
- <!-- ══════════════════════════════════════════
977
- SECTION 9: PHƯƠNG THỨC THANH TOÁN
978
- ══════════════════════════════════════════ -->
979
- <section class="section" id="s9">
980
- <div class="section-header" style="border-color:var(--teal)">
981
- <div class="icon" style="background:var(--teal-light)">💳</div>
982
- <div><h2 style="color:var(--teal)">9. Phương thức thanh toán</h2></div>
983
- </div>
984
-
985
- <div class="card">
986
- <div class="card-title">5 loại thanh toán — 3 trục khác nhau</div>
987
- <div class="callout c-info" style="margin-bottom:16px">
988
- <strong>💡 Chú ý: 3 trục khái niệm khác nhau</strong><br>
989
- • <strong>Loại hình thức (enum)</strong>: TienMat / ChuyenKhoan / QuetThe — xác định kênh thực tế<br>
990
- • <strong>Đích phân bổ</strong>: Công nợ (ChargeToAccount) — không phải payment line, là cơ chế ghi nợ<br>
991
- • <strong>Chế độ UI</strong>: "Khác" — mở split-payment modal cho N phương thức kết hợp
992
- </div>
993
- <div class="g3">
994
- <div class="method-card m-cash">
995
- <div class="method-icon">💵</div>
996
- <div class="method-name">Tiền mặt</div>
997
- <div class="method-sub">TienMat (0) — Phổ biến nhất, hỗ trợ hoàn cọc</div>
998
- </div>
999
- <div class="method-card m-bank">
1000
- <div class="method-icon">📱</div>
1001
- <div class="method-name">Chuyển khoản / QR</div>
1002
- <div class="method-sub">ChuyenKhoan (1) — Sinh QR VietQR, hỗ trợ hoàn cọc</div>
1003
- </div>
1004
- <div class="method-card m-card">
1005
- <div class="method-icon">💳</div>
1006
- <div class="method-name">Quẹt thẻ</div>
1007
- <div class="method-sub">QuetThe (2) — Chọn máy POS / tài khoản</div>
1008
- </div>
1009
- <div class="method-card m-debt">
1010
- <div class="method-icon">📋</div>
1011
- <div class="method-name">Công nợ</div>
1012
- <div class="method-sub">ChargeToAccount — Không phải dòng payment, lưu vào DServiceTurn</div>
1013
- </div>
1014
- <div class="method-card m-split">
1015
- <div class="method-icon">➕</div>
1016
- <div class="method-name">Kết hợp nhiều PT</div>
1017
- <div class="method-sub">"Khác" → mở _PaymentMethodMultipleModal, N phương thức + N số tiền</div>
1018
- </div>
1019
- </div>
1020
- </div>
1021
-
1022
- <div class="card">
1023
- <div class="card-title">Thanh toán kết hợp (Split Payment) — Validation</div>
1024
- <div class="g2">
1025
- <div>
1026
- <table class="tbl">
1027
- <tr><th>Kiểm tra</th><th>Lớp</th><th>Rule</th></tr>
1028
- <tr><td>Phải chọn loại PT</td><td><span class="tag tag-blue">FE</span></td><td>Bắt buộc</td></tr>
1029
- <tr><td>Bank/thẻ phải chọn TK</td><td><span class="tag tag-blue">FE</span></td><td>Bắt buộc</td></tr>
1030
- <tr><td>Số tiền &gt; 0</td><td><span class="tag tag-blue">FE</span></td><td>Mỗi dòng</td></tr>
1031
- <tr><td>Không dòng nào vượt số còn lại</td><td><span class="tag tag-green">BE</span></td><td>Trừ dòng cuối</td></tr>
1032
- <tr><td>Recompute totalAmountPayable</td><td><span class="tag tag-green">BE</span></td><td>Phải khớp với FE</td></tr>
1033
- </table>
1034
- <span class="hint mt-12">_PaymentMethodMultipleModal.js:55-76 · OrderValidator.cs:1531</span>
1035
- </div>
1036
- <div class="callout c-info" style="margin:0;align-self:start">
1037
- <strong>💡 Công nợ không phải payment line</strong>
1038
- Không tạo dòng <code>orderPayments</code>. Lưu vào <code>DServiceTurn.ChargeToAccount</code> riêng biệt.<br><br>
1039
- <strong>Underpay được phép</strong> nếu phần thiếu phân bổ vào công nợ hoặc bớt tiền lẻ. Không có gate "phải trả đủ 100%".
1040
- </div>
1041
- </div>
1042
- </div>
1043
- </section>
1044
-
1045
- <!-- ══════════════════════════════════════════
1046
- SECTION 10: PHÂN BỔ TIỀN THỪA/THIẾU
1047
- ══════════════════════════════════════════ -->
1048
- <section class="section" id="s10">
1049
- <div class="section-header" style="border-color:var(--red)">
1050
- <div class="icon" style="background:var(--red-light)">⚖️</div>
1051
- <div><h2 style="color:var(--red)">10. Phân bổ tiền thừa / thiếu</h2></div>
1052
- </div>
1053
-
1054
- <div class="card">
1055
- <div class="card-title">Nguyên tắc "Bảo toàn chênh lệch" — Bất biến cốt lõi</div>
1056
- <div style="display:flex;align-items:center;justify-content:center;gap:20px;padding:20px 0;flex-wrap:wrap">
1057
- <div style="text-align:center">
1058
- <div style="font-size:1.5rem;font-weight:900;color:var(--blue)">Tiền khách đưa</div>
1059
- <div style="font-size:.8rem;color:var(--gray)">totalPayment</div>
1060
- </div>
1061
- <div style="font-size:2rem;font-weight:900;color:var(--gray)">−</div>
1062
- <div style="text-align:center">
1063
- <div style="font-size:1.5rem;font-weight:900;color:var(--red)">Tiền cần trả</div>
1064
- <div style="font-size:.8rem;color:var(--gray)">totalAmountPayable</div>
1065
- </div>
1066
- <div style="font-size:2rem;font-weight:900;color:var(--blue)">=</div>
1067
- <div style="text-align:center">
1068
- <div style="font-size:1.5rem;font-weight:900;color:var(--yellow)">Chênh lệch</div>
1069
- <div style="font-size:.8rem;color:var(--gray)">moneyChange</div>
1070
- </div>
1071
- </div>
1072
- <div class="callout c-danger">
1073
- <strong>📐 Bất biến: Tổng phân bổ PHẢI = moneyChange (không được lệch 1 đồng)</strong>
1074
- Server kiểm tra tại OrderValidator.cs:411 (thừa) và :460 (thiếu). Vi phạm → từ chối lưu.
1075
- </div>
1076
-
1077
- <div class="dtree mt-20">
1078
- <div class="dt-root">moneyChange = totalPayment − totalAmountPayable</div>
1079
- <div class="dt-branches">
1080
- <div class="dt-branch dt-pos">
1081
- <div class="dt-label">▲ Thừa (moneyChange &gt; 0)</div>
1082
- <div>Khách đưa nhiều hơn hóa đơn. Phân bổ vào <em>1 hoặc nhiều</em>:</div>
1083
- <ul>
1084
- <li>💰 <strong>Tiền thừa trả khách</strong> — hoàn mặt</li>
1085
- <li>📋 <strong>Tính vào công nợ</strong> — dùng lần sau</li>
1086
- <li>🎁 <strong>Khách không lấy tiền thừa</strong> — tặng lại</li>
1087
- </ul>
1088
- <div class="dt-invar">Σ(changeReturned + chargeToAccount + unclaimedChange) = moneyChange</div>
1089
- </div>
1090
- <div class="dt-branch dt-zero">
1091
- <div class="dt-label">= Khớp (moneyChange = 0)</div>
1092
- <div>Lý tưởng. Không cần phân bổ gì thêm. Mọi trường = 0.</div>
1093
- </div>
1094
- <div class="dt-branch dt-neg">
1095
- <div class="dt-label">▼ Thiếu (moneyChange &lt; 0)</div>
1096
- <div>Khách đưa ít hơn. Phân bổ phần thiếu vào:</div>
1097
- <ul>
1098
- <li>📋 <strong>Tính vào công nợ</strong> — khách nợ nhà hàng</li>
1099
- <li>🎰 <strong>Bớt tiền lẻ cho khách</strong> — nhà hàng giảm</li>
1100
- </ul>
1101
- <div style="font-size:.78rem;margin-top:6px;color:var(--red)">⛔ KHÔNG được dùng: tiền thừa trả / khách không lấy</div>
1102
- <div class="dt-invar">Σ(chargeToAccount + roundOffDiscount) = |moneyChange|</div>
1103
- </div>
1104
- </div>
1105
- </div>
1106
- </div>
1107
-
1108
- <div class="card">
1109
- <div class="card-title">Bảng 4 trường phân bổ — FE ↔ Backend mapping</div>
1110
- <table class="tbl">
1111
- <tr><th>Tên FE</th><th>Tên Backend (DB)</th><th>Ý nghĩa</th><th>Khi dùng</th></tr>
1112
- <tr>
1113
- <td><code>changeReturned</code></td>
1114
- <td>TienThuaTraKhach</td>
1115
- <td>Tiền mặt trả lại khách</td>
1116
- <td><span class="tag tag-green">Chỉ thừa</span></td>
1117
- </tr>
1118
- <tr>
1119
- <td><code>chargeToAccount</code></td>
1120
- <td>TinhVaoCongNo</td>
1121
- <td>Ghi vào công nợ khách</td>
1122
- <td><span class="tag tag-yellow">Cả hai</span></td>
1123
- </tr>
1124
- <tr>
1125
- <td><code>unclaimedChange</code></td>
1126
- <td>KhachKhongLayTienThua</td>
1127
- <td>Khách tặng lại nhà hàng</td>
1128
- <td><span class="tag tag-green">Chỉ thừa</span></td>
1129
- </tr>
1130
- <tr>
1131
- <td><code>roundOffDiscount</code></td>
1132
- <td>BotTienLeChoKhach</td>
1133
- <td>Làm tròn, nhà hàng giảm</td>
1134
- <td><span class="tag tag-red">Chỉ thiếu</span></td>
1135
- </tr>
1136
- </table>
1137
- <span class="hint mt-12">OrderValidator.cs:379-382</span>
1138
- </div>
1139
- </section>
1140
-
1141
- <!-- ══════════════════════════════════════════
1142
- SECTION 11: DEPOSIT EXCESS
1143
- ══════════════════════════════════════════ -->
1144
- <section class="section" id="s11">
1145
- <div class="section-header" style="border-color:var(--purple)">
1146
- <div class="icon" style="background:var(--purple-light)">🏦</div>
1147
- <div><h2 style="color:var(--purple)">11. Tiền cọc dư (Deposit Excess)</h2></div>
1148
- </div>
1149
-
1150
- <div class="card">
1151
- <div class="card-title">Khi nào xảy ra &amp; Logic xử lý</div>
1152
- <div class="g2">
1153
- <div>
1154
- <div class="callout c-purple" style="margin-bottom:14px">
1155
- <strong>Trigger: totalAmountPayable &lt; 0</strong>
1156
- Cọc đã đặt nhiều hơn tổng hóa đơn → <code>isDepositExcess = true</code>
1157
- </div>
1158
- <div class="formula" style="font-size:.85rem">
1159
- <div class="f-label">Ví dụ: Cọc 500k, hóa đơn 300k</div>
1160
- 300,000 − 500,000 = <span class="result">−200,000</span><br>
1161
- excessAmount = |−200,000| = <span class="plus">200,000đ cần hoàn</span><br><br>
1162
- refundPayment = clamp(input, 0, 200,000)<br>
1163
- <span class="eq">chargeToAccount = 200,000 − refundPayment</span>
1164
- </div>
1165
- <span class="hint">_BillOrderModal.js:26,862-928</span>
1166
- </div>
1167
- <div>
1168
- <div class="card-title" style="font-size:.9rem">Rules hoàn cọc</div>
1169
- <table class="tbl">
1170
- <tr><th>Rule</th><th>Chi tiết</th></tr>
1171
- <tr>
1172
- <td>PT được hoàn</td>
1173
- <td><span class="tag tag-green">Tiền mặt</span> <span class="tag tag-blue">Chuyển khoản</span> — KHÔNG dùng thẻ</td>
1174
- </tr>
1175
- <tr>
1176
- <td>Clamp refund</td>
1177
- <td><code>refundPayment ∈ [0, excessAmount]</code></td>
1178
- </tr>
1179
- <tr>
1180
- <td>Phần còn lại</td>
1181
- <td>Chuyển sang <code>chargeToAccount</code> (tín dụng lần sau)</td>
1182
- </tr>
1183
- <tr>
1184
- <td>QR amount</td>
1185
- <td>Dùng <code>refundPayment</code> thay vì totalAmountPayable</td>
1186
- </tr>
1187
- </table>
1188
- <span class="hint mt-12">isDepositExcessPaymentMethod — _BillOrderModal.js:32</span>
1189
- </div>
1190
- </div>
1191
- </div>
1192
- </section>
1193
-
1194
- <!-- ══════════════════════════════════════════
1195
- SECTION 12: XÁC NHẬN & LƯU
1196
- ══════════════════════════════════════════ -->
1197
- <section class="section" id="s12">
1198
- <div class="section-header" style="border-color:var(--green)">
1199
- <div class="icon" style="background:var(--green-light)">✅</div>
1200
- <div><h2 style="color:var(--green)">12. Xác nhận &amp; Lưu thanh toán</h2></div>
1201
- </div>
1202
-
1203
- <div class="g2">
1204
- <div class="card">
1205
- <div class="card-title">Luồng save() khi bấm "Xác nhận"</div>
1206
- <div class="timeline">
1207
- <div class="tl-item">
1208
- <div class="tl-dot"></div>
1209
- <h4>Validate FE</h4>
1210
- <p><code>$form.valid()</code> — trường bắt buộc. Chặn &gt;1 CTKM nếu combine OFF.</p>
1211
- <span class="tl-hint">_BillOrderModal.js:573-590</span>
1212
- </div>
1213
- <div class="tl-item">
1214
- <div class="tl-dot" style="background:var(--teal);box-shadow:0 0 0 2px var(--teal)"></div>
1215
- <h4>API #1 — Cập nhật món tặng</h4>
1216
- <p><code>createOrEditFnBForMobile()</code> — convert gift dishes sang trạng thái cuối</p>
1217
- <span class="tl-hint">_BillOrderModal.js:644</span>
1218
- </div>
1219
- <div class="tl-item">
1220
- <div class="tl-dot" style="background:var(--purple);box-shadow:0 0 0 2px var(--purple)"></div>
1221
- <h4>API #2 — Lưu thanh toán</h4>
1222
- <p><code>POST /api/services/app/Orders/CreateOrEditOrderPaymentFnb</code></p>
1223
- <span class="tl-hint">_BillOrderModal.js:645</span>
1224
- </div>
1225
- <div class="tl-item">
1226
- <div class="tl-dot" style="background:var(--yellow);box-shadow:0 0 0 2px var(--yellow)"></div>
1227
- <h4>Server double-check độc lập</h4>
1228
- <p>Tính lại <code>totalAmountPayable</code> từ DB. Công thức server giống FE nhưng có thêm <code>shippingFeeByCustomer − ShippingFee − PhiTraHang</code> (= 0 trong FnB). Server <strong>clamp về 0</strong> nếu âm trước khi so sánh. FE gửi <code>SoTienCanTra</code> = 0 trong trường hợp deposit-excess. Không khớp → lỗi.</p>
1229
- <span class="tl-hint">OrderValidator.cs:1486-1499</span>
1230
- </div>
1231
- <div class="tl-item">
1232
- <div class="tl-dot" style="background:var(--green);box-shadow:0 0 0 2px var(--green)"></div>
1233
- <h4>Lưu DB &amp; cập nhật UI</h4>
1234
- <p>DServiceTurn: Status=Done, PaymentStatus=Paid, FinishedTime. Toast "Thành công". Modal đóng. Reload list.</p>
1235
- <span class="tl-hint">OrdersAppService.cs:189-236 · Index.js:488-501</span>
1236
- </div>
1237
- </div>
1238
- </div>
1239
-
1240
- <div class="card">
1241
- <div class="card-title">Payload gửi lên server</div>
1242
- <table class="tbl">
1243
- <tr><th>Field</th><th>Ý nghĩa</th></tr>
1244
- <tr><td><code>serviceTurnId</code></td><td>ID lượt phục vụ</td></tr>
1245
- <tr><td><code>customerId</code></td><td>ID khách (optional)</td></tr>
1246
- <tr><td><code>totalAmountPayable</code></td><td>Tiền cần trả (FE tính)</td></tr>
1247
- <tr><td><code>totalPayment</code></td><td>Tổng tiền khách đưa</td></tr>
1248
- <tr><td><code>orderPayments[]</code></td><td>Danh sách dòng thanh toán</td></tr>
1249
- <tr><td><code>changeReturned</code></td><td>Tiền thừa trả</td></tr>
1250
- <tr><td><code>chargeToAccount</code></td><td>Công nợ</td></tr>
1251
- <tr><td><code>unclaimedChange</code></td><td>Khách không lấy</td></tr>
1252
- <tr><td><code>roundOffDiscount</code></td><td>Bớt tiền lẻ</td></tr>
1253
- <tr><td><code>refundPayment</code></td><td>Hoàn cọc (nếu dư)</td></tr>
1254
- <tr><td><code>eivOrderDetail</code></td><td>Dữ liệu hóa đơn điện tử</td></tr>
1255
- </table>
1256
- <div class="callout c-info mt-12">
1257
- <strong>Không có auto-print sau thanh toán</strong>
1258
- Receipt phải in thủ công qua nút in riêng biệt.
1259
- </div>
1260
- </div>
1261
- </div>
1262
- </section>
1263
-
1264
- <!-- ══════════════════════════════════════════
1265
- SECTION 13: IN ẤN & HÓA ĐƠN
1266
- ══════════════════════════════════════════ -->
1267
- <section class="section" id="s13">
1268
- <div class="section-header" style="border-color:var(--gray)">
1269
- <div class="icon" style="background:var(--gray-light)">🖨️</div>
1270
- <div><h2 style="color:#374151">13. In ấn &amp; Hóa đơn</h2></div>
1271
- </div>
1272
-
1273
- <div class="g3">
1274
- <div class="card">
1275
- <div class="card-title">🧾 Phiếu tạm tính (Temporary Bill)</div>
1276
- <ul style="font-size:.86rem;padding-left:16px;display:flex;flex-direction:column;gap:6px">
1277
- <li><strong>Khi nào:</strong> In trước khi thanh toán, khi khách muốn xem hóa đơn</li>
1278
- <li><strong>Dữ liệu:</strong> Tổng tiền − Giảm giá + Thuế + Phí DV (sàn 0)</li>
1279
- <li><strong>Không bao gồm:</strong> Tiền cọc, phương thức TT, tên thu ngân</li>
1280
- <li><strong>Trigger:</strong> Nút in thủ công trên _BillOrderModal</li>
1281
- </ul>
1282
- <div class="callout c-warn mt-12" style="font-size:.82rem">
1283
- <strong>⚠️ FNB-721</strong>
1284
- Tiền khách cần trả trên phiếu TT sàn tại 0 (không in số âm khi cọc dư).
1285
- </div>
1286
- <span class="hint">_PrintTemporaryBillModal.js:62-70 · _BillOrderModal.js:1274</span>
1287
- </div>
1288
-
1289
- <div class="card">
1290
- <div class="card-title">🍳 Phiếu bếp (Kitchen Slip)</div>
1291
- <ul style="font-size:.86rem;padding-left:16px;display:flex;flex-direction:column;gap:6px">
1292
- <li><strong>Khi nào:</strong> Auto khi gửi bếp</li>
1293
- <li><strong>Nội dung:</strong> Danh sách món, ghi chú, subdetails (nguyên liệu, side dish), icon</li>
1294
- <li><strong>Máy in:</strong> Nhiệt 80mm</li>
1295
- <li><strong>Lọc:</strong> Chỉ status WaitingConfirm/Confirmed; bỏ Cancel và DiscountOrder</li>
1296
- <li><strong>Routing:</strong> Không phân khu bếp tại FE — 1 phiếu duy nhất</li>
1297
- </ul>
1298
- <div class="callout c-info mt-12" style="font-size:.82rem">
1299
- <strong>Hủy sau bếp:</strong> Không in phiếu hủy — bếp biết qua KDS display poll.
1300
- </div>
1301
- <span class="hint">Commons.js:1779-1888</span>
1302
- </div>
1303
-
1304
- <div class="card">
1305
- <div class="card-title">📄 Hóa đơn điện tử (E-Invoice)</div>
1306
- <ul style="font-size:.86rem;padding-left:16px;display:flex;flex-direction:column;gap:6px">
1307
- <li><strong>Khi nào:</strong> Tùy chọn — nhân viên mở modal invoice riêng</li>
1308
- <li><strong>Luồng:</strong> openInvoiceModal() → invoiceQRModal → dữ liệu lưu vào eivOrderDetail</li>
1309
- <li><strong>Truyền lên:</strong> eivOrderDetail trong payment payload</li>
1310
- <li><strong>Backend:</strong> Xử lý phát hành hóa đơn theo quy trình riêng</li>
1311
- <li><strong>Khác phiếu TT:</strong> Đây là hóa đơn pháp lý, không phải bill tạm</li>
1312
- </ul>
1313
- <span class="hint">_BillOrderModal.js:1313-1325</span>
1314
- </div>
1315
- </div>
1316
- </section>
1317
-
1318
- <!-- ══════════════════════════════════════════
1319
- SECTION 14: QR & E-INVOICE
1320
- ══════════════════════════════════════════ -->
1321
- <section class="section" id="s14">
1322
- <div class="section-header" style="border-color:var(--blue)">
1323
- <div class="icon" style="background:var(--blue-light)">📲</div>
1324
- <div><h2 style="color:var(--blue)">14. QR Payment &amp; Chi tiết kỹ thuật</h2></div>
1325
- </div>
1326
-
1327
- <div class="g2">
1328
- <div class="card">
1329
- <div class="card-title">QR Code sinh như thế nào?</div>
1330
- <div style="background:var(--gray-light);border-radius:10px;padding:16px;font-family:'Courier New',monospace;font-size:.82rem;line-height:1.8;margin-bottom:14px">
1331
- <span style="color:var(--gray)">// _BillOrderModal.js:722-729</span><br>
1332
- URL = <span style="color:var(--green)">`https://img.vietqr.io/image/</span><br>
1333
- &nbsp;&nbsp;{bankCode}-{accountNumber}-qr_only.png<br>
1334
- &nbsp;&nbsp;?amount=<span style="color:var(--blue)">{amount}</span><br>
1335
- &nbsp;&nbsp;&amp;addInfo=""<br>
1336
- &nbsp;&nbsp;&amp;accountName={accountName}<span style="color:var(--green)">`</span><br><br>
1337
- amount = isDepositExcess<br>
1338
- &nbsp;&nbsp;? refundPayment<br>
1339
- &nbsp;&nbsp;: totalAmountPayable (≥0)
1340
- </div>
1341
- <table class="tbl">
1342
- <tr><th>PT</th><th>Có QR?</th></tr>
1343
- <tr><td>Chuyển khoản (ChuyenKhoan)</td><td><span class="tag tag-green">✓ Có</span></td></tr>
1344
- <tr><td>Quẹt thẻ (QuetThe)</td><td><span class="tag tag-red">✗ Không</span> (hiện ảnh tĩnh payment-transfer.svg)</td></tr>
1345
- <tr><td>Tiền mặt (TienMat)</td><td><span class="tag tag-red">✗ Không</span></td></tr>
1346
- </table>
1347
- </div>
1348
-
1349
- <div class="card">
1350
- <div class="card-title">Server double-check — Tại sao quan trọng?</div>
1351
- <div class="g2" style="gap:12px">
1352
- <div style="background:var(--green-xlight);border:2px solid var(--green);border-radius:10px;padding:14px">
1353
- <div style="font-weight:800;color:var(--green);margin-bottom:8px">✅ Khớp → Cho lưu</div>
1354
- <div style="font-size:.83rem">Server tính = FE gửi lên<br><code>server_total == input.SoTienCanTra</code></div>
1355
- </div>
1356
- <div style="background:var(--red-xlight);border:2px solid var(--red);border-radius:10px;padding:14px">
1357
- <div style="font-weight:800;color:var(--red);margin-bottom:8px">❌ Không khớp → Từ chối</div>
1358
- <div style="font-size:.83rem">Có thể do giá thay đổi, giảm giá sai, cọc tính nhầm</div>
1359
- </div>
1360
- </div>
1361
- <div class="callout c-warn mt-12">
1362
- <strong>Mục đích: Chống gian lận và lỗi FE</strong>
1363
- Nếu chỉ tin FE gửi lên, dữ liệu sai có thể vào DB. Server tính độc lập bảo vệ toàn vẹn dữ liệu.
1364
- </div>
1365
- <div class="card-title mt-20" style="font-size:.9rem">Dữ liệu lưu vào DB sau thanh toán</div>
1366
- <table class="tbl">
1367
- <tr><th>Field</th><th>Bảng</th><th>Giá trị</th></tr>
1368
- <tr><td>Status</td><td>DServiceTurn</td><td><span class="tag tag-green">Done</span></td></tr>
1369
- <tr><td>PaymentStatus</td><td>DServiceTurn</td><td><span class="tag tag-green">Paid</span></td></tr>
1370
- <tr><td>FinishedTime</td><td>DServiceTurn</td><td>DateTime.Now</td></tr>
1371
- <tr><td>ChargeToAccount, ChangeReturned, ...</td><td>DServiceTurn</td><td>Phân bổ chênh lệch</td></tr>
1372
- <tr><td>RefundPayment</td><td>DServiceTurn</td><td>Hoàn cọc (nếu có)</td></tr>
1373
- <tr><td>orderPayments[]</td><td>ThanhToanDonHang</td><td>Dòng tiền thực thu</td></tr>
1374
- </table>
1375
- </div>
1376
- </div>
1377
- </section>
1378
-
1379
- <!-- FOOTER -->
1380
- <div class="footer-card">
1381
- <h3>🎯 Tóm tắt triết lý thiết kế hệ thống</h3>
1382
- <p>FnB POS được xây dựng trên 3 nguyên tắc: <strong>(1) Mọi chênh lệch tiền phải được khai báo rõ ràng</strong> — không có tiền "lơ lửng"; <strong>(2) Server luôn kiểm tra độc lập</strong> — không tin tưởng hoàn toàn vào FE; <strong>(3) Trạng thái được tính real-time</strong> — bàn, món ăn, đơn hàng không lưu trạng thái trùng lặp mà tính từ dữ liệu nguồn.</p>
1383
- <div class="meta">
1384
- Tổng hợp từ 9 agent nghiên cứu song song · _BillOrderModal.js · Index.js · OrdersAppService.cs · OrderValidator.cs · DServiceTurnsManager.cs · _ClassPromotionManager.js · Commons.js
1385
- </div>
1386
- </div>
1387
-
1388
- </div><!-- /container -->
1389
- </body>
1390
- </html>