@pisell/pisellos 2.2.313 → 2.2.315

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 (215) 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/modules/BookingContext/utils/productExtend.d.ts +7 -0
  10. package/dist/modules/BookingContext/utils/productExtend.js +62 -18
  11. package/dist/modules/BookingContext/utils/resources.js +1 -1
  12. package/dist/modules/BusinessAvailability/index.d.ts +30 -0
  13. package/dist/modules/BusinessAvailability/index.js +204 -0
  14. package/dist/modules/Cart/index.js +4 -3
  15. package/dist/modules/Cart/types.d.ts +2 -0
  16. package/dist/modules/Cart/utils/cartProduct.d.ts +3 -1
  17. package/dist/modules/Cart/utils/cartProduct.js +59 -32
  18. package/dist/modules/FulfillmentTiming/index.d.ts +11 -0
  19. package/dist/modules/FulfillmentTiming/index.js +407 -0
  20. package/dist/modules/FulfillmentTiming/types.d.ts +67 -0
  21. package/dist/modules/FulfillmentTiming/types.js +1 -0
  22. package/dist/modules/OpenData/types.d.ts +14 -0
  23. package/dist/modules/Order/index.d.ts +14 -2
  24. package/dist/modules/Order/index.js +856 -486
  25. package/dist/modules/Order/localEftposState.d.ts +13 -0
  26. package/dist/modules/Order/localEftposState.js +39 -0
  27. package/dist/modules/Order/pickupTiming.d.ts +46 -0
  28. package/dist/modules/Order/pickupTiming.js +53 -0
  29. package/dist/modules/Order/types.d.ts +30 -2
  30. package/dist/modules/Order/utils.js +25 -7
  31. package/dist/modules/Payment/index.js +9 -10
  32. package/dist/modules/Product/types.d.ts +11 -0
  33. package/dist/modules/ProductList/index.js +1 -1
  34. package/dist/modules/ResourcePlanner/planner.d.ts +2 -0
  35. package/dist/modules/ResourcePlanner/planner.js +332 -23
  36. package/dist/modules/ResourcePlanner/types.d.ts +29 -0
  37. package/dist/modules/Schedule/index.d.ts +8 -0
  38. package/dist/modules/Schedule/index.js +141 -39
  39. package/dist/modules/index.d.ts +1 -0
  40. package/dist/modules/index.js +1 -0
  41. package/dist/plugins/request.d.ts +7 -0
  42. package/dist/server/index.d.ts +1 -0
  43. package/dist/server/index.js +1536 -1309
  44. package/dist/server/modules/form/index.d.ts +19 -0
  45. package/dist/server/modules/form/index.js +205 -0
  46. package/dist/server/modules/form/types.d.ts +13 -0
  47. package/dist/server/modules/form/types.js +3 -0
  48. package/dist/server/modules/index.d.ts +3 -0
  49. package/dist/server/modules/index.js +4 -0
  50. package/dist/server/modules/order/index.d.ts +15 -0
  51. package/dist/server/modules/order/index.js +920 -688
  52. package/dist/server/modules/order/types.d.ts +1 -0
  53. package/dist/server/test.json +1 -1
  54. package/dist/server/utils/small-ticket.d.ts +1 -85
  55. package/dist/server/utils/small-ticket.js +2 -2542
  56. package/dist/shared/payment/loadPaymentMethods.d.ts +9 -0
  57. package/dist/shared/payment/loadPaymentMethods.js +66 -0
  58. package/dist/shared/receipt/customerInvoiceLanguages.d.ts +8 -0
  59. package/dist/shared/receipt/customerInvoiceLanguages.js +60 -0
  60. package/dist/shared/receipt/loadCustomerInvoiceLayout.d.ts +27 -0
  61. package/dist/shared/receipt/loadCustomerInvoiceLayout.js +184 -0
  62. package/dist/shared/receipt/small-ticket.d.ts +96 -0
  63. package/dist/shared/receipt/small-ticket.js +2751 -0
  64. package/dist/shared/receipt/templateVisibility.d.ts +23 -0
  65. package/dist/shared/receipt/templateVisibility.js +64 -0
  66. package/dist/shared/sales/loadSalesOrderSnapshot.d.ts +9 -0
  67. package/dist/shared/sales/loadSalesOrderSnapshot.js +21 -0
  68. package/dist/solution/BaseSales/fulfillmentItemRules.d.ts +27 -0
  69. package/dist/solution/BaseSales/fulfillmentItemRules.js +750 -0
  70. package/dist/solution/BaseSales/index.d.ts +39 -16
  71. package/dist/solution/BaseSales/index.js +1754 -1154
  72. package/dist/solution/BaseSales/pickupTiming.d.ts +47 -0
  73. package/dist/solution/BaseSales/pickupTiming.js +616 -0
  74. package/dist/solution/BaseSales/types.d.ts +11 -0
  75. package/dist/solution/BaseSales/utils/parseSalesResponse.js +3 -0
  76. package/dist/solution/BookingByStep/index.d.ts +4 -2
  77. package/dist/solution/BookingByStep/index.js +8 -3
  78. package/dist/solution/BookingTicket/index.d.ts +8 -2
  79. package/dist/solution/BookingTicket/index.js +513 -436
  80. package/dist/solution/BookingTicket/utils/addProductDecision.d.ts +0 -1
  81. package/dist/solution/BookingTicket/utils/addProductDecision.js +1 -1
  82. package/dist/solution/BookingTicket/utils/cartView.js +3 -1
  83. package/dist/solution/OrderInvoicePreview/index.d.ts +66 -0
  84. package/dist/solution/OrderInvoicePreview/index.js +340 -0
  85. package/dist/solution/RegisterAndLogin/config.js +54 -11
  86. package/dist/solution/RegisterAndLogin/index.d.ts +2 -0
  87. package/dist/solution/RegisterAndLogin/index.js +1 -1
  88. package/dist/solution/RegisterAndLogin/utils.d.ts +12 -4
  89. package/dist/solution/RegisterAndLogin/utils.js +36 -16
  90. package/dist/solution/Sales/index.js +1 -1
  91. package/dist/solution/UnifiedBookingSales/businessAvailability.d.ts +27 -0
  92. package/dist/solution/UnifiedBookingSales/businessAvailability.js +292 -0
  93. package/dist/solution/UnifiedBookingSales/index.d.ts +36 -4
  94. package/dist/solution/UnifiedBookingSales/index.js +1714 -905
  95. package/dist/solution/UnifiedBookingSales/types.d.ts +16 -2
  96. package/dist/solution/VenueBooking/index.d.ts +15 -2
  97. package/dist/solution/VenueBooking/index.js +1062 -785
  98. package/dist/solution/VenueBooking/types.d.ts +2 -1
  99. package/dist/solution/VenueBooking/types.js +1 -0
  100. package/dist/solution/VenueBooking/utils/dateSummary.d.ts +1 -0
  101. package/dist/solution/VenueBooking/utils/dateSummary.js +7 -4
  102. package/dist/solution/VenueBooking/utils/smartPricing.d.ts +71 -0
  103. package/dist/solution/VenueBooking/utils/smartPricing.js +273 -0
  104. package/dist/solution/VenueBooking/utils/timeSlot.d.ts +1 -0
  105. package/dist/solution/VenueBooking/utils/timeSlot.js +6 -3
  106. package/dist/solution/index.d.ts +1 -0
  107. package/dist/solution/index.js +2 -1
  108. package/lib/index.d.ts +2 -0
  109. package/lib/index.js +21 -1
  110. package/lib/model/strategy/adapter/itemRule/adapter.d.ts +3 -1
  111. package/lib/model/strategy/adapter/itemRule/adapter.js +54 -4
  112. package/lib/model/strategy/adapter/itemRule/evaluator.d.ts +3 -1
  113. package/lib/model/strategy/adapter/itemRule/evaluator.js +9 -0
  114. package/lib/model/strategy/adapter/itemRule/type.d.ts +40 -1
  115. package/lib/model/strategy/adapter/itemRule/type.js +10 -0
  116. package/lib/modules/BookingContext/utils/productExtend.d.ts +7 -0
  117. package/lib/modules/BookingContext/utils/productExtend.js +45 -2
  118. package/lib/modules/BookingContext/utils/resources.js +1 -1
  119. package/lib/modules/BusinessAvailability/index.d.ts +30 -0
  120. package/lib/modules/BusinessAvailability/index.js +146 -0
  121. package/lib/modules/Cart/index.js +4 -2
  122. package/lib/modules/Cart/types.d.ts +2 -0
  123. package/lib/modules/Cart/utils/cartProduct.d.ts +3 -1
  124. package/lib/modules/Cart/utils/cartProduct.js +39 -20
  125. package/lib/modules/FulfillmentTiming/index.d.ts +11 -0
  126. package/lib/modules/FulfillmentTiming/index.js +359 -0
  127. package/lib/modules/FulfillmentTiming/types.d.ts +67 -0
  128. package/lib/modules/FulfillmentTiming/types.js +5 -0
  129. package/lib/modules/OpenData/types.d.ts +14 -0
  130. package/lib/modules/Order/index.d.ts +14 -2
  131. package/lib/modules/Order/index.js +240 -38
  132. package/lib/modules/Order/localEftposState.d.ts +13 -0
  133. package/lib/modules/Order/localEftposState.js +44 -0
  134. package/lib/modules/Order/pickupTiming.d.ts +46 -0
  135. package/lib/modules/Order/pickupTiming.js +61 -0
  136. package/lib/modules/Order/types.d.ts +30 -2
  137. package/lib/modules/Order/utils.js +21 -5
  138. package/lib/modules/Payment/index.js +3 -4
  139. package/lib/modules/Product/types.d.ts +11 -0
  140. package/lib/modules/ProductList/index.js +1 -1
  141. package/lib/modules/ResourcePlanner/planner.d.ts +2 -0
  142. package/lib/modules/ResourcePlanner/planner.js +252 -8
  143. package/lib/modules/ResourcePlanner/types.d.ts +29 -0
  144. package/lib/modules/Schedule/index.d.ts +8 -0
  145. package/lib/modules/Schedule/index.js +68 -12
  146. package/lib/modules/index.d.ts +1 -0
  147. package/lib/modules/index.js +11 -0
  148. package/lib/plugins/request.d.ts +7 -0
  149. package/lib/server/index.d.ts +1 -0
  150. package/lib/server/index.js +158 -37
  151. package/lib/server/modules/form/index.d.ts +19 -0
  152. package/lib/server/modules/form/index.js +73 -0
  153. package/lib/server/modules/form/types.d.ts +13 -0
  154. package/lib/server/modules/form/types.js +9 -0
  155. package/lib/server/modules/index.d.ts +3 -0
  156. package/lib/server/modules/index.js +15 -1
  157. package/lib/server/modules/order/index.d.ts +15 -0
  158. package/lib/server/modules/order/index.js +114 -3
  159. package/lib/server/modules/order/types.d.ts +1 -0
  160. package/lib/server/test.json +1 -1
  161. package/lib/server/utils/small-ticket.d.ts +1 -85
  162. package/lib/server/utils/small-ticket.js +9 -2305
  163. package/lib/shared/payment/loadPaymentMethods.d.ts +9 -0
  164. package/lib/shared/payment/loadPaymentMethods.js +40 -0
  165. package/lib/shared/receipt/customerInvoiceLanguages.d.ts +8 -0
  166. package/lib/shared/receipt/customerInvoiceLanguages.js +58 -0
  167. package/lib/shared/receipt/loadCustomerInvoiceLayout.d.ts +27 -0
  168. package/lib/shared/receipt/loadCustomerInvoiceLayout.js +124 -0
  169. package/lib/shared/receipt/small-ticket.d.ts +96 -0
  170. package/lib/shared/receipt/small-ticket.js +2546 -0
  171. package/lib/shared/receipt/templateVisibility.d.ts +23 -0
  172. package/lib/shared/receipt/templateVisibility.js +63 -0
  173. package/lib/shared/sales/loadSalesOrderSnapshot.d.ts +9 -0
  174. package/lib/shared/sales/loadSalesOrderSnapshot.js +22 -0
  175. package/lib/solution/BaseSales/fulfillmentItemRules.d.ts +27 -0
  176. package/lib/solution/BaseSales/fulfillmentItemRules.js +259 -0
  177. package/lib/solution/BaseSales/index.d.ts +39 -16
  178. package/lib/solution/BaseSales/index.js +333 -31
  179. package/lib/solution/BaseSales/pickupTiming.d.ts +47 -0
  180. package/lib/solution/BaseSales/pickupTiming.js +290 -0
  181. package/lib/solution/BaseSales/types.d.ts +11 -0
  182. package/lib/solution/BaseSales/utils/parseSalesResponse.js +3 -0
  183. package/lib/solution/BookingByStep/index.d.ts +4 -2
  184. package/lib/solution/BookingByStep/index.js +7 -3
  185. package/lib/solution/BookingTicket/index.d.ts +8 -2
  186. package/lib/solution/BookingTicket/index.js +65 -6
  187. package/lib/solution/BookingTicket/utils/addProductDecision.d.ts +0 -1
  188. package/lib/solution/BookingTicket/utils/addProductDecision.js +1 -1
  189. package/lib/solution/BookingTicket/utils/cartView.js +3 -1
  190. package/lib/solution/OrderInvoicePreview/index.d.ts +66 -0
  191. package/lib/solution/OrderInvoicePreview/index.js +246 -0
  192. package/lib/solution/RegisterAndLogin/config.js +57 -11
  193. package/lib/solution/RegisterAndLogin/index.d.ts +2 -0
  194. package/lib/solution/RegisterAndLogin/index.js +9 -1
  195. package/lib/solution/RegisterAndLogin/utils.d.ts +12 -4
  196. package/lib/solution/RegisterAndLogin/utils.js +24 -17
  197. package/lib/solution/Sales/index.js +1 -1
  198. package/lib/solution/UnifiedBookingSales/businessAvailability.d.ts +27 -0
  199. package/lib/solution/UnifiedBookingSales/businessAvailability.js +138 -0
  200. package/lib/solution/UnifiedBookingSales/index.d.ts +36 -4
  201. package/lib/solution/UnifiedBookingSales/index.js +561 -74
  202. package/lib/solution/UnifiedBookingSales/types.d.ts +16 -2
  203. package/lib/solution/VenueBooking/index.d.ts +15 -2
  204. package/lib/solution/VenueBooking/index.js +243 -96
  205. package/lib/solution/VenueBooking/types.d.ts +2 -1
  206. package/lib/solution/VenueBooking/types.js +1 -0
  207. package/lib/solution/VenueBooking/utils/dateSummary.d.ts +1 -0
  208. package/lib/solution/VenueBooking/utils/dateSummary.js +7 -4
  209. package/lib/solution/VenueBooking/utils/smartPricing.d.ts +71 -0
  210. package/lib/solution/VenueBooking/utils/smartPricing.js +224 -0
  211. package/lib/solution/VenueBooking/utils/timeSlot.d.ts +1 -0
  212. package/lib/solution/VenueBooking/utils/timeSlot.js +5 -2
  213. package/lib/solution/index.d.ts +1 -0
  214. package/lib/solution/index.js +11 -0
  215. package/package.json +1 -1
@@ -0,0 +1,23 @@
1
+ /** Template children are direct module members; legacy missing values stay visible. */
2
+ export interface InvoiceDataVisibility {
3
+ handover: {
4
+ show: boolean;
5
+ method: boolean;
6
+ fulfillmentTime: boolean;
7
+ };
8
+ customer: {
9
+ show: boolean;
10
+ name: boolean;
11
+ email: boolean;
12
+ phone: boolean;
13
+ };
14
+ card: {
15
+ show: boolean;
16
+ passTypes: ReadonlySet<string> | null;
17
+ };
18
+ }
19
+ export declare function invoiceSettingIsVisible(value: unknown): boolean;
20
+ export declare function getInvoiceTemplateModules(settings: unknown): Record<string, any>[];
21
+ export declare function resolveInvoiceDataVisibility(settings: unknown): InvoiceDataVisibility;
22
+ /** Whether an old unfiltered snapshot is unsafe for this template. */
23
+ export declare function requiresInvoiceDataFiltering(settings: unknown): boolean;
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getInvoiceTemplateModules = getInvoiceTemplateModules;
7
+ exports.invoiceSettingIsVisible = invoiceSettingIsVisible;
8
+ exports.requiresInvoiceDataFiltering = requiresInvoiceDataFiltering;
9
+ exports.resolveInvoiceDataVisibility = resolveInvoiceDataVisibility;
10
+ /** Template children are direct module members; legacy missing values stay visible. */
11
+
12
+ function invoiceSettingIsVisible(value) {
13
+ return value !== 0 && value !== false;
14
+ }
15
+ function getInvoiceTemplateModules(settings) {
16
+ if (Array.isArray(settings)) return settings.filter(isRecord);
17
+ if (!isRecord(settings)) return [];
18
+ if (isRecord(settings.template_settings)) {
19
+ return getInvoiceTemplateModules(settings.template_settings);
20
+ }
21
+ const modules = settings.modules ?? settings.module;
22
+ return Array.isArray(modules) ? modules.filter(isRecord) : [];
23
+ }
24
+ function isRecord(value) {
25
+ return !!value && typeof value === 'object' && !Array.isArray(value);
26
+ }
27
+ function resolveInvoiceDataVisibility(settings) {
28
+ const modules = getInvoiceTemplateModules(settings);
29
+ // Native templates use the first occurrence of each module.
30
+ const module = key => modules.find(item => item.key === key);
31
+ const handover = module('handover');
32
+ const customer = module('customer');
33
+ const card = module('card');
34
+ const field = (config, key) => invoiceSettingIsVisible(config?.[key]?.is_show);
35
+ const passTypes = Array.isArray(card?.pass_type) && card.pass_type.length > 0 ? new Set(card.pass_type.filter(tag => typeof tag === 'string' && tag.length > 0)) : null;
36
+ return {
37
+ handover: {
38
+ show: invoiceSettingIsVisible(handover?.is_show),
39
+ method: field(handover, 'method'),
40
+ fulfillmentTime: field(handover, 'fulfillment_time')
41
+ },
42
+ customer: {
43
+ show: invoiceSettingIsVisible(customer?.is_show),
44
+ name: field(customer, 'name'),
45
+ email: field(customer, 'email'),
46
+ phone: field(customer, 'phone')
47
+ },
48
+ card: {
49
+ show: invoiceSettingIsVisible(card?.is_show),
50
+ passTypes
51
+ }
52
+ };
53
+ }
54
+
55
+ /** Whether an old unfiltered snapshot is unsafe for this template. */
56
+ function requiresInvoiceDataFiltering(settings) {
57
+ const {
58
+ handover,
59
+ customer,
60
+ card
61
+ } = resolveInvoiceDataVisibility(settings);
62
+ return !handover.show || !handover.method || !handover.fulfillmentTime || !customer.show || !customer.name || !customer.email || !customer.phone || !card.show || card.passTypes !== null;
63
+ }
@@ -0,0 +1,9 @@
1
+ import type { RequestPlugin } from '../../plugins/request';
2
+ export declare const SALES_ORDER_RELATIONS: string[];
3
+ /** Shared transport contract; remote callers use their host's /h5 prefix. */
4
+ export declare function loadSalesOrderSnapshot(request: RequestPlugin, params: {
5
+ lookup: string | number;
6
+ transport: 'remote' | 'os-server';
7
+ forceRemote?: boolean;
8
+ with?: string[];
9
+ }): Promise<any>;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.SALES_ORDER_RELATIONS = void 0;
7
+ exports.loadSalesOrderSnapshot = loadSalesOrderSnapshot;
8
+ const SALES_ORDER_RELATIONS = exports.SALES_ORDER_RELATIONS = ['products', 'bookings', 'payments', 'customer', 'summary', 'contacts_info', 'order_meta_list', 'refunds', 'notes'];
9
+
10
+ /** Shared transport contract; remote callers use their host's /h5 prefix. */
11
+ function loadSalesOrderSnapshot(request, params) {
12
+ const lookup = String(params.lookup ?? '').trim();
13
+ if (!lookup) throw new Error('加载销售详情需要 lookup');
14
+ return request.get(`/order/sales/${encodeURIComponent(lookup)}`, {
15
+ with: params.with ?? SALES_ORDER_RELATIONS,
16
+ ...(params.forceRemote && !lookup.startsWith('LOCAL_') ? {
17
+ forceRemote: true
18
+ } : {})
19
+ }, params.transport === 'os-server' ? {
20
+ osServer: true
21
+ } : undefined);
22
+ }
@@ -0,0 +1,27 @@
1
+ import type { OrderModule } from '../../modules/Order';
2
+ import type { ShopServiceType } from '../../modules/Order/types';
3
+ import type { ProductList } from '../../modules/ProductList';
4
+ import type { OpenDataConfig } from '../../modules/OpenData/types';
5
+ interface FulfillmentItemRulesHost {
6
+ getOrder: () => OrderModule;
7
+ getProducts: () => ProductList | undefined;
8
+ loadOpenData: () => Promise<OpenDataConfig | null>;
9
+ getScope: () => string;
10
+ getOtherParams: () => Record<string, any>;
11
+ fetchStrategyModels: (ids: number[]) => Promise<any>;
12
+ reportError: (error: unknown) => void;
13
+ }
14
+ /** Executes item rules at the sales boundary, independent of the calling template. */
15
+ export declare class FulfillmentItemRulesController {
16
+ private readonly host;
17
+ private sequence;
18
+ private pending;
19
+ private evaluator;
20
+ constructor(host: FulfillmentItemRulesHost);
21
+ invalidate(): void;
22
+ apply(serviceType: ShopServiceType): Promise<void>;
23
+ private loadProducts;
24
+ private getMainProductNumber;
25
+ private loadPrefillSources;
26
+ }
27
+ export {};
@@ -0,0 +1,259 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.FulfillmentItemRulesController = void 0;
7
+ var _dayjs = _interopRequireDefault(require("dayjs"));
8
+ var _itemRule = require("../../model/strategy/adapter/itemRule");
9
+ var _transformBaseProductToOrderProduct = require("./utils/transformBaseProductToOrderProduct");
10
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
11
+ const ownershipField = 'fulfillment_item_rule';
12
+ function positiveIds(value) {
13
+ if (!Array.isArray(value)) return [];
14
+ return [...new Set(value.map(Number).filter(id => Number.isInteger(id) && id > 0))];
15
+ }
16
+ function lineIdentity(line) {
17
+ return {
18
+ product_id: line.product_id,
19
+ product_variant_id: line.product_variant_id,
20
+ unique_identification_number: line.metadata?.unique_identification_number || line.unique_identification_number,
21
+ product_sku: line.product_sku,
22
+ product_bundle: line.product_bundle
23
+ };
24
+ }
25
+ function cartFingerprint(lines) {
26
+ return JSON.stringify(lines.map(line => [lineIdentity(line), line.num, line.metadata?.[ownershipField]]));
27
+ }
28
+ function selectionFingerprint(lines, excludedIds) {
29
+ const quantities = new Map();
30
+ for (const line of lines) {
31
+ if (excludedIds.has(Number(line.product_id)) || line.metadata?._giftInfo) continue;
32
+ const key = JSON.stringify([Number(line.product_id), Number(line.product_variant_id) || 0]);
33
+ quantities.set(key, (quantities.get(key) || 0) + Number(line.num || 0));
34
+ }
35
+ return JSON.stringify([...quantities].sort(([left], [right]) => left.localeCompare(right)));
36
+ }
37
+ function ownerKey(action, product) {
38
+ return JSON.stringify([action.strategyId, action.ruleId, product.product_id, product.product_variant_id ?? 0]);
39
+ }
40
+ function ownedLines(lines, key) {
41
+ return lines.filter(line => line.metadata?.[ownershipField] === key);
42
+ }
43
+
44
+ /** Executes item rules at the sales boundary, independent of the calling template. */
45
+ class FulfillmentItemRulesController {
46
+ sequence = 0;
47
+ pending = Promise.resolve();
48
+ evaluator = new _itemRule.ItemRuleEvaluator();
49
+ constructor(host) {
50
+ this.host = host;
51
+ }
52
+ invalidate() {
53
+ this.sequence += 1;
54
+ }
55
+ apply(serviceType) {
56
+ const sequence = ++this.sequence;
57
+ // Even an unsupported mode supersedes any pending takeaway operation.
58
+ if (serviceType !== 'takeaway' && serviceType !== 'dine_in') return Promise.resolve();
59
+ const order = this.host.getOrder();
60
+ const tempOrder = order.getTempOrder?.();
61
+ const scope = this.host.getScope();
62
+ const identity = JSON.stringify([tempOrder?.order_id, tempOrder?.external_sale_number, tempOrder?.customer_id]);
63
+ const initialProducts = tempOrder?.products;
64
+ const initialFingerprint = cartFingerprint(initialProducts || []);
65
+ const isCurrent = () => sequence === this.sequence && scope === this.host.getScope() && this.host.getOrder() === order && order.getTempOrder?.() === tempOrder && identity === JSON.stringify([tempOrder?.order_id, tempOrder?.external_sale_number, tempOrder?.customer_id]) && tempOrder?.metadata?.shop_service_data?.service_type === serviceType;
66
+ const run = async () => {
67
+ const openData = await this.host.loadOpenData();
68
+ const ids = positiveIds(openData?.[`fulfillment.${serviceType}_fees_rules`]);
69
+ if (!ids.length || !isCurrent()) return;
70
+ if (!tempOrder) return;
71
+ const assertSnapshot = () => {
72
+ if (!isCurrent() || tempOrder.products !== initialProducts || cartFingerprint(tempOrder.products) !== initialFingerprint) {
73
+ throw new Error('The order changed while preparing fulfillment item rules');
74
+ }
75
+ };
76
+ assertSnapshot();
77
+ const response = await this.host.fetchStrategyModels(ids);
78
+ assertSnapshot();
79
+ if (response?.code !== 200 || !Array.isArray(response?.data?.list)) {
80
+ throw new Error('Unable to load fulfillment item rules');
81
+ }
82
+ // Response order need not match the configured order (the latter breaks priority ties).
83
+ const configs = ids.map(id => {
84
+ const config = response.data.list.find(item => Number(item?.id) === id)?.metadata;
85
+ if (config?.metadata?.type !== 'item_rule' || !Array.isArray(config.actions) || !config.conditions) {
86
+ throw new Error(`Invalid or missing fulfillment item rule ${id}`);
87
+ }
88
+ return config;
89
+ });
90
+ const runtime = this.host.getOtherParams();
91
+ const pax = runtime.itemRule?.pax || {};
92
+ const businessData = {
93
+ executionScope: 'set_handover',
94
+ serviceType,
95
+ channel: String(runtime.channel ?? runtime.platform ?? ''),
96
+ pax: {
97
+ total: Number(pax.total) || 0,
98
+ adult: Number(pax.adult) || 0,
99
+ child: Number(pax.child) || 0
100
+ },
101
+ cartItems: tempOrder.products.map(line => ({
102
+ product_id: Number(line.product_id),
103
+ product_variant_id: Number(line.product_variant_id) || 0,
104
+ quantity: Number(line.num) || 0
105
+ })),
106
+ strategyConfigs: configs,
107
+ custom: runtime.itemRule?.custom
108
+ };
109
+ const initialEvaluation = this.evaluator.evaluate(businessData);
110
+ const requiresMainQuantity = initialEvaluation.rawResults.some(result => result.applicable && result.matchedActions.some(action => action.type === 'PREFILL_CART' && action.target === 'cart' && action.config?.scope === 'set_handover' && action.config.products?.some(product => product.quantityFrom === 'main_product_number')));
111
+ const actions = requiresMainQuantity ? this.evaluator.evaluate({
112
+ ...businessData,
113
+ mainProductNumber: await this.getMainProductNumber(tempOrder.products, configs)
114
+ }).cartActions : initialEvaluation.cartActions;
115
+ assertSnapshot();
116
+ // Prepare every addition before changing the cart; missing products must not leave a half-loaded batch.
117
+ const sources = await this.loadPrefillSources(actions, tempOrder.products);
118
+ assertSnapshot();
119
+ const targetIds = new Set(actions.flatMap(action => action.products.map(product => Number(product.product_id))));
120
+ const originalSelection = selectionFingerprint(tempOrder.products, targetIds);
121
+ // Promotion may add gifts or split/merge rows. Only a change to the purchased
122
+ // quantities invalidates this selection, not promotion's own line reshaping.
123
+ const targetIsCurrent = () => isCurrent() && selectionFingerprint(tempOrder.products, targetIds) === originalSelection;
124
+ for (const action of actions) {
125
+ if (!targetIsCurrent()) return;
126
+ if (action.type === 'DELETE_PRODUCT') {
127
+ const deletedIds = new Set(action.products.map(product => Number(product.product_id)));
128
+ const lines = tempOrder.products.filter(line => deletedIds.has(Number(line.product_id)));
129
+ if (lines.length) {
130
+ await order.removeProductsFromOrder(lines.map(lineIdentity), {
131
+ isCurrentOrder: targetIsCurrent
132
+ });
133
+ }
134
+ continue;
135
+ }
136
+ for (const product of action.products) {
137
+ if (!targetIsCurrent()) return;
138
+ const key = ownerKey(action, product);
139
+ const existing = ownedLines(tempOrder.products, key);
140
+ const quantity = Math.max(0, Math.floor(Number(product.quantity) || 0));
141
+ if (product.repeat_skip && existing.length) {
142
+ if (quantity === 0) {
143
+ await order.removeProductsFromOrder(existing.map(lineIdentity), {
144
+ isCurrentOrder: targetIsCurrent
145
+ });
146
+ continue;
147
+ }
148
+ // Keep one owned row and its pricing; never absorb independently added products.
149
+ if (existing.length > 1) {
150
+ await order.removeProductsFromOrder(existing.slice(1).map(lineIdentity), {
151
+ isCurrentOrder: targetIsCurrent
152
+ });
153
+ }
154
+ if (!targetIsCurrent()) return;
155
+ if (Number(existing[0].num) !== quantity) {
156
+ await order.updateOrderProductQuantity({
157
+ ...lineIdentity(existing[0]),
158
+ num: quantity
159
+ }, {
160
+ isCurrentOrder: targetIsCurrent
161
+ });
162
+ }
163
+ continue;
164
+ }
165
+ if (quantity === 0) continue;
166
+ const source = sources.get(Number(product.product_id));
167
+ const line = (0, _transformBaseProductToOrderProduct.transformBaseProductToOrderProduct)({
168
+ sourceProduct: source,
169
+ payload: {
170
+ product_id: product.product_id,
171
+ product_variant_id: product.product_variant_id ?? 0,
172
+ quantity
173
+ }
174
+ });
175
+ if (!line) throw new Error(`Unable to prepare fulfillment product ${product.product_id}`);
176
+ line.metadata = {
177
+ ...line.metadata,
178
+ [ownershipField]: key
179
+ };
180
+ const beforeProducts = tempOrder.products;
181
+ const beforeFingerprint = cartFingerprint(beforeProducts);
182
+ await order.addProductToOrder(line, undefined, {
183
+ insertAtStart: true,
184
+ disableMerge: true,
185
+ availabilityCommitGuard: () => {
186
+ if (!targetIsCurrent() || tempOrder.products !== beforeProducts || cartFingerprint(tempOrder.products) !== beforeFingerprint) {
187
+ throw new Error('The order changed while adding a fulfillment product');
188
+ }
189
+ },
190
+ availabilityCommitTargetGuard: () => targetIsCurrent() && ownedLines(tempOrder.products, key).length > 0
191
+ });
192
+ }
193
+ }
194
+ };
195
+ // Serialize writes; a newer choice invalidates the old request before its next mutation.
196
+ const task = this.pending.then(run).catch(error => {
197
+ if (sequence === this.sequence) this.host.reportError(error);
198
+ // Fees are best effort by product decision: Next must remain usable on failure.
199
+ });
200
+ this.pending = task;
201
+ return task;
202
+ }
203
+ async loadProducts(ids) {
204
+ if (!ids.length) return [];
205
+ const products = this.host.getProducts();
206
+ if (!products) throw new Error('products module is unavailable for fulfillment item rules');
207
+ const runtime = this.host.getOtherParams();
208
+ const now = (0, _dayjs.default)();
209
+ return products.loadProducts({
210
+ product_ids: ids,
211
+ cacheId: runtime.cacheId,
212
+ customer_id: this.host.getOrder().getTempOrder()?.customer_id ?? undefined,
213
+ schedule_date: now.format('YYYY-MM-DD'),
214
+ schedule_datetime: now.format('YYYY-MM-DD HH:mm:ss')
215
+ });
216
+ }
217
+ async getMainProductNumber(lines, configs) {
218
+ const feeIds = new Set(configs.flatMap(config => config.actions.flatMap(action => action.config?.scope === 'set_handover' && ['PREFILL_CART', 'DELETE_PRODUCT'].includes(action.type) ? (action.config.products || []).map(product => Number(product.product_id)) : [])));
219
+ const candidates = lines.filter(line => !line.metadata?.[ownershipField] && !feeIds.has(Number(line.product_id)));
220
+ if (!candidates.length) return 0;
221
+ const ids = positiveIds(candidates.map(line => line.product_id));
222
+ const cached = (await this.host.getProducts()?.queryProducts({
223
+ includeIds: ids
224
+ })) || [];
225
+ const sources = new Map();
226
+ for (const line of candidates) {
227
+ const cachedSource = cached.find(product => Number(product.id) === Number(line.product_id));
228
+ const source = Array.isArray(cachedSource?.data_variants) ? cachedSource : line._origin;
229
+ if (Array.isArray(source?.data_variants)) sources.set(Number(line.product_id), source);
230
+ }
231
+ const missing = ids.filter(id => !sources.has(id));
232
+ for (const source of await this.loadProducts(missing)) sources.set(Number(source.id ?? source.product_id), source);
233
+ if (ids.some(id => !sources.has(id))) throw new Error('Unable to identify fulfillment main products');
234
+ return candidates.reduce((sum, line) => {
235
+ const source = sources.get(Number(line.product_id));
236
+ const isMain = source?.data_variants?.some(variant => variant?.data?.client_meta?.is_main_product === 1);
237
+ return sum + (isMain ? Math.max(0, Math.floor(Number(line.num) || 0)) : 0);
238
+ }, 0);
239
+ }
240
+ async loadPrefillSources(actions, lines) {
241
+ const ids = positiveIds(actions.flatMap(action => action.type === 'PREFILL_CART' ? action.products.filter(product => product.quantity > 0).map(product => product.product_id) : []));
242
+ const cached = ids.length ? (await this.host.getProducts()?.queryProducts({
243
+ includeIds: ids
244
+ })) || [] : [];
245
+ const sources = new Map(cached.map(product => [Number(product.id), product]));
246
+ // Existing owned rows can supply their original product if the catalog no longer contains it.
247
+ for (const line of lines) {
248
+ if (line.metadata?.[ownershipField] && line._origin && !sources.has(Number(line.product_id))) {
249
+ sources.set(Number(line.product_id), line._origin);
250
+ }
251
+ }
252
+ for (const source of await this.loadProducts(ids.filter(id => !sources.has(id)))) {
253
+ sources.set(Number(source.id ?? source.product_id), source);
254
+ }
255
+ if (ids.some(id => !sources.has(id))) throw new Error('Unable to load fulfillment fee products');
256
+ return sources;
257
+ }
258
+ }
259
+ exports.FulfillmentItemRulesController = FulfillmentItemRulesController;
@@ -1,7 +1,10 @@
1
1
  import { Module, ModuleOptions, PisellCore } from '../../types';
2
2
  import { BaseModule } from '../../modules/BaseModule';
3
- import { BaseSalesOrderProduct, BaseSalesOrderProductIdentity, BaseSalesPaymentStatus, BaseSalesUpdateOrderProductQuantityParams, BaseSalesCalculateProductBookingPriceParams, BaseSalesPriceRecalculationContextChange, BaseSalesProductBookingPriceResult, BaseSalesQuotationCustomerScopeInfo, BaseSalesScanCodeResult, BaseSalesCashPaymentOptions, BaseSalesCashPaymentConfig, BaseSalesPayCashParams, BaseSalesPayCashResult, BaseSalesCommitPaymentMethodParams, BaseSalesCommitPaymentMethodResult, BaseSalesConfirmWalletPaymentResult } from './types';
3
+ import { type LocalEftposFinalization } from '../../modules/Order/localEftposState';
4
+ import { BaseSalesOrderProduct, BaseSalesOrderProductIdentity, BaseSalesPaymentStatus, BaseSalesSubmitSalesOrderParams, BaseSalesUpdateOrderProductQuantityParams, BaseSalesCalculateProductBookingPriceParams, BaseSalesPriceRecalculationContextChange, BaseSalesProductBookingPriceResult, BaseSalesQuotationCustomerScopeInfo, BaseSalesScanCodeResult, BaseSalesCashPaymentOptions, BaseSalesCashPaymentConfig, BaseSalesPayCashParams, BaseSalesPayCashResult, BaseSalesCommitPaymentMethodParams, BaseSalesCommitPaymentMethodResult, BaseSalesConfirmWalletPaymentResult } from './types';
4
5
  import { OrderModule } from '../../modules/Order';
6
+ import { type PickupTimingBookingInput } from '../../modules/Order/pickupTiming';
7
+ import type { PickupTimingAvailability, PickupTimingSelection, PickupTimingValidationResult } from '../../modules/FulfillmentTiming';
5
8
  import type { AddProductBookingInput, AddProductToOrderOptions, LoadSalesDetailParams, OrderPaymentData, OrderPaymentUpdateOptions, OrderPaymentUpdateResult, OrderPaymentSource, SendCustomerPayLinkParams, SyncPaymentsToOrderParams, SyncPaymentsToOrderResult, UpdateTempOrderCustomerInput, UpdateOrderBookingParams, UpdateOrderProductParams, OrderTempOrder, OrderSummary, OrderPromotionEvaluator, OrderGiftSelectResolver, OrderUnfulfilledPromotion, OrderLastGiftActions, RemoveProductsFromOrderOptions, ReplaceTempOrderOptions, SalesNote, SalesNotesMutationSnapshot, SetSalesNoteInput, PickupReferenceMode, ShopServiceData, ShopServiceType } from '../../modules/Order/types';
6
9
  import type { SubmitPayloadEnhancer } from '../../modules/Order/utils';
7
10
  import type { Discount, VirtualCurrencyAsset } from '../../modules/Discount/types';
@@ -16,6 +19,7 @@ import { QuotationModule } from '../../modules/Quotation';
16
19
  import type { ISalesDetail, ISalesDetailHeader, ISalesBooking, ISalesPayment, ISalesSurcharge } from './utils/parseSalesResponse';
17
20
  import { AppPlugin } from '../../plugins/app';
18
21
  import { type TransformBaseProductToOrderProductParams } from './utils/transformBaseProductToOrderProduct';
22
+ import type { OpenDataConfig } from '../../modules/OpenData/types';
19
23
  export interface BaseSalesState {
20
24
  order?: OrderModule;
21
25
  products?: ProductList;
@@ -76,6 +80,9 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
76
80
  private serverOrderChangeHydrateInFlight;
77
81
  private queuedServerOrderChanges;
78
82
  private serverOrderChangeSyncEnabled;
83
+ protected pickupTimingEnabled: boolean;
84
+ private pickupTimingController?;
85
+ private fulfillmentItemRulesController?;
79
86
  constructor(name?: string, version?: string);
80
87
  /**
81
88
  * 子类可覆盖此方法以追加/收敛要注册的子模块。
@@ -99,6 +106,7 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
99
106
  private isCurrentSalesOrderRecord;
100
107
  setServerOrderChangeSyncEnabled(enabled: boolean): void;
101
108
  private queueLatestServerOrderChange;
109
+ private shouldSyncServerOrderChanges;
102
110
  private drainQueuedServerOrderChanges;
103
111
  private syncCurrentSalesDetailFromServerOrderChange;
104
112
  private registerServerOrderChangeSync;
@@ -153,6 +161,25 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
153
161
  private getAuthoritativeBundleUnitPrice;
154
162
  private mergeProductForPriceQuery;
155
163
  protected getSubmitOrderSalesChannel(): string | undefined;
164
+ /** Only solutions opting into Pickup Timing supply effective OpenData. */
165
+ protected loadPickupTimingOpenData(_force: boolean): Promise<Record<string, unknown>>;
166
+ protected loadFulfillmentItemRuleOpenData(): Promise<OpenDataConfig | null>;
167
+ private getFulfillmentItemRulesController;
168
+ protected isPickupTimingEnabled(): boolean;
169
+ protected getPickupTimingContextData(key: string): unknown;
170
+ protected getPickupTimingScope(): string;
171
+ protected resetPickupTimingCache(): void;
172
+ private getPickupTimingController;
173
+ getPickupTimingAvailability(params?: {
174
+ date?: string;
175
+ force?: boolean;
176
+ }): Promise<PickupTimingAvailability>;
177
+ getPickupTimingSelection(): PickupTimingSelection;
178
+ setPickupTimingSelection(selection: PickupTimingSelection): Promise<PickupTimingSelection>;
179
+ validatePickupTimingSelection(selection: PickupTimingSelection): Promise<PickupTimingValidationResult>;
180
+ preparePickupTimingForCheckout(options?: {
181
+ freeze?: boolean;
182
+ }): Promise<PickupTimingSelection>;
156
183
  /** Subclasses with target-scoped OpenData override this guard before reusing the cache. */
157
184
  protected isInvoiceOpenDataCacheCurrent(): boolean;
158
185
  private getFulfillmentRefModeConfigs;
@@ -249,7 +276,10 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
249
276
  updateTempOrderShopDiscount(amount: string | number): string;
250
277
  updateTempOrderDepositAmount(amount: string | number): OrderSummary;
251
278
  setPickupReferenceMode(mode: PickupReferenceMode): ShopServiceData;
252
- setServiceType(serviceType: ShopServiceType): ShopServiceData;
279
+ setServiceType(serviceType: ShopServiceType): Promise<ShopServiceData>;
280
+ getPickupTimingBooking(): import("../../modules/Order").PickupTimingBooking | null;
281
+ setPickupTimingBooking(input: PickupTimingBookingInput): import("../../modules/Order").PickupTimingBooking;
282
+ clearPickupTimingBooking(): void;
253
283
  updateTempOrderContactsInfo(contactsInfo: Record<string, any> | null): Record<string, any> | null;
254
284
  /** @deprecated OrderModule 不再负责 tempOrder localStorage 持久化。 */
255
285
  setEnableTempOrderPersist(enabled: boolean): void;
@@ -315,13 +345,8 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
315
345
  productList?: Record<string, any>[];
316
346
  discountList?: Discount[];
317
347
  }): Promise<void>;
318
- submitScanOrder<T = any>(params?: {
319
- payments?: OrderPaymentSource[];
320
- paymentStatus?: BaseSalesPaymentStatus;
321
- smallTicketDataFlag?: number;
322
- confirmPendingVoucherPayments?: boolean;
323
- enhancePayload?: SubmitPayloadEnhancer;
324
- }): Promise<T>;
348
+ submitScanOrder<T = any>(params?: BaseSalesSubmitSalesOrderParams): Promise<T>;
349
+ private buildVirtualSettlementOrderData;
325
350
  private buildVirtualSettlementSubmitData;
326
351
  /**
327
352
  * 提交当前 Sales 订单。
@@ -329,13 +354,11 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
329
354
  * PaymentModal 场景通过 smallTicketDataFlag=1 请求 B 端小票数据;
330
355
  * payments 会交给 OrderModule 统一映射为 Sales 协议支付项。
331
356
  */
332
- submitSalesOrder<T = any>(params?: {
333
- payments?: OrderPaymentSource[];
334
- paymentStatus?: BaseSalesPaymentStatus;
335
- smallTicketDataFlag?: number;
336
- confirmPendingVoucherPayments?: boolean;
337
- enhancePayload?: SubmitPayloadEnhancer;
338
- }): Promise<T>;
357
+ submitSalesOrder<T = any>(params?: BaseSalesSubmitSalesOrderParams): Promise<T>;
358
+ private pendingLocalEftposFinalizations;
359
+ finalizeLocalEftposPayment(params: LocalEftposFinalization): Promise<any>;
360
+ /** Entry gate also reads SQLite state, so stale sessions/restarts cannot bypass it. */
361
+ ensureSalesOrderReadyForPayment(): Promise<void>;
339
362
  saveSalesOrderDraft<T = any>(params?: {
340
363
  platform?: string;
341
364
  businessCode?: string;