@pisell/pisellos 0.10.49 → 0.10.51

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 (62) hide show
  1. package/dist/modules/Discount/types.d.ts +2 -0
  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 +19 -1
  8. package/dist/modules/Order/index.js +362 -217
  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 +45 -26
  15. package/dist/modules/Rules/types.d.ts +4 -0
  16. package/dist/modules/index.d.ts +1 -0
  17. package/dist/modules/index.js +1 -0
  18. package/dist/server/modules/order/index.d.ts +8 -0
  19. package/dist/server/modules/order/index.js +506 -453
  20. package/dist/server/modules/order/types.d.ts +1 -0
  21. package/dist/server/utils/small-ticket.js +173 -95
  22. package/dist/solution/BaseSales/index.d.ts +23 -0
  23. package/dist/solution/BaseSales/index.js +1216 -1031
  24. package/dist/solution/BaseSales/pickupTiming.d.ts +37 -0
  25. package/dist/solution/BaseSales/pickupTiming.js +482 -0
  26. package/dist/solution/BaseSales/utils/parseSalesResponse.js +3 -0
  27. package/dist/solution/BookingByStep/index.d.ts +1 -1
  28. package/dist/solution/BookingTicket/utils/cartView.js +3 -1
  29. package/dist/solution/UnifiedBookingSales/index.d.ts +2 -0
  30. package/dist/solution/UnifiedBookingSales/index.js +583 -531
  31. package/lib/model/strategy/adapter/promotion/index.js +46 -1
  32. package/lib/modules/Discount/types.d.ts +2 -0
  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 +19 -1
  39. package/lib/modules/Order/index.js +120 -7
  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 +24 -13
  46. package/lib/modules/Rules/types.d.ts +4 -0
  47. package/lib/modules/index.d.ts +1 -0
  48. package/lib/modules/index.js +11 -0
  49. package/lib/server/modules/order/index.d.ts +8 -0
  50. package/lib/server/modules/order/index.js +19 -0
  51. package/lib/server/modules/order/types.d.ts +1 -0
  52. package/lib/server/utils/small-ticket.js +93 -22
  53. package/lib/solution/BaseSales/index.d.ts +23 -0
  54. package/lib/solution/BaseSales/index.js +104 -1
  55. package/lib/solution/BaseSales/pickupTiming.d.ts +37 -0
  56. package/lib/solution/BaseSales/pickupTiming.js +222 -0
  57. package/lib/solution/BaseSales/utils/parseSalesResponse.js +3 -0
  58. package/lib/solution/BookingByStep/index.d.ts +1 -1
  59. package/lib/solution/BookingTicket/utils/cartView.js +3 -1
  60. package/lib/solution/UnifiedBookingSales/index.d.ts +2 -0
  61. package/lib/solution/UnifiedBookingSales/index.js +20 -0
  62. package/package.json +1 -1
@@ -29,6 +29,10 @@ export interface RulesCalcDiscountOptions {
29
29
  discountId?: number;
30
30
  selectedList?: SetDiscountSelectedParams[];
31
31
  scan?: boolean;
32
+ /** 本次扫码优先使用的次卡实例;普通重算不传,继续保留商品行当前选择。 */
33
+ preferredEntitlementPassIds?: number[];
34
+ /** 普通重算保留商品行已应用的次卡;最优选择传 false 恢复自动排序。 */
35
+ preserveEntitlementSelection?: boolean;
32
36
  /** Change time 重报价目标行:允许已选编辑态折扣卡重新应用到这些 booking 商品。 */
33
37
  reapplyBookingDiscountProductUids?: string[];
34
38
  }
@@ -17,6 +17,7 @@ export * from './Schedule';
17
17
  export * from './Quotation';
18
18
  export * from './ScanOrderLogger';
19
19
  export * from './OpenData';
20
+ export * from './FulfillmentTiming';
20
21
  export * from './BookingContext';
21
22
  export * from './Holder';
22
23
  export * from './ResourcePlanner';
@@ -212,6 +212,17 @@ Object.keys(_OpenData).forEach(function (key) {
212
212
  }
213
213
  });
214
214
  });
215
+ var _FulfillmentTiming = require("./FulfillmentTiming");
216
+ Object.keys(_FulfillmentTiming).forEach(function (key) {
217
+ if (key === "default" || key === "__esModule") return;
218
+ if (key in exports && exports[key] === _FulfillmentTiming[key]) return;
219
+ Object.defineProperty(exports, key, {
220
+ enumerable: true,
221
+ get: function () {
222
+ return _FulfillmentTiming[key];
223
+ }
224
+ });
225
+ });
215
226
  var _BookingContext = require("./BookingContext");
216
227
  Object.keys(_BookingContext).forEach(function (key) {
217
228
  if (key === "default" || key === "__esModule") return;
@@ -24,6 +24,7 @@ export declare class OrderModule extends BaseModule implements Module {
24
24
  private lastRecentSqliteWriteCleanupAtMs;
25
25
  /** 最近一次 SSE 全量拉取完成时间 */
26
26
  private lastServerFullFetchAtMs;
27
+ private businessDayRefreshPromise;
27
28
  constructor(name?: string, version?: string);
28
29
  /**
29
30
  * 广播订单变更事件,兼容全量列表订阅并额外携带本次变更订单。
@@ -130,6 +131,13 @@ export declare class OrderModule extends BaseModule implements Module {
130
131
  loadOrdersByServer(options?: {
131
132
  trigger?: string;
132
133
  }): Promise<OrderData[]>;
134
+ /**
135
+ * 供展示屏按需补齐当前营业日的订单快照;成功标记复用全量拉取落库逻辑。
136
+ * 拉取或落库失败时不记录已刷新,下一次调用仍可重试。
137
+ */
138
+ refreshOrdersForCurrentBusinessDay(options?: {
139
+ trigger?: string;
140
+ }): Promise<void>;
133
141
  /**
134
142
  * 静默全量刷新:后台重新拉取全量 SSE 订单数据并更新本地
135
143
  * 拿到完整数据后一次性替换 store,触发订单变更与同步完成事件
@@ -61,6 +61,7 @@ class OrderModule extends _BaseModule.BaseModule {
61
61
  lastRecentSqliteWriteCleanupAtMs = 0;
62
62
  /** 最近一次 SSE 全量拉取完成时间 */
63
63
  lastServerFullFetchAtMs = 0;
64
+ businessDayRefreshPromise = null;
64
65
  constructor(name, version) {
65
66
  super(name, version);
66
67
  }
@@ -864,6 +865,24 @@ class OrderModule extends _BaseModule.BaseModule {
864
865
  return memoryOrders;
865
866
  }
866
867
 
868
+ /**
869
+ * 供展示屏按需补齐当前营业日的订单快照;成功标记复用全量拉取落库逻辑。
870
+ * 拉取或落库失败时不记录已刷新,下一次调用仍可重试。
871
+ */
872
+ async refreshOrdersForCurrentBusinessDay(options = {}) {
873
+ if (this.businessDayRefreshPromise) return this.businessDayRefreshPromise;
874
+ if (!this.orderDataSource || !this.getCurrentShopId() || this.hasPulledOrdersToday()) return;
875
+ const refreshPromise = this.silentRefresh({
876
+ trigger: options.trigger || 'business_day_rollover'
877
+ }).then(() => undefined);
878
+ this.businessDayRefreshPromise = refreshPromise;
879
+ try {
880
+ await refreshPromise;
881
+ } finally {
882
+ this.businessDayRefreshPromise = null;
883
+ }
884
+ }
885
+
867
886
  /**
868
887
  * 静默全量刷新:后台重新拉取全量 SSE 订单数据并更新本地
869
888
  * 拿到完整数据后一次性替换 store,触发订单变更与同步完成事件
@@ -454,6 +454,7 @@ export interface OrderData {
454
454
  id: number | string;
455
455
  tag: 'point_card';
456
456
  product_id?: number | string;
457
+ code?: string;
457
458
  product_name: string;
458
459
  par_value: string;
459
460
  used_par_value: string;
@@ -7,6 +7,7 @@ exports.buildSmallTicketData = buildSmallTicketData;
7
7
  exports.hasSmallTicketData = hasSmallTicketData;
8
8
  var _dayjs = _interopRequireDefault(require("dayjs"));
9
9
  var _decimal = _interopRequireDefault(require("decimal.js"));
10
+ var _pickupTiming = require("../../modules/Order/pickupTiming");
10
11
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
11
12
  const DEFAULT_LOCALES = ['en', 'zh_CN'];
12
13
  const SERVICE_TYPE_LABEL_KEYS = {
@@ -511,7 +512,16 @@ function resolveProductBasePrice(product) {
511
512
  const variantId = product.product_variant_id ?? product.variant_id;
512
513
  const variant = Array.isArray(origin.variant) && isPresentId(variantId) ? origin.variant.find(item => isSameId(item?.id, variantId)) : undefined;
513
514
  const isV2 = metadata.price_schema_version === 2 || metadata.price_schema_version === '2';
514
- return firstPresentValue(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);
515
+ const quantity = toNumber(product.product_quantity ?? product.quantity ?? product.num ?? 1, 1);
516
+ const hasEntitlementPass = normalizeArray(product.discount_list).some(discount => getProductWalletDiscountType(discount) === 'entitlement_pass');
517
+ const hasOptions = normalizeArray(product.options).length > 0 || normalizeArray(product.product_sku?.option).length > 0;
518
+ const hasCombinations = normalizeArray(product.combinations).length > 0 || normalizeArray(product.product_bundle).length > 0 || normalizeArray(product.bundle).length > 0;
519
+ const aggregateSourcePrice = firstPresentValue(metadata.source_product_price, product.price);
520
+ const originalPrice = product.original_price;
521
+ // 部分旧 entitlement 商品把 source/price 持久化成了整行总价,但
522
+ // original_price 仍是单件价。仅在等式能明确证明该口径时做窄幅回退。
523
+ const useOriginalPriceAsLegacyUnitPrice = hasEntitlementPass && quantity > 1 && !hasOptions && !hasCombinations && isNumericAmount(aggregateSourcePrice) && isNumericAmount(originalPrice) && new _decimal.default(aggregateSourcePrice).eq(new _decimal.default(originalPrice).mul(quantity));
524
+ return firstPresentValue(useOriginalPriceAsLegacyUnitPrice ? originalPrice : undefined, metadata.source_product_price, variant?.price, product._origin?.price, product._origin?.base_price, !isV2 ? metadata.main_product_original_price : undefined, product.base_price, product.price);
515
525
  }
516
526
  function normalizeProductDiscounts(params) {
517
527
  const {
@@ -527,7 +537,9 @@ function normalizeProductDiscounts(params) {
527
537
  const title = resolveDiscountTitleWithPercent(discount, locale);
528
538
  const amount = discount.amount ?? discount.value ?? discount.discount_amount;
529
539
  const isMarkup = isManualMarkup(discount);
530
- const totalAmount = new _decimal.default(toMoneyNumber(amount)).mul(quantity);
540
+ const isAggregatedBundleDiscount = Boolean(getReceiptDiscountBundleMapId(discount));
541
+ const isEntitlementPass = getProductWalletDiscountType(discount) === 'entitlement_pass';
542
+ const totalAmount = new _decimal.default(toMoneyNumber(amount)).mul(isAggregatedBundleDiscount || isEntitlementPass ? 1 : quantity);
531
543
  const formattedAmount = isMarkup ? formatMoney(totalAmount.abs(), currencySymbol) : formatNegativeMoney(totalAmount, currencySymbol);
532
544
  return {
533
545
  item: title,
@@ -620,6 +632,9 @@ function buildProductQuantityPriceText(params) {
620
632
  }
621
633
  function resolveDiscountTitleWithPercent(discount, locale) {
622
634
  const title = resolveDiscountTitle(discount, locale);
635
+ if (getProductWalletDiscountType(discount) === 'entitlement_pass') {
636
+ return title;
637
+ }
623
638
  const percentRaw = discount.discount?.percent;
624
639
  const cardType = discount.discount?.discount_card_type ?? 'percent';
625
640
  const percent = percentRaw !== undefined && percentRaw !== null ? Math.trunc(Number(percentRaw)) : NaN;
@@ -760,9 +775,11 @@ function normalizeExistingCombinations(combinations, locale, currencySymbol) {
760
775
  options: rawOptions
761
776
  });
762
777
  const nestedCombinations = Array.isArray(combination.combinations) ? normalizeExistingCombinations(combination.combinations, locale, currencySymbol) : [];
778
+ const productTitle = getBundleProductTitleSource(combination, locale);
763
779
  const snapshot = {
764
780
  ...combination
765
781
  };
782
+ delete snapshot.product_title;
766
783
  delete snapshot.product_note;
767
784
  delete snapshot.note;
768
785
  delete snapshot.combinations;
@@ -782,6 +799,9 @@ function normalizeExistingCombinations(combinations, locale, currencySymbol) {
782
799
  const shouldIncludeBasePrice = normalizedOptions.length > 0 || nestedCombinations.length > 0 || hasProductDiscount(combination, extensionList);
783
800
  return {
784
801
  ...snapshot,
802
+ ...(productTitle ? {
803
+ product_title: productTitle
804
+ } : {}),
785
805
  product_quantity: quantity,
786
806
  total_selling_price: formatCalculatedMoney(new _decimal.default(toMoneyNumber(unitPrice)).mul(quantity), currencySymbol),
787
807
  ...(shouldIncludeBasePrice ? {
@@ -850,18 +870,18 @@ function resolveBundleProductTitle(params) {
850
870
  locale,
851
871
  parentProduct
852
872
  } = params;
853
- const ownTitle = getProductTitleSource(bundle, locale);
873
+ const ownTitle = getBundleProductTitleSource(bundle, locale);
854
874
  if (ownTitle) return ownTitle;
855
875
  const parentBundleItem = resolveBundleItemFromParentProduct({
856
876
  bundle,
857
877
  parentProduct
858
878
  });
859
- const parentBundleTitle = parentBundleItem ? getProductTitleSource(parentBundleItem, locale) : '';
879
+ const parentBundleTitle = parentBundleItem ? getBundleProductTitleSource(parentBundleItem, locale) : '';
860
880
  if (parentBundleTitle) return parentBundleTitle;
861
881
  const productId = bundle.bundle_product_id ?? bundle._bundle_product_id ?? bundle.product_id ?? bundle.id;
862
882
  const fallbackProduct = resolveProductFromMap(productId, productMap);
863
883
  if (!fallbackProduct) return '';
864
- return getProductTitleSource(fallbackProduct, locale);
884
+ return getBundleProductTitleSource(fallbackProduct, locale);
865
885
  }
866
886
  function resolveProductFromMap(productId, productMap) {
867
887
  if (productId === undefined || productId === null || productId === '') return null;
@@ -933,7 +953,11 @@ function resolveVariantItem(group, itemPositionId) {
933
953
  }
934
954
  function getProductTitleSource(source, locale) {
935
955
  if (!source || typeof source !== 'object') return '';
936
- return resolveTitleValue(source.product_title, locale) || resolveTitleValue(source.title, locale) || resolveTitleValue(source.name, locale);
956
+ return resolveTitleValue(source.product_title, locale) || resolveTitleValue(source.title_i18n, locale) || resolveTitleValue(source.title, locale) || resolveTitleValue(source.name, locale);
957
+ }
958
+ function getBundleProductTitleSource(source, locale) {
959
+ if (!source || typeof source !== 'object') return '';
960
+ return resolveTitleValue(source.bundle_title, locale) || resolveTitleValue(source.trans_title, locale) || resolveTitleValue(source.title_i18n, locale) || resolveTitleValue(source.product_title, locale) || resolveTitleValue(source.title, locale) || resolveTitleValue(source.name, locale);
937
961
  }
938
962
  function resolveTitleValue(value, locale) {
939
963
  if (value === undefined || value === null) return '';
@@ -1173,11 +1197,21 @@ function buildDelivery(order, locale) {
1173
1197
  const serviceTypeLabelKey = resolveServiceTypeLabelKey(serviceType);
1174
1198
  if (!serviceTypeLabelKey) return [];
1175
1199
  const items = [{
1176
- item: '',
1177
- value: label(locale, serviceTypeLabelKey),
1178
- size: 2,
1179
- align: 'left'
1200
+ item: label(locale, serviceTypeLabelKey),
1201
+ value: '',
1202
+ size: 2
1180
1203
  }];
1204
+ if (serviceType === 'pick_up') {
1205
+ const pickupTime = formatPickupBookingStart(order.bookings, locale);
1206
+ if (pickupTime) {
1207
+ items.push({
1208
+ item: pickupTime,
1209
+ value: '',
1210
+ size: 2
1211
+ });
1212
+ return items;
1213
+ }
1214
+ }
1181
1215
  const handoverTime = firstPresentValue(order.handover_time, order.handover_at, order.pickup_time, order.pickup_at, order.scheduled_pickup_time, order.delivery_time, order.delivery_at, order.scheduled_delivery_time, order.fulfillment_time, order.fulfillment_at, order.metadata?.handover_time, order.metadata?.handover_at, order.metadata?.pickup_time, order.metadata?.pickup_at, order.metadata?.scheduled_pickup_time, order.metadata?.delivery_time, order.metadata?.delivery_at, order.metadata?.scheduled_delivery_time, order.metadata?.fulfillment_time, order.metadata?.fulfillment_at);
1182
1216
  if (isPresentId(handoverTime)) {
1183
1217
  items.push({
@@ -1187,8 +1221,27 @@ function buildDelivery(order, locale) {
1187
1221
  }
1188
1222
  return items;
1189
1223
  }
1224
+ function formatPickupBookingStart(bookings, locale) {
1225
+ const booking = normalizeArray(bookings).find(_pickupTiming.isFulfillmentTimingBooking);
1226
+ const date = stringify(booking?.start_date).trim();
1227
+ const time = stringify(booking?.start_time).trim();
1228
+ if (!/^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/.test(date) || !/^([01]\d|2[0-3]):[0-5]\d(?::[0-5]\d)?$/.test(time)) {
1229
+ return '';
1230
+ }
1231
+
1232
+ // Validate only the calendar date in UTC; display the stored shop-local
1233
+ // fields verbatim so the printing device's timezone cannot shift pickup.
1234
+ const calendarDate = new Date(`${date}T00:00:00.000Z`);
1235
+ if (!Number.isFinite(calendarDate.getTime()) || calendarDate.toISOString().slice(0, 10) !== date) {
1236
+ return '';
1237
+ }
1238
+ const [year, month, day] = date.split('-');
1239
+ const dateText = locale === 'en' ? `${day}/${month}/${year}` : `${year}/${month}/${day}`;
1240
+ return `${time.slice(0, 5)} ${dateText}`;
1241
+ }
1190
1242
  function buildAppointment(order, locale) {
1191
- const bookings = sortBookings(normalizeArray(order.bookings));
1243
+ const parentScheduleId = order.metadata?.appointment_booking_parent_schedule_id;
1244
+ const bookings = sortBookings(normalizeArray(order.bookings).filter(booking => !(0, _pickupTiming.isFulfillmentTimingBooking)(booking) && isReceiptAppointmentBooking(booking, parentScheduleId)));
1192
1245
  if (bookings.length) {
1193
1246
  const allResources = normalizeArray(order.resources || order.resource_list);
1194
1247
  return bookings.flatMap(booking => buildAppointmentRows({
@@ -1205,6 +1258,19 @@ function buildAppointment(order, locale) {
1205
1258
  value: getLocalizedValue(tableName, locale)
1206
1259
  }];
1207
1260
  }
1261
+ function isReceiptAppointmentBooking(booking, parentScheduleId) {
1262
+ if (!booking || typeof booking !== 'object' || Array.isArray(booking)) {
1263
+ return false;
1264
+ }
1265
+ if (isPresentId(parentScheduleId) && isSameId(booking.schedule_event_id, parentScheduleId)) {
1266
+ return false;
1267
+ }
1268
+ const parentId = booking.parent_id;
1269
+ if (parentId === undefined || parentId === null || parentId === '') {
1270
+ return true;
1271
+ }
1272
+ return Number(parentId) !== 0;
1273
+ }
1208
1274
  function buildAppointmentRows(params) {
1209
1275
  const {
1210
1276
  booking,
@@ -1591,11 +1657,14 @@ function getProductWalletDiscountTime(asset) {
1591
1657
  }
1592
1658
  function getProductEntitlementBalance(asset) {
1593
1659
  for (const record of asset.records) {
1594
- const balance = getEntitlementBalance(record.metadata?.balance, record.balance, record.discount?.metadata?.balance, record.discount?.balance);
1660
+ const balance = getProductDiscountEntitlementBalance(record);
1595
1661
  if (balance) return balance;
1596
1662
  }
1597
1663
  return null;
1598
1664
  }
1665
+ function getProductDiscountEntitlementBalance(record) {
1666
+ return getEntitlementBalance(record.metadata?.balance, record.balance, record.discount?.metadata?.balance, record.discount?.balance);
1667
+ }
1599
1668
  function getEntitlementBalance(...values) {
1600
1669
  for (const value of values) {
1601
1670
  if (!isNumericAmount(value)) continue;
@@ -1668,7 +1737,7 @@ function buildPointCardSnapshotAssets(params) {
1668
1737
  codes: []
1669
1738
  })) return [];
1670
1739
  const rows = [{
1671
- item: stringify(pointCard.product_name).trim() || label(params.locale, 'pointCard'),
1740
+ item: appendMaskedCardCode(stringify(pointCard.product_name).trim() || label(params.locale, 'pointCard'), pointCard.code),
1672
1741
  value: ''
1673
1742
  }];
1674
1743
  const remainingPoints = getPointCardSnapshotRemainingPoints(pointCard);
@@ -2140,9 +2209,14 @@ function isPointCardPayment(payment) {
2140
2209
  return code === 'POINTCARD' || type === 'point_card';
2141
2210
  }
2142
2211
  function maskMachineCode(code) {
2143
- const text = stringify(code);
2144
- if (text.length <= 6) return text;
2145
- return `${text.slice(0, 2)}**${text.slice(-4)}`;
2212
+ const text = stringify(code).trim();
2213
+ if (!text) return '';
2214
+ if (text.length <= 6) return '*'.repeat(text.length);
2215
+ return `${text.slice(0, 2)}****${text.slice(-4)}`;
2216
+ }
2217
+ function appendMaskedCardCode(name, code) {
2218
+ const maskedCode = maskMachineCode(code);
2219
+ return maskedCode ? `${name}(${maskedCode})` : name;
2146
2220
  }
2147
2221
  function trimDecimal(value) {
2148
2222
  return value.toDecimalPlaces(2).toFixed(2).replace(/\.?0+$/, '');
@@ -2151,16 +2225,13 @@ function getFundRecordPaymentName(payment, locale) {
2151
2225
  const fundRecord = getFundRecord(payment);
2152
2226
  if (!fundRecord) return '';
2153
2227
  const name = getLocalizedValue(fundRecord.wallet_pass_name, locale);
2154
- const code = maskMachineCode(fundRecord.code);
2155
- if (!name && !code) return '';
2156
- if (!code) return name;
2228
+ if (!name && !maskMachineCode(fundRecord.code)) return '';
2157
2229
  const fallbackName = getLocalizedValue(payment.name || payment.title || payment.payment_method_name || payment.method_name || payment.code, locale);
2158
- return `${name || fallbackName}(${code})`;
2230
+ return appendMaskedCardCode(name || fallbackName, fundRecord.code);
2159
2231
  }
2160
2232
  function appendMachineCodeToName(name, payment) {
2161
2233
  const fundRecord = getFundRecord(payment);
2162
- if (!fundRecord?.code) return name;
2163
- return `${name}(${maskMachineCode(fundRecord.code)})`;
2234
+ return appendMaskedCardCode(name, fundRecord?.code);
2164
2235
  }
2165
2236
  function paymentMethodName(payment, locale) {
2166
2237
  const fundRecordName = getFundRecordPaymentName(payment, locale);
@@ -2,6 +2,8 @@ import { Module, ModuleOptions, PisellCore } from '../../types';
2
2
  import { BaseModule } from '../../modules/BaseModule';
3
3
  import { BaseSalesOrderProduct, BaseSalesOrderProductIdentity, BaseSalesPaymentStatus, BaseSalesUpdateOrderProductQuantityParams, BaseSalesCalculateProductBookingPriceParams, BaseSalesPriceRecalculationContextChange, BaseSalesProductBookingPriceResult, BaseSalesQuotationCustomerScopeInfo, BaseSalesScanCodeResult, BaseSalesCashPaymentConfig, BaseSalesPayCashParams, BaseSalesPayCashResult, BaseSalesCommitPaymentMethodParams, BaseSalesCommitPaymentMethodResult, BaseSalesConfirmWalletPaymentResult } from './types';
4
4
  import { OrderModule } from '../../modules/Order';
5
+ import { type PickupTimingBookingInput } from '../../modules/Order/pickupTiming';
6
+ import type { PickupTimingAvailability, PickupTimingSelection, PickupTimingValidationResult } from '../../modules/FulfillmentTiming';
5
7
  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
8
  import type { SubmitPayloadEnhancer } from '../../modules/Order/utils';
7
9
  import type { Discount, VirtualCurrencyAsset } from '../../modules/Discount/types';
@@ -75,6 +77,8 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
75
77
  private serverOrderChangeHydrateInFlight;
76
78
  private queuedServerOrderChanges;
77
79
  private serverOrderChangeSyncEnabled;
80
+ protected pickupTimingEnabled: boolean;
81
+ private pickupTimingController?;
78
82
  constructor(name?: string, version?: string);
79
83
  /**
80
84
  * 子类可覆盖此方法以追加/收敛要注册的子模块。
@@ -151,6 +155,22 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
151
155
  private getAuthoritativeBundleUnitPrice;
152
156
  private mergeProductForPriceQuery;
153
157
  protected getSubmitOrderSalesChannel(): string | undefined;
158
+ /** Only solutions opting into Pickup Timing supply effective OpenData. */
159
+ protected loadPickupTimingOpenData(_force: boolean): Promise<Record<string, unknown>>;
160
+ protected isPickupTimingEnabled(): boolean;
161
+ protected getPickupTimingContextData(key: string): unknown;
162
+ protected getPickupTimingScope(): string;
163
+ private getPickupTimingController;
164
+ getPickupTimingAvailability(params?: {
165
+ date?: string;
166
+ force?: boolean;
167
+ }): Promise<PickupTimingAvailability>;
168
+ getPickupTimingSelection(): PickupTimingSelection;
169
+ setPickupTimingSelection(selection: PickupTimingSelection): Promise<PickupTimingSelection>;
170
+ validatePickupTimingSelection(selection: PickupTimingSelection): Promise<PickupTimingValidationResult>;
171
+ preparePickupTimingForCheckout(options?: {
172
+ freeze?: boolean;
173
+ }): Promise<PickupTimingSelection>;
154
174
  /** Subclasses with target-scoped OpenData override this guard before reusing the cache. */
155
175
  protected isInvoiceOpenDataCacheCurrent(): boolean;
156
176
  private getFulfillmentRefModeConfigs;
@@ -248,6 +268,9 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
248
268
  updateTempOrderDepositAmount(amount: string | number): OrderSummary;
249
269
  setPickupReferenceMode(mode: PickupReferenceMode): ShopServiceData;
250
270
  setServiceType(serviceType: ShopServiceType): ShopServiceData;
271
+ getPickupTimingBooking(): import("../../modules/Order").PickupTimingBooking | null;
272
+ setPickupTimingBooking(input: PickupTimingBookingInput): import("../../modules/Order").PickupTimingBooking;
273
+ clearPickupTimingBooking(): void;
251
274
  updateTempOrderContactsInfo(contactsInfo: Record<string, any> | null): Record<string, any> | null;
252
275
  /** @deprecated OrderModule 不再负责 tempOrder localStorage 持久化。 */
253
276
  setEnableTempOrderPersist(enabled: boolean): void;
@@ -21,6 +21,8 @@ Object.keys(_types).forEach(function (key) {
21
21
  });
22
22
  });
23
23
  var _Order = require("../../modules/Order");
24
+ var _pickupTiming = require("../../modules/Order/pickupTiming");
25
+ var _pickupTiming2 = require("./pickupTiming");
24
26
  var _decimal = _interopRequireDefault(require("decimal.js"));
25
27
  var _lodashEs = require("lodash-es");
26
28
  var _types2 = require("../BookingByStep/types");
@@ -497,6 +499,8 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
497
499
  serverOrderChangeHydrateInFlight = null;
498
500
  queuedServerOrderChanges = [];
499
501
  serverOrderChangeSyncEnabled = false;
502
+ pickupTimingEnabled = false;
503
+ pickupTimingController;
500
504
  constructor(name, version) {
501
505
  super(name, version);
502
506
  }
@@ -1146,6 +1150,66 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1146
1150
  return this.otherParams?.channel;
1147
1151
  }
1148
1152
 
1153
+ /** Only solutions opting into Pickup Timing supply effective OpenData. */
1154
+ async loadPickupTimingOpenData(_force) {
1155
+ return {};
1156
+ }
1157
+ isPickupTimingEnabled() {
1158
+ return this.pickupTimingEnabled && this.otherParams?.enablePickupTiming !== false;
1159
+ }
1160
+ getPickupTimingContextData(key) {
1161
+ const context = this.core?.context;
1162
+ const getData = context?.getData;
1163
+ return typeof getData === 'function' ? getData.call(context, key) : undefined;
1164
+ }
1165
+ getPickupTimingScope() {
1166
+ return JSON.stringify({
1167
+ shop: this.getPickupTimingContextData('shop_id'),
1168
+ businessCode: this.otherParams?.businessCode ?? this.otherParams?.business_code,
1169
+ channel: this.getSubmitOrderSalesChannel(),
1170
+ timezone: this.getPickupTimingContextData('timezone'),
1171
+ platform: (0, _platform.normalizeRuntimePlatform)(this.otherParams?.platform),
1172
+ cacheId: this.cacheId
1173
+ });
1174
+ }
1175
+ getPickupTimingController() {
1176
+ if (!this.pickupTimingController) {
1177
+ this.pickupTimingController = new _pickupTiming2.PickupTimingController({
1178
+ getOrder: () => {
1179
+ if (!this.store.order) throw new Error('order 模块未初始化');
1180
+ return this.store.order;
1181
+ },
1182
+ getSchedule: () => this.store.schedule,
1183
+ loadOpenData: force => this.loadPickupTimingOpenData(force),
1184
+ getTimezone: () => String(this.getPickupTimingContextData('timezone') ?? ''),
1185
+ getScope: () => this.getPickupTimingScope(),
1186
+ isPos: () => (0, _platform.normalizeRuntimePlatform)(this.otherParams?.platform) === 'pos'
1187
+ });
1188
+ }
1189
+ return this.pickupTimingController;
1190
+ }
1191
+ getPickupTimingAvailability(params) {
1192
+ return this.getPickupTimingController().getAvailability(params);
1193
+ }
1194
+ getPickupTimingSelection() {
1195
+ return this.getPickupTimingController().getSelection();
1196
+ }
1197
+ setPickupTimingSelection(selection) {
1198
+ return this.getPickupTimingController().setSelection(selection);
1199
+ }
1200
+ validatePickupTimingSelection(selection) {
1201
+ return this.getPickupTimingController().validate(selection);
1202
+ }
1203
+ preparePickupTimingForCheckout(options) {
1204
+ if (!this.isPickupTimingEnabled()) {
1205
+ return Promise.resolve({
1206
+ serviceType: String(this.getTempOrder()?.metadata?.shop_service_data?.service_type || ''),
1207
+ mode: 'asap'
1208
+ });
1209
+ }
1210
+ return this.getPickupTimingController().prepare(options);
1211
+ }
1212
+
1149
1213
  /** Subclasses with target-scoped OpenData override this guard before reusing the cache. */
1150
1214
  isInvoiceOpenDataCacheCurrent() {
1151
1215
  return true;
@@ -1537,9 +1601,12 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1537
1601
  /** 获取根 booking(is_next === 0 / is_root === 1),多个时取第一个。 */
1538
1602
  getRootBooking() {
1539
1603
  if (!this.currentSalesDetail) return null;
1540
- if (this.currentSalesDetail.rootBooking) return this.currentSalesDetail.rootBooking;
1604
+ if (this.currentSalesDetail.rootBooking && !(0, _pickupTiming.isFulfillmentTimingBooking)(this.currentSalesDetail.rootBooking)) {
1605
+ return this.currentSalesDetail.rootBooking;
1606
+ }
1541
1607
  const bookings = this.currentSalesDetail.bookings || [];
1542
1608
  return bookings.find(booking => {
1609
+ if ((0, _pickupTiming.isFulfillmentTimingBooking)(booking)) return false;
1543
1610
  const parentId = booking?.parent_id;
1544
1611
  return parentId === 0 || parentId === null || parentId === undefined;
1545
1612
  }) || null;
@@ -1656,6 +1723,16 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1656
1723
  if (!this.store.order) throw new Error('order 模块未初始化');
1657
1724
  return this.store.order.updateTempOrderServiceType(serviceType);
1658
1725
  }
1726
+ getPickupTimingBooking() {
1727
+ return this.store.order?.getPickupTimingBooking() || null;
1728
+ }
1729
+ setPickupTimingBooking(input) {
1730
+ if (!this.store.order) throw new Error('order 模块未初始化');
1731
+ return this.store.order.setPickupTimingBooking(input);
1732
+ }
1733
+ clearPickupTimingBooking() {
1734
+ this.store.order?.clearPickupTimingBooking();
1735
+ }
1659
1736
  updateTempOrderContactsInfo(contactsInfo) {
1660
1737
  if (!this.store.order) throw new Error('order 模块未初始化');
1661
1738
  return this.store.order.updateTempOrderContactsInfo(contactsInfo);
@@ -2111,6 +2188,8 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2111
2188
  holders: [],
2112
2189
  isFormSubject: product => (0, _utils.resolveIsFormSubject)(tempOrder, product),
2113
2190
  orderTotalAmount
2191
+ }, {
2192
+ preserveEntitlementSelection: false
2114
2193
  }) || result;
2115
2194
  if (result.productList) {
2116
2195
  tempOrder.products = preserveManualProductDiscountProducts(tempOrder.products, result.productList);
@@ -2454,6 +2533,9 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2454
2533
  if (!this.store.order) {
2455
2534
  throw new Error('BaseSales 解决方案需要 order 模块支持');
2456
2535
  }
2536
+ if (this.isPickupTimingEnabled()) await this.preparePickupTimingForCheckout({
2537
+ freeze: true
2538
+ });
2457
2539
  const virtualSettlement = await this.buildVirtualSettlementSubmitData();
2458
2540
  // this.store.order.persistTempOrder();
2459
2541
  const inferredPaymentStatus = this.getSubmitPaymentStatus(params?.paymentStatus);
@@ -2511,6 +2593,9 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2511
2593
  if (!this.store.order) {
2512
2594
  throw new Error('BaseSales 解决方案需要 order 模块支持');
2513
2595
  }
2596
+ if (this.isPickupTimingEnabled()) await this.preparePickupTimingForCheckout({
2597
+ freeze: false
2598
+ });
2514
2599
  return this.store.order.saveTempOrderAsDraft({
2515
2600
  cacheId: this.cacheId,
2516
2601
  platform: params?.platform ?? this.otherParams?.platform,
@@ -2526,6 +2611,11 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2526
2611
  if (!this.store.order) {
2527
2612
  throw new Error('BaseSales 解决方案需要 order 模块支持');
2528
2613
  }
2614
+ if (this.isPickupTimingEnabled()) {
2615
+ await this.preparePickupTimingForCheckout({
2616
+ freeze: true
2617
+ });
2618
+ }
2529
2619
  const inferredPaymentStatus = this.getSubmitPaymentStatus(params?.paymentStatus);
2530
2620
  const smallTicketDataFlag = params?.smallTicketDataFlag ?? this.getDefaultCheckoutSmallTicketDataFlag();
2531
2621
  const enhancePayload = this.buildSubmitPayloadEnhancerWithOpenDataConfig(params?.enhancePayload);
@@ -3025,6 +3115,10 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
3025
3115
  };
3026
3116
  }
3027
3117
  const result = await this.store.payment.wallet.initializeWalletDataFromBusinessAsync(walletBusinessData);
3118
+ this.store.order.mergePointCardSnapshotCodes?.(result.userIdentificationCodes, {
3119
+ customerId: walletBusinessData.customer_id,
3120
+ orderIdentity: snapshot?.identity
3121
+ });
3028
3122
  await this.core.effects.emit(`${this.name}:onWalletDataInitialized`, {
3029
3123
  orderId: snapshot?.identity?.orderId,
3030
3124
  customerId: walletBusinessData.customer_id,
@@ -3151,6 +3245,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
3151
3245
  async importWalletAssetsSnapshot(snapshot) {
3152
3246
  if (!this.store.payment) throw new Error('payment 模块未初始化');
3153
3247
  const state = this.store.payment.wallet.importWalletAssetsSnapshot(snapshot);
3248
+ this.store.order?.mergePointCardSnapshotCodes?.(state.items);
3154
3249
  await this.syncSelectedWalletAssets(state);
3155
3250
  return this.publishWalletAssetsState(state);
3156
3251
  }
@@ -3172,6 +3267,10 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
3172
3267
  });
3173
3268
  return this.publishWalletAssetsState(emptyState);
3174
3269
  }
3270
+ const pointCardSnapshotMergeContext = {
3271
+ customerId: businessData.customer_id,
3272
+ orderIdentity: this.store.order.getOrderSnapshot()?.identity
3273
+ };
3175
3274
  const previousState = wallet.getWalletAssetsState();
3176
3275
  const hasPreviousWalletContext = previousState.status !== 'idle' || previousState.items.length > 0 || previousState.selectedIds.length > 0 || previousState.customerId !== undefined;
3177
3276
  const customerChanged = hasPreviousWalletContext && String(previousState.customerId ?? '') !== String(businessData.customer_id ?? '');
@@ -3194,6 +3293,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
3194
3293
  if (state.status !== 'error') {
3195
3294
  const committed = this.getCommittedWalletAssets();
3196
3295
  state = wallet.setCommittedWalletAssets(committed.ids, committed.assets);
3296
+ this.store.order.mergePointCardSnapshotCodes?.(wallet.getUserIdentificationCodes?.() || [], pointCardSnapshotMergeContext);
3197
3297
  }
3198
3298
  await this.syncSelectedWalletAssets(state);
3199
3299
  return this.publishWalletAssetsState(state);
@@ -3434,6 +3534,9 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
3434
3534
  if (!Number.isFinite(actualPaidAmount) || actualPaidAmount + 0.01 < amount) {
3435
3535
  throw new Error('顾客实收现金不能小于支付金额');
3436
3536
  }
3537
+ if (this.isPickupTimingEnabled()) await this.preparePickupTimingForCheckout({
3538
+ freeze: true
3539
+ });
3437
3540
  this.window?.interaction?.utils?.postMessageToApp?.({
3438
3541
  module: 'till',
3439
3542
  key: 'open_till',
@@ -0,0 +1,37 @@
1
+ import type { OrderModule } from '../../modules/Order';
2
+ import type { ScheduleModule } from '../../modules/Schedule';
3
+ import { type PickupTimingAvailability, type PickupTimingSelection, type PickupTimingValidationResult } from '../../modules/FulfillmentTiming';
4
+ interface PickupTimingHost {
5
+ getOrder: () => OrderModule;
6
+ getSchedule: () => ScheduleModule | undefined;
7
+ loadOpenData: (force: boolean) => Promise<Record<string, unknown>>;
8
+ getTimezone: () => string;
9
+ getScope: () => string;
10
+ isPos: () => boolean;
11
+ now?: () => Date;
12
+ }
13
+ /** Coordinates fresh configuration with the independent appointment; UI keeps edit drafts. */
14
+ export declare class PickupTimingController {
15
+ private readonly host;
16
+ private latest?;
17
+ private readonly requests;
18
+ private writeSequence;
19
+ constructor(host: PickupTimingHost);
20
+ private now;
21
+ private snapshot;
22
+ getAvailability(params?: {
23
+ date?: string;
24
+ force?: boolean;
25
+ }): Promise<PickupTimingAvailability>;
26
+ getSelection(): PickupTimingSelection;
27
+ private validateSnapshot;
28
+ validate(selection: PickupTimingSelection): Promise<PickupTimingValidationResult>;
29
+ setSelection(selection: PickupTimingSelection, options?: {
30
+ frozen?: boolean;
31
+ }): Promise<PickupTimingSelection>;
32
+ /** Called before a new checkout/payment; payment callbacks keep their already prepared appointment. */
33
+ prepare(options?: {
34
+ freeze?: boolean;
35
+ }): Promise<PickupTimingSelection>;
36
+ }
37
+ export {};