@pisell/pisellos 2.2.314 → 2.2.316

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