@pisell/pisellos 0.10.52 → 0.10.54

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 (44) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.js +3 -1
  3. package/dist/modules/OpenData/index.js +1 -1
  4. package/dist/modules/Order/index.js +5 -6
  5. package/dist/modules/Payment/index.js +9 -10
  6. package/dist/modules/Schedule/index.js +10 -5
  7. package/dist/plugins/request.d.ts +3 -0
  8. package/dist/server/utils/small-ticket.d.ts +1 -85
  9. package/dist/server/utils/small-ticket.js +2 -2576
  10. package/dist/shared/payment/loadPaymentMethods.d.ts +9 -0
  11. package/dist/shared/payment/loadPaymentMethods.js +66 -0
  12. package/dist/shared/receipt/small-ticket.d.ts +85 -0
  13. package/dist/shared/receipt/small-ticket.js +2576 -0
  14. package/dist/shared/sales/loadSalesOrderSnapshot.d.ts +9 -0
  15. package/dist/shared/sales/loadSalesOrderSnapshot.js +21 -0
  16. package/dist/solution/BookingByStep/index.d.ts +1 -1
  17. package/dist/solution/BookingTicket/index.d.ts +1 -1
  18. package/dist/solution/OrderInvoicePreview/index.d.ts +56 -0
  19. package/dist/solution/OrderInvoicePreview/index.js +269 -0
  20. package/dist/solution/index.d.ts +1 -0
  21. package/dist/solution/index.js +2 -1
  22. package/lib/index.d.ts +2 -0
  23. package/lib/index.js +21 -1
  24. package/lib/model/strategy/adapter/promotion/index.js +46 -1
  25. package/lib/modules/OpenData/index.js +1 -1
  26. package/lib/modules/Order/index.js +5 -7
  27. package/lib/modules/Payment/index.js +3 -4
  28. package/lib/modules/Schedule/index.js +5 -2
  29. package/lib/plugins/request.d.ts +3 -0
  30. package/lib/server/utils/small-ticket.d.ts +1 -85
  31. package/lib/server/utils/small-ticket.js +9 -2335
  32. package/lib/shared/payment/loadPaymentMethods.d.ts +9 -0
  33. package/lib/shared/payment/loadPaymentMethods.js +40 -0
  34. package/lib/shared/receipt/small-ticket.d.ts +85 -0
  35. package/lib/shared/receipt/small-ticket.js +2342 -0
  36. package/lib/shared/sales/loadSalesOrderSnapshot.d.ts +9 -0
  37. package/lib/shared/sales/loadSalesOrderSnapshot.js +22 -0
  38. package/lib/solution/BookingByStep/index.d.ts +1 -1
  39. package/lib/solution/BookingTicket/index.d.ts +1 -1
  40. package/lib/solution/OrderInvoicePreview/index.d.ts +56 -0
  41. package/lib/solution/OrderInvoicePreview/index.js +194 -0
  42. package/lib/solution/index.d.ts +1 -0
  43. package/lib/solution/index.js +11 -0
  44. package/package.json +1 -1
@@ -3,2340 +3,14 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.buildSmallTicketData = buildSmallTicketData;
7
- exports.hasSmallTicketData = hasSmallTicketData;
8
- var _dayjs = _interopRequireDefault(require("dayjs"));
9
- var _decimal = _interopRequireDefault(require("decimal.js"));
10
- var _pickupTiming = require("../../modules/Order/pickupTiming");
11
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
12
- const DEFAULT_LOCALES = ['en', 'zh_CN'];
13
- const SERVICE_TYPE_LABEL_KEYS = {
14
- takeaway: 'takeAway',
15
- pick_up: 'pickUp',
16
- table_service: 'tableService',
17
- delivery: 'delivery',
18
- room_service: 'roomService',
19
- dine_in: 'dineIn',
20
- delivery_by_uber: 'deliveryByUber',
21
- delivery_by_panda: 'deliveryByPanda',
22
- delivery_by_merchant: 'deliveryByMerchant'
23
- };
24
- const LABELS = {
25
- en: {
26
- contactName: 'Contact name',
27
- contactMobile: 'Contact Mobile',
28
- email: 'email',
29
- dineIn: 'Dine in',
30
- takeAway: 'Takeaway',
31
- delivery: 'Delivery',
32
- pickUp: 'Pickup',
33
- tableService: 'Table Service',
34
- roomService: 'Room Service',
35
- deliveryByUber: 'Uber Eats Delivery',
36
- deliveryByPanda: 'HungryPanda Delivery',
37
- deliveryByMerchant: 'Merchant Delivery',
38
- shopService: 'Dine-in',
39
- pickUpTime: 'Pick-up time',
40
- asap: 'ASAP',
41
- cash: 'Cash',
42
- cashReceived: 'Cash received',
43
- changeDue: 'Change due',
44
- eftpos: 'Eftpos',
45
- wallet: 'Wallet',
46
- giftCard: 'Gift card',
47
- pointCard: 'Point card',
48
- balance: 'Balance',
49
- validity: 'Validity',
50
- remainingAmount: 'Remaining amount',
51
- redeemPoints: 'Redeem points',
52
- remainingPoints: 'Points balance',
53
- entitlementRemaining: 'Remaining',
54
- table: 'Table',
55
- bookingId: 'Booking ID',
56
- bookingDateTime: 'Date & time',
57
- bookingResource: 'Resource',
58
- bookingHolder: 'Holder',
59
- orderDiscount: 'Order discount',
60
- manualDiscount: 'Manual discount',
61
- manualMarkup: 'Manual markup',
62
- payProcessingFee: 'Pay processing fee',
63
- surchargeIncluded: 'Surcharge(Included)',
64
- rounding: 'Rounding',
65
- gross: 'Gross',
66
- tare: 'Tare',
67
- handoverTime: 'Handover time',
68
- refund: 'Refund'
69
- },
70
- zh_CN: {
71
- contactName: '联系人姓名',
72
- contactMobile: '联系电话',
73
- email: '邮箱',
74
- dineIn: '堂食',
75
- takeAway: '外带',
76
- delivery: '配送',
77
- pickUp: '自取',
78
- tableService: '送餐到桌',
79
- roomService: '客房服务',
80
- deliveryByUber: 'Uber 配送',
81
- deliveryByPanda: '熊猫外卖配送',
82
- deliveryByMerchant: '商家配送',
83
- shopService: '堂食',
84
- pickUpTime: '取餐时间',
85
- asap: '尽快',
86
- cash: '现金支付',
87
- cashReceived: '收取现金',
88
- changeDue: '找零',
89
- eftpos: '刷卡支付',
90
- wallet: '钱包支付',
91
- giftCard: '礼品卡',
92
- pointCard: '积分卡',
93
- balance: '余额',
94
- validity: '有效期',
95
- remainingAmount: '剩余金额',
96
- redeemPoints: '本次使用积分',
97
- remainingPoints: '积分余额',
98
- entitlementRemaining: '剩余次数',
99
- table: '桌台',
100
- bookingId: '预约编号',
101
- bookingDateTime: '日期和时间',
102
- bookingResource: '资源',
103
- bookingHolder: '预约人',
104
- orderDiscount: '整单折扣',
105
- manualDiscount: '手动折扣',
106
- manualMarkup: '手动加价',
107
- payProcessingFee: '支付手续费',
108
- surchargeIncluded: '手续费(已含)',
109
- rounding: '抹零',
110
- gross: '重量',
111
- tare: '去皮',
112
- handoverTime: '交付时间',
113
- refund: '退款'
114
- },
115
- zh_HK: {
116
- contactName: '聯絡人姓名',
117
- contactMobile: '聯絡電話',
118
- email: '電郵',
119
- dineIn: '堂食',
120
- takeAway: '外帶',
121
- delivery: '配送',
122
- pickUp: '自取',
123
- tableService: '送餐到桌',
124
- roomService: '客房服務',
125
- deliveryByUber: 'Uber 配送',
126
- deliveryByPanda: '熊貓外賣配送',
127
- deliveryByMerchant: '商家配送',
128
- shopService: '堂食',
129
- pickUpTime: '取餐時間',
130
- asap: '盡快',
131
- cash: '現金支付',
132
- cashReceived: '收取現金',
133
- changeDue: '找零',
134
- eftpos: '刷卡支付',
135
- wallet: '錢包支付',
136
- giftCard: '禮品卡',
137
- pointCard: '積分卡',
138
- balance: '餘額',
139
- validity: '有效期',
140
- remainingAmount: '剩餘金額',
141
- redeemPoints: '本次使用積分',
142
- remainingPoints: '積分餘額',
143
- entitlementRemaining: '剩餘次數',
144
- table: '桌台',
145
- bookingId: '預約編號',
146
- bookingDateTime: '日期和時間',
147
- bookingResource: '資源',
148
- bookingHolder: '預約人',
149
- orderDiscount: '整單折扣',
150
- manualDiscount: '手動折扣',
151
- manualMarkup: '手動加價',
152
- payProcessingFee: '支付手續費',
153
- surchargeIncluded: '手續費(已含)',
154
- rounding: '抹零',
155
- gross: '重量',
156
- tare: '去皮',
157
- handoverTime: '交付時間',
158
- refund: '退款'
159
- },
160
- ja: {
161
- contactName: '連絡先名',
162
- contactMobile: '連絡先電話',
163
- email: 'メール',
164
- dineIn: '店内飲食',
165
- takeAway: 'テイクアウト',
166
- delivery: '配達',
167
- pickUp: '受け取り',
168
- tableService: 'テーブルサービス',
169
- roomService: 'ルームサービス',
170
- deliveryByUber: 'Uber Eats 配達',
171
- deliveryByPanda: 'HungryPanda 配達',
172
- deliveryByMerchant: '店舗による配達',
173
- shopService: '店内飲食',
174
- pickUpTime: '受取時間',
175
- asap: 'できるだけ早く',
176
- cash: '現金',
177
- eftpos: 'カード',
178
- wallet: 'ウォレット',
179
- giftCard: 'ギフトカード',
180
- pointCard: 'ポイントカード',
181
- balance: '残高',
182
- validity: '有効期限',
183
- remainingAmount: '残高',
184
- redeemPoints: '利用ポイント',
185
- remainingPoints: 'ポイント残高',
186
- entitlementRemaining: '残り回数',
187
- table: 'テーブル',
188
- bookingId: '予約ID',
189
- bookingDateTime: '日時',
190
- bookingResource: 'リソース',
191
- bookingHolder: '予約者',
192
- orderDiscount: '注文割引',
193
- manualDiscount: '手動割引',
194
- manualMarkup: '手動値上げ',
195
- payProcessingFee: '決済手数料',
196
- rounding: '端数調整',
197
- gross: '総重量',
198
- tare: '風袋',
199
- handoverTime: '受け渡し時間',
200
- refund: '返金',
201
- cashReceived: 'お預かり',
202
- changeDue: 'お釣り'
203
- },
204
- pt: {
205
- contactName: 'Nome de contato',
206
- contactMobile: 'Celular de contato',
207
- email: 'e-mail',
208
- dineIn: 'Consumo no local',
209
- takeAway: 'Para levar',
210
- delivery: 'Entrega',
211
- pickUp: 'Recolha',
212
- tableService: 'Serviço à mesa',
213
- roomService: 'Serviço de quarto',
214
- deliveryByUber: 'Entrega pela Uber Eats',
215
- deliveryByPanda: 'Entrega pela HungryPanda',
216
- deliveryByMerchant: 'Entrega pelo comerciante',
217
- shopService: 'Consumo no local',
218
- pickUpTime: 'Hora de recolha',
219
- asap: 'Assim que possível',
220
- cash: 'Dinheiro',
221
- eftpos: 'Cartão',
222
- wallet: 'Carteira',
223
- giftCard: 'Cartão-presente',
224
- pointCard: 'Cartão de pontos',
225
- balance: 'Saldo',
226
- validity: 'Validade',
227
- remainingAmount: 'Saldo restante',
228
- redeemPoints: 'Pontos utilizados',
229
- remainingPoints: 'Saldo de pontos',
230
- entitlementRemaining: 'Utilizações restantes',
231
- table: 'Mesa',
232
- bookingId: 'ID da reserva',
233
- bookingDateTime: 'Data e hora',
234
- bookingResource: 'Recurso',
235
- bookingHolder: 'Titular',
236
- orderDiscount: 'Desconto do pedido',
237
- manualDiscount: 'Desconto manual',
238
- manualMarkup: 'Acréscimo manual',
239
- payProcessingFee: 'Taxa de processamento',
240
- rounding: 'Arredondamento',
241
- gross: 'Peso bruto',
242
- tare: 'Tara',
243
- handoverTime: 'Hora de entrega',
244
- refund: 'Reembolso',
245
- cashReceived: 'Dinheiro recebido',
246
- changeDue: 'Troco'
247
- }
248
- };
249
- function buildSmallTicketData(params) {
250
- const order = params.order || {};
251
- const shopInfo = params.shopInfo || {};
252
- const locales = resolveLocales(shopInfo, params.locales);
253
- const productMap = params.productMap || {};
254
- return locales.reduce((data, locale) => {
255
- data[locale] = buildLocaleData({
256
- order,
257
- shopInfo,
258
- locale,
259
- productMap
260
- });
261
- return data;
262
- }, {});
263
- }
264
- function hasSmallTicketData(value) {
265
- return !!value && typeof value === 'object' && Object.keys(value).length > 0;
266
- }
267
- function buildLocaleData(params) {
268
- const {
269
- order,
270
- shopInfo,
271
- locale,
272
- productMap
273
- } = params;
274
- const products = buildProducts(order, locale, productMap);
275
- const summary = getSummary(order);
276
- const currencySymbol = getCurrencySymbol(order, shopInfo);
277
- const financials = resolveReceiptFinancials(order, summary);
278
- const delivery = buildDelivery(order, locale);
279
- return {
280
- base_data: buildBaseData({
281
- order,
282
- shopInfo,
283
- locale,
284
- currencySymbol,
285
- financials
286
- }),
287
- products,
288
- fees: buildFees({
289
- order,
290
- shopInfo,
291
- locale,
292
- products,
293
- summary,
294
- currencySymbol,
295
- financials
296
- }),
297
- customer: buildCustomer(order, locale),
298
- appointment: buildAppointment(order, locale),
299
- ...(delivery.length ? {
300
- delivery
301
- } : {}),
302
- resources: buildResources(order, locale),
303
- payment_data: buildPaymentData({
304
- order,
305
- locale,
306
- currencySymbol
307
- }),
308
- wallets: buildWallets({
309
- order,
310
- locale,
311
- currencySymbol
312
- }),
313
- tickets: Array.isArray(order.tickets) ? order.tickets : [],
314
- source_shop: [],
315
- refund_data: buildRefundData({
316
- order,
317
- locale,
318
- currencySymbol
319
- }),
320
- expend_data: []
321
- };
322
- }
323
- function buildBaseData(params) {
324
- const {
325
- order,
326
- shopInfo,
327
- locale,
328
- currencySymbol,
329
- financials
330
- } = params;
331
- const orderNumber = stringify(order.buzzer || order.shop_full_order_number);
332
- return {
333
- shop_name: stringify(shopInfo.name || order.shop_name),
334
- note: stringify(order.note),
335
- shop_order_number: stringify(order.shop_order_number),
336
- order_number: orderNumber,
337
- shop_tax_number: stringify(shopInfo.tax_number || order.shop_tax_number),
338
- shop_phone: stringify(shopInfo.phone || order.shop_phone),
339
- shop_address: stringify(shopInfo.receipt_address || shopInfo.address || buildShopAddress(shopInfo) || order.shop_address),
340
- original_payment_status: financials.paymentStatus,
341
- payment_status: financials.paymentStatus,
342
- total_amount: formatMoney(financials.totalAmount, currencySymbol),
343
- has_pay: formatMoney(financials.netPaymentApplied, currencySymbol),
344
- amount_gap: formatCalculatedMoney(financials.amountGap ?? 0, currencySymbol),
345
- is_repeat: 0,
346
- buzzer: stringify(order.buzzer || order.pickup_number || order.metadata?.buzzer),
347
- order_no: orderNumber,
348
- order_time: formatDateTime(order.created_at || order.sales_time || new Date(), locale),
349
- ...(isPresentId(order.updated_at) ? {
350
- updated_at: formatDateTime(order.updated_at, locale)
351
- } : {})
352
- };
353
- }
354
- function buildProducts(order, locale, productMap) {
355
- const products = Array.isArray(order.products) ? order.products : [];
356
- const allergyByProductUid = buildProductAllergyMap(order, locale);
357
- return products.map(rawProduct => {
358
- const product = restoreReceiptDiscountHierarchy(rawProduct);
359
- const quantity = toNumber(product.product_quantity ?? product.quantity ?? product.num ?? 1, 1);
360
- const originalPrice = product.original_price ?? product.selling_price ?? product.payment_price ?? product.price ?? 0;
361
- const sellingPrice = product.selling_price ?? product.payment_price ?? product.price ?? originalPrice;
362
- const productTitle = resolveProductTitle({
363
- product,
364
- productMap,
365
- locale
366
- });
367
- const parentProduct = resolveProductFromMap(product.product_id ?? product.id, productMap);
368
- const currencySymbol = getCurrencySymbol(order, null);
369
- const options = normalizeProductOptions({
370
- product,
371
- locale,
372
- currencySymbol,
373
- parentProduct
374
- });
375
- const combinations = normalizeProductCombinations({
376
- product,
377
- productMap,
378
- locale,
379
- currencySymbol,
380
- parentProduct
381
- });
382
- const resolvedVariant = resolveProductVariantText({
383
- product,
384
- parentProduct,
385
- locale
386
- });
387
- const productUid = firstPresentValue(product.metadata?.unique_identification_number, product.unique_identification_number);
388
- const bookingUid = firstPresentValue(product.booking_uid, product.metadata?.booking_uid);
389
- const extensionList = buildProductExtensionList({
390
- product,
391
- locale,
392
- currencySymbol,
393
- quantity,
394
- originalPrice
395
- });
396
- const productNote = resolveProductNote(product, locale);
397
- const allergy = isPresentId(productUid) ? allergyByProductUid.get(String(productUid).trim()) || '' : '';
398
- const basePrice = resolveProductBasePrice(product);
399
- const resolvedCombinations = Array.isArray(product.combinations) && product.combinations.length ? normalizeExistingCombinations(product.combinations, locale, currencySymbol) : combinations;
400
- const shouldIncludeBasePrice = options.length > 0 || resolvedCombinations.length > 0 || hasProductDiscount(product, extensionList);
401
- return {
402
- product_title: productTitle,
403
- product_quantity: quantity,
404
- selling_price: formatMoney(sellingPrice, currencySymbol),
405
- total_selling_price: formatCalculatedMoney(new _decimal.default(toMoneyNumber(sellingPrice)).mul(quantity), currencySymbol),
406
- original_price: formatMoney(originalPrice, currencySymbol),
407
- ...(shouldIncludeBasePrice ? {
408
- base_price: formatCalculatedMoney(basePrice ?? 0, currencySymbol)
409
- } : {}),
410
- total_original_price: formatMoney(new _decimal.default(toMoneyNumber(originalPrice)).mul(quantity), currencySymbol),
411
- ...(options.length ? {
412
- options_total_price: sumNormalizedOptionTotals(options, currencySymbol)
413
- } : {}),
414
- ...(resolvedCombinations.length ? {
415
- package_items_total_price: sumCombinationTotals(resolvedCombinations, currencySymbol)
416
- } : {}),
417
- extension_list: extensionList,
418
- ...(isPresentId(productUid) ? {
419
- product_uid: productUid
420
- } : {}),
421
- ...(isPresentId(bookingUid) ? {
422
- booking_uid: bookingUid
423
- } : {}),
424
- ...(productNote ? {
425
- product_note: productNote
426
- } : {}),
427
- ...(allergy ? {
428
- allergy
429
- } : {}),
430
- ...(options.length ? {
431
- options
432
- } : {}),
433
- ...(resolvedVariant ? {
434
- variant: resolvedVariant
435
- } : {}),
436
- ...(resolvedCombinations.length ? {
437
- combinations: resolvedCombinations
438
- } : {})
439
- };
440
- });
441
- }
442
- const RECEIPT_BUNDLE_MAP_ID_KEY = 'custom_product_bundle_map_id';
443
- function restoreReceiptDiscountHierarchy(source) {
444
- if (!source || typeof source !== 'object' || Array.isArray(source)) {
445
- return source;
446
- }
447
- const childKey = Array.isArray(source.combinations) && source.combinations.length ? 'combinations' : Array.isArray(source.product_bundle) && source.product_bundle.length ? 'product_bundle' : null;
448
- if (!childKey) return {
449
- ...source
450
- };
451
- const children = source[childKey].map(child => child && typeof child === 'object' && !Array.isArray(child) ? restoreReceiptDiscountHierarchy(child) : child);
452
- const restored = {
453
- ...source,
454
- [childKey]: children
455
- };
456
- if (!Array.isArray(source.discount_list) || !source.discount_list.length) {
457
- return restored;
458
- }
459
- const descendantsByMapId = new Map();
460
- indexReceiptDiscountTargets(children, descendantsByMapId);
461
- if (!descendantsByMapId.size) return restored;
462
- const ownDiscounts = [];
463
- for (const discount of source.discount_list) {
464
- const mapId = getReceiptDiscountBundleMapId(discount);
465
- const target = mapId ? descendantsByMapId.get(mapId) : undefined;
466
- if (!target) {
467
- ownDiscounts.push(discount);
468
- continue;
469
- }
470
- const targetDiscounts = Array.isArray(target.discount_list) ? target.discount_list : [];
471
- if (!targetDiscounts.some(item => isSameReceiptDiscount(item, discount))) {
472
- target.discount_list = [...targetDiscounts, discount];
473
- }
474
- }
475
- restored.discount_list = ownDiscounts;
476
- return restored;
477
- }
478
- function indexReceiptDiscountTargets(nodes, targets) {
479
- for (const node of nodes) {
480
- if (!node || typeof node !== 'object' || Array.isArray(node)) continue;
481
- const mapId = getReceiptBundleMapId(node);
482
- if (mapId && !targets.has(mapId)) targets.set(mapId, node);
483
- const children = Array.isArray(node.combinations) && node.combinations.length ? node.combinations : Array.isArray(node.product_bundle) ? node.product_bundle : [];
484
- indexReceiptDiscountTargets(children, targets);
485
- }
486
- }
487
- function normalizeReceiptId(value) {
488
- if (value === undefined || value === null) return null;
489
- const normalized = String(value).trim();
490
- return normalized ? normalized : null;
491
- }
492
- function getReceiptBundleMapId(node) {
493
- return normalizeReceiptId(node.metadata?.[RECEIPT_BUNDLE_MAP_ID_KEY] ?? node._id);
494
- }
495
- function getReceiptDiscountBundleMapId(discount) {
496
- return normalizeReceiptId(discount?.metadata?.[RECEIPT_BUNDLE_MAP_ID_KEY]);
497
- }
498
- function isSameReceiptDiscount(left, right) {
499
- const leftOrderDiscountId = normalizeReceiptId(left?.order_discount_id);
500
- const rightOrderDiscountId = normalizeReceiptId(right?.order_discount_id);
501
- if (leftOrderDiscountId && rightOrderDiscountId) {
502
- return leftOrderDiscountId === rightOrderDiscountId;
503
- }
504
- const leftResourceId = normalizeReceiptId(left?.discount?.resource_id ?? left?.resource_id ?? left?.discount_id);
505
- const rightResourceId = normalizeReceiptId(right?.discount?.resource_id ?? right?.resource_id ?? right?.discount_id);
506
- if (!leftResourceId || !rightResourceId) return false;
507
- return leftResourceId === rightResourceId && getReceiptDiscountBundleMapId(left) === getReceiptDiscountBundleMapId(right) && String(left?.amount ?? left?.value ?? left?.discount_amount ?? '') === String(right?.amount ?? right?.value ?? right?.discount_amount ?? '');
508
- }
509
- function resolveProductBasePrice(product) {
510
- const metadata = product.metadata || {};
511
- const origin = product._origin || metadata.origin || {};
512
- const variantId = product.product_variant_id ?? product.variant_id;
513
- const variant = Array.isArray(origin.variant) && isPresentId(variantId) ? origin.variant.find(item => isSameId(item?.id, variantId)) : undefined;
514
- const isV2 = metadata.price_schema_version === 2 || metadata.price_schema_version === '2';
515
- const quantity = toNumber(product.product_quantity ?? product.quantity ?? product.num ?? 1, 1);
516
- const hasEntitlementPass = normalizeArray(product.discount_list).some(discount => getProductWalletDiscountType(discount) === 'entitlement_pass');
517
- const hasOptions = normalizeArray(product.options).length > 0 || normalizeArray(product.product_sku?.option).length > 0;
518
- const hasCombinations = normalizeArray(product.combinations).length > 0 || normalizeArray(product.product_bundle).length > 0 || normalizeArray(product.bundle).length > 0;
519
- const aggregateSourcePrice = firstPresentValue(metadata.source_product_price, product.price);
520
- const originalPrice = product.original_price;
521
- // 部分旧 entitlement 商品把 source/price 持久化成了整行总价,但
522
- // original_price 仍是单件价。仅在等式能明确证明该口径时做窄幅回退。
523
- const useOriginalPriceAsLegacyUnitPrice = hasEntitlementPass && quantity > 1 && !hasOptions && !hasCombinations && isNumericAmount(aggregateSourcePrice) && isNumericAmount(originalPrice) && new _decimal.default(aggregateSourcePrice).eq(new _decimal.default(originalPrice).mul(quantity));
524
- return firstPresentValue(useOriginalPriceAsLegacyUnitPrice ? originalPrice : undefined, metadata.source_product_price, variant?.price, product._origin?.price, product._origin?.base_price, !isV2 ? metadata.main_product_original_price : undefined, product.base_price, product.price);
525
- }
526
- function normalizeProductDiscounts(params) {
527
- const {
528
- product,
529
- locale,
530
- currencySymbol,
531
- quantity
532
- } = params;
533
- const rows = normalizeArray(product.discount_list).filter(discount => {
534
- const amount = discount.amount ?? discount.value ?? discount.discount_amount;
535
- return isNonZero(amount);
536
- }).map(discount => {
537
- const title = resolveDiscountTitleWithPercent(discount, locale);
538
- const amount = discount.amount ?? discount.value ?? discount.discount_amount;
539
- const isMarkup = isManualMarkup(discount);
540
- const isAggregatedBundleDiscount = Boolean(getReceiptDiscountBundleMapId(discount));
541
- const isEntitlementPass = getProductWalletDiscountType(discount) === 'entitlement_pass';
542
- const totalAmount = new _decimal.default(toMoneyNumber(amount)).mul(isAggregatedBundleDiscount || isEntitlementPass ? 1 : quantity);
543
- const formattedAmount = isMarkup ? formatMoney(totalAmount.abs(), currencySymbol) : formatNegativeMoney(totalAmount, currencySymbol);
544
- return {
545
- item: title,
546
- value: formattedAmount,
547
- isMarkup
548
- };
549
- });
550
- return rows.sort((left, right) => Number(right.isMarkup) - Number(left.isMarkup)).map(({
551
- isMarkup: _isMarkup,
552
- ...row
553
- }) => row);
554
- }
555
- function isManualMarkup(discount) {
556
- if (discount.type !== 'product') return false;
557
- if (discount.discount?.discount_type === 'change_price') return true;
558
- const amount = discount.amount ?? discount.value ?? discount.discount_amount;
559
- return new _decimal.default(toMoneyNumber(amount)).isNegative();
560
- }
561
- function hasProductDiscount(product, extensionList = []) {
562
- const hasDiscountListAmount = normalizeArray(product.discount_list).some(discount => isNonZero(discount.amount ?? discount.value ?? discount.discount_amount));
563
- return hasDiscountListAmount || extensionList.some(row => /^\s*-/.test(stringify(row?.value)));
564
- }
565
- function buildProductExtensionList(params) {
566
- const {
567
- product,
568
- locale,
569
- currencySymbol,
570
- quantity,
571
- originalPrice
572
- } = params;
573
- const discountList = normalizeProductDiscounts({
574
- product,
575
- locale,
576
- currencySymbol,
577
- quantity
578
- });
579
- const extensionList = [...buildProductOpenWeightRows({
580
- product,
581
- locale,
582
- currencySymbol
583
- }), ...(quantity > 1 ? [{
584
- item: buildProductQuantityPriceText({
585
- quantity,
586
- currencySymbol,
587
- price: originalPrice
588
- }),
589
- value: ''
590
- }] : [])];
591
- const productExtensionList = extensionList.length ? extensionList : normalizeArray(product.extension_list);
592
- return [...productExtensionList, ...discountList];
593
- }
594
- function buildProductOpenWeightRows(params) {
595
- const {
596
- product,
597
- locale,
598
- currencySymbol
599
- } = params;
600
- const productSku = product.product_sku || {};
601
- if (productSku.open_sold_weight != 1) return [];
602
- const unit = productSku.unit ?? '';
603
- const manualFlag = productSku.weight_source === 'manual' ? '(M) ' : '';
604
- const hasTare = toNumber(productSku.tare_weight, 0) > 0;
605
- const rows = [];
606
- if (hasTare) {
607
- rows.push({
608
- item: `${manualFlag}${label(locale, 'gross')}: ${productSku.weight ?? 0}${unit}`,
609
- value: ''
610
- });
611
- rows.push({
612
- item: `${label(locale, 'tare')}: ${productSku.tare_weight}${unit}`,
613
- value: ''
614
- });
615
- }
616
- const netWeight = productSku.net_weight ?? 0;
617
- const unitPrice = productSku.unit_price ?? 0;
618
- const netItem = `${netWeight} ${unit} NET @ ${currencySymbol}${unitPrice}/${unit}`;
619
- rows.push({
620
- item: hasTare ? netItem : `${manualFlag}${netItem}`,
621
- value: ''
622
- });
623
- return rows;
624
- }
625
- function buildProductQuantityPriceText(params) {
626
- const {
627
- quantity,
628
- currencySymbol,
629
- price
630
- } = params;
631
- return `${quantity} @ ${currencySymbol}${stringify(price)} EACH`;
632
- }
633
- function resolveDiscountTitleWithPercent(discount, locale) {
634
- const title = resolveDiscountTitle(discount, locale);
635
- if (getProductWalletDiscountType(discount) === 'entitlement_pass') {
636
- return title;
637
- }
638
- const percentRaw = discount.discount?.percent;
639
- const cardType = discount.discount?.discount_card_type ?? 'percent';
640
- const percent = percentRaw !== undefined && percentRaw !== null ? Math.trunc(Number(percentRaw)) : NaN;
641
- if (percentRaw === undefined || percentRaw === null) return title;
642
- if (cardType !== 'percent') return title;
643
- if (Number.isNaN(percent)) return title;
644
- return `${title} (${percent}%)`;
645
- }
646
- function resolveDiscountTitle(discount, locale) {
647
- const title = getLocalizedValue(discount.discount?.name ?? discount.discount?.title ?? discount.discount?.message ?? discount.name ?? discount.title ?? discount.message, locale);
648
- if (title) return title;
649
- if (discount.type === 'product') {
650
- return label(locale, isManualMarkup(discount) ? 'manualMarkup' : 'manualDiscount');
651
- }
652
- return '';
653
- }
654
- function resolveProductTitle(params) {
655
- const {
656
- product,
657
- productMap,
658
- locale
659
- } = params;
660
- const ownTitle = getProductTitleSource(product, locale);
661
- if (ownTitle) return ownTitle;
662
- const productId = product.product_id ?? product.id;
663
- const fallbackProduct = resolveProductFromMap(productId, productMap);
664
- if (!fallbackProduct) return ownTitle || '';
665
- return getProductTitleSource(fallbackProduct, locale);
666
- }
667
- function normalizeProductOptions(params) {
668
- const {
669
- product,
670
- locale,
671
- currencySymbol,
672
- parentProduct
673
- } = params;
674
- const rawOptions = Array.isArray(params.options) ? params.options : Array.isArray(product.options) ? product.options : Array.isArray(product.product_sku?.option) ? product.product_sku.option : [];
675
- return rawOptions.map(option => {
676
- const price = option.original_price ?? option.add_price ?? option.price;
677
- const quantity = toNumber(option.num ?? option.quantity ?? 1, 1);
678
- const value = option.value ?? option.option ?? option.item;
679
- const name = getProductTitleSource(option, locale) || resolveOptionTitleFromParentProduct({
680
- option,
681
- parentProduct,
682
- locale
683
- });
684
- return {
685
- name,
686
- ...(option.num !== undefined || option.quantity !== undefined ? {
687
- num: quantity
688
- } : {}),
689
- ...(price !== undefined && price !== null && price !== '' ? {
690
- price: formatMoney(price, currencySymbol)
691
- } : {}),
692
- total_price: formatCalculatedMoney(new _decimal.default(toMoneyNumber(price ?? 0)).mul(quantity), currencySymbol),
693
- ...(value !== undefined && value !== null && value !== '' ? {
694
- value: getLocalizedValue(value, locale)
695
- } : {})
696
- };
697
- });
698
- }
699
- function normalizeProductCombinations(params) {
700
- const {
701
- product,
702
- productMap,
703
- locale,
704
- currencySymbol,
705
- parentProduct
706
- } = params;
707
- const bundles = Array.isArray(product.product_bundle) ? product.product_bundle : [];
708
- return bundles.map(bundle => {
709
- const combinationsOptions = normalizeProductOptions({
710
- product: {},
711
- locale,
712
- currencySymbol,
713
- options: Array.isArray(bundle.option) ? bundle.option : [],
714
- parentProduct
715
- });
716
- const baseTitle = resolveBundleProductTitle({
717
- bundle,
718
- productMap,
719
- locale,
720
- parentProduct
721
- });
722
- const productNote = resolveProductNote(bundle, locale);
723
- const nestedCombinations = Array.isArray(bundle.combinations) ? normalizeExistingCombinations(bundle.combinations, locale, currencySymbol) : [];
724
- const quantity = toNumber(bundle.num ?? bundle.quantity ?? 1, 1);
725
- const formattedPrice = formatBundlePrice(bundle, currencySymbol);
726
- const optionsTotal = sumNormalizedOptionTotals(combinationsOptions, currencySymbol);
727
- const explicitBasePrice = resolveCombinationBasePrice(bundle);
728
- const basePrice = isPresentId(explicitBasePrice) ? explicitBasePrice : 0;
729
- const extensionList = buildProductExtensionList({
730
- product: bundle,
731
- locale,
732
- currencySymbol,
733
- quantity,
734
- originalPrice: formattedPrice
735
- });
736
- const shouldIncludeBasePrice = combinationsOptions.length > 0 || nestedCombinations.length > 0 || hasProductDiscount(bundle, extensionList);
737
- return {
738
- product_title: baseTitle,
739
- product_quantity: quantity,
740
- price: formattedPrice,
741
- total_selling_price: formatCalculatedMoney(new _decimal.default(toMoneyNumber(formattedPrice)).mul(quantity), currencySymbol),
742
- ...(shouldIncludeBasePrice ? {
743
- base_price: formatCalculatedMoney(basePrice, currencySymbol)
744
- } : {}),
745
- combinations_options: combinationsOptions,
746
- ...(combinationsOptions.length ? {
747
- options_total_price: optionsTotal
748
- } : {}),
749
- ...(nestedCombinations.length ? {
750
- package_items_total_price: sumCombinationTotals(nestedCombinations, currencySymbol)
751
- } : {}),
752
- ...(productNote ? {
753
- product_note: productNote
754
- } : {}),
755
- ...(extensionList.length ? {
756
- extension_list: extensionList
757
- } : {}),
758
- ...(nestedCombinations.length ? {
759
- combinations: nestedCombinations
760
- } : {})
761
- };
762
- });
763
- }
764
- function normalizeExistingCombinations(combinations, locale, currencySymbol) {
765
- return combinations.map(combination => {
766
- if (!combination || typeof combination !== 'object' || Array.isArray(combination)) {
767
- return combination;
768
- }
769
- const productNote = resolveProductNote(combination, locale);
770
- const rawOptions = Array.isArray(combination.combinations_options) ? combination.combinations_options : Array.isArray(combination.options) ? combination.options : [];
771
- const normalizedOptions = normalizeProductOptions({
772
- product: {},
773
- locale,
774
- currencySymbol,
775
- options: rawOptions
776
- });
777
- const nestedCombinations = Array.isArray(combination.combinations) ? normalizeExistingCombinations(combination.combinations, locale, currencySymbol) : [];
778
- const productTitle = getBundleProductTitleSource(combination, locale);
779
- const snapshot = {
780
- ...combination
781
- };
782
- delete snapshot.product_title;
783
- delete snapshot.product_note;
784
- delete snapshot.note;
785
- delete snapshot.combinations;
786
- delete snapshot.discount_list;
787
- const quantity = toNumber(combination.product_quantity ?? combination.num ?? combination.quantity ?? 1, 1);
788
- const unitPrice = firstPresentValue(combination.selling_price, combination.price, combination.original_price) ?? 0;
789
- const optionsAmount = sumMoneyValues(normalizedOptions.map(option => option.total_price));
790
- const explicitBasePrice = resolveCombinationBasePrice(combination, true);
791
- const basePrice = isPresentId(explicitBasePrice) ? explicitBasePrice : 0;
792
- const extensionList = buildProductExtensionList({
793
- product: combination,
794
- locale,
795
- currencySymbol,
796
- quantity,
797
- originalPrice: unitPrice
798
- });
799
- const shouldIncludeBasePrice = normalizedOptions.length > 0 || nestedCombinations.length > 0 || hasProductDiscount(combination, extensionList);
800
- return {
801
- ...snapshot,
802
- ...(productTitle ? {
803
- product_title: productTitle
804
- } : {}),
805
- product_quantity: quantity,
806
- total_selling_price: formatCalculatedMoney(new _decimal.default(toMoneyNumber(unitPrice)).mul(quantity), currencySymbol),
807
- ...(shouldIncludeBasePrice ? {
808
- base_price: formatCalculatedMoney(basePrice, currencySymbol)
809
- } : {}),
810
- ...(Array.isArray(combination.combinations_options) ? {
811
- combinations_options: normalizedOptions
812
- } : Array.isArray(combination.options) ? {
813
- options: normalizedOptions
814
- } : {}),
815
- ...(normalizedOptions.length ? {
816
- options_total_price: formatCalculatedMoney(optionsAmount, currencySymbol)
817
- } : {}),
818
- ...(nestedCombinations.length ? {
819
- package_items_total_price: sumCombinationTotals(nestedCombinations, currencySymbol)
820
- } : {}),
821
- ...(productNote ? {
822
- product_note: productNote
823
- } : {}),
824
- ...(extensionList.length ? {
825
- extension_list: extensionList
826
- } : {}),
827
- ...(nestedCombinations.length ? {
828
- combinations: nestedCombinations
829
- } : {})
830
- };
831
- });
832
- }
833
- function sumMoneyValues(values) {
834
- return values.reduce((sum, value) => sum.plus(toMoneyNumber(value)), new _decimal.default(0));
835
- }
836
- function resolveCombinationBasePrice(combination, preferOriginalPrice = false) {
837
- const fallbackPrices = preferOriginalPrice ? [combination.original_price, combination.product_price, combination.price] : [combination.price, combination.original_price, combination.product_price];
838
- return firstPresentValue(combination.base_price, combination.metadata?.source_product_price, ...fallbackPrices);
839
- }
840
- function sumNormalizedOptionTotals(options, currencySymbol) {
841
- return formatCalculatedMoney(sumMoneyValues(options.map(option => option.total_price)), currencySymbol);
842
- }
843
- function sumCombinationTotals(combinations, currencySymbol) {
844
- return formatCalculatedMoney(sumMoneyValues(combinations.map(combination => combination.total_selling_price)), currencySymbol);
845
- }
846
- function resolveProductNote(product, locale) {
847
- const productNote = getLocalizedValue(product.product_note, locale).trim();
848
- if (productNote) return productNote;
849
- return getLocalizedValue(product.note, locale).trim();
850
- }
851
- function buildProductAllergyMap(order, locale) {
852
- const allergyByProductUid = new Map();
853
- for (const note of normalizeArray(order.notes)) {
854
- if (!note || typeof note !== 'object' || Array.isArray(note) || String(note.note_type || '').trim() !== 'allergy') {
855
- continue;
856
- }
857
- const content = getLocalizedValue(note.content?.text, locale).trim();
858
- if (!content) continue;
859
- const relation = normalizeArray(note.note_relations).find(item => item && typeof item === 'object' && !Array.isArray(item) && item.relation_type === 'about' && item.target_type === 'order_detail' && Number(item.is_primary) === 1);
860
- const productUid = String(relation?.target_uuid ?? '').trim();
861
- if (!productUid || allergyByProductUid.has(productUid)) continue;
862
- allergyByProductUid.set(productUid, content);
863
- }
864
- return allergyByProductUid;
865
- }
866
- function resolveBundleProductTitle(params) {
867
- const {
868
- bundle,
869
- productMap,
870
- locale,
871
- parentProduct
872
- } = params;
873
- const ownTitle = getBundleProductTitleSource(bundle, locale);
874
- if (ownTitle) return ownTitle;
875
- const parentBundleItem = resolveBundleItemFromParentProduct({
876
- bundle,
877
- parentProduct
878
- });
879
- const parentBundleTitle = parentBundleItem ? getBundleProductTitleSource(parentBundleItem, locale) : '';
880
- if (parentBundleTitle) return parentBundleTitle;
881
- const productId = bundle.bundle_product_id ?? bundle._bundle_product_id ?? bundle.product_id ?? bundle.id;
882
- const fallbackProduct = resolveProductFromMap(productId, productMap);
883
- if (!fallbackProduct) return '';
884
- return getBundleProductTitleSource(fallbackProduct, locale);
885
- }
886
- function resolveProductFromMap(productId, productMap) {
887
- if (productId === undefined || productId === null || productId === '') return null;
888
- return productMap[productId] || productMap[String(productId)] || null;
889
- }
890
- function resolveProductVariantText(params) {
891
- const {
892
- product,
893
- parentProduct,
894
- locale
895
- } = params;
896
- if (product.variant) return getLocalizedValue(product.variant, locale);
897
- if (!parentProduct) return '';
898
- const productVariantId = product.product_variant_id ?? product.variant_id;
899
- if (!isNonZeroId(productVariantId)) return '';
900
- const matchedVariant = normalizeArray(parentProduct.variant).find(variant => isSameId(variant.id, productVariantId));
901
- if (!matchedVariant) return '';
902
- const resolvedFromSkuKey = resolveVariantTextFromSkuKey({
903
- skuKey: matchedVariant.sku_key,
904
- parentProduct,
905
- locale
906
- });
907
- if (resolvedFromSkuKey) return resolvedFromSkuKey;
908
- return resolveVariantTextFromNameMap({
909
- nameMap: matchedVariant.name || matchedVariant.title,
910
- parentProduct,
911
- locale
912
- });
913
- }
914
- function resolveVariantTextFromSkuKey(params) {
915
- const skuKey = stringify(params.skuKey);
916
- if (!skuKey) return '';
917
- return skuKey.split('_').map(item => {
918
- const [groupPositionId, itemPositionId] = item.split(':');
919
- return resolveVariantTextItem({
920
- parentProduct: params.parentProduct,
921
- groupPositionId,
922
- itemPositionId,
923
- locale: params.locale
924
- });
925
- }).filter(Boolean).join(',');
926
- }
927
- function resolveVariantTextFromNameMap(params) {
928
- if (!params.nameMap || typeof params.nameMap !== 'object') return '';
929
- return Object.entries(params.nameMap).map(([groupPositionId, value]) => {
930
- const group = resolveVariantGroup(params.parentProduct, groupPositionId);
931
- if (!group) return '';
932
- const groupName = getLocalizedValue(group.name, params.locale);
933
- const itemName = getLocalizedValue(value, params.locale);
934
- if (!groupName || !itemName) return '';
935
- return `${groupName}:${itemName}`;
936
- }).filter(Boolean).join(',');
937
- }
938
- function resolveVariantTextItem(params) {
939
- const group = resolveVariantGroup(params.parentProduct, params.groupPositionId);
940
- const item = resolveVariantItem(group, params.itemPositionId);
941
- if (!group || !item) return '';
942
- const groupName = getLocalizedValue(group.name, params.locale);
943
- const itemName = getLocalizedValue(item.name, params.locale);
944
- if (!groupName || !itemName) return '';
945
- return `${groupName}:${itemName}`;
946
- }
947
- function resolveVariantGroup(parentProduct, groupPositionId) {
948
- return normalizeArray(parentProduct.variant_group).find(group => isSameId(group.position_id, groupPositionId) || isSameId(group.id, groupPositionId)) || null;
949
- }
950
- function resolveVariantItem(group, itemPositionId) {
951
- if (!group) return null;
952
- return normalizeArray(group.variant_item).find(item => isSameId(item.position_id, itemPositionId) || isSameId(item.id, itemPositionId)) || null;
953
- }
954
- function getProductTitleSource(source, locale) {
955
- if (!source || typeof source !== 'object') return '';
956
- return resolveTitleValue(source.product_title, locale) || resolveTitleValue(source.title_i18n, locale) || resolveTitleValue(source.title, locale) || resolveTitleValue(source.name, locale);
957
- }
958
- function getBundleProductTitleSource(source, locale) {
959
- if (!source || typeof source !== 'object') return '';
960
- return resolveTitleValue(source.bundle_title, locale) || resolveTitleValue(source.trans_title, locale) || resolveTitleValue(source.title_i18n, locale) || resolveTitleValue(source.product_title, locale) || resolveTitleValue(source.title, locale) || resolveTitleValue(source.name, locale);
961
- }
962
- function resolveTitleValue(value, locale) {
963
- if (value === undefined || value === null) return '';
964
- if (typeof value !== 'object') return stringify(value);
965
- const dashedLocale = locale.replace('_', '-');
966
- const underscoredLocale = locale.replace('-', '_');
967
- return stringify(value[locale] ?? value[dashedLocale] ?? value[underscoredLocale] ?? value.original ?? value.auto ?? '');
968
- }
969
- function resolveOptionTitleFromParentProduct(params) {
970
- const {
971
- option,
972
- parentProduct,
973
- locale
974
- } = params;
975
- if (!parentProduct) return '';
976
- const optionItems = flattenOptionItems(parentProduct);
977
- const matchedOption = optionItems.find(item => isOptionItemMatch(option, item));
978
- return getProductTitleSource(matchedOption, locale);
979
- }
980
- function flattenOptionItems(parentProduct) {
981
- const optionGroups = Array.isArray(parentProduct.option_group) ? parentProduct.option_group : [];
982
- return optionGroups.flatMap(group => {
983
- const items = normalizeArray(group.option_item || group.option_items || group.option || group.options || group.items || group.children || group.product_option_item);
984
- return items.map(item => ({
985
- ...item,
986
- option_group_id: item.option_group_id ?? group.id ?? group.option_group_id
987
- }));
988
- });
989
- }
990
- function isOptionItemMatch(option, item) {
991
- const optionItemId = option.option_group_item_id ?? option.product_option_item_id ?? option.id;
992
- const itemId = item.option_group_item_id ?? item.product_option_item_id ?? item.id;
993
- if (!isSameId(optionItemId, itemId)) return false;
994
- const optionGroupId = option.option_group_id ?? option.group_id;
995
- const itemGroupId = item.option_group_id ?? item.group_id;
996
- if (isPresentId(optionGroupId) && isPresentId(itemGroupId)) {
997
- return isSameId(optionGroupId, itemGroupId);
998
- }
999
- return true;
1000
- }
1001
- function resolveBundleItemFromParentProduct(params) {
1002
- const {
1003
- bundle,
1004
- parentProduct
1005
- } = params;
1006
- if (!parentProduct) return null;
1007
- const bundleItems = flattenBundleItems(parentProduct);
1008
- const bundleVariantId = bundle.bundle_variant_id ?? bundle.variant_id ?? bundle.product_variant_id;
1009
- if (isNonZeroId(bundleVariantId)) {
1010
- const matchedByVariant = bundleItems.find(item => isSameId(item.bundle_variant_id ?? item.variant_id ?? item.product_variant_id, bundleVariantId));
1011
- if (matchedByVariant) return matchedByVariant;
1012
- }
1013
- const bundleId = bundle.bundle_id ?? bundle.id;
1014
- if (isPresentId(bundleId)) {
1015
- const matchedByBundleId = bundleItems.find(item => isSameId(item.id ?? item.bundle_id, bundleId));
1016
- if (matchedByBundleId) return matchedByBundleId;
1017
- }
1018
- const bundleGroupId = bundle.bundle_group_id ?? bundle.group_id;
1019
- const bundleProductId = bundle.bundle_product_id ?? bundle._bundle_product_id ?? bundle.product_id;
1020
- if (isPresentId(bundleGroupId) && isPresentId(bundleProductId)) {
1021
- const matchedByGroupAndProduct = bundleItems.find(item => isSameId(item.group_id ?? item.bundle_group_id, bundleGroupId) && isSameId(item.bundle_product_id ?? item._bundle_product_id ?? item.product_id, bundleProductId));
1022
- if (matchedByGroupAndProduct) return matchedByGroupAndProduct;
1023
- }
1024
- if (isPresentId(bundleProductId)) {
1025
- return bundleItems.find(item => isSameId(item.bundle_product_id ?? item._bundle_product_id ?? item.product_id, bundleProductId)) || null;
1026
- }
1027
- return null;
1028
- }
1029
- function flattenBundleItems(parentProduct) {
1030
- const bundleGroups = Array.isArray(parentProduct.bundle_group) ? parentProduct.bundle_group : [];
1031
- return bundleGroups.flatMap(group => {
1032
- const items = Array.isArray(group.bundle_item) ? group.bundle_item : [];
1033
- return items.map(item => ({
1034
- ...item,
1035
- group_id: item.group_id ?? group.id ?? group.group_id
1036
- }));
1037
- });
1038
- }
1039
- function isNonZeroId(value) {
1040
- return isPresentId(value) && String(value) !== '0';
1041
- }
1042
- function isPresentId(value) {
1043
- return value !== undefined && value !== null && value !== '';
1044
- }
1045
- function firstPresentValue(...values) {
1046
- return values.find(value => isPresentId(value));
1047
- }
1048
- function isSameId(left, right) {
1049
- if (!isPresentId(left) || !isPresentId(right)) return false;
1050
- return String(left) === String(right);
1051
- }
1052
- function formatBundlePrice(bundle, currencySymbol) {
1053
- const value = bundle.bundle_selling_price ?? bundle.bundle_sum_price ?? bundle.price ?? 0;
1054
- const amount = new _decimal.default(toMoneyNumber(value));
1055
- // 套餐价格协议:只有 markdown 是减价;加价与商品原价都保持正数。
1056
- const isNegative = bundle.price_type === 'markdown';
1057
- if (isNegative) return `-${currencySymbol}${amount.abs().toFixed(2)}`;
1058
- return formatMoney(amount, currencySymbol);
1059
- }
1060
- function buildFees(params) {
1061
- const {
1062
- order,
1063
- shopInfo,
1064
- locale,
1065
- products,
1066
- summary,
1067
- currencySymbol,
1068
- financials
1069
- } = params;
1070
- const fees = [];
1071
- for (const surcharge of normalizeArray(order.surcharges || summary.surcharges)) {
1072
- const value = surcharge.amount ?? surcharge.value ?? surcharge.total_amount;
1073
- if (!isNonZero(value)) continue;
1074
- fees.push({
1075
- item: getLocalizedValue(surcharge.name || surcharge.title || surcharge.item, locale),
1076
- value: formatMoney(value, currencySymbol)
1077
- });
1078
- }
1079
- const discountAmount = firstPresentValue(order.shop_discount, summary.discount_amount, order.discount_amount);
1080
- if (isNonZero(discountAmount)) {
1081
- fees.push({
1082
- item: label(locale, 'orderDiscount'),
1083
- value: formatNegativeMoney(discountAmount, currencySymbol)
1084
- });
1085
- }
1086
- const taxFee = firstPresentValue(summary.tax_fee, order.tax_fee);
1087
- const taxTitle = resolveTaxTitle({
1088
- summary,
1089
- order,
1090
- shopInfo,
1091
- locale
1092
- });
1093
- const isPriceIncludeTax = firstPresentValue(summary.is_price_include_tax, order.is_price_include_tax, shopInfo.is_price_include_tax);
1094
- const roundingAmount = firstPresentValue(summary.rounding_amount, order.rounding_amount);
1095
- const payProcessingFee = financials.payProcessingFee;
1096
- if (isNonZero(payProcessingFee)) {
1097
- fees.push({
1098
- item: label(locale, 'payProcessingFee'),
1099
- value: formatMoney(payProcessingFee, currencySymbol)
1100
- });
1101
- }
1102
- const productExpectAmount = firstPresentValue(summary.product_expect_amount, summary.product_amount, order.product_expect_amount);
1103
- const productOriginalAmount = firstPresentValue(summary.product_original_amount, order.product_original_amount) ?? calculateProductOriginalAmount(order);
1104
- const totalSavingsAmount = new _decimal.default(toMoneyNumber(productOriginalAmount)).minus(toMoneyNumber(productExpectAmount)).plus(toMoneyNumber(discountAmount));
1105
- const itemDiscountAmount = new _decimal.default(toMoneyNumber(productExpectAmount ?? 0)).minus(toMoneyNumber(productOriginalAmount));
1106
- return {
1107
- fees,
1108
- countFee: {
1109
- total_amount: formatMoney(firstPresentValue(summary.total_amount, order.total_amount) ?? 0, currencySymbol),
1110
- original_item_amount: formatMoney(productOriginalAmount, currencySymbol),
1111
- product_expect_amount: formatMoney(productExpectAmount ?? 0, currencySymbol),
1112
- item_discount_amount: formatCalculatedMoney(itemDiscountAmount, currencySymbol),
1113
- expect_amount: formatMoney(financials.totalAmount, currencySymbol),
1114
- product_quantity: toNumber(firstPresentValue(summary.product_quantity, products.reduce((sum, product) => sum + product.product_quantity, 0)), 0),
1115
- has_pay: formatMoney(financials.orderPaidAmount, currencySymbol),
1116
- payment_method: '',
1117
- tax_title: taxTitle,
1118
- total_refund_amount: formatMoney(financials.totalRefundAmount, currencySymbol),
1119
- customer_paid_amount: formatMoney(financials.customerPaidAmount, currencySymbol),
1120
- net_payment_applied: formatMoney(financials.netPaymentApplied, currencySymbol),
1121
- ...(isPresentId(roundingAmount) ? {
1122
- rounding_amount: formatMoney(roundingAmount, currencySymbol)
1123
- } : {}),
1124
- ...(isNonZero(totalSavingsAmount) ? {
1125
- order_total_discount: formatMoney(totalSavingsAmount, currencySymbol)
1126
- } : {}),
1127
- ...(financials.hasPayProcessingFee ? {
1128
- pay_processing_fee: formatMoney(payProcessingFee, currencySymbol)
1129
- } : {}),
1130
- ...(isPresentId(taxFee) ? {
1131
- tax_fee: formatMoney(taxFee, currencySymbol)
1132
- } : {}),
1133
- ...(isPresentId(isPriceIncludeTax) ? {
1134
- is_price_include_tax: normalizeBooleanFlag(isPriceIncludeTax)
1135
- } : {})
1136
- }
1137
- };
1138
- }
1139
- function resolveReceiptFinancials(order, summary) {
1140
- const totalAmount = firstPresentValue(summary.expect_amount, order.expect_amount, summary.total_amount, order.total_amount) ?? 0;
1141
- const rawPayProcessingFee = firstPresentValue(summary.pay_service_charge_amount, order.pay_service_charge_amount);
1142
- const payProcessingFee = rawPayProcessingFee ?? 0;
1143
- const orderPaidAmount = firstPresentValue(summary.order_paid_amount, order.order_paid_amount, sumAppliedPayments(order.payments)) ?? 0;
1144
- const totalRefundAmount = firstPresentValue(summary.total_refund_amount, order.total_refund_amount) ?? 0;
1145
- const netPaymentApplied = _decimal.default.max(new _decimal.default(toMoneyNumber(orderPaidAmount)).minus(toMoneyNumber(totalRefundAmount)), 0);
1146
- const fallbackCustomerPaidAmount = _decimal.default.max(sumCustomerPaidPayments(order.payments), new _decimal.default(toMoneyNumber(orderPaidAmount)).plus(toMoneyNumber(payProcessingFee)));
1147
- const customerPaidAmount = firstPresentValue(summary.customer_paid_amount, order.customer_paid_amount, fallbackCustomerPaidAmount) ?? 0;
1148
- const amountGap = firstPresentValue(summary.amount_gap, order.amount_gap);
1149
- return {
1150
- totalAmount,
1151
- orderPaidAmount,
1152
- payProcessingFee,
1153
- customerPaidAmount,
1154
- totalRefundAmount,
1155
- netPaymentApplied,
1156
- amountGap,
1157
- paymentStatus: resolveReceiptPaymentStatus({
1158
- order,
1159
- summary
1160
- }),
1161
- hasPayProcessingFee: isPresentId(rawPayProcessingFee)
1162
- };
1163
- }
1164
- function resolveReceiptPaymentStatus(params) {
1165
- return [params.order.payment_status, params.summary.payment_status, params.order.original_payment_status].map(status => stringify(status).trim()).find(Boolean) || '';
1166
- }
1167
- function resolveTaxTitle(params) {
1168
- return [params.summary.tax_title, params.order.tax_title, params.shopInfo.tax_title].map(title => getLocalizedValue(title, params.locale).trim()).find(Boolean) || 'Tax';
1169
- }
1170
- function calculateProductOriginalAmount(order) {
1171
- return normalizeArray(order.products).reduce((total, product) => {
1172
- const quantity = toNumber(product.product_quantity ?? product.quantity ?? product.num ?? 1, 1);
1173
- const originalPrice = product.original_price ?? product.selling_price ?? product.payment_price ?? product.price ?? 0;
1174
- return total.plus(new _decimal.default(toMoneyNumber(originalPrice)).mul(quantity));
1175
- }, new _decimal.default(0));
1176
- }
1177
- function buildCustomer(order, locale) {
1178
- return [{
1179
- item: label(locale, 'contactName'),
1180
- value: normalizeCustomerDisplayValue(order.customer_name || order.customer?.name || order.customer?.display_name)
1181
- }, {
1182
- item: label(locale, 'contactMobile'),
1183
- value: stringify(order.phone || order.customer?.phone)
1184
- }, {
1185
- item: label(locale, 'email'),
1186
- value: stringify(order.email || order.customer?.email)
1187
- }];
1188
- }
1189
- function normalizeCustomerDisplayValue(value) {
1190
- const text = stringify(value).trim();
1191
- if (!text) return 'Walk-In';
1192
- if (text.toLowerCase() === 'select a customer') return 'Walk-In';
1193
- return text;
1194
- }
1195
- function buildDelivery(order, locale) {
1196
- const serviceType = stringify(order.metadata?.shop_service_data?.service_type).trim();
1197
- const serviceTypeLabelKey = resolveServiceTypeLabelKey(serviceType);
1198
- if (!serviceTypeLabelKey) return [];
1199
- const items = [{
1200
- item: label(locale, serviceTypeLabelKey),
1201
- value: '',
1202
- size: 2
1203
- }];
1204
- if (serviceType === 'pick_up') {
1205
- const pickupTime = formatPickupBookingStart(order.bookings, locale);
1206
- if (pickupTime) {
1207
- items.push({
1208
- item: pickupTime,
1209
- value: '',
1210
- size: 2
1211
- });
1212
- return items;
1213
- }
1214
- }
1215
- const handoverTime = firstPresentValue(order.handover_time, order.handover_at, order.pickup_time, order.pickup_at, order.scheduled_pickup_time, order.delivery_time, order.delivery_at, order.scheduled_delivery_time, order.fulfillment_time, order.fulfillment_at, order.metadata?.handover_time, order.metadata?.handover_at, order.metadata?.pickup_time, order.metadata?.pickup_at, order.metadata?.scheduled_pickup_time, order.metadata?.delivery_time, order.metadata?.delivery_at, order.metadata?.scheduled_delivery_time, order.metadata?.fulfillment_time, order.metadata?.fulfillment_at);
1216
- if (isPresentId(handoverTime)) {
1217
- items.push({
1218
- item: label(locale, 'handoverTime'),
1219
- value: formatDateTime(handoverTime, locale)
1220
- });
1221
- }
1222
- return items;
1223
- }
1224
- function formatPickupBookingStart(bookings, locale) {
1225
- const booking = normalizeArray(bookings).find(_pickupTiming.isFulfillmentTimingBooking);
1226
- const date = stringify(booking?.start_date).trim();
1227
- const time = stringify(booking?.start_time).trim();
1228
- if (!/^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/.test(date) || !/^([01]\d|2[0-3]):[0-5]\d(?::[0-5]\d)?$/.test(time)) {
1229
- return '';
1230
- }
1231
-
1232
- // Validate only the calendar date in UTC; display the stored shop-local
1233
- // fields verbatim so the printing device's timezone cannot shift pickup.
1234
- const calendarDate = new Date(`${date}T00:00:00.000Z`);
1235
- if (!Number.isFinite(calendarDate.getTime()) || calendarDate.toISOString().slice(0, 10) !== date) {
1236
- return '';
1237
- }
1238
- const [year, month, day] = date.split('-');
1239
- const dateText = locale === 'en' ? `${day}/${month}/${year}` : `${year}/${month}/${day}`;
1240
- return `${time.slice(0, 5)} ${dateText}`;
1241
- }
1242
- function buildAppointment(order, locale) {
1243
- const parentScheduleId = order.metadata?.appointment_booking_parent_schedule_id;
1244
- const bookings = sortBookings(normalizeArray(order.bookings).filter(booking => !(0, _pickupTiming.isFulfillmentTimingBooking)(booking) && isReceiptAppointmentBooking(booking, parentScheduleId)));
1245
- if (bookings.length) {
1246
- const allResources = normalizeArray(order.resources || order.resource_list);
1247
- return bookings.flatMap(booking => buildAppointmentRows({
1248
- booking,
1249
- allResources,
1250
- bookingCount: bookings.length,
1251
- locale
1252
- }));
1253
- }
1254
- const tableName = order.table_name || order.metadata?.table_name || order.metadata?.table?.name || order.form_data?.table_name;
1255
- if (!tableName) return [];
1256
- return [{
1257
- item: label(locale, 'table'),
1258
- value: getLocalizedValue(tableName, locale)
1259
- }];
1260
- }
1261
- function isReceiptAppointmentBooking(booking, parentScheduleId) {
1262
- if (!booking || typeof booking !== 'object' || Array.isArray(booking)) {
1263
- return false;
1264
- }
1265
- if (isPresentId(parentScheduleId) && isSameId(booking.schedule_event_id, parentScheduleId)) {
1266
- return false;
1267
- }
1268
- const parentId = booking.parent_id;
1269
- if (parentId === undefined || parentId === null || parentId === '') {
1270
- return true;
1271
- }
1272
- return Number(parentId) !== 0;
1273
- }
1274
- function buildAppointmentRows(params) {
1275
- const {
1276
- booking,
1277
- allResources,
1278
- bookingCount,
1279
- locale
1280
- } = params;
1281
- const productUid = stringify(firstPresentValue(booking.product_uid, booking.metadata?.product_uid)).trim();
1282
- const bookingUid = stringify(firstPresentValue(booking.metadata?.unique_identification_number, booking.booking_uid)).trim();
1283
- const association = {
1284
- product_uid: productUid,
1285
- booking_uid: bookingUid
1286
- };
1287
- const bookingId = stringify(firstPresentValue(booking.booking_id, booking.schedule_event_id, bookingUid)).trim();
1288
- const bookingTime = formatBookingRange(booking, locale);
1289
- const resourceText = formatBookingResources(resolveBookingResourcesForReceipt(booking, allResources, bookingCount), locale);
1290
- const holder = getLocalizedValue(booking.holder?.name ?? booking.metadata?.holder?.name, locale).trim();
1291
- return [{
1292
- item: label(locale, 'bookingId'),
1293
- value: bookingId,
1294
- ...association
1295
- }, {
1296
- item: label(locale, 'bookingDateTime'),
1297
- value: bookingTime,
1298
- ...association
1299
- }, {
1300
- item: label(locale, 'bookingResource'),
1301
- value: resourceText,
1302
- ...association
1303
- }, {
1304
- item: label(locale, 'bookingHolder'),
1305
- value: holder,
1306
- ...association
1307
- }].filter(row => Boolean(row.value));
1308
- }
1309
- function formatBookingRange(booking, locale) {
1310
- const startDate = stringify(booking.start_date);
1311
- const startTime = stringify(booking.start_time);
1312
- const endDate = stringify(booking.end_date || startDate);
1313
- const endTime = stringify(booking.end_time);
1314
- const start = firstValidBookingDate(booking.start_at, booking.start_datetime, booking.start_date_time, `${startDate} ${startTime}`.trim());
1315
- const end = firstValidBookingDate(booking.end_at, booking.end_datetime, booking.end_date_time, endTime ? `${endDate} ${endTime}`.trim() : '');
1316
- if (!start) {
1317
- return [startDate, startTime, endTime ? `- ${endDate} ${endTime}` : ''].filter(Boolean).join(' ');
1318
- }
1319
- const dateFormat = locale === 'en' ? 'DD/MM/YYYY' : 'YYYY/MM/DD';
1320
- const startText = start.format(`${dateFormat} HH:mm`);
1321
- if (!end) return startText;
1322
- return `${startText} - ${end.isSame(start, 'day') ? end.format('HH:mm') : end.format(`${dateFormat} HH:mm`)}`;
1323
- }
1324
- function firstValidBookingDate(...values) {
1325
- for (const value of values) {
1326
- if (!isPresentId(value)) continue;
1327
- const parsed = (0, _dayjs.default)(value);
1328
- if (parsed.isValid()) return parsed;
1329
- }
1330
- return null;
1331
- }
1332
- function resolveBookingResourcesForReceipt(booking, allResources, bookingCount) {
1333
- const embedded = normalizeArray(booking.resources).filter(resource => Boolean(resource) && typeof resource === 'object' && !Array.isArray(resource));
1334
- const resourceIds = new Set([booking.resource_id, booking.relation_id, ...normalizeArray(booking.resource_ids), ...normalizeArray(booking.metadata?.resource_ids)].map(value => stringify(value).trim()).filter(Boolean));
1335
- for (const resource of embedded) {
1336
- const id = stringify(resource.id ?? resource.resource_id ?? resource.relation_id).trim();
1337
- if (id) resourceIds.add(id);
1338
- }
1339
- if (resourceIds.size) {
1340
- const matched = allResources.filter(resource => resourceIds.has(stringify(resource.id ?? resource.resource_id ?? resource.relation_id).trim()));
1341
- if (matched.length) return matched;
1342
- }
1343
- if (embedded.length) return embedded;
1344
- if (bookingCount === 1 && allResources.length) return allResources;
1345
- return [];
1346
- }
1347
- function formatBookingResources(resources, locale) {
1348
- return resources.map(resource => [getLocalizedValue(resource.form_title || resource.metadata?.form_name || resource.relation_form_name || resource.metadata?.relation_form_name, locale), getLocalizedValue(resource.title || resource.name || resource.main_field || resource.metadata?.main_field || resource.metadata?.resource_name, locale)].filter(Boolean).join(' ')).filter(Boolean).join(', ');
1349
- }
1350
- function sortBookings(bookings) {
1351
- return bookings.map((booking, index) => ({
1352
- booking,
1353
- index
1354
- })).sort((left, right) => {
1355
- const leftStart = bookingStartTimestamp(left.booking);
1356
- const rightStart = bookingStartTimestamp(right.booking);
1357
- if (leftStart !== rightStart) return leftStart < rightStart ? -1 : 1;
1358
- for (const field of ['booking_id', 'schedule_event_id']) {
1359
- const fieldOrder = compareOptionalValues(left.booking?.[field], right.booking?.[field]);
1360
- if (fieldOrder !== 0) return fieldOrder;
1361
- }
1362
- const bookingUidOrder = compareOptionalValues(firstPresentValue(left.booking?.metadata?.unique_identification_number, left.booking?.booking_uid), firstPresentValue(right.booking?.metadata?.unique_identification_number, right.booking?.booking_uid));
1363
- return bookingUidOrder || left.index - right.index;
1364
- }).map(({
1365
- booking
1366
- }) => booking);
1367
- }
1368
- function bookingStartTimestamp(booking) {
1369
- const startValues = [booking?.start_at, booking?.start_datetime, booking?.booking_time, isPresentId(booking?.start_date) ? `${booking.start_date} ${stringify(booking.start_time)}`.trim() : undefined];
1370
- for (const startValue of startValues) {
1371
- if (!isPresentId(startValue)) continue;
1372
- const timestamp = (0, _dayjs.default)(startValue).valueOf();
1373
- if (Number.isFinite(timestamp)) return timestamp;
1374
- }
1375
- return Number.POSITIVE_INFINITY;
1376
- }
1377
- function compareOptionalValues(left, right) {
1378
- const hasLeft = isPresentId(left);
1379
- const hasRight = isPresentId(right);
1380
- if (!hasLeft && !hasRight) return 0;
1381
- if (!hasLeft) return 1;
1382
- if (!hasRight) return -1;
1383
- return String(left).localeCompare(String(right), undefined, {
1384
- numeric: true
1385
- });
1386
- }
1387
- function buildResources(order, locale) {
1388
- return normalizeArray(order.resources || order.resource_list).map(resource => ({
1389
- id: resource.id || resource.resource_id || '',
1390
- code: '',
1391
- title: getLocalizedValue(resource.title || resource.name, locale),
1392
- form_title: getLocalizedValue(resource.form_title || resource.type || '', locale)
1393
- }));
1394
- }
1395
- function buildPaymentData(params) {
1396
- const payments = normalizeArray(params.order.payments);
1397
- return payments.filter(payment => isPrintablePayment(payment)).map(payment => buildPaymentItems({
1398
- payment,
1399
- locale: params.locale,
1400
- currencySymbol: params.currencySymbol,
1401
- includeFundRecordBalance: false,
1402
- includeFundRecordUsage: true,
1403
- includePaymentAmount: true
1404
- }));
1405
- }
1406
- function buildRefundData(params) {
1407
- const payments = normalizeArray(params.order.payments);
1408
- const refunds = Array.from(normalizeArray(params.order.refunds).reduce((result, refund, index) => {
1409
- const identity = refundIdentity(refund, index);
1410
- const candidate = {
1411
- refund,
1412
- index
1413
- };
1414
- const existing = result.get(identity);
1415
- if (!existing || shouldPreferRefundCandidate(candidate, existing)) {
1416
- result.set(identity, candidate);
1417
- }
1418
- return result;
1419
- }, new Map()).values()).filter(({
1420
- refund
1421
- }) => isPrintableRefund(refund)).sort((left, right) => {
1422
- const leftTime = refundTimestamp(left.refund);
1423
- const rightTime = refundTimestamp(right.refund);
1424
- if (leftTime !== rightTime) return leftTime < rightTime ? -1 : 1;
1425
- return left.index - right.index;
1426
- });
1427
- return refunds.map(({
1428
- refund
1429
- }) => {
1430
- const payment = resolveRefundPayment(refund, payments);
1431
- const methodName = paymentMethodName(payment, params.locale) || label(params.locale, 'refund');
1432
- const amount = firstPresentValue(refund.amount, refund.refund_amount, refund.total_amount) ?? 0;
1433
- const items = [{
1434
- item: methodName,
1435
- value: formatNegativeMoney(amount, params.currencySymbol)
1436
- }];
1437
- const occurredAt = firstPresentValue(refund.refund_time, refund.refunded_at, refund.created_at, refund.updated_at);
1438
- if (isPresentId(occurredAt)) {
1439
- items.push({
1440
- item: formatDateTime(occurredAt, params.locale),
1441
- value: ''
1442
- });
1443
- }
1444
- return items;
1445
- });
1446
- }
1447
- function refundIdentity(refund, index) {
1448
- if (isPresentId(refund.order_refund_id)) return `refund-id:${refund.order_refund_id}`;
1449
- if (isPresentId(refund.refund_number)) return `refund-number:${refund.refund_number}`;
1450
- return `refund-index:${index}`;
1451
- }
1452
- function shouldPreferRefundCandidate(candidate, existing) {
1453
- const candidatePrintable = isPrintableRefund(candidate.refund);
1454
- const existingPrintable = isPrintableRefund(existing.refund);
1455
- if (candidatePrintable !== existingPrintable) return candidatePrintable;
1456
- const candidateTime = refundFreshnessTimestamp(candidate.refund);
1457
- const existingTime = refundFreshnessTimestamp(existing.refund);
1458
- if (candidateTime !== existingTime) return candidateTime > existingTime;
1459
- return candidate.index > existing.index;
1460
- }
1461
- function refundFreshnessTimestamp(refund) {
1462
- const timestampValue = firstPresentValue(refund.updated_at, refund.refunded_at, refund.refund_time, refund.created_at);
1463
- if (!isPresentId(timestampValue)) return Number.NEGATIVE_INFINITY;
1464
- const timestamp = (0, _dayjs.default)(timestampValue).valueOf();
1465
- return Number.isFinite(timestamp) ? timestamp : Number.NEGATIVE_INFINITY;
1466
- }
1467
- function refundTimestamp(refund) {
1468
- const occurredAt = firstPresentValue(refund.refund_time, refund.refunded_at, refund.created_at, refund.updated_at);
1469
- if (!isPresentId(occurredAt)) return Number.POSITIVE_INFINITY;
1470
- const timestamp = (0, _dayjs.default)(occurredAt).valueOf();
1471
- return Number.isFinite(timestamp) ? timestamp : Number.POSITIVE_INFINITY;
1472
- }
1473
- function isPrintableRefund(refund) {
1474
- const status = stringify(refund.status).trim().toLowerCase();
1475
- if (!status) return true;
1476
- return !['pending', 'payment_pending', 'payment_processing', 'refund_processing', 'processing', 'failed', 'error', 'cancelled', 'canceled', 'voided'].includes(status);
1477
- }
1478
- function resolveRefundPayment(refund, payments) {
1479
- if (refund.payment && typeof refund.payment === 'object' && !Array.isArray(refund.payment)) {
1480
- return refund.payment;
1481
- }
1482
- const orderPaymentId = firstPresentValue(refund.order_payment_id, refund.payment_id);
1483
- const matchedPayment = payments.find(payment => isSameId(firstPresentValue(payment.order_payment_id, payment.id), orderPaymentId));
1484
- return matchedPayment || refund;
1485
- }
1486
- function buildWallets(params) {
1487
- const virtualCurrencySnapshots = collectVirtualCurrencySnapshots(params.order);
1488
- const displayedWalletPayments = normalizeArray(params.order.payments).filter(payment => isPrintablePayment(payment) && !!getFundRecord(payment));
1489
- const paymentWallets = displayedWalletPayments.map(payment => {
1490
- const virtualCurrencySnapshot = findVirtualCurrencySnapshotForPayment(payment, virtualCurrencySnapshots.byId);
1491
- return buildPaymentItems({
1492
- payment,
1493
- locale: params.locale,
1494
- currencySymbol: params.currencySymbol,
1495
- includeFundRecordBalance: true,
1496
- includeFundRecordUsage: false,
1497
- includePaymentAmount: false,
1498
- virtualCurrencyUnit: virtualCurrencySnapshot ? getVirtualCurrencySnapshotUnit(virtualCurrencySnapshot, params.locale) : undefined
1499
- });
1500
- });
1501
- const displayedWalletAssetIdentities = collectDisplayedWalletAssetIdentities(displayedWalletPayments);
1502
- const voucherWallets = buildVoucherAssets({
1503
- ...params,
1504
- displayedWalletAssetIdentities
1505
- });
1506
- const productDiscountWallets = buildProductDiscountAssets({
1507
- ...params,
1508
- displayedWalletAssetIdentities
1509
- });
1510
- return [...paymentWallets, ...voucherWallets, ...productDiscountWallets, ...buildPointCardSnapshotAssets({
1511
- order: params.order,
1512
- locale: params.locale,
1513
- displayedWalletAssetIdentities
1514
- }), ...buildVirtualCurrencySnapshotAssets({
1515
- snapshots: virtualCurrencySnapshots.items,
1516
- order: params.order,
1517
- locale: params.locale,
1518
- displayedWalletAssetIdentities
1519
- })];
1520
- }
1521
- function collectDisplayedWalletAssetIdentities(payments) {
1522
- const identities = [];
1523
- payments.forEach(payment => {
1524
- const fundRecord = getFundRecord(payment);
1525
- const ids = Array.from(new Set([fundRecord?.voucher_id, payment.voucher_id].map(value => normalizeWalletPassId(value)).filter(value => !!value)));
1526
- const codes = normalizeWalletAssetCodes(fundRecord?.code, fundRecord?.encoded);
1527
- if (!ids.length) {
1528
- registerWalletAssetIdentity(identities, {
1529
- id: null,
1530
- codes
1531
- });
1532
- return;
1533
- }
1534
- ids.forEach(id => {
1535
- registerWalletAssetIdentity(identities, {
1536
- id,
1537
- codes
1538
- });
1539
- });
1540
- });
1541
- return identities;
1542
- }
1543
- const PRODUCT_WALLET_DISCOUNT_TYPES = new Set(['good_pass', 'discount_card', 'product_discount_card', 'entitlement_pass']);
1544
- function buildProductDiscountAssets(params) {
1545
- const fallbackAssetTime = firstPresentValue(params.order.updated_at, params.order.created_at);
1546
- return collectProductWalletDiscountAssets(params.order).flatMap(asset => {
1547
- if (!claimWalletAssetIdentity(params.displayedWalletAssetIdentities, {
1548
- id: asset.id,
1549
- codes: asset.codes
1550
- })) return [];
1551
- const name = getProductWalletDiscountName(asset, params.locale);
1552
- const assetTime = getProductWalletDiscountTime(asset) ?? fallbackAssetTime;
1553
- const timeRow = isPresentId(assetTime) ? {
1554
- item: formatDateTime(assetTime, params.locale),
1555
- value: ''
1556
- } : null;
1557
- if (asset.type === 'entitlement_pass') {
1558
- const remaining = getProductEntitlementBalance(asset);
1559
- return [[{
1560
- item: name,
1561
- value: ''
1562
- }, ...(remaining ? [{
1563
- item: label(params.locale, 'entitlementRemaining'),
1564
- value: formatEntitlementUses(remaining, params.locale)
1565
- }] : []), ...(timeRow ? [timeRow] : [])]];
6
+ var _smallTicket = require("../../shared/receipt/small-ticket");
7
+ Object.keys(_smallTicket).forEach(function (key) {
8
+ if (key === "default" || key === "__esModule") return;
9
+ if (key in exports && exports[key] === _smallTicket[key]) return;
10
+ Object.defineProperty(exports, key, {
11
+ enumerable: true,
12
+ get: function () {
13
+ return _smallTicket[key];
1566
14
  }
1567
- const percent = getProductWalletDiscountPercent(asset);
1568
- if (percent) {
1569
- return [[{
1570
- item: appendPercentToVoucherName(name, percent),
1571
- value: formatVoucherDiscount(percent, params.locale)
1572
- }, ...(timeRow ? [timeRow] : [])]];
1573
- }
1574
- const balance = getProductWalletMoneyBalance(asset);
1575
- return [[{
1576
- item: name,
1577
- value: ''
1578
- }, ...(balance ? [{
1579
- item: label(params.locale, 'balance'),
1580
- value: formatMoney(balance, params.currencySymbol)
1581
- }] : []), ...(timeRow ? [timeRow] : [])]];
1582
- });
1583
- }
1584
- function collectProductWalletDiscountAssets(order) {
1585
- const assets = [];
1586
- const assetsById = new Map();
1587
- normalizeArray(order.products).forEach(rawProduct => {
1588
- if (!rawProduct || typeof rawProduct !== 'object') return;
1589
- visitProductWalletDiscounts(restoreReceiptDiscountHierarchy(rawProduct), assets, assetsById);
1590
- });
1591
- return assets;
1592
- }
1593
- function visitProductWalletDiscounts(product, assets, assetsById) {
1594
- normalizeArray(product.discount_list).forEach(record => {
1595
- if (!record || typeof record !== 'object' || Array.isArray(record)) return;
1596
- const type = getProductWalletDiscountType(record);
1597
- if (!type) return;
1598
- const id = getProductWalletDiscountAssetId(record);
1599
- if (!id) return;
1600
- const codes = getProductWalletDiscountCodes(record);
1601
- const existing = assetsById.get(id);
1602
- if (existing) {
1603
- existing.records.push(record);
1604
- existing.codes = Array.from(new Set([...existing.codes, ...codes]));
1605
- return;
1606
- }
1607
- const asset = {
1608
- id,
1609
- type,
1610
- codes,
1611
- records: [record]
1612
- };
1613
- assetsById.set(id, asset);
1614
- assets.push(asset);
1615
- });
1616
- for (const childKey of ['combinations', 'product_bundle', 'bundle']) {
1617
- normalizeArray(product[childKey]).forEach(child => {
1618
- if (!child || typeof child !== 'object' || Array.isArray(child)) return;
1619
- visitProductWalletDiscounts(child, assets, assetsById);
1620
- });
1621
- }
1622
- }
1623
- function getProductWalletDiscountType(record) {
1624
- for (const value of [record.tag, record.type, record.discount?.tag, record.discount?.type]) {
1625
- const type = stringify(value).trim().toLowerCase();
1626
- if (PRODUCT_WALLET_DISCOUNT_TYPES.has(type)) return type;
1627
- }
1628
- return null;
1629
- }
1630
- function getProductWalletDiscountAssetId(record) {
1631
- for (const value of [record.discount?.resource_id, record.resource_id, record.id, record.discount_id]) {
1632
- const id = normalizeWalletPassId(value);
1633
- if (id && id !== '0') return id;
1634
- }
1635
- return null;
1636
- }
1637
- function getProductWalletDiscountCodes(record) {
1638
- return normalizeWalletAssetCodes(record.code, record.encoded, record.discount?.code, record.discount?.encoded);
1639
- }
1640
- function getProductWalletDiscountName(asset, locale) {
1641
- for (const record of asset.records) {
1642
- for (const value of [record.discount?.title, record.discount?.name, record.discount?.message, record.product_name, record.format_title, record.title, record.name, record.message]) {
1643
- const name = getLocalizedValue(value, locale).trim();
1644
- if (name) return name;
1645
- }
1646
- }
1647
- return asset.codes[0] || label(locale, 'giftCard');
1648
- }
1649
- function getProductWalletDiscountTime(asset) {
1650
- for (const field of ['updated_at', 'created_at', 'collection_time']) {
1651
- for (const record of asset.records) {
1652
- const value = firstPresentValue(record[field], record.discount?.[field]);
1653
- if (isPresentId(value)) return value;
1654
- }
1655
- }
1656
- return undefined;
1657
- }
1658
- function getProductEntitlementBalance(asset) {
1659
- for (const record of asset.records) {
1660
- const balance = getProductDiscountEntitlementBalance(record);
1661
- if (balance) return balance;
1662
- }
1663
- return null;
1664
- }
1665
- function getProductDiscountEntitlementBalance(record) {
1666
- return getEntitlementBalance(record.metadata?.balance, record.balance, record.discount?.metadata?.balance, record.discount?.balance);
1667
- }
1668
- function getEntitlementBalance(...values) {
1669
- for (const value of values) {
1670
- if (!isNumericAmount(value)) continue;
1671
- const balance = new _decimal.default(value);
1672
- if (balance.isFinite() && balance.isInteger() && balance.gte(0)) {
1673
- return balance;
1674
- }
1675
- }
1676
- return null;
1677
- }
1678
- function formatEntitlementUses(value, locale) {
1679
- const count = value.toFixed(0);
1680
- switch (normalizeLocaleKey(locale)) {
1681
- case 'zh_CN':
1682
- case 'zh_HK':
1683
- return `${count}次`;
1684
- case 'ja':
1685
- return `${count}回`;
1686
- case 'pt':
1687
- return `${count} ${value.eq(1) ? 'utilização' : 'utilizações'}`;
1688
- default:
1689
- return `${count} ${value.eq(1) ? 'use' : 'uses'}`;
1690
- }
1691
- }
1692
- function getProductWalletDiscountPercent(asset) {
1693
- if (asset.type !== 'discount_card' && asset.type !== 'product_discount_card') return null;
1694
- for (const record of asset.records) {
1695
- const discountType = stringify(firstPresentValue(record.discount_card_type, record.metadata?.discount_card_type, record.discount?.discount_card_type, record.discount?.metadata?.discount_card_type)).trim().toLowerCase();
1696
- if (discountType && discountType !== 'percent' && discountType !== 'percentage') continue;
1697
- const candidates = [record.percent, record.discount?.percent];
1698
- if (discountType === 'percent' || discountType === 'percentage') {
1699
- candidates.push(record.par_value, record.discount?.par_value);
1700
- }
1701
- for (const value of candidates) {
1702
- if (!isNumericAmount(value)) continue;
1703
- const percent = new _decimal.default(value);
1704
- if (percent.isFinite() && percent.gt(0) && percent.lte(100)) {
1705
- return percent;
1706
- }
1707
- }
1708
- }
1709
- return null;
1710
- }
1711
- function getProductWalletMoneyBalance(asset) {
1712
- for (const record of asset.records) {
1713
- for (const value of [record.metadata?.balance, record.balance, record.discount?.metadata?.balance, record.discount?.balance]) {
1714
- if (!isNumericAmount(value)) continue;
1715
- return _decimal.default.max(new _decimal.default(value), 0);
1716
- }
1717
- }
1718
- for (const record of asset.records) {
1719
- const parValue = firstPresentValue(record.par_value, record.discount?.par_value);
1720
- const usedParValue = firstPresentValue(record.used_par_value, record.discount?.used_par_value);
1721
- if (!isNumericAmount(parValue) || !isNumericAmount(usedParValue)) continue;
1722
- return _decimal.default.max(new _decimal.default(parValue).minus(new _decimal.default(usedParValue)), 0);
1723
- }
1724
- return null;
1725
- }
1726
- function buildPointCardSnapshotAssets(params) {
1727
- const snapshots = params.order.order_meta_list?.point_cards_snapshot;
1728
- if (!Array.isArray(snapshots)) return [];
1729
- const snapshotTime = firstPresentValue(params.order.updated_at, params.order.created_at);
1730
- return snapshots.flatMap(snapshot => {
1731
- if (!snapshot || typeof snapshot !== 'object' || Array.isArray(snapshot)) return [];
1732
- const pointCard = snapshot;
1733
- if (stringify(pointCard.tag).trim().toLowerCase() !== 'point_card') return [];
1734
- const assetId = normalizeWalletPassId(pointCard.id);
1735
- if (!assetId || !claimWalletAssetIdentity(params.displayedWalletAssetIdentities, {
1736
- id: assetId,
1737
- codes: []
1738
- })) return [];
1739
- const rows = [{
1740
- item: appendMaskedCardCode(stringify(pointCard.product_name).trim() || label(params.locale, 'pointCard'), pointCard.code),
1741
- value: ''
1742
- }];
1743
- const remainingPoints = getPointCardSnapshotRemainingPoints(pointCard);
1744
- if (remainingPoints) {
1745
- rows.push({
1746
- item: label(params.locale, 'remainingPoints'),
1747
- value: trimDecimal(remainingPoints)
1748
- });
1749
- }
1750
- if (isPresentId(snapshotTime)) {
1751
- rows.push({
1752
- item: formatDateTime(snapshotTime, params.locale),
1753
- value: ''
1754
- });
1755
- }
1756
- return [rows];
1757
- });
1758
- }
1759
- function collectVirtualCurrencySnapshots(order) {
1760
- const items = [];
1761
- const byId = new Map();
1762
- const snapshots = order.order_meta_list?.virtual_currency_snapshot;
1763
- if (!Array.isArray(snapshots)) return {
1764
- items,
1765
- byId
1766
- };
1767
- snapshots.forEach(snapshot => {
1768
- if (!snapshot || typeof snapshot !== 'object' || Array.isArray(snapshot)) return;
1769
- const virtualCurrency = snapshot;
1770
- if (stringify(virtualCurrency.tag).trim().toLowerCase() !== 'virtual_currency') return;
1771
- const id = normalizeWalletPassId(virtualCurrency.id);
1772
- if (!id || byId.has(id)) return;
1773
- byId.set(id, virtualCurrency);
1774
- items.push(virtualCurrency);
1775
15
  });
1776
- return {
1777
- items,
1778
- byId
1779
- };
1780
- }
1781
- function findVirtualCurrencySnapshotForPayment(payment, snapshotsById) {
1782
- const fundRecord = getFundRecord(payment);
1783
- for (const value of [fundRecord?.voucher_id, payment.voucher_id]) {
1784
- const id = normalizeWalletPassId(value);
1785
- if (!id) continue;
1786
- const snapshot = snapshotsById.get(id);
1787
- if (snapshot) return snapshot;
1788
- }
1789
- return undefined;
1790
- }
1791
- function buildVirtualCurrencySnapshotAssets(params) {
1792
- const snapshotTime = firstPresentValue(params.order.updated_at, params.order.created_at);
1793
- return params.snapshots.flatMap(virtualCurrency => {
1794
- const assetId = normalizeWalletPassId(virtualCurrency.id);
1795
- if (!assetId || !claimWalletAssetIdentity(params.displayedWalletAssetIdentities, {
1796
- id: assetId,
1797
- codes: []
1798
- })) return [];
1799
- const rows = [{
1800
- item: stringify(virtualCurrency.product_name).trim(),
1801
- value: ''
1802
- }];
1803
- const balance = getVirtualCurrencySnapshotBalance(virtualCurrency);
1804
- if (balance) {
1805
- rows.push({
1806
- item: label(params.locale, 'balance'),
1807
- value: formatVirtualCurrencyBalance(balance, getVirtualCurrencySnapshotUnit(virtualCurrency, params.locale))
1808
- });
1809
- }
1810
- if (isPresentId(snapshotTime)) {
1811
- rows.push({
1812
- item: formatDateTime(snapshotTime, params.locale),
1813
- value: ''
1814
- });
1815
- }
1816
- return [rows];
1817
- });
1818
- }
1819
- function getVirtualCurrencySnapshotBalance(snapshot) {
1820
- if (!isNumericAmount(snapshot.par_value) || !isNumericAmount(snapshot.used_par_value)) {
1821
- return null;
1822
- }
1823
- return _decimal.default.max(new _decimal.default(snapshot.par_value).minus(new _decimal.default(snapshot.used_par_value)), 0);
1824
- }
1825
- function getVirtualCurrencySnapshotUnit(snapshot, locale) {
1826
- return getNonEmptyLocalizedValue(snapshot.name, locale);
1827
- }
1828
- function getNonEmptyLocalizedValue(value, locale) {
1829
- if (value === undefined || value === null) return '';
1830
- if (typeof value !== 'object' || Array.isArray(value)) return stringify(value).trim();
1831
- const dashedLocale = locale.replace('_', '-');
1832
- const underscoredLocale = locale.replace('-', '_');
1833
- for (const candidate of [value[locale], value[dashedLocale], value[underscoredLocale], value.original, value.auto, value.default, value.en]) {
1834
- const text = stringify(candidate).trim();
1835
- if (text) return text;
1836
- }
1837
- return '';
1838
- }
1839
- function formatVirtualCurrencyBalance(value, unit) {
1840
- const amount = trimDecimal(value);
1841
- const normalizedUnit = unit.trim();
1842
- return normalizedUnit ? `${amount} ${normalizedUnit}` : amount;
1843
- }
1844
- function normalizeWalletPassId(value) {
1845
- if (typeof value === 'number') return Number.isFinite(value) ? String(value) : null;
1846
- if (typeof value !== 'string') return null;
1847
- return value.trim() || null;
1848
- }
1849
- function firstNormalizedWalletAssetId(...values) {
1850
- for (const value of values) {
1851
- const id = normalizeWalletPassId(value);
1852
- if (id) return id;
1853
- }
1854
- return null;
1855
- }
1856
- function normalizeWalletAssetCodes(...values) {
1857
- return Array.from(new Set(values.map(value => normalizeWalletPassId(value)).filter(value => !!value)));
1858
- }
1859
- function registerWalletAssetIdentity(identities, candidate) {
1860
- if (!candidate.id && !candidate.codes.length) return;
1861
- const existing = identities.find(identity => isSameWalletAssetIdentity(identity, candidate));
1862
- if (existing) {
1863
- mergeWalletAssetIdentity(existing, candidate);
1864
- return;
1865
- }
1866
- identities.push({
1867
- id: candidate.id,
1868
- codes: [...candidate.codes]
1869
- });
1870
- }
1871
- function claimWalletAssetIdentity(identities, candidate) {
1872
- if (!candidate.id && !candidate.codes.length) return true;
1873
- const existing = identities.find(identity => isSameWalletAssetIdentity(identity, candidate));
1874
- if (existing) {
1875
- mergeWalletAssetIdentity(existing, candidate);
1876
- return false;
1877
- }
1878
- identities.push({
1879
- id: candidate.id,
1880
- codes: [...candidate.codes]
1881
- });
1882
- return true;
1883
- }
1884
- function isSameWalletAssetIdentity(left, right) {
1885
- if (left.id && right.id) return left.id === right.id;
1886
- return left.codes.some(code => right.codes.includes(code));
1887
- }
1888
- function mergeWalletAssetIdentity(target, source) {
1889
- if (!target.id && source.id) target.id = source.id;
1890
- target.codes = Array.from(new Set([...target.codes, ...source.codes]));
1891
- }
1892
- function getPointCardSnapshotRemainingPoints(snapshot) {
1893
- if (!isNumericAmount(snapshot.par_value) || !isNumericAmount(snapshot.used_par_value)) {
1894
- return null;
1895
- }
1896
- return _decimal.default.max(new _decimal.default(snapshot.par_value).minus(new _decimal.default(snapshot.used_par_value)), 0);
1897
- }
1898
- function buildVoucherAssets(params) {
1899
- const fallbackAssetTime = firstPresentValue(params.order.updated_at, params.order.created_at);
1900
- return normalizeArray(params.order.vouchers).flatMap(voucher => {
1901
- if (!voucher || typeof voucher !== 'object' || Array.isArray(voucher)) return [];
1902
- if (!claimWalletAssetIdentity(params.displayedWalletAssetIdentities, getVoucherAssetIdentity(voucher))) return [];
1903
- const name = getVoucherAssetName(voucher, params.locale);
1904
- const assetTime = firstPresentValue(voucher.updated_at, voucher.created_at, voucher.collection_time, fallbackAssetTime);
1905
- const timeRow = isPresentId(assetTime) ? {
1906
- item: formatDateTime(assetTime, params.locale),
1907
- value: ''
1908
- } : null;
1909
- if (isEntitlementVoucher(voucher)) {
1910
- const remaining = getEntitlementBalance(voucher.metadata?.balance, voucher.balance);
1911
- return [[{
1912
- item: name,
1913
- value: ''
1914
- }, ...(remaining ? [{
1915
- item: label(params.locale, 'entitlementRemaining'),
1916
- value: formatEntitlementUses(remaining, params.locale)
1917
- }] : []), ...(timeRow ? [timeRow] : [])]];
1918
- }
1919
- if (isTicketVoucher(voucher)) {
1920
- const validity = getVoucherValidity(voucher, params.locale);
1921
- return [[{
1922
- item: name,
1923
- value: ''
1924
- }, ...(validity ? [{
1925
- item: label(params.locale, 'validity'),
1926
- value: validity
1927
- }] : [])]];
1928
- }
1929
- const percent = getVoucherDiscountPercent(voucher);
1930
- if (percent) {
1931
- return [[{
1932
- item: appendPercentToVoucherName(name, percent),
1933
- value: formatVoucherDiscount(percent, params.locale)
1934
- }, ...(timeRow ? [timeRow] : [])]];
1935
- }
1936
- const balance = getVoucherBalance(voucher);
1937
- return [[{
1938
- item: name,
1939
- value: ''
1940
- }, {
1941
- item: label(params.locale, 'balance'),
1942
- value: formatMoney(balance, params.currencySymbol)
1943
- }, ...(timeRow ? [timeRow] : [])]];
1944
- });
1945
- }
1946
- const TICKET_VOUCHER_TAGS = new Set(['ticket', 'product_package_ticket', 'appointment_ticket', 'session_ticket']);
1947
- function isEntitlementVoucher(voucher) {
1948
- return [voucher.tag, voucher.type].some(value => stringify(value).trim().toLowerCase() === 'entitlement_pass');
1949
- }
1950
- function isTicketVoucher(voucher) {
1951
- return TICKET_VOUCHER_TAGS.has(stringify(voucher.tag).trim().toLowerCase());
1952
- }
1953
- function getVoucherValidity(voucher, locale) {
1954
- const availableTime = firstPresentValue(voucher.available_time, voucher.avaliable_time, voucher.metadata?.available_time, voucher.metadata?.avaliable_time);
1955
- const availableTimeText = formatVoucherAvailableTime(availableTime, locale);
1956
- if (availableTimeText) return availableTimeText;
1957
- const startDate = firstPresentValue(voucher.valid_from, voucher.start_at, voucher.start_date_time, voucher.start_date, voucher.metadata?.valid_from, voucher.metadata?.start_at, voucher.metadata?.start_date);
1958
- const endDate = firstPresentValue(voucher.valid_to, voucher.end_at, voucher.end_date_time, voucher.expire_date, voucher.end_date, voucher.expire_time, voucher.metadata?.valid_to, voucher.metadata?.end_at, voucher.metadata?.expire_date, voucher.metadata?.end_date, voucher.metadata?.expire_time);
1959
- return formatVoucherValidityRange(startDate, endDate, locale, {
1960
- startTime: voucher.start_time ?? voucher.metadata?.start_time,
1961
- endTime: voucher.end_time ?? voucher.metadata?.end_time
1962
- });
1963
- }
1964
- function formatVoucherAvailableTime(value, locale) {
1965
- if (!isPresentId(value)) return '';
1966
- if (Array.isArray(value)) {
1967
- return value.map(item => formatVoucherAvailableTime(item, locale)).filter(Boolean).join(', ');
1968
- }
1969
- if (typeof value !== 'object') return stringify(value).trim();
1970
- const start = firstPresentValue(value.start, value.start_at, value.start_date_time, value.start_date);
1971
- const end = firstPresentValue(value.end, value.end_at, value.end_date_time, value.expire_time, value.expire_date, value.end_date);
1972
- if (isPresentId(start) || isPresentId(end)) {
1973
- return formatVoucherValidityRange(start, end, locale, {
1974
- startTime: value.start_time,
1975
- endTime: value.end_time
1976
- });
1977
- }
1978
- return getLocalizedValue(value, locale).trim();
1979
- }
1980
- function formatVoucherValidityRange(startValue, endValue, locale, times = {}) {
1981
- const start = combineVoucherDateAndTime(startValue, times.startTime);
1982
- const end = combineVoucherDateAndTime(endValue, times.endTime);
1983
- const startPart = formatVoucherValidityDate(start, locale);
1984
- const endPart = formatVoucherValidityDate(end, locale);
1985
- if (startPart.text && endPart.text) {
1986
- const parsedStart = (0, _dayjs.default)(start);
1987
- const parsedEnd = (0, _dayjs.default)(end);
1988
- const sameDay = parsedStart.isValid() && parsedEnd.isValid() && parsedStart.isSame(parsedEnd, 'day');
1989
- if (sameDay && startPart.time && endPart.time) {
1990
- return `${startPart.time} - ${endPart.time} ${startPart.date}`;
1991
- }
1992
- return `${startPart.text} - ${endPart.text}`;
1993
- }
1994
- return startPart.text || endPart.text;
1995
- }
1996
- function combineVoucherDateAndTime(dateValue, timeValue) {
1997
- if (!isPresentId(dateValue) || !isPresentId(timeValue)) return dateValue;
1998
- const dateText = stringify(dateValue).trim();
1999
- if (/\d{1,2}:\d{2}/.test(dateText)) return dateValue;
2000
- return `${dateText} ${stringify(timeValue).trim()}`;
2001
- }
2002
- function formatVoucherValidityDate(value, locale) {
2003
- if (!isPresentId(value)) return {
2004
- date: '',
2005
- time: '',
2006
- text: ''
2007
- };
2008
- const source = stringify(value).trim();
2009
- const parsed = (0, _dayjs.default)(source);
2010
- if (!parsed.isValid()) return {
2011
- date: '',
2012
- time: '',
2013
- text: source
2014
- };
2015
- const hasTime = /\d{1,2}:\d{2}/.test(source);
2016
- const date = parsed.format(locale === 'en' ? 'DD/MM/YYYY' : 'YYYY/MM/DD');
2017
- const time = hasTime ? parsed.format(/\d{1,2}:\d{2}:\d{2}/.test(source) ? 'HH:mm:ss' : 'HH:mm') : '';
2018
- return {
2019
- date,
2020
- time,
2021
- text: time ? `${time} ${date}` : date
2022
- };
2023
- }
2024
- function getVoucherAssetIdentity(voucher) {
2025
- return {
2026
- id: firstNormalizedWalletAssetId(voucher.voucher_id, voucher.id),
2027
- codes: normalizeWalletAssetCodes(voucher.code, voucher.encoded)
2028
- };
2029
- }
2030
- function getVoucherAssetName(voucher, locale) {
2031
- return getLocalizedValue(voucher.product_name || voucher.name || voucher.title, locale) || stringify(voucher.encoded || voucher.code || label(locale, 'giftCard'));
2032
- }
2033
- function getVoucherDiscountPercent(voucher) {
2034
- const discountType = stringify(voucher.metadata?.discount_card_type || voucher.discount_card_type).toLowerCase();
2035
- if (discountType !== 'percent' && discountType !== 'percentage') return null;
2036
- const percent = new _decimal.default(toMoneyNumber(voucher.par_value));
2037
- if (!percent.isFinite() || percent.lte(0) || percent.gt(100)) return null;
2038
- return percent;
2039
- }
2040
- function appendPercentToVoucherName(name, percent) {
2041
- if (/\d+(?:\.\d+)?\s*(?:%|折)|\boff\b/i.test(name)) return name;
2042
- const separator = /^[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}]/u.test(name) ? '' : ' ';
2043
- return `${trimDecimal(percent)}%${separator}${name}`;
2044
- }
2045
- function formatVoucherDiscount(percent, locale) {
2046
- const normalizedLocale = normalizeLocaleKey(locale);
2047
- if (normalizedLocale === 'zh_CN' || normalizedLocale === 'zh_HK') {
2048
- return `${trimDecimal(percent)}% OFF`;
2049
- }
2050
- if (normalizedLocale === 'pt') return `Desconto ${trimDecimal(percent)}%`;
2051
- return normalizedLocale === 'en' ? `Discount ${trimDecimal(percent)}% OFF` : `${trimDecimal(percent)}% OFF`;
2052
- }
2053
- function getVoucherBalance(voucher) {
2054
- for (const value of [voucher.metadata?.balance, voucher.balance]) {
2055
- if (!isNumericAmount(value)) continue;
2056
- return _decimal.default.max(new _decimal.default(value), 0);
2057
- }
2058
- const parValue = new _decimal.default(toMoneyNumber(voucher.par_value));
2059
- const usedParValue = new _decimal.default(toMoneyNumber(voucher.used_par_value));
2060
- return _decimal.default.max(parValue.minus(usedParValue), 0);
2061
- }
2062
- function buildPaymentItems(params) {
2063
- const {
2064
- payment,
2065
- locale,
2066
- currencySymbol,
2067
- includeFundRecordBalance,
2068
- includeFundRecordUsage,
2069
- includePaymentAmount,
2070
- virtualCurrencyUnit
2071
- } = params;
2072
- const serviceFee = getPaymentServiceFee(payment);
2073
- const items = [{
2074
- item: paymentMethodName(payment, locale),
2075
- value: includePaymentAmount ? formatMoney(resolveReceiptPaymentAmount(payment, serviceFee), currencySymbol) : ''
2076
- }];
2077
- items.push(...buildCashChangePaymentItems({
2078
- payment,
2079
- locale,
2080
- currencySymbol
2081
- }));
2082
- if (serviceFee.gt(0)) {
2083
- items.push({
2084
- item: label(locale, 'surchargeIncluded'),
2085
- value: formatMoney(serviceFee, currencySymbol)
2086
- });
2087
- }
2088
- const fundRecord = getFundRecord(payment);
2089
- if (fundRecord) {
2090
- items.push(...buildFundRecordPaymentItems({
2091
- payment,
2092
- fundRecord,
2093
- locale,
2094
- currencySymbol,
2095
- includeBalance: includeFundRecordBalance,
2096
- includeUsage: includeFundRecordUsage,
2097
- virtualCurrencyUnit
2098
- }));
2099
- } else {
2100
- const remainingAmount = payment.remaining_amount ?? payment.balance ?? payment.metadata?.remaining_amount;
2101
- if (remainingAmount !== undefined && remainingAmount !== null) {
2102
- items.push({
2103
- item: label(locale, 'remainingAmount'),
2104
- value: formatMoney(remainingAmount, currencySymbol)
2105
- });
2106
- }
2107
- }
2108
- items.push({
2109
- item: formatDateTime(payment.paid_at || payment.created_at || payment.updated_at || new Date(), locale),
2110
- value: ''
2111
- });
2112
- return items;
2113
- }
2114
- function getPaymentServiceFee(payment) {
2115
- return new _decimal.default(toMoneyNumber(payment.service_fee));
2116
- }
2117
- function resolveReceiptPaymentAmount(payment, serviceFee) {
2118
- const amount = new _decimal.default(toMoneyNumber(payment.amount ?? payment.origin_amount));
2119
- const hasAmount = payment.amount !== undefined && payment.amount !== null && payment.amount !== '';
2120
- const hasOriginAmount = payment.origin_amount !== undefined && payment.origin_amount !== null && payment.origin_amount !== '';
2121
- if (!hasAmount || !hasOriginAmount || serviceFee.lte(0)) return amount;
2122
- const originAmount = new _decimal.default(toMoneyNumber(payment.origin_amount));
2123
- return amount.eq(originAmount) ? amount.plus(serviceFee) : amount;
2124
- }
2125
- function buildCashChangePaymentItems(params) {
2126
- const {
2127
- payment,
2128
- locale,
2129
- currencySymbol
2130
- } = params;
2131
- if (!shouldDisplayCashChange(payment)) return [];
2132
- return [{
2133
- item: label(locale, 'cashReceived'),
2134
- value: formatMoney(payment.metadata.actual_paid_amount, currencySymbol)
2135
- }, {
2136
- item: label(locale, 'changeDue'),
2137
- value: formatMoney(payment.metadata.change_given_amount, currencySymbol)
2138
- }];
2139
- }
2140
- function shouldDisplayCashChange(payment) {
2141
- if (stringify(payment.status).toLowerCase() !== 'paid') return false;
2142
- const paymentMethod = stringify(payment.code ?? payment.payment_method).toUpperCase();
2143
- if (paymentMethod !== 'CASHMANUAL') return false;
2144
- const actualPaidAmount = payment.metadata?.actual_paid_amount;
2145
- const changeGivenAmount = payment.metadata?.change_given_amount;
2146
- if (!isNumericAmount(actualPaidAmount) || !isNumericAmount(changeGivenAmount)) return false;
2147
- return new _decimal.default(changeGivenAmount).toDecimalPlaces(2).gt(0);
2148
- }
2149
- function isNumericAmount(value) {
2150
- if (typeof value === 'number') return Number.isFinite(value);
2151
- if (typeof value !== 'string') return false;
2152
- const normalized = value.trim();
2153
- return normalized !== '' && /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i.test(normalized);
2154
- }
2155
- function buildFundRecordPaymentItems(params) {
2156
- const {
2157
- payment,
2158
- fundRecord,
2159
- locale,
2160
- currencySymbol,
2161
- includeBalance,
2162
- includeUsage,
2163
- virtualCurrencyUnit
2164
- } = params;
2165
- const items = [];
2166
- const usingBeforeValue = fundRecord.using_before_value;
2167
- const usingAfterValue = fundRecord.using_after_value;
2168
- if (isPointCardPayment(payment) && usingBeforeValue !== undefined && usingAfterValue !== undefined) {
2169
- const usedPoints = new _decimal.default(toMoneyNumber(usingBeforeValue)).minus(toMoneyNumber(usingAfterValue));
2170
- if (includeUsage) {
2171
- items.push({
2172
- item: label(locale, 'redeemPoints'),
2173
- value: trimDecimal(usedPoints)
2174
- });
2175
- }
2176
- if (includeBalance) {
2177
- items.push({
2178
- item: label(locale, 'remainingPoints'),
2179
- value: trimDecimal(new _decimal.default(toMoneyNumber(usingAfterValue)))
2180
- });
2181
- }
2182
- return items;
2183
- }
2184
- if (includeBalance && usingAfterValue !== undefined && usingAfterValue !== null) {
2185
- if (virtualCurrencyUnit !== undefined) {
2186
- if (!isNumericAmount(usingAfterValue)) return items;
2187
- items.push({
2188
- item: label(locale, 'balance'),
2189
- value: formatVirtualCurrencyBalance(new _decimal.default(usingAfterValue), virtualCurrencyUnit)
2190
- });
2191
- return items;
2192
- }
2193
- items.push({
2194
- item: label(locale, 'balance'),
2195
- value: formatMoney(usingAfterValue, currencySymbol)
2196
- });
2197
- }
2198
- return items;
2199
- }
2200
- function getFundRecord(payment) {
2201
- const fundRecord = payment.fund_record;
2202
- if (!fundRecord || typeof fundRecord !== 'object' || Array.isArray(fundRecord)) return null;
2203
- if (!fundRecord.voucher_id && !fundRecord.code) return null;
2204
- return fundRecord;
2205
- }
2206
- function isPointCardPayment(payment) {
2207
- const code = stringify(payment.code || payment.payment_method).toUpperCase();
2208
- const type = stringify(payment.type || payment.fund_record?.tag).toLowerCase();
2209
- return code === 'POINTCARD' || type === 'point_card';
2210
- }
2211
- function maskMachineCode(code) {
2212
- const text = stringify(code).trim();
2213
- if (!text) return '';
2214
- if (text.length <= 6) return '*'.repeat(text.length);
2215
- return `${text.slice(0, 2)}****${text.slice(-4)}`;
2216
- }
2217
- function appendMaskedCardCode(name, code) {
2218
- const maskedCode = maskMachineCode(code);
2219
- return maskedCode ? `${name}(${maskedCode})` : name;
2220
- }
2221
- function trimDecimal(value) {
2222
- return value.toDecimalPlaces(2).toFixed(2).replace(/\.?0+$/, '');
2223
- }
2224
- function getFundRecordPaymentName(payment, locale) {
2225
- const fundRecord = getFundRecord(payment);
2226
- if (!fundRecord) return '';
2227
- const name = getLocalizedValue(fundRecord.wallet_pass_name, locale);
2228
- if (!name && !maskMachineCode(fundRecord.code)) return '';
2229
- const fallbackName = getLocalizedValue(payment.name || payment.title || payment.payment_method_name || payment.method_name || payment.code, locale);
2230
- return appendMaskedCardCode(name || fallbackName, fundRecord.code);
2231
- }
2232
- function appendMachineCodeToName(name, payment) {
2233
- const fundRecord = getFundRecord(payment);
2234
- return appendMaskedCardCode(name, fundRecord?.code);
2235
- }
2236
- function paymentMethodName(payment, locale) {
2237
- const fundRecordName = getFundRecordPaymentName(payment, locale);
2238
- if (fundRecordName) return fundRecordName;
2239
- const raw = payment.name || payment.title || payment.payment_method_name || payment.method_name;
2240
- if (raw) return appendMachineCodeToName(getLocalizedValue(raw, locale), payment);
2241
- const code = stringify(payment.code || payment.type || payment.payment_method || payment.method).toLowerCase();
2242
- if (code.includes('cash')) return label(locale, 'cash');
2243
- if (code.includes('eftpos') || code.includes('card')) return label(locale, 'eftpos');
2244
- if (code.includes('wallet')) return label(locale, 'wallet');
2245
- if (code.includes('gift')) return label(locale, 'giftCard');
2246
- return stringify(payment.code || payment.type || payment.payment_method || payment.method);
2247
- }
2248
- function getSummary(order) {
2249
- return order.summary && typeof order.summary === 'object' ? order.summary : order;
2250
- }
2251
- function getCurrencySymbol(order, shopInfo) {
2252
- return stringify(order.currency_symbol || shopInfo?.currency_symbol || order.shop_symbol || '$') || '$';
2253
- }
2254
- function resolveLocales(shopInfo, locales) {
2255
- const preferred = [...(locales || []), shopInfo.primary_locale, ...DEFAULT_LOCALES].filter(Boolean);
2256
- return Array.from(new Set(preferred.map(normalizeLocaleKey)));
2257
- }
2258
- function normalizeLocaleKey(locale) {
2259
- return locale.replace('-', '_');
2260
- }
2261
- function label(locale, key) {
2262
- return LABELS[locale]?.[key] || LABELS.en[key] || key;
2263
- }
2264
- function getLocalizedValue(value, locale) {
2265
- if (value === undefined || value === null) return '';
2266
- if (typeof value !== 'object') return stringify(value);
2267
- const dashedLocale = locale.replace('_', '-');
2268
- const underscoredLocale = locale.replace('-', '_');
2269
- return stringify(value[locale] ?? value[dashedLocale] ?? value[underscoredLocale] ?? value.original ?? value.auto ?? value.default ?? value.en ?? '');
2270
- }
2271
- function resolveServiceTypeLabelKey(serviceType) {
2272
- return SERVICE_TYPE_LABEL_KEYS[serviceType] || null;
2273
- }
2274
- function normalizeBooleanFlag(value) {
2275
- if (value === true || value === 1 || stringify(value).trim() === '1') return 1;
2276
- return 0;
2277
- }
2278
- function buildShopAddress(shopInfo) {
2279
- return [shopInfo.address_street, shopInfo.address_detail, shopInfo.city, shopInfo.province, shopInfo.country, shopInfo.zip].filter(Boolean).join(', ');
2280
- }
2281
- function formatDateTime(value, locale) {
2282
- const date = (0, _dayjs.default)(value);
2283
- if (!date.isValid()) return stringify(value);
2284
- if (locale === 'en') return date.format('HH:mm:ss DD/MM/YYYY');
2285
- return date.format('YYYY/MM/DD HH:mm:ss');
2286
- }
2287
- function formatMoney(value, currencySymbol) {
2288
- if (value instanceof _decimal.default) return `${currencySymbol}${value.toFixed(2)}`;
2289
- if (typeof value === 'string' && value.trim().startsWith(currencySymbol)) return value;
2290
- const amount = new _decimal.default(toMoneyNumber(value));
2291
- return `${currencySymbol}${amount.toFixed(2)}`;
2292
- }
2293
- function formatCalculatedMoney(value, currencySymbol) {
2294
- const amount = new _decimal.default(toMoneyNumber(value));
2295
- const sign = amount.isNegative() ? '-' : '';
2296
- return `${sign}${currencySymbol}${amount.abs().toFixed(2)}`;
2297
- }
2298
- function formatNegativeMoney(value, currencySymbol) {
2299
- const amount = new _decimal.default(toMoneyNumber(value)).abs();
2300
- return `-${currencySymbol}${amount.toFixed(2)}`;
2301
- }
2302
- function sumAppliedPayments(payments) {
2303
- return normalizeArray(payments).filter(payment => isPrintablePayment(payment)).reduce((sum, payment) => {
2304
- if (isPresentId(payment.origin_amount)) {
2305
- return sum.plus(toMoneyNumber(payment.origin_amount));
2306
- }
2307
- const amount = new _decimal.default(toMoneyNumber(payment.amount));
2308
- const serviceFee = _decimal.default.max(toMoneyNumber(payment.service_fee), 0);
2309
- return sum.plus(_decimal.default.max(amount.minus(serviceFee), 0));
2310
- }, new _decimal.default(0));
2311
- }
2312
- function sumCustomerPaidPayments(payments) {
2313
- return normalizeArray(payments).filter(payment => isPrintablePayment(payment)).reduce((sum, payment) => {
2314
- return sum.plus(toMoneyNumber(payment.amount ?? payment.origin_amount));
2315
- }, new _decimal.default(0));
2316
- }
2317
- function isPrintablePayment(payment) {
2318
- const status = stringify(payment.status).toLowerCase();
2319
- if (!status) return true;
2320
- return ['paid', 'success', 'succeeded', 'completed', 'partially_refunded', 'refunded'].includes(status);
2321
- }
2322
- function normalizeArray(value) {
2323
- return Array.isArray(value) ? value : [];
2324
- }
2325
- function isNonZero(value) {
2326
- return !new _decimal.default(toMoneyNumber(value)).isZero();
2327
- }
2328
- function toMoneyNumber(value) {
2329
- if (value instanceof _decimal.default) return value.toString();
2330
- if (typeof value === 'number') return Number.isFinite(value) ? value : 0;
2331
- if (typeof value !== 'string') return 0;
2332
- const normalized = value.replace(/[^\d.-]/g, '');
2333
- return normalized || 0;
2334
- }
2335
- function toNumber(value, fallback) {
2336
- const numberValue = Number(value);
2337
- return Number.isFinite(numberValue) ? numberValue : fallback;
2338
- }
2339
- function stringify(value) {
2340
- if (value === undefined || value === null) return '';
2341
- return String(value);
2342
- }
16
+ });