@pisell/pisellos 2.2.291 → 2.2.293

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 (174) hide show
  1. package/dist/core/index.d.ts +7 -0
  2. package/dist/core/index.js +108 -65
  3. package/dist/model/strategy/adapter/itemRule/adapter.d.ts +4 -1
  4. package/dist/model/strategy/adapter/itemRule/adapter.js +135 -35
  5. package/dist/model/strategy/adapter/itemRule/evaluator.d.ts +8 -1
  6. package/dist/model/strategy/adapter/itemRule/evaluator.js +25 -1
  7. package/dist/model/strategy/adapter/itemRule/type.d.ts +44 -0
  8. package/dist/model/strategy/adapter/itemRule/type.js +19 -1
  9. package/dist/model/strategy/adapter/promotion/index.js +0 -9
  10. package/dist/modules/BookingContext/index.d.ts +3 -0
  11. package/dist/modules/BookingContext/index.js +42 -13
  12. package/dist/modules/BookingContext/types.d.ts +4 -4
  13. package/dist/modules/BookingContext/types.js +3 -3
  14. package/dist/modules/OpenData/types.d.ts +1 -0
  15. package/dist/modules/Order/index.d.ts +18 -3
  16. package/dist/modules/Order/index.js +885 -485
  17. package/dist/modules/Order/salesNotes.d.ts +31 -0
  18. package/dist/modules/Order/salesNotes.js +492 -0
  19. package/dist/modules/Order/types.d.ts +93 -5
  20. package/dist/modules/Order/types.js +5 -0
  21. package/dist/modules/Order/utils.d.ts +12 -0
  22. package/dist/modules/Order/utils.js +96 -31
  23. package/dist/modules/Product/types.d.ts +2 -0
  24. package/dist/modules/ProductList/clientDataVariants.d.ts +50 -0
  25. package/dist/modules/ProductList/clientDataVariants.js +216 -0
  26. package/dist/modules/ProductList/index.d.ts +18 -3
  27. package/dist/modules/ProductList/index.js +220 -38
  28. package/dist/modules/ProductList/types.d.ts +10 -0
  29. package/dist/modules/Quotation/index.d.ts +22 -0
  30. package/dist/modules/Quotation/index.js +293 -28
  31. package/dist/modules/ResourcePlanner/index.d.ts +0 -22
  32. package/dist/modules/ResourcePlanner/index.js +1 -180
  33. package/dist/modules/ResourcePlanner/localClock.d.ts +33 -0
  34. package/dist/modules/ResourcePlanner/localClock.js +183 -0
  35. package/dist/modules/ResourcePlanner/planner.d.ts +5 -14
  36. package/dist/modules/ResourcePlanner/planner.js +2111 -958
  37. package/dist/modules/ResourcePlanner/types.d.ts +359 -187
  38. package/dist/modules/ResourcePlanner/types.js +33 -1
  39. package/dist/plugins/request.d.ts +1 -0
  40. package/dist/server/index.d.ts +17 -2
  41. package/dist/server/index.js +1134 -932
  42. package/dist/server/modules/floor-plan/index.d.ts +4 -0
  43. package/dist/server/modules/floor-plan/index.js +240 -98
  44. package/dist/server/modules/menu/index.js +48 -23
  45. package/dist/server/modules/order/index.d.ts +17 -7
  46. package/dist/server/modules/order/index.js +419 -207
  47. package/dist/server/modules/products/index.d.ts +10 -4
  48. package/dist/server/modules/products/index.js +177 -84
  49. package/dist/server/modules/resource/index.js +21 -13
  50. package/dist/server/utils/product.d.ts +1 -1
  51. package/dist/server/utils/product.js +186 -22
  52. package/dist/server/utils/small-ticket.d.ts +9 -1
  53. package/dist/server/utils/small-ticket.js +131 -84
  54. package/dist/solution/BaseSales/index.d.ts +26 -3
  55. package/dist/solution/BaseSales/index.js +1384 -905
  56. package/dist/solution/BaseSales/types.d.ts +2 -0
  57. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +3 -0
  58. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +10 -3
  59. package/dist/solution/BaseSales/utils.js +6 -3
  60. package/dist/solution/BookingByStep/index.d.ts +4 -4
  61. package/dist/solution/BookingTicket/index.d.ts +1 -1
  62. package/dist/solution/BookingTicket/index.js +4 -2
  63. package/dist/solution/BookingTicket/utils/addProductDecision.d.ts +2 -1
  64. package/dist/solution/BookingTicket/utils/addProductDecision.js +6 -3
  65. package/dist/solution/BookingTicket/utils/orderCustomer.js +3 -2
  66. package/dist/solution/RegisterAndLogin/config.d.ts +9 -3
  67. package/dist/solution/RegisterAndLogin/config.js +95 -40
  68. package/dist/solution/RegisterAndLogin/index.js +4 -1
  69. package/dist/solution/ScanOrder/index.d.ts +1 -0
  70. package/dist/solution/ScanOrder/index.js +31 -27
  71. package/dist/solution/ScanOrder/types.d.ts +2 -0
  72. package/dist/solution/ScanOrder/utils.d.ts +1 -0
  73. package/dist/solution/ScanOrder/utils.js +8 -3
  74. package/dist/solution/UnifiedBookingSales/index.d.ts +128 -74
  75. package/dist/solution/UnifiedBookingSales/index.js +3877 -963
  76. package/dist/solution/UnifiedBookingSales/remoteOccupancyCache.d.ts +49 -0
  77. package/dist/solution/UnifiedBookingSales/remoteOccupancyCache.js +430 -0
  78. package/dist/solution/UnifiedBookingSales/types.d.ts +161 -77
  79. package/dist/solution/UnifiedBookingSales/types.js +4 -9
  80. package/dist/solution/VenueBooking/index.d.ts +3 -0
  81. package/dist/solution/VenueBooking/index.js +549 -497
  82. package/dist/solution/VenueBooking/utils/dateSummary.d.ts +1 -0
  83. package/dist/solution/VenueBooking/utils/dateSummary.js +3 -1
  84. package/dist/solution/VenueBooking/utils/slotMerge.d.ts +2 -0
  85. package/dist/solution/VenueBooking/utils/slotMerge.js +3 -1
  86. package/dist/utils/platform.d.ts +8 -0
  87. package/dist/utils/platform.js +15 -0
  88. package/lib/core/index.d.ts +7 -0
  89. package/lib/core/index.js +20 -2
  90. package/lib/model/strategy/adapter/itemRule/adapter.d.ts +4 -1
  91. package/lib/model/strategy/adapter/itemRule/adapter.js +73 -4
  92. package/lib/model/strategy/adapter/itemRule/evaluator.d.ts +8 -1
  93. package/lib/model/strategy/adapter/itemRule/evaluator.js +23 -1
  94. package/lib/model/strategy/adapter/itemRule/type.d.ts +44 -0
  95. package/lib/model/strategy/adapter/itemRule/type.js +19 -1
  96. package/lib/modules/BookingContext/index.d.ts +3 -0
  97. package/lib/modules/BookingContext/index.js +35 -8
  98. package/lib/modules/BookingContext/types.d.ts +4 -4
  99. package/lib/modules/BookingContext/types.js +3 -3
  100. package/lib/modules/OpenData/types.d.ts +1 -0
  101. package/lib/modules/Order/index.d.ts +18 -3
  102. package/lib/modules/Order/index.js +325 -28
  103. package/lib/modules/Order/salesNotes.d.ts +31 -0
  104. package/lib/modules/Order/salesNotes.js +382 -0
  105. package/lib/modules/Order/types.d.ts +93 -5
  106. package/lib/modules/Order/types.js +4 -0
  107. package/lib/modules/Order/utils.d.ts +12 -0
  108. package/lib/modules/Order/utils.js +85 -16
  109. package/lib/modules/Product/types.d.ts +2 -0
  110. package/lib/modules/ProductList/clientDataVariants.d.ts +50 -0
  111. package/lib/modules/ProductList/clientDataVariants.js +204 -0
  112. package/lib/modules/ProductList/index.d.ts +18 -3
  113. package/lib/modules/ProductList/index.js +137 -8
  114. package/lib/modules/ProductList/types.d.ts +10 -0
  115. package/lib/modules/Quotation/index.d.ts +22 -0
  116. package/lib/modules/Quotation/index.js +158 -11
  117. package/lib/modules/ResourcePlanner/index.d.ts +0 -22
  118. package/lib/modules/ResourcePlanner/index.js +9 -129
  119. package/lib/modules/ResourcePlanner/localClock.d.ts +33 -0
  120. package/lib/modules/ResourcePlanner/localClock.js +205 -0
  121. package/lib/modules/ResourcePlanner/planner.d.ts +5 -14
  122. package/lib/modules/ResourcePlanner/planner.js +1807 -783
  123. package/lib/modules/ResourcePlanner/types.d.ts +359 -187
  124. package/lib/modules/ResourcePlanner/types.js +13 -1
  125. package/lib/plugins/request.d.ts +1 -0
  126. package/lib/server/index.d.ts +17 -2
  127. package/lib/server/index.js +149 -21
  128. package/lib/server/modules/floor-plan/index.d.ts +4 -0
  129. package/lib/server/modules/floor-plan/index.js +60 -7
  130. package/lib/server/modules/menu/index.js +26 -4
  131. package/lib/server/modules/order/index.d.ts +17 -7
  132. package/lib/server/modules/order/index.js +194 -57
  133. package/lib/server/modules/products/index.d.ts +10 -4
  134. package/lib/server/modules/products/index.js +93 -23
  135. package/lib/server/modules/resource/index.js +12 -2
  136. package/lib/server/utils/product.d.ts +1 -1
  137. package/lib/server/utils/product.js +163 -3
  138. package/lib/server/utils/small-ticket.d.ts +9 -1
  139. package/lib/server/utils/small-ticket.js +123 -79
  140. package/lib/solution/BaseSales/index.d.ts +26 -3
  141. package/lib/solution/BaseSales/index.js +396 -70
  142. package/lib/solution/BaseSales/types.d.ts +2 -0
  143. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +3 -0
  144. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +9 -2
  145. package/lib/solution/BaseSales/utils.js +3 -0
  146. package/lib/solution/BookingByStep/index.d.ts +4 -4
  147. package/lib/solution/BookingTicket/index.d.ts +1 -1
  148. package/lib/solution/BookingTicket/index.js +2 -1
  149. package/lib/solution/BookingTicket/utils/addProductDecision.d.ts +2 -1
  150. package/lib/solution/BookingTicket/utils/addProductDecision.js +5 -2
  151. package/lib/solution/BookingTicket/utils/orderCustomer.js +2 -1
  152. package/lib/solution/RegisterAndLogin/config.d.ts +9 -3
  153. package/lib/solution/RegisterAndLogin/config.js +49 -8
  154. package/lib/solution/RegisterAndLogin/index.js +4 -1
  155. package/lib/solution/ScanOrder/index.d.ts +1 -0
  156. package/lib/solution/ScanOrder/index.js +5 -1
  157. package/lib/solution/ScanOrder/types.d.ts +2 -0
  158. package/lib/solution/ScanOrder/utils.d.ts +1 -0
  159. package/lib/solution/ScanOrder/utils.js +4 -0
  160. package/lib/solution/UnifiedBookingSales/index.d.ts +128 -74
  161. package/lib/solution/UnifiedBookingSales/index.js +2592 -424
  162. package/lib/solution/UnifiedBookingSales/remoteOccupancyCache.d.ts +49 -0
  163. package/lib/solution/UnifiedBookingSales/remoteOccupancyCache.js +316 -0
  164. package/lib/solution/UnifiedBookingSales/types.d.ts +161 -77
  165. package/lib/solution/UnifiedBookingSales/types.js +4 -6
  166. package/lib/solution/VenueBooking/index.d.ts +3 -0
  167. package/lib/solution/VenueBooking/index.js +33 -9
  168. package/lib/solution/VenueBooking/utils/dateSummary.d.ts +1 -0
  169. package/lib/solution/VenueBooking/utils/dateSummary.js +3 -1
  170. package/lib/solution/VenueBooking/utils/slotMerge.d.ts +2 -0
  171. package/lib/solution/VenueBooking/utils/slotMerge.js +3 -1
  172. package/lib/utils/platform.d.ts +8 -0
  173. package/lib/utils/platform.js +22 -0
  174. package/package.json +1 -1
@@ -0,0 +1,382 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.buildSalesNoteText = buildSalesNoteText;
7
+ exports.decodeSalesNotes = decodeSalesNotes;
8
+ exports.decodeSalesNotesSnapshot = decodeSalesNotesSnapshot;
9
+ exports.encodeSalesNotes = encodeSalesNotes;
10
+ exports.finalizeSalesNotesForCheckout = finalizeSalesNotesForCheckout;
11
+ exports.getSalesNotePolicy = getSalesNotePolicy;
12
+ const MAX_UUID_LENGTH = 255;
13
+ const LOCALIZED_NOTE_LOCALES = ['en', 'zh-CN', 'zh-HK', 'ja', 'pt'];
14
+ const UUID_V4_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
15
+ /**
16
+ * 备注业务规则统一注册在 OS;调用方只传 note_type、目标行和原始文本。
17
+ * 后续新增备注类型时在这里增加 policy,不把 importance / Relation 规则下放给 UI。
18
+ */
19
+ const SALES_NOTE_POLICIES = {
20
+ allergy: {
21
+ note_type: 'allergy',
22
+ importance: 'high',
23
+ uniquePerPrimaryTarget: true,
24
+ primary_relation_type: 'about',
25
+ secondary_order_relation_type: 'related_to'
26
+ }
27
+ };
28
+ function getSalesNotePolicy(note_type) {
29
+ return SALES_NOTE_POLICIES[note_type] || null;
30
+ }
31
+ function normalizeUuid(value, field, options) {
32
+ const uuid = String(value ?? '').trim();
33
+ if (!uuid && !options?.allowEmpty) {
34
+ throw new Error(`[Order Notes] ${field} 不能为空`);
35
+ }
36
+ if (uuid.length > MAX_UUID_LENGTH) {
37
+ throw new Error(`[Order Notes] ${field} 不能超过 ${MAX_UUID_LENGTH} 个字符`);
38
+ }
39
+ return uuid;
40
+ }
41
+ function normalizeEntityUuid(value, field) {
42
+ const uuid = normalizeUuid(value, field);
43
+ if (!UUID_V4_PATTERN.test(uuid)) {
44
+ throw new Error(`[Order Notes] ${field} 必须为 UUID v4`);
45
+ }
46
+ return uuid;
47
+ }
48
+ function normalizeTextRecord(value) {
49
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return {};
50
+ return Object.entries(value).reduce((result, [locale, text]) => {
51
+ if (typeof text === 'string') result[locale] = text;
52
+ return result;
53
+ }, {});
54
+ }
55
+ function normalizeLocalizedLabel(label) {
56
+ if (typeof label === 'string') {
57
+ const text = label.trim();
58
+ return text ? {
59
+ default: text
60
+ } : {};
61
+ }
62
+ return Object.entries(label || {}).reduce((result, [locale, value]) => {
63
+ const text = String(value ?? '').trim();
64
+ if (text) result[locale] = text;
65
+ return result;
66
+ }, {});
67
+ }
68
+ function getLocaleCandidates(locale) {
69
+ const raw = String(locale || '').trim();
70
+ const normalized = raw.replace(/_/g, '-');
71
+ const base = normalized.split('-')[0];
72
+ return Array.from(new Set([raw, normalized, base].filter(Boolean)));
73
+ }
74
+ function normalizeLocaleLookupKey(locale) {
75
+ return String(locale || '').trim().replace(/_/g, '-').toLowerCase();
76
+ }
77
+ function resolveLocalizedLabel(label, locale) {
78
+ if (typeof label === 'string') return label.trim();
79
+ const normalized = normalizeLocalizedLabel(label);
80
+ const entries = Object.entries(normalized);
81
+ const lowerCaseLookup = new Map(entries.map(([key, value]) => [normalizeLocaleLookupKey(key), value]));
82
+ for (const candidate of getLocaleCandidates(locale)) {
83
+ const matched = lowerCaseLookup.get(normalizeLocaleLookupKey(candidate));
84
+ if (matched) return matched;
85
+ }
86
+ const localeBase = normalizeLocaleLookupKey(locale).split('-')[0];
87
+ const sameLanguage = entries.find(([key]) => normalizeLocaleLookupKey(key).split('-')[0] === localeBase)?.[1];
88
+ return sameLanguage || lowerCaseLookup.get('en') || lowerCaseLookup.get('zh-cn') || lowerCaseLookup.get('default') || entries[0]?.[1] || '';
89
+ }
90
+ function joinNoteParts(parts) {
91
+ return parts.map(part => String(part ?? '')).filter(part => part.trim().length > 0).join(',');
92
+ }
93
+ function buildSalesNoteText(input) {
94
+ if (input.kind === 'plain') {
95
+ const text = String(input.input ?? '');
96
+ if (!text.trim()) throw new Error('[Order Notes] 备注输入不能为空');
97
+ return {
98
+ 'zh-CN': '',
99
+ original: text
100
+ };
101
+ }
102
+ if (input.kind === 'resolved') {
103
+ const text = normalizeTextRecord(input.text);
104
+ if (!Object.values(text).some(value => value.trim())) {
105
+ throw new Error('[Order Notes] 备注文本不能为空');
106
+ }
107
+ return text;
108
+ }
109
+ const inputText = String(input.input ?? '');
110
+ const seenIds = new Set();
111
+ const selections = (input.selections || []).filter(selection => {
112
+ const id = String(selection?.id ?? '').trim();
113
+ if (!id || seenIds.has(id)) return false;
114
+ seenIds.add(id);
115
+ return true;
116
+ });
117
+ if (!selections.length && !inputText.trim()) {
118
+ throw new Error('[Order Notes] 过敏选项和补充说明不能同时为空');
119
+ }
120
+ const result = {};
121
+ for (const locale of LOCALIZED_NOTE_LOCALES) {
122
+ result[locale] = joinNoteParts([...selections.map(selection => resolveLocalizedLabel(selection.label, locale)), inputText]);
123
+ }
124
+ result.original = joinNoteParts([...selections.map(selection => resolveLocalizedLabel(selection.label, input.locale)), inputText]);
125
+ return result;
126
+ }
127
+ function validateSalesNote(note, options = {}) {
128
+ normalizeEntityUuid(note.uuid, 'Note uuid');
129
+ const note_type = String(note.note_type || '').trim();
130
+ const policy = getSalesNotePolicy(note_type);
131
+ if (!policy) {
132
+ throw new Error(`[Order Notes] 当前未注册备注类型: ${note_type || '(empty)'}`);
133
+ }
134
+ if (note.importance !== policy.importance) {
135
+ throw new Error(`[Order Notes] ${note_type} importance 必须为 ${policy.importance}`);
136
+ }
137
+ const text = note.content?.text;
138
+ if (!text || typeof text !== 'object' || Array.isArray(text) || Object.values(text).some(value => typeof value !== 'string') || !Object.values(text).some(value => value.trim())) {
139
+ throw new Error('[Order Notes] content.text 不能为空');
140
+ }
141
+ if (!Array.isArray(note.note_relations) || note.note_relations.length === 0) {
142
+ throw new Error('[Order Notes] note_relations 不能为空');
143
+ }
144
+ const relationUuids = new Set();
145
+ let primaryCount = 0;
146
+ let orderRelationCount = 0;
147
+ for (const relation of note.note_relations) {
148
+ const relationUuid = normalizeEntityUuid(relation.uuid, 'Relation uuid');
149
+ if (relationUuids.has(relationUuid)) {
150
+ throw new Error('[Order Notes] 同一 Note 内 Relation uuid 不能重复');
151
+ }
152
+ relationUuids.add(relationUuid);
153
+ if (relation.is_primary !== 0 && relation.is_primary !== 1) {
154
+ throw new Error('[Order Notes] is_primary 只允许整数 0 或 1');
155
+ }
156
+ if (relation.relation_type !== 'about' && relation.relation_type !== 'related_to') {
157
+ throw new Error('[Order Notes] relation_type 不合法');
158
+ }
159
+ if (relation.target_type !== 'order' && relation.target_type !== 'order_detail') {
160
+ throw new Error('[Order Notes] target_type 不合法');
161
+ }
162
+ if (relation.is_primary === 1) {
163
+ primaryCount += 1;
164
+ if (relation.relation_type !== policy.primary_relation_type || relation.target_type !== 'order_detail') {
165
+ throw new Error('[Order Notes] 主关系必须使用 about + order_detail');
166
+ }
167
+ const targetUuid = normalizeUuid(relation.target_uuid, 'Relation target_uuid');
168
+ if (options.productTargetUuids && !options.productTargetUuids.has(targetUuid)) {
169
+ throw new Error('[Order Notes] 商品 Relation target_uuid 不属于当前订单');
170
+ }
171
+ continue;
172
+ }
173
+ if (relation.target_type === 'order' && relation.relation_type === policy.secondary_order_relation_type) {
174
+ orderRelationCount += 1;
175
+ const targetUuid = normalizeUuid(relation.target_uuid, 'Relation target_uuid', {
176
+ allowEmpty: options.allowUnresolvedOrderTarget === true
177
+ });
178
+ if (options.expectedOrderTargetUuid !== undefined && targetUuid !== options.expectedOrderTargetUuid) {
179
+ throw new Error('[Order Notes] 订单 Relation target_uuid 与 shop_order_number 不一致');
180
+ }
181
+ continue;
182
+ }
183
+ throw new Error('[Order Notes] 非主体关系必须使用 related_to + order');
184
+ }
185
+ if (primaryCount !== 1) {
186
+ throw new Error('[Order Notes] 每条 Note 必须且只能有一个主关系');
187
+ }
188
+ if (orderRelationCount !== 1) {
189
+ throw new Error('[Order Notes] 每条商品 Note 必须且只能有一个订单冗余关系');
190
+ }
191
+ }
192
+ function encodeSalesNotes(notes, options = {}) {
193
+ const entityUuids = new Set();
194
+ const uniquePolicyTargets = new Set();
195
+ return (notes || []).map(note => {
196
+ validateSalesNote(note, options);
197
+ const noteUuid = normalizeEntityUuid(note.uuid, 'Note uuid');
198
+ if (entityUuids.has(noteUuid)) {
199
+ throw new Error('[Order Notes] Note/Relation uuid 不能重复');
200
+ }
201
+ entityUuids.add(noteUuid);
202
+ const note_type = String(note.note_type).trim();
203
+ const noteRelations = note.note_relations.map(relation => {
204
+ const relationUuid = normalizeEntityUuid(relation.uuid, 'Relation uuid');
205
+ if (entityUuids.has(relationUuid)) {
206
+ throw new Error('[Order Notes] Note/Relation uuid 不能重复');
207
+ }
208
+ entityUuids.add(relationUuid);
209
+ const allowEmptyTarget = options.allowUnresolvedOrderTarget === true && relation.is_primary === 0 && relation.relation_type === 'related_to' && relation.target_type === 'order';
210
+ return {
211
+ uuid: relationUuid,
212
+ relation_type: relation.relation_type,
213
+ target_type: relation.target_type,
214
+ target_uuid: normalizeUuid(relation.target_uuid, 'Relation target_uuid', {
215
+ allowEmpty: allowEmptyTarget
216
+ }),
217
+ is_primary: relation.is_primary
218
+ };
219
+ });
220
+ const primaryRelation = noteRelations.find(relation => relation.is_primary === 1);
221
+ const policy = getSalesNotePolicy(note_type);
222
+ if (policy.uniquePerPrimaryTarget) {
223
+ const policyTargetKey = [note_type, primaryRelation.target_type, primaryRelation.target_uuid].join(':');
224
+ if (uniquePolicyTargets.has(policyTargetKey)) {
225
+ throw new Error('[Order Notes] 同一商品行不能重复提交同类型 Note');
226
+ }
227
+ uniquePolicyTargets.add(policyTargetKey);
228
+ }
229
+ return {
230
+ uuid: noteUuid,
231
+ note_type,
232
+ content: {
233
+ text: {
234
+ ...(note.content?.text || {})
235
+ }
236
+ },
237
+ importance: note.importance,
238
+ note_relations: noteRelations
239
+ };
240
+ });
241
+ }
242
+ function decodeRelation(value) {
243
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
244
+ const raw = value;
245
+ const uuid = String(raw.uuid ?? '').trim();
246
+ const target_uuid = String(raw.target_uuid ?? '').trim();
247
+ const relation_type = raw.relation_type;
248
+ const target_type = raw.target_type;
249
+ const is_primary = raw.is_primary;
250
+ if (!uuid || relation_type !== 'about' && relation_type !== 'related_to' || target_type !== 'order' && target_type !== 'order_detail' || is_primary !== 0 && is_primary !== 1) {
251
+ return null;
252
+ }
253
+ return {
254
+ uuid,
255
+ relation_type,
256
+ target_type,
257
+ target_uuid,
258
+ is_primary
259
+ };
260
+ }
261
+ function decodeSalesNotesSnapshot(value, options = {}) {
262
+ if (!Array.isArray(value)) return {
263
+ notes: [],
264
+ complete: false
265
+ };
266
+ const notes = [];
267
+ const entityUuids = new Set();
268
+ const uniquePolicyTargets = new Set();
269
+ let complete = true;
270
+ for (const item of value) {
271
+ if (!item || typeof item !== 'object' || Array.isArray(item)) {
272
+ complete = false;
273
+ continue;
274
+ }
275
+ const raw = item;
276
+ const uuid = String(raw.uuid ?? '').trim();
277
+ const note_type = String(raw.note_type ?? '').trim();
278
+ const importance = String(raw.importance ?? '').trim();
279
+ const rawText = raw.content?.text;
280
+ const text = normalizeTextRecord(rawText);
281
+ if (!rawText || typeof rawText !== 'object' || Array.isArray(rawText) || Object.keys(text).length !== Object.keys(rawText).length) {
282
+ complete = false;
283
+ continue;
284
+ }
285
+ const rawNoteRelations = Array.isArray(raw.note_relations) ? raw.note_relations : null;
286
+ const noteRelations = rawNoteRelations ? rawNoteRelations.map(relation => decodeRelation(relation)).filter(relation => relation !== null) : [];
287
+ if (!rawNoteRelations || noteRelations.length !== rawNoteRelations.length) {
288
+ complete = false;
289
+ continue;
290
+ }
291
+ const note = {
292
+ uuid,
293
+ note_type,
294
+ content: {
295
+ text
296
+ },
297
+ importance,
298
+ note_relations: noteRelations
299
+ };
300
+ try {
301
+ validateSalesNote(note, {
302
+ allowUnresolvedOrderTarget: options.allowUnresolvedOrderTarget
303
+ });
304
+ if (entityUuids.has(note.uuid)) {
305
+ complete = false;
306
+ continue;
307
+ }
308
+ entityUuids.add(note.uuid);
309
+ let hasDuplicateRelationUuid = false;
310
+ for (const relation of note.note_relations) {
311
+ if (entityUuids.has(relation.uuid)) {
312
+ hasDuplicateRelationUuid = true;
313
+ break;
314
+ }
315
+ }
316
+ if (hasDuplicateRelationUuid) {
317
+ complete = false;
318
+ continue;
319
+ }
320
+ note.note_relations.forEach(relation => entityUuids.add(relation.uuid));
321
+ const policy = getSalesNotePolicy(note.note_type);
322
+ const primaryRelation = note.note_relations.find(relation => relation.is_primary === 1);
323
+ if (policy?.uniquePerPrimaryTarget && primaryRelation) {
324
+ const policyTargetKey = [note.note_type, primaryRelation.target_type, primaryRelation.target_uuid].join(':');
325
+ if (uniquePolicyTargets.has(policyTargetKey)) {
326
+ complete = false;
327
+ continue;
328
+ }
329
+ uniquePolicyTargets.add(policyTargetKey);
330
+ }
331
+ notes.push(note);
332
+ } catch {
333
+ complete = false;
334
+ }
335
+ }
336
+ return {
337
+ notes,
338
+ complete
339
+ };
340
+ }
341
+ function decodeSalesNotes(value, options = {}) {
342
+ return decodeSalesNotesSnapshot(value, options).notes;
343
+ }
344
+ function finalizeSalesNotesForCheckout(params) {
345
+ if (!Array.isArray(params.notes)) {
346
+ throw new Error('[Order Notes] checkout notes 必须为数组');
347
+ }
348
+ if (params.notes.length === 0) return [];
349
+ const shopOrderNumber = normalizeUuid(params.shopOrderNumber, 'shop_order_number');
350
+ const productTargetUuids = new Set();
351
+ for (const rawTargetUuid of params.productTargetUuids || []) {
352
+ const targetUuid = normalizeUuid(rawTargetUuid, '商品 target_uuid');
353
+ if (productTargetUuids.has(targetUuid)) {
354
+ throw new Error('[Order Notes] 商品行业务标识不能重复');
355
+ }
356
+ productTargetUuids.add(targetUuid);
357
+ }
358
+ const decoded = decodeSalesNotesSnapshot(params.notes, {
359
+ allowUnresolvedOrderTarget: true
360
+ });
361
+ if (!decoded.complete || decoded.notes.length !== params.notes.length) {
362
+ throw new Error('[Order Notes] checkout Notes 快照不完整');
363
+ }
364
+ const finalizedNotes = decoded.notes.map(note => ({
365
+ ...note,
366
+ content: {
367
+ text: {
368
+ ...note.content.text
369
+ }
370
+ },
371
+ note_relations: note.note_relations.map(relation => ({
372
+ ...relation,
373
+ ...(relation.is_primary === 0 && relation.relation_type === 'related_to' && relation.target_type === 'order' ? {
374
+ target_uuid: shopOrderNumber
375
+ } : {})
376
+ }))
377
+ }));
378
+ return encodeSalesNotes(finalizedNotes, {
379
+ expectedOrderTargetUuid: shopOrderNumber,
380
+ productTargetUuids
381
+ });
382
+ }
@@ -8,6 +8,13 @@ import type { PaymentItem } from '../Payment/types';
8
8
  import type { ICustomer } from '../AccountList/types';
9
9
  import type { OrderProductDiscountItem } from '../../server/modules/order/types';
10
10
  export type { OrderProductDiscountItem } from '../../server/modules/order/types';
11
+ export type PickupReferenceMode = 'counter_pickup' | 'table_service';
12
+ export type ShopServiceType = 'dine_in' | 'takeaway';
13
+ export interface ShopServiceData {
14
+ pickup_reference_mode?: PickupReferenceMode;
15
+ service_type?: ShopServiceType | '';
16
+ [key: string]: any;
17
+ }
11
18
  export declare enum OrderHooks {
12
19
  OnOrderCreate = "order:onOrderCreate",
13
20
  OnOrderUpdate = "order:onOrderUpdate",
@@ -191,6 +198,8 @@ export interface OrderLocalizedText {
191
198
  export interface OrderProduct extends OrderProductIdentity {
192
199
  order_detail_id: number | null;
193
200
  num: number;
201
+ /** Product 或当前 Variant 的生产编码 */
202
+ production_code?: string;
194
203
  bundle_edit?: number;
195
204
  product_sku: OrderProductSku;
196
205
  gift_card?: number;
@@ -236,6 +245,72 @@ export interface OrderSummary {
236
245
  tax_title?: string;
237
246
  tax_rate?: number;
238
247
  }
248
+ export type SalesNoteRelationType = 'about' | 'related_to';
249
+ export type SalesNoteTargetType = 'order' | 'order_detail';
250
+ export type SalesNoteLocalizedText = string | Record<string, string>;
251
+ export interface SalesNoteLocalizedSelection {
252
+ id: string;
253
+ label: SalesNoteLocalizedText;
254
+ }
255
+ export type SalesNoteTextInput = {
256
+ kind: 'localizedSelections';
257
+ selections: SalesNoteLocalizedSelection[];
258
+ locale: string;
259
+ input?: string;
260
+ } | {
261
+ kind: 'plain';
262
+ input: string;
263
+ } | {
264
+ kind: 'resolved';
265
+ text: Record<string, string>;
266
+ };
267
+ export interface SalesNoteRelation {
268
+ uuid: string;
269
+ relation_type: SalesNoteRelationType;
270
+ target_type: SalesNoteTargetType;
271
+ target_uuid: string;
272
+ is_primary: 0 | 1;
273
+ }
274
+ export interface SalesNote {
275
+ uuid: string;
276
+ note_type: string;
277
+ content: {
278
+ text: Record<string, string>;
279
+ };
280
+ importance: string;
281
+ note_relations: SalesNoteRelation[];
282
+ }
283
+ export interface SalesNoteTarget {
284
+ target_type: 'order_detail';
285
+ target_uuid: string;
286
+ }
287
+ export interface SetSalesNoteInput {
288
+ note_type: string;
289
+ target: SalesNoteTarget;
290
+ text: SalesNoteTextInput;
291
+ }
292
+ export interface SalesNoteDraftInput {
293
+ note_type: string;
294
+ text: SalesNoteTextInput;
295
+ }
296
+ export interface SalesNotesRuntimeState {
297
+ loaded: boolean;
298
+ dirty: boolean;
299
+ }
300
+ /**
301
+ * Sales Notes 乐观写入前的可恢复快照。
302
+ * 该对象应视为 opaque token,只用于原样传回 restoreSalesNotesMutationSnapshot。
303
+ */
304
+ export interface SalesNotesMutationSnapshot {
305
+ orderIdentity: {
306
+ orderId: number | string | null;
307
+ externalSaleNumber: string;
308
+ };
309
+ notes: SalesNote[];
310
+ state: SalesNotesRuntimeState;
311
+ }
312
+ export type SalesNoteProtocolRelation = SalesNoteRelation;
313
+ export type SalesNoteProtocol = SalesNote;
239
314
  export interface OrderTempOrder {
240
315
  order_id: number | null;
241
316
  external_sale_number: string;
@@ -275,6 +350,7 @@ export interface OrderTempOrder {
275
350
  created_at: string | undefined;
276
351
  request_unique_idempotency_token?: string;
277
352
  products: OrderProduct[];
353
+ notes: SalesNote[];
278
354
  bookings: any[];
279
355
  payments: any[];
280
356
  surcharges: any[];
@@ -287,6 +363,7 @@ export interface OrderTempOrder {
287
363
  summary?: OrderSummary;
288
364
  _extend?: {
289
365
  productsByUid?: Record<string, Record<string, any>>;
366
+ salesNotesState?: SalesNotesRuntimeState;
290
367
  [key: string]: any;
291
368
  };
292
369
  }
@@ -295,7 +372,7 @@ export interface OrderSubmitProduct extends Omit<OrderProduct, 'unique_identific
295
372
  payment_price: string;
296
373
  product_sku: OrderProductSku;
297
374
  }
298
- export interface OrderSubmitPayload extends Omit<OrderTempOrder, 'platform' | 'products' | '_extend' | 'customer' | 'discount_list' | 'created_at'> {
375
+ export interface OrderSubmitPayload extends Omit<OrderTempOrder, 'platform' | 'products' | 'notes' | '_extend' | 'customer' | 'discount_list' | 'created_at'> {
299
376
  platform: string;
300
377
  created_at?: string | undefined;
301
378
  request_unique_idempotency_token?: string;
@@ -304,6 +381,7 @@ export interface OrderSubmitPayload extends Omit<OrderTempOrder, 'platform' | 'p
304
381
  form_record_id: number | string;
305
382
  }>;
306
383
  products: OrderSubmitProduct[];
384
+ notes?: SalesNoteProtocol[];
307
385
  summary?: OrderSummary;
308
386
  small_ticket_data_flag?: number;
309
387
  }
@@ -332,6 +410,8 @@ export interface AddProductToOrderOptions {
332
410
  insertAfterProductUid?: string;
333
411
  /** 跳过同商品/规格匹配,始终新增购物车行。 */
334
412
  disableMerge?: boolean;
413
+ /** 商品行 UID 落定后,由 OrderModule 原子绑定到该行的 Sales Notes 草稿。 */
414
+ salesNotes?: SalesNoteDraftInput[];
335
415
  }
336
416
  /**
337
417
  * 删除商品后的可选位置保持策略。
@@ -747,9 +827,17 @@ export interface OrderModuleAPI {
747
827
  updateTempOrderShopDiscount: (amount: string | number) => string;
748
828
  updateTempOrderDepositAmount: (amount: string | number) => OrderSummary;
749
829
  updateTempOrderCustomer: (customer: UpdateTempOrderCustomerInput) => OrderSnapshot['customer'];
830
+ updateTempOrderPickupReferenceMode: (mode: PickupReferenceMode) => ShopServiceData;
831
+ updateTempOrderServiceType: (serviceType: ShopServiceType) => ShopServiceData;
750
832
  updateTempOrderBuzzer: (buzzer: string) => string;
751
833
  updateTempOrderContactsInfo: (contactsInfo: Record<string, any> | null) => Record<string, any> | null;
752
834
  addProductToOrder: (product: Partial<OrderProduct> & OrderProductIdentity, booking?: AddProductBookingInput, options?: AddProductToOrderOptions) => Promise<OrderProduct[]>;
835
+ getSalesNotes: () => SalesNote[];
836
+ createSalesNotesMutationSnapshot: () => SalesNotesMutationSnapshot;
837
+ restoreSalesNotesMutationSnapshot: (snapshot: SalesNotesMutationSnapshot) => void;
838
+ setSalesNote: (input: SetSalesNoteInput) => Promise<SalesNote>;
839
+ removeSalesNote: (uuid: string) => Promise<void>;
840
+ clearSalesNotes: () => Promise<void>;
753
841
  updateOrderProduct: (params: UpdateOrderProductParams) => Promise<OrderProduct[]>;
754
842
  /** 批量更新购物车行,末尾仅触发一次促销重算与 summary 刷新 */
755
843
  updateOrderProducts: (paramsList: UpdateOrderProductParams[]) => Promise<OrderProduct[]>;
@@ -897,10 +985,10 @@ export interface OrderModuleAPI {
897
985
  /** 读取上次 applyPromotion 产出的赠品操作 diff(toAdd / toReduce / toRemove) */
898
986
  getLastGiftActions: () => OrderLastGiftActions | null;
899
987
  /**
900
- * 取消订单
901
- * @param params 取消订单参数
902
- * @returns 后端返回结果
903
- */
988
+ * 取消订单
989
+ * @param params 取消订单参数
990
+ * @returns 后端返回结果
991
+ */
904
992
  cancelOrder: (params: CancelOrderParams) => Promise<any>;
905
993
  /**
906
994
  * 变更预约状态
@@ -59,6 +59,10 @@ let OrderHooks = exports.OrderHooks = /*#__PURE__*/function (OrderHooks) {
59
59
  * - `source`:缺省表示真实业务变更;`detailHydrate` 仅表示打开详情时快照已就绪
60
60
  * 清空时整个 payload 为 null。
61
61
  */
62
+ /**
63
+ * Sales Notes 乐观写入前的可恢复快照。
64
+ * 该对象应视为 opaque token,只用于原样传回 restoreSalesNotesMutationSnapshot。
65
+ */
62
66
  /**
63
67
  * 添加商品行时的顺序控制。
64
68
  * 默认追加到 products 尾部;预约数量同步可插入到既有预约组的最新行之后,
@@ -210,6 +210,18 @@ export interface SubmitPayloadEnhancerContext {
210
210
  now: Date;
211
211
  }
212
212
  export type SubmitPayloadEnhancer = (payload: OrderSubmitPayload, ctx: SubmitPayloadEnhancerContext) => OrderSubmitPayload;
213
+ /**
214
+ * 解析 checkout 的订单类型。
215
+ * dine_in 普通落库单在新增桌台资源后,即使原类型仍是 virtual,也必须按 table-order 提交。
216
+ */
217
+ export declare function resolveSubmitOrderType(params: {
218
+ requestedType?: string;
219
+ tempOrderType?: string;
220
+ orderId?: unknown;
221
+ businessCode?: string;
222
+ tempOrderBusinessCode?: string;
223
+ bookings?: unknown;
224
+ }): string;
213
225
  export declare function buildSubmitPayload(params: {
214
226
  tempOrder: OrderTempOrder;
215
227
  cacheId?: string;