@pisell/pisellos 0.10.50 → 0.10.52

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 (64) hide show
  1. package/dist/model/strategy/adapter/promotion/index.js +0 -9
  2. package/dist/modules/FulfillmentTiming/index.d.ts +11 -0
  3. package/dist/modules/FulfillmentTiming/index.js +407 -0
  4. package/dist/modules/FulfillmentTiming/types.d.ts +67 -0
  5. package/dist/modules/FulfillmentTiming/types.js +1 -0
  6. package/dist/modules/OpenData/types.d.ts +6 -0
  7. package/dist/modules/Order/index.d.ts +18 -1
  8. package/dist/modules/Order/index.js +322 -195
  9. package/dist/modules/Order/pickupTiming.d.ts +46 -0
  10. package/dist/modules/Order/pickupTiming.js +53 -0
  11. package/dist/modules/Order/types.d.ts +11 -1
  12. package/dist/modules/Order/utils.d.ts +10 -0
  13. package/dist/modules/Order/utils.js +145 -85
  14. package/dist/modules/Rules/index.js +3 -2
  15. package/dist/modules/Schedule/index.d.ts +8 -0
  16. package/dist/modules/Schedule/index.js +134 -37
  17. package/dist/modules/index.d.ts +1 -0
  18. package/dist/modules/index.js +1 -0
  19. package/dist/server/modules/order/index.d.ts +8 -0
  20. package/dist/server/modules/order/index.js +506 -453
  21. package/dist/server/modules/order/types.d.ts +1 -0
  22. package/dist/server/utils/small-ticket.js +165 -94
  23. package/dist/solution/BaseSales/index.d.ts +24 -0
  24. package/dist/solution/BaseSales/index.js +1231 -1037
  25. package/dist/solution/BaseSales/pickupTiming.d.ts +43 -0
  26. package/dist/solution/BaseSales/pickupTiming.js +589 -0
  27. package/dist/solution/BaseSales/utils/parseSalesResponse.js +3 -0
  28. package/dist/solution/BookingByStep/index.d.ts +1 -1
  29. package/dist/solution/BookingTicket/index.d.ts +2 -2
  30. package/dist/solution/BookingTicket/utils/cartView.js +3 -1
  31. package/dist/solution/UnifiedBookingSales/index.d.ts +13 -4
  32. package/dist/solution/UnifiedBookingSales/index.js +873 -708
  33. package/lib/modules/FulfillmentTiming/index.d.ts +11 -0
  34. package/lib/modules/FulfillmentTiming/index.js +359 -0
  35. package/lib/modules/FulfillmentTiming/types.d.ts +67 -0
  36. package/lib/modules/FulfillmentTiming/types.js +5 -0
  37. package/lib/modules/OpenData/types.d.ts +6 -0
  38. package/lib/modules/Order/index.d.ts +18 -1
  39. package/lib/modules/Order/index.js +101 -3
  40. package/lib/modules/Order/pickupTiming.d.ts +46 -0
  41. package/lib/modules/Order/pickupTiming.js +61 -0
  42. package/lib/modules/Order/types.d.ts +11 -1
  43. package/lib/modules/Order/utils.d.ts +10 -0
  44. package/lib/modules/Order/utils.js +58 -4
  45. package/lib/modules/Rules/index.js +3 -2
  46. package/lib/modules/Schedule/index.d.ts +8 -0
  47. package/lib/modules/Schedule/index.js +64 -11
  48. package/lib/modules/index.d.ts +1 -0
  49. package/lib/modules/index.js +11 -0
  50. package/lib/server/modules/order/index.d.ts +8 -0
  51. package/lib/server/modules/order/index.js +19 -0
  52. package/lib/server/modules/order/types.d.ts +1 -0
  53. package/lib/server/utils/small-ticket.js +86 -22
  54. package/lib/solution/BaseSales/index.d.ts +24 -0
  55. package/lib/solution/BaseSales/index.js +109 -1
  56. package/lib/solution/BaseSales/pickupTiming.d.ts +43 -0
  57. package/lib/solution/BaseSales/pickupTiming.js +269 -0
  58. package/lib/solution/BaseSales/utils/parseSalesResponse.js +3 -0
  59. package/lib/solution/BookingByStep/index.d.ts +1 -1
  60. package/lib/solution/BookingTicket/index.d.ts +2 -2
  61. package/lib/solution/BookingTicket/utils/cartView.js +3 -1
  62. package/lib/solution/UnifiedBookingSales/index.d.ts +13 -4
  63. package/lib/solution/UnifiedBookingSales/index.js +130 -46
  64. package/package.json +1 -1
@@ -4,10 +4,17 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.OrderModule = void 0;
7
+ Object.defineProperty(exports, "isFulfillmentTimingBooking", {
8
+ enumerable: true,
9
+ get: function () {
10
+ return _pickupTiming.isFulfillmentTimingBooking;
11
+ }
12
+ });
7
13
  var _BaseModule = require("../BaseModule");
8
14
  var _types = require("./types");
9
15
  var _utils = require("./utils");
10
16
  var _utils2 = require("../Product/utils");
17
+ var _pickupTiming = require("./pickupTiming");
11
18
  var _dayjs = _interopRequireDefault(require("dayjs"));
12
19
  var _availability = require("../Discount/availability");
13
20
  var _osPrivatePayload = require("../../utils/os-private-payload");
@@ -130,6 +137,8 @@ class OrderModule extends _BaseModule.BaseModule {
130
137
  sessionStorageRestoreRecord = null;
131
138
  /** 只允许当前订单最新的 prepare/config 请求回写客户资产态。 */
132
139
  customerWalletPrepareConfigEpoch = 0;
140
+ /** Payment 可能早于 prepare/config 返回;先按实例 ID 暂存卡号等待 Snapshot。 */
141
+ pointCardSnapshotCodeCandidates = new Map();
133
142
  salesSummaryModuleName;
134
143
  rulesHooksOverride;
135
144
  moduleHooksOverride;
@@ -1159,17 +1168,55 @@ class OrderModule extends _BaseModule.BaseModule {
1159
1168
  }
1160
1169
  clearCustomerWalletRuntimeState() {
1161
1170
  this.customerWalletPrepareConfigEpoch += 1;
1171
+ this.pointCardSnapshotCodeCandidates.clear();
1162
1172
  this.store.availableWalletIds = [];
1163
1173
  this.store.virtualCurrencyList = [];
1164
1174
  }
1175
+ rememberPointCardSnapshotCodeCandidates(value) {
1176
+ (0, _utils.collectPointCardSnapshotCodeCandidates)(value).forEach(({
1177
+ id,
1178
+ code
1179
+ }) => {
1180
+ if (!this.pointCardSnapshotCodeCandidates.has(id)) {
1181
+ this.pointCardSnapshotCodeCandidates.set(id, code);
1182
+ }
1183
+ });
1184
+ }
1185
+ getRememberedPointCardSnapshotCodeCandidates() {
1186
+ return [...this.pointCardSnapshotCodeCandidates].map(([id, code]) => ({
1187
+ id,
1188
+ code
1189
+ }));
1190
+ }
1191
+ getPointCardSnapshotOrderIdentityKey(identity) {
1192
+ if (identity?.externalSaleNumber) {
1193
+ return `external:${identity.externalSaleNumber}`;
1194
+ }
1195
+ if (identity?.orderId !== undefined && identity.orderId !== null) {
1196
+ return `order:${identity.orderId}`;
1197
+ }
1198
+ if (identity?.localId) return `local:${identity.localId}`;
1199
+ if (identity?.orderNumber) return `number:${identity.orderNumber}`;
1200
+ return '';
1201
+ }
1202
+ isPointCardSnapshotMergeContextCurrent(context) {
1203
+ if (!context) return true;
1204
+ if (context.customerId !== undefined && String(context.customerId ?? '') !== String(this.store.tempOrder?.customer_id ?? '')) {
1205
+ return false;
1206
+ }
1207
+ const expectedOrderKey = this.getPointCardSnapshotOrderIdentityKey(context.orderIdentity);
1208
+ return !expectedOrderKey || expectedOrderKey === this.getPointCardSnapshotOrderIdentityKey(this.getOrderIdentity());
1209
+ }
1165
1210
  writeCustomerWalletSnapshotsFromPrepareConfig(params) {
1166
1211
  const tempOrder = this.store.tempOrder;
1167
1212
  if (!tempOrder) return;
1168
1213
  if (String(tempOrder.customer_id ?? '') !== String(params.customerId)) return;
1214
+ const currentOrderMetaList = (0, _utils.normalizeOrderMetaList)(tempOrder.order_meta_list);
1169
1215
  const virtualCurrencyList = Array.isArray(params.virtualCurrencyList) ? params.virtualCurrencyList : [];
1216
+ const snapshotsWithExistingCodes = (0, _utils.mergePointCardSnapshotCodes)(params.availableWalletPassList, currentOrderMetaList?.point_cards_snapshot);
1170
1217
  tempOrder.order_meta_list = {
1171
- ...((0, _utils.normalizeOrderMetaList)(tempOrder.order_meta_list) || {}),
1172
- point_cards_snapshot: (0, _utils.buildPointCardSnapshotFromWalletPassList)(params.availableWalletPassList),
1218
+ ...(currentOrderMetaList || {}),
1219
+ point_cards_snapshot: (0, _utils.mergePointCardSnapshotCodes)(snapshotsWithExistingCodes, this.getRememberedPointCardSnapshotCodeCandidates()),
1173
1220
  virtual_currency_snapshot: (0, _utils.buildVirtualCurrencySnapshotFromList)(virtualCurrencyList)
1174
1221
  };
1175
1222
  this.store.availableWalletIds = [...params.availableWalletIds];
@@ -1177,6 +1224,31 @@ class OrderModule extends _BaseModule.BaseModule {
1177
1224
  this.persistTempOrder();
1178
1225
  this.saveDraftInBackground();
1179
1226
  }
1227
+
1228
+ /**
1229
+ * 将 Payment WalletPass 返回的实例 code 回填到当前订单 Snapshot。
1230
+ * 这里只按资产 ID 补空值,不新增卡、不覆盖 prepare/config 冻结的余额。
1231
+ */
1232
+ mergePointCardSnapshotCodes(walletPassList, context) {
1233
+ const tempOrder = this.store.tempOrder;
1234
+ const currentOrderMetaList = (0, _utils.normalizeOrderMetaList)(tempOrder?.order_meta_list);
1235
+ const currentSnapshots = currentOrderMetaList?.point_cards_snapshot || [];
1236
+ if (!tempOrder || !this.isPointCardSnapshotMergeContextCurrent(context)) {
1237
+ return currentSnapshots;
1238
+ }
1239
+ this.rememberPointCardSnapshotCodeCandidates(walletPassList);
1240
+ if (!currentSnapshots.length) return currentSnapshots;
1241
+ const nextSnapshots = (0, _utils.mergePointCardSnapshotCodes)(currentSnapshots, this.getRememberedPointCardSnapshotCodeCandidates());
1242
+ const hasChanged = nextSnapshots.some((snapshot, index) => snapshot.code !== currentSnapshots[index]?.code);
1243
+ if (!hasChanged) return currentSnapshots;
1244
+ tempOrder.order_meta_list = {
1245
+ ...(currentOrderMetaList || {}),
1246
+ point_cards_snapshot: nextSnapshots
1247
+ };
1248
+ this.persistTempOrder();
1249
+ this.saveDraftInBackground();
1250
+ return nextSnapshots;
1251
+ }
1180
1252
  createExternalSaleNumber() {
1181
1253
  const timestamp = (0, _dayjs.default)().format('YYYYMMDDHHmmssSSS');
1182
1254
  const random6 = String(Math.floor(Math.random() * 1000000)).padStart(6, '0');
@@ -2208,6 +2280,7 @@ class OrderModule extends _BaseModule.BaseModule {
2208
2280
  const bookingByBookingUid = new Map();
2209
2281
  bookings.forEach(booking => {
2210
2282
  if (!booking || typeof booking !== 'object') return;
2283
+ if ((0, _pickupTiming.isFulfillmentTimingBooking)(booking)) return;
2211
2284
  const productUid = booking.product_uid || booking.metadata?.product_uid;
2212
2285
  if (productUid !== undefined && productUid !== null && String(productUid) !== '') {
2213
2286
  bookingByProductUid.set(String(productUid), booking);
@@ -2258,6 +2331,7 @@ class OrderModule extends _BaseModule.BaseModule {
2258
2331
  let baseHolder = null;
2259
2332
  const seen = new Set();
2260
2333
  for (const booking of tempOrder.bookings || []) {
2334
+ if ((0, _pickupTiming.isFulfillmentTimingBooking)(booking)) continue;
2261
2335
  const holder = booking?.holder && typeof booking.holder === 'object' ? booking.holder : null;
2262
2336
  if (!baseHolder && holder) {
2263
2337
  baseHolder = holder;
@@ -2298,7 +2372,7 @@ class OrderModule extends _BaseModule.BaseModule {
2298
2372
  * this.syncTempOrderHolderFromBookings(tempOrder);
2299
2373
  */
2300
2374
  syncTempOrderHolderFromBookings(tempOrder) {
2301
- const bookings = tempOrder.bookings || [];
2375
+ const bookings = (tempOrder.bookings || []).filter(booking => !(0, _pickupTiming.isFulfillmentTimingBooking)(booking));
2302
2376
  const hasHolderSource = bookings.some(booking => {
2303
2377
  return booking?.holder !== undefined || booking?.metadata?.holder_id !== undefined;
2304
2378
  });
@@ -2323,6 +2397,7 @@ class OrderModule extends _BaseModule.BaseModule {
2323
2397
  const bookingUid = product?.booking_uid || product?.metadata?.booking_uid;
2324
2398
  if (!productUid && !bookingUid) return;
2325
2399
  tempOrder.bookings = (tempOrder.bookings || []).filter(booking => {
2400
+ if ((0, _pickupTiming.isFulfillmentTimingBooking)(booking)) return true;
2326
2401
  const currentBookingUid = this.getBookingUniqueIdentificationNumber(booking);
2327
2402
  if (bookingUid && currentBookingUid === String(bookingUid)) return false;
2328
2403
  if (productUid && String(booking?.product_uid || '') === String(productUid)) return false;
@@ -2736,10 +2811,33 @@ class OrderModule extends _BaseModule.BaseModule {
2736
2811
  });
2737
2812
  }
2738
2813
  updateTempOrderServiceType(serviceType) {
2814
+ if (serviceType !== 'pick_up') {
2815
+ const tempOrder = this.ensureTempOrder();
2816
+ tempOrder.bookings = (tempOrder.bookings || []).filter(booking => !(0, _pickupTiming.isFulfillmentTimingBooking)(booking));
2817
+ }
2739
2818
  return this.updateTempOrderShopServiceData({
2740
2819
  service_type: serviceType
2741
2820
  });
2742
2821
  }
2822
+ getPickupTimingBooking() {
2823
+ return (this.store.tempOrder?.bookings || []).find(_pickupTiming.isFulfillmentTimingBooking) || null;
2824
+ }
2825
+ setPickupTimingBooking(input) {
2826
+ const tempOrder = this.ensureTempOrder();
2827
+ const bookings = tempOrder.bookings || [];
2828
+ const firstIndex = bookings.findIndex(_pickupTiming.isFulfillmentTimingBooking);
2829
+ const nextBooking = (0, _pickupTiming.buildPickupTimingBooking)(input, bookings[firstIndex]);
2830
+ // Keep the first row's identity and position; repair duplicate marked rows only.
2831
+ tempOrder.bookings = firstIndex === -1 ? [...bookings, nextBooking] : bookings.flatMap((booking, index) => index === firstIndex ? [nextBooking] : (0, _pickupTiming.isFulfillmentTimingBooking)(booking) ? [] : [booking]);
2832
+ this.notifyTempOrderChanged();
2833
+ return nextBooking;
2834
+ }
2835
+ clearPickupTimingBooking() {
2836
+ const tempOrder = this.store.tempOrder;
2837
+ if (!tempOrder?.bookings?.some(_pickupTiming.isFulfillmentTimingBooking)) return;
2838
+ tempOrder.bookings = tempOrder.bookings.filter(booking => !(0, _pickupTiming.isFulfillmentTimingBooking)(booking));
2839
+ this.notifyTempOrderChanged();
2840
+ }
2743
2841
  updateTempOrderContactsInfo(contactsInfo) {
2744
2842
  const tempOrder = this.ensureTempOrder();
2745
2843
  tempOrder.contacts_info = contactsInfo && typeof contactsInfo === 'object' && !Array.isArray(contactsInfo) ? contactsInfo : null;
@@ -0,0 +1,46 @@
1
+ export type PickupTimingSelectionMode = 'asap' | 'scheduled';
2
+ export type PickupTimingSource = 'asap' | 'schedule' | 'pos_manual';
3
+ export interface PickupTimingBookingInput {
4
+ mode: PickupTimingSelectionMode;
5
+ source: PickupTimingSource;
6
+ /** Store-local calendar date and minute; the caller validates availability. */
7
+ date: string;
8
+ time: string;
9
+ /** Absolute timestamp disambiguates a repeated local minute at a DST change. */
10
+ instant?: string;
11
+ timezone?: string;
12
+ frozen?: boolean;
13
+ }
14
+ export interface PickupTimingBookingMetadata {
15
+ unique_identification_number: string;
16
+ is_fulfillment_timing: true;
17
+ fulfillment_timing: {
18
+ method: 'pick_up';
19
+ mode: PickupTimingSelectionMode;
20
+ source: PickupTimingSource;
21
+ instant?: string;
22
+ timezone?: string;
23
+ frozen?: boolean;
24
+ };
25
+ [key: string]: any;
26
+ }
27
+ export interface PickupTimingBooking {
28
+ schedule_id: number | string;
29
+ start_date: string;
30
+ end_date: string;
31
+ start_time: string;
32
+ end_time: string;
33
+ select_date: string;
34
+ duration: 0;
35
+ is_all: 0;
36
+ sub_type: 'minutes';
37
+ like_status: string;
38
+ resources: [];
39
+ metadata: PickupTimingBookingMetadata;
40
+ schedule_event_id?: number | string;
41
+ [key: string]: any;
42
+ }
43
+ /** The explicit marker is the only authority; unlinked ordinary bookings remain ordinary. */
44
+ export declare function isFulfillmentTimingBooking(booking: unknown): boolean;
45
+ /** Update an independent pickup appointment without changing its persisted row identity. */
46
+ export declare function buildPickupTimingBooking(input: PickupTimingBookingInput, existing?: Record<string, any> | null): PickupTimingBooking;
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.buildPickupTimingBooking = buildPickupTimingBooking;
7
+ exports.isFulfillmentTimingBooking = isFulfillmentTimingBooking;
8
+ var _orderCollectionIdentity = require("./utils/orderCollectionIdentity");
9
+ /** The explicit marker is the only authority; unlinked ordinary bookings remain ordinary. */
10
+ function isFulfillmentTimingBooking(booking) {
11
+ return booking?.metadata?.is_fulfillment_timing === true;
12
+ }
13
+
14
+ /** Update an independent pickup appointment without changing its persisted row identity. */
15
+ function buildPickupTimingBooking(input, existing) {
16
+ const booking = {
17
+ ...(existing || {})
18
+ };
19
+ const metadata = {
20
+ ...(booking.metadata || {})
21
+ };
22
+ // A fulfillment appointment never becomes a product/resource/holder purchase line.
23
+ for (const key of ['product_uid', 'product', 'holder', 'holder_id', 'collect_pax', 'capacity']) {
24
+ delete booking[key];
25
+ delete metadata[key];
26
+ }
27
+ delete metadata.booking_uid;
28
+ return {
29
+ ...booking,
30
+ schedule_id: booking.schedule_id || 0,
31
+ start_date: input.date,
32
+ end_date: input.date,
33
+ start_time: input.time,
34
+ end_time: input.time,
35
+ select_date: input.date,
36
+ duration: 0,
37
+ is_all: 0,
38
+ sub_type: 'minutes',
39
+ like_status: booking.like_status || 'common',
40
+ resources: [],
41
+ metadata: {
42
+ ...metadata,
43
+ unique_identification_number: String(metadata.unique_identification_number || (0, _orderCollectionIdentity.createUuidV4)()),
44
+ is_fulfillment_timing: true,
45
+ fulfillment_timing: {
46
+ method: 'pick_up',
47
+ mode: input.mode,
48
+ source: input.source,
49
+ ...(input.instant !== undefined ? {
50
+ instant: input.instant
51
+ } : {}),
52
+ ...(input.timezone !== undefined ? {
53
+ timezone: input.timezone
54
+ } : {}),
55
+ ...(input.frozen !== undefined ? {
56
+ frozen: input.frozen
57
+ } : {})
58
+ }
59
+ }
60
+ };
61
+ }
@@ -7,9 +7,10 @@ import type { SubmitPayloadEnhancer } from './utils';
7
7
  import type { PaymentItem } from '../Payment/types';
8
8
  import type { ICustomer } from '../AccountList/types';
9
9
  import type { OrderProductDiscountItem } from '../../server/modules/order/types';
10
+ import type { PickupTimingBooking, PickupTimingBookingInput } from './pickupTiming';
10
11
  export type { OrderProductDiscountItem } from '../../server/modules/order/types';
11
12
  export type PickupReferenceMode = 'counter_pickup' | 'table_service';
12
- export type ShopServiceType = 'dine_in' | 'takeaway';
13
+ export type ShopServiceType = 'dine_in' | 'takeaway' | 'pick_up' | 'delivery';
13
14
  export interface ShopServiceData {
14
15
  pickup_reference_mode?: PickupReferenceMode;
15
16
  service_type?: ShopServiceType | '';
@@ -199,10 +200,15 @@ export interface PointCardSnapshotItem {
199
200
  id: number | string;
200
201
  tag: 'point_card';
201
202
  product_id?: number | string;
203
+ code?: string;
202
204
  product_name: string;
203
205
  par_value: string;
204
206
  used_par_value: string;
205
207
  }
208
+ export interface PointCardSnapshotMergeContext {
209
+ customerId?: number | string | null;
210
+ orderIdentity?: OrderIdentitySnapshot | null;
211
+ }
206
212
  export interface VirtualCurrencySnapshotItem {
207
213
  id: number | string;
208
214
  tag: 'virtual_currency';
@@ -858,6 +864,9 @@ export interface OrderModuleAPI {
858
864
  updateTempOrderCustomer: (customer: UpdateTempOrderCustomerInput) => OrderSnapshot['customer'];
859
865
  updateTempOrderPickupReferenceMode: (mode: PickupReferenceMode) => ShopServiceData;
860
866
  updateTempOrderServiceType: (serviceType: ShopServiceType) => ShopServiceData;
867
+ getPickupTimingBooking: () => PickupTimingBooking | null;
868
+ setPickupTimingBooking: (input: PickupTimingBookingInput) => PickupTimingBooking;
869
+ clearPickupTimingBooking: () => void;
861
870
  updateTempOrderBuzzer: (buzzer: string) => string;
862
871
  updateTempOrderContactsInfo: (contactsInfo: Record<string, any> | null) => Record<string, any> | null;
863
872
  addProductToOrder: (product: Partial<OrderProduct> & OrderProductIdentity, booking?: AddProductBookingInput, options?: AddProductToOrderOptions) => Promise<OrderProduct[]>;
@@ -975,6 +984,7 @@ export interface OrderModuleAPI {
975
984
  getDiscountList: () => Discount[];
976
985
  getAvailableWalletIds: () => number[];
977
986
  getCustomerWalletPrepareConfigEpoch: () => number;
987
+ mergePointCardSnapshotCodes: (walletPassList: unknown, context?: PointCardSnapshotMergeContext) => PointCardSnapshotItem[];
978
988
  applyDiscount: () => void;
979
989
  /**
980
990
  * 注入促销评估器。
@@ -18,6 +18,16 @@ export interface OrderProductsDepositSummary {
18
18
  import type { WindowPlugin } from '../../plugins';
19
19
  export declare function normalizePointCardSnapshotItem(value: unknown): PointCardSnapshotItem | null;
20
20
  export declare function buildPointCardSnapshotFromWalletPassList(value: unknown): PointCardSnapshotItem[];
21
+ export declare function collectPointCardSnapshotCodeCandidates(value: unknown): Array<{
22
+ id: string;
23
+ code: string;
24
+ }>;
25
+ /**
26
+ * Payment 的 WalletPass 列表包含实例 code,而 prepare/config 通常只提供
27
+ * 卡名称和余额。按实例 ID 仅补齐 Snapshot 缺失的 code,避免后到的运行态
28
+ * 数据覆盖下单时已经冻结的卡名称和余额。
29
+ */
30
+ export declare function mergePointCardSnapshotCodes(snapshotsValue: unknown, walletPassesValue: unknown): PointCardSnapshotItem[];
21
31
  export declare function normalizeVirtualCurrencySnapshotItem(value: unknown): VirtualCurrencySnapshotItem | null;
22
32
  export declare function buildVirtualCurrencySnapshotFromList(value: unknown): VirtualCurrencySnapshotItem[];
23
33
  export declare function normalizeOrderMetaList(value: unknown): OrderMetaList | undefined;
@@ -14,6 +14,7 @@ exports.buildSubmitPayload = buildSubmitPayload;
14
14
  exports.buildVirtualCurrencySnapshotFromList = buildVirtualCurrencySnapshotFromList;
15
15
  exports.calculateOrderProductsDeposit = calculateOrderProductsDeposit;
16
16
  exports.clearTempOrderHolderAssignments = clearTempOrderHolderAssignments;
17
+ exports.collectPointCardSnapshotCodeCandidates = collectPointCardSnapshotCodeCandidates;
17
18
  exports.composeLinePrice = composeLinePrice;
18
19
  exports.createDefaultOrderRulesHooks = createDefaultOrderRulesHooks;
19
20
  exports.createDefaultTempOrder = createDefaultTempOrder;
@@ -66,6 +67,7 @@ Object.defineProperty(exports, "mergeManualProductDiscountIntoList", {
66
67
  }
67
68
  });
68
69
  exports.mergeOrderProductDisplayFields = mergeOrderProductDisplayFields;
70
+ exports.mergePointCardSnapshotCodes = mergePointCardSnapshotCodes;
69
71
  exports.mergeRelationForms = void 0;
70
72
  exports.normalizeBookingIsAll = normalizeBookingIsAll;
71
73
  exports.normalizeBookingSubType = normalizeBookingSubType;
@@ -133,6 +135,7 @@ var _utils = require("../Holder/utils");
133
135
  var _utils2 = require("../../solution/ScanOrder/utils");
134
136
  var _orderCollectionIdentity = require("./utils/orderCollectionIdentity");
135
137
  var _salesNotes = require("./salesNotes");
138
+ var _pickupTiming = require("./pickupTiming");
136
139
  var _manualProductDiscount = require("./utils/manualProductDiscount");
137
140
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
138
141
  _dayjs.default.extend(_customParseFormat.default);
@@ -165,6 +168,10 @@ function normalizePointCardSnapshotItem(value) {
165
168
  if (productId !== undefined) {
166
169
  snapshot.product_id = productId;
167
170
  }
171
+ const code = normalizeSnapshotScalar(value.code);
172
+ if (code !== undefined) {
173
+ snapshot.code = String(code).trim();
174
+ }
168
175
  return snapshot;
169
176
  }
170
177
  function buildPointCardSnapshotFromWalletPassList(value) {
@@ -175,6 +182,46 @@ function buildPointCardSnapshotFromWalletPassList(value) {
175
182
  return result;
176
183
  }, []);
177
184
  }
185
+ function collectPointCardSnapshotCodeCandidates(value) {
186
+ if (!Array.isArray(value)) return [];
187
+ const codeById = new Map();
188
+ value.forEach(item => {
189
+ if (!isPlainObject(item)) return;
190
+ const id = normalizeSnapshotScalar(item.id ?? item.voucher_id);
191
+ const code = normalizeSnapshotScalar(item.code);
192
+ if (id === undefined || code === undefined) return;
193
+ const identity = String(id).trim();
194
+ const normalizedCode = String(code).trim();
195
+ if (!identity || !normalizedCode || codeById.has(identity)) return;
196
+ codeById.set(identity, normalizedCode);
197
+ });
198
+ return [...codeById].map(([id, code]) => ({
199
+ id,
200
+ code
201
+ }));
202
+ }
203
+
204
+ /**
205
+ * Payment 的 WalletPass 列表包含实例 code,而 prepare/config 通常只提供
206
+ * 卡名称和余额。按实例 ID 仅补齐 Snapshot 缺失的 code,避免后到的运行态
207
+ * 数据覆盖下单时已经冻结的卡名称和余额。
208
+ */
209
+ function mergePointCardSnapshotCodes(snapshotsValue, walletPassesValue) {
210
+ const snapshots = buildPointCardSnapshotFromWalletPassList(snapshotsValue);
211
+ if (!snapshots.length || !Array.isArray(walletPassesValue)) return snapshots;
212
+ const codeById = new Map(collectPointCardSnapshotCodeCandidates(walletPassesValue).map(({
213
+ id,
214
+ code
215
+ }) => [id, code]));
216
+ return snapshots.map(snapshot => {
217
+ if (snapshot.code) return snapshot;
218
+ const code = codeById.get(String(snapshot.id).trim());
219
+ return code ? {
220
+ ...snapshot,
221
+ code
222
+ } : snapshot;
223
+ });
224
+ }
178
225
  function normalizeSnapshotNameMap(value) {
179
226
  if (typeof value === 'string' || typeof value === 'number') {
180
227
  const normalizedText = String(value).trim();
@@ -1251,7 +1298,7 @@ function normalizeSubmitProduct(product, formatOptions = {}) {
1251
1298
  payment_price: formatOptions.virtualSettlementMode ? virtualPrice : submitProduct.payment_price ?? submitProduct.selling_price
1252
1299
  };
1253
1300
  }
1254
- const SUBMIT_BOOKING_METADATA_WHITELIST = ['unique_identification_number', 'collect_pax', 'capacity', 'holder_id', 'resource_select_type', 'holder'];
1301
+ const SUBMIT_BOOKING_METADATA_WHITELIST = ['unique_identification_number', 'collect_pax', 'capacity', 'holder_id', 'resource_select_type', 'holder', 'is_fulfillment_timing', 'fulfillment_timing'];
1255
1302
  function normalizeBookingIsAll(booking) {
1256
1303
  return booking.is_all === true || booking.is_all === 1 || booking.is_all === '1' || booking.is_all === 'true' ? 1 : 0;
1257
1304
  }
@@ -1270,7 +1317,9 @@ function normalizeSubmitBooking(booking) {
1270
1317
  cleanMetadata[key] = rawMetadata[key];
1271
1318
  }
1272
1319
  }
1273
- if (cleanMetadata.holder_id === undefined) cleanMetadata.holder_id = null;
1320
+ if (cleanMetadata.holder_id === undefined && !(0, _pickupTiming.isFulfillmentTimingBooking)(booking)) {
1321
+ cleanMetadata.holder_id = null;
1322
+ }
1274
1323
  const bookingRecord = booking;
1275
1324
  const startDate = bookingRecord.start_date;
1276
1325
  const shouldRestoreSelectDate = bookingRecord.select_date === undefined || bookingRecord.select_date === null || bookingRecord.select_date === '';
@@ -1282,6 +1331,10 @@ function normalizeSubmitBooking(booking) {
1282
1331
  } : {}),
1283
1332
  is_all: normalizeBookingIsAll(bookingRecord),
1284
1333
  sub_type: normalizeBookingSubType(bookingRecord),
1334
+ ...((0, _pickupTiming.isFulfillmentTimingBooking)(booking) ? {
1335
+ like_status: bookingRecord.like_status || 'common',
1336
+ schedule_id: bookingRecord.schedule_id || 0
1337
+ } : {}),
1285
1338
  metadata: cleanMetadata
1286
1339
  };
1287
1340
  }
@@ -1367,6 +1420,7 @@ function hasBoundBookingResource(bookings) {
1367
1420
  if (!Array.isArray(bookings)) return false;
1368
1421
  return bookings.some(booking => {
1369
1422
  if (!booking || typeof booking !== 'object') return false;
1423
+ if ((0, _pickupTiming.isFulfillmentTimingBooking)(booking)) return false;
1370
1424
  if (Number(booking.parent_id) === 0) return false;
1371
1425
  const resources = booking.resources;
1372
1426
  if (!Array.isArray(resources)) return false;
@@ -1402,7 +1456,7 @@ function resolveSubmitOrderType(params) {
1402
1456
  return 'table-order';
1403
1457
  }
1404
1458
  if (currentType) return currentType;
1405
- if (Array.isArray(bookings) && bookings.length > 0) {
1459
+ if (Array.isArray(bookings) && bookings.some(booking => !(0, _pickupTiming.isFulfillmentTimingBooking)(booking))) {
1406
1460
  return businessCode === 'dine_in' ? 'table-order' : 'appointment_booking';
1407
1461
  }
1408
1462
  return 'virtual';
@@ -1487,7 +1541,7 @@ function buildSubmitPayload(params) {
1487
1541
  delivery_type: tempOrder.delivery_type || 'nil',
1488
1542
  schedule_date: scheduleDate,
1489
1543
  // created_at: tempOrder.created_at || formatDateTime(now), 和后端协定这个字段先不提交到接口
1490
- bookings: (tempOrder.bookings || []).filter(booking => Number(booking.parent_id) !== 0).map(booking => normalizeSubmitBooking(booking)),
1544
+ bookings: (tempOrder.bookings || []).filter(booking => (0, _pickupTiming.isFulfillmentTimingBooking)(booking) || Number(booking.parent_id) !== 0).map(booking => normalizeSubmitBooking(booking)),
1491
1545
  payments: tempOrder.payments || [],
1492
1546
  relation_forms: tempOrder.relation_forms || [],
1493
1547
  // contacts: tempOrder.contacts || [],
@@ -1187,11 +1187,12 @@ class RulesModule extends _BaseModule.BaseModule {
1187
1187
  });
1188
1188
  const appliedEntitlementPass = options?.scan || options?.preserveEntitlementSelection === false ? undefined : applicableDiscounts.find(discount => (0, _entitlementPass.isEntitlementPass)(discount) && discount.isSelected === true && appliedWalletDiscountIds.has(String(discount.id)));
1189
1189
 
1190
- // 次卡内部按本次手选/扫码、当前行已应用、默认排序选择;仍保留次卡相对其他券的优先级。
1190
+ // 次卡内部按本次手选/扫码、当前行已应用、默认排序选择。
1191
1191
  const scannedSelectedDiscountCard = applicableDiscounts.find(n => n.isScan && n.isSelected && !(0, _entitlementPass.isFullCoveragePass)(n));
1192
1192
  const selectedDiscountCard = explicitlySelectedDiscountCard || appliedDiscountCard || scannedSelectedDiscountCard;
1193
1193
  const selectedEntitlementPass = (explicitlySelectedDiscount && (0, _entitlementPass.isEntitlementPass)(explicitlySelectedDiscount) ? explicitlySelectedDiscount : undefined) || applicableDiscounts.find(discount => (0, _entitlementPass.isEntitlementPass)(discount) && preferredEntitlementPassIds.has(String(discount.id))) || appliedEntitlementPass || applicableDiscounts.find(discount => (0, _entitlementPass.isEntitlementPass)(discount));
1194
- const selectedDiscount = selectedEntitlementPass || explicitlySelectedDiscount || selectedDiscountCard || applicableDiscounts[0];
1194
+ // 手选优先;普通重算保留当前折扣卡,扫码和最优选择仍使用原有优先级。
1195
+ const selectedDiscount = explicitlySelectedDiscount || (options?.preserveEntitlementSelection !== false && preferredEntitlementPassIds.size === 0 ? appliedDiscountCard : undefined) || selectedEntitlementPass || selectedDiscountCard || applicableDiscounts[0];
1195
1196
  // 实际次数分配必须使用同一顺序,避免选中状态切换后仍将次数分配给默认卡。
1196
1197
  const prioritizedApplicableDiscounts = selectedDiscount && (0, _entitlementPass.isFullCoveragePass)(selectedDiscount) ? [selectedDiscount, ...applicableDiscounts.filter(discount => {
1197
1198
  return (0, _entitlementPass.isFullCoveragePass)(discount) && String(discount.id) !== String(selectedDiscount.id);
@@ -10,6 +10,10 @@ export declare class ScheduleModule extends BaseModule implements Module, Schedu
10
10
  private openCache;
11
11
  private fatherModule;
12
12
  private hasLoadedScheduleList;
13
+ private scheduleListPromise;
14
+ private scheduleListRequestVersion;
15
+ private scheduleCatalogScope;
16
+ private otherParams;
13
17
  constructor(name?: string, version?: string);
14
18
  initialize(core: PisellCore, options: ModuleOptions): Promise<void>;
15
19
  /**
@@ -19,6 +23,9 @@ export declare class ScheduleModule extends BaseModule implements Module, Schedu
19
23
  * scheduleModule.updateOtherParams({ openCache: true, cacheId: 'cache-2' });
20
24
  */
21
25
  updateOtherParams(params: Record<string, any>): void;
26
+ private syncScheduleCatalogScope;
27
+ private resetScheduleCatalog;
28
+ destroy(): void;
22
29
  /**
23
30
  * 加载当前店铺下所有 schedule
24
31
  *
@@ -27,6 +34,7 @@ export declare class ScheduleModule extends BaseModule implements Module, Schedu
27
34
  loadAllSchedule(options?: {
28
35
  useCache?: boolean;
29
36
  }): Promise<void>;
37
+ private loadScheduleCatalog;
30
38
  setScheduleList(list: ScheduleItem[]): void;
31
39
  private syncScheduleMap;
32
40
  getScheduleList(): ScheduleItem[];
@@ -24,10 +24,15 @@ class ScheduleModule extends _BaseModule.BaseModule {
24
24
  openCache = false;
25
25
  fatherModule;
26
26
  hasLoadedScheduleList = false;
27
+ scheduleListPromise = null;
28
+ scheduleListRequestVersion = 0;
29
+ scheduleCatalogScope = '';
30
+ otherParams = {};
27
31
  constructor(name, version) {
28
32
  super(name, version);
29
33
  }
30
34
  async initialize(core, options) {
35
+ this.resetScheduleCatalog();
31
36
  this.core = core;
32
37
 
33
38
  // 获取依赖的插件
@@ -40,8 +45,9 @@ class ScheduleModule extends _BaseModule.BaseModule {
40
45
  // this.state = { ...this.state, ...options.initialState }
41
46
  // }
42
47
  this.store = options?.store;
48
+ this.updateOtherParams(options.otherParams || {});
43
49
  if (options.initialState) {
44
- this.store.scheduleList = options.initialState.scheduleList;
50
+ this.store.scheduleList = options.initialState.scheduleList || [];
45
51
  this.syncScheduleMap();
46
52
  this.store.availabilityDateList = options.initialState.availabilityDateList;
47
53
  this.hasLoadedScheduleList = Array.isArray(options.initialState.scheduleList);
@@ -51,13 +57,6 @@ class ScheduleModule extends _BaseModule.BaseModule {
51
57
  this.store.availabilityDateList = [];
52
58
  this.hasLoadedScheduleList = false;
53
59
  }
54
- if (options.otherParams?.cacheId) {
55
- this.openCache = options.otherParams.openCache;
56
- this.cacheId = options.otherParams.cacheId;
57
- }
58
- if (options.otherParams?.fatherModule) {
59
- this.fatherModule = options.otherParams.fatherModule;
60
- }
61
60
  }
62
61
 
63
62
  /**
@@ -68,9 +67,34 @@ class ScheduleModule extends _BaseModule.BaseModule {
68
67
  */
69
68
  updateOtherParams(params) {
70
69
  const otherParams = params || {};
70
+ this.otherParams = {
71
+ ...otherParams
72
+ };
71
73
  this.openCache = otherParams.openCache ?? false;
72
74
  this.cacheId = otherParams.cacheId;
73
75
  this.fatherModule = otherParams.fatherModule;
76
+ this.syncScheduleCatalogScope();
77
+ }
78
+ syncScheduleCatalogScope() {
79
+ const context = this.core?.context;
80
+ const shopId = typeof context?.getData === 'function' ? context.getData('shop_id') : context?.shop_id;
81
+ const params = this.otherParams;
82
+ const scope = JSON.stringify([this.cacheId, this.fatherModule, params.businessCode ?? params.business_code, params.channel, params.platform, params.businessTimezone, shopId].map(value => String(value ?? '').trim()));
83
+ if (scope !== this.scheduleCatalogScope) {
84
+ this.resetScheduleCatalog();
85
+ this.scheduleCatalogScope = scope;
86
+ }
87
+ }
88
+ resetScheduleCatalog() {
89
+ this.scheduleListRequestVersion += 1;
90
+ this.scheduleListPromise = null;
91
+ this.hasLoadedScheduleList = false;
92
+ this.store.scheduleList = [];
93
+ this.store.map = new Map();
94
+ }
95
+ destroy() {
96
+ this.resetScheduleCatalog();
97
+ super.destroy();
74
98
  }
75
99
 
76
100
  /**
@@ -79,14 +103,40 @@ class ScheduleModule extends _BaseModule.BaseModule {
79
103
  * @memberof ScheduleModule
80
104
  */
81
105
  async loadAllSchedule(options = {}) {
82
- const scheduleList = await this.request.get(`/schedule`, {
106
+ this.syncScheduleCatalogScope();
107
+ const useCache = options.useCache ?? true;
108
+ if (useCache) {
109
+ if (this.scheduleListPromise) return this.scheduleListPromise;
110
+ if (this.hasLoadedScheduleList) return;
111
+ }
112
+ const requestVersion = ++this.scheduleListRequestVersion;
113
+ const promise = this.loadScheduleCatalog(useCache, requestVersion);
114
+ this.scheduleListPromise = promise;
115
+ try {
116
+ await promise;
117
+ } finally {
118
+ if (this.scheduleListPromise === promise) {
119
+ this.scheduleListPromise = null;
120
+ }
121
+ }
122
+ }
123
+ async loadScheduleCatalog(useCache, requestVersion) {
124
+ const response = await this.request.get(`/schedule`, {
83
125
  num: 999
84
126
  }, {
85
- useCache: options.useCache ?? true
127
+ useCache
86
128
  });
87
- this.setScheduleList(scheduleList.data?.list || []);
129
+ if (response.code !== undefined && Number(response.code) !== 200 || response.status === false || typeof response.status === 'number' && response.status >= 400 || response.success === false) {
130
+ throw new Error(response.message || 'Schedule catalog request failed');
131
+ }
132
+ this.syncScheduleCatalogScope();
133
+ // A refresh or a different runtime may have superseded this request.
134
+ if (requestVersion === this.scheduleListRequestVersion) {
135
+ this.setScheduleList(response.data?.list || []);
136
+ }
88
137
  }
89
138
  setScheduleList(list) {
139
+ this.syncScheduleCatalogScope();
90
140
  this.store.scheduleList = list;
91
141
  this.syncScheduleMap();
92
142
  this.hasLoadedScheduleList = true;
@@ -95,9 +145,11 @@ class ScheduleModule extends _BaseModule.BaseModule {
95
145
  this.store.map = new Map((this.store.scheduleList || []).map(schedule => [Number(schedule.id), schedule]));
96
146
  }
97
147
  getScheduleList() {
148
+ this.syncScheduleCatalogScope();
98
149
  return this.store.scheduleList || [];
99
150
  }
100
151
  isScheduleListLoaded() {
152
+ this.syncScheduleCatalogScope();
101
153
  return this.hasLoadedScheduleList;
102
154
  }
103
155
  async loadScheduleAvailableDate({
@@ -149,6 +201,7 @@ class ScheduleModule extends _BaseModule.BaseModule {
149
201
  return dates;
150
202
  }
151
203
  getScheduleListByIds(ids) {
204
+ this.syncScheduleCatalogScope();
152
205
  const idSet = new Set(ids.map(id => String(id)));
153
206
  if (this.store.map instanceof Map && this.store.map.size > 0) {
154
207
  return ids.map(id => this.store.map.get(Number(id))).filter(Boolean);