@pisell/pisellos 2.2.233 → 2.2.234

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 (49) hide show
  1. package/dist/modules/Order/index.d.ts +36 -1
  2. package/dist/modules/Order/index.js +496 -308
  3. package/dist/modules/Order/types.d.ts +11 -0
  4. package/dist/modules/Order/utils.d.ts +1 -0
  5. package/dist/modules/Order/utils.js +1 -1
  6. package/dist/server/index.d.ts +5 -0
  7. package/dist/server/index.js +855 -633
  8. package/dist/server/modules/order/index.d.ts +2 -0
  9. package/dist/server/modules/order/index.js +86 -32
  10. package/dist/solution/BaseSales/index.d.ts +6 -0
  11. package/dist/solution/BaseSales/index.js +240 -197
  12. package/dist/solution/BookingByStep/index.d.ts +1 -1
  13. package/dist/solution/BookingTicket/index.d.ts +38 -2
  14. package/dist/solution/BookingTicket/index.js +642 -313
  15. package/dist/solution/BookingTicket/types.d.ts +62 -0
  16. package/dist/solution/BookingTicket/types.js +30 -0
  17. package/dist/solution/BookingTicket/utils/addTimeAvailability.d.ts +35 -0
  18. package/dist/solution/BookingTicket/utils/addTimeAvailability.js +95 -0
  19. package/dist/solution/BookingTicket/utils/cartView.d.ts +9 -1
  20. package/dist/solution/BookingTicket/utils/cartView.js +76 -5
  21. package/dist/solution/BookingTicket/utils/exampleData.d.ts +123 -0
  22. package/dist/solution/BookingTicket/utils/exampleData.js +183 -0
  23. package/dist/solution/BookingTicket/utils/resolveBestAddTimePlan.d.ts +9 -13
  24. package/dist/solution/BookingTicket/utils/resolveBestAddTimePlan.js +42 -33
  25. package/lib/model/strategy/adapter/promotion/index.js +0 -49
  26. package/lib/modules/Order/index.d.ts +36 -1
  27. package/lib/modules/Order/index.js +98 -10
  28. package/lib/modules/Order/types.d.ts +11 -0
  29. package/lib/modules/Order/utils.d.ts +1 -0
  30. package/lib/modules/Order/utils.js +1 -1
  31. package/lib/server/index.d.ts +5 -0
  32. package/lib/server/index.js +224 -63
  33. package/lib/server/modules/order/index.d.ts +2 -0
  34. package/lib/server/modules/order/index.js +43 -1
  35. package/lib/solution/BaseSales/index.d.ts +6 -0
  36. package/lib/solution/BaseSales/index.js +20 -1
  37. package/lib/solution/BookingByStep/index.d.ts +1 -1
  38. package/lib/solution/BookingTicket/index.d.ts +38 -2
  39. package/lib/solution/BookingTicket/index.js +194 -3
  40. package/lib/solution/BookingTicket/types.d.ts +62 -0
  41. package/lib/solution/BookingTicket/utils/addTimeAvailability.d.ts +35 -0
  42. package/lib/solution/BookingTicket/utils/addTimeAvailability.js +125 -0
  43. package/lib/solution/BookingTicket/utils/cartView.d.ts +9 -1
  44. package/lib/solution/BookingTicket/utils/cartView.js +48 -2
  45. package/lib/solution/BookingTicket/utils/exampleData.d.ts +123 -0
  46. package/lib/solution/BookingTicket/utils/exampleData.js +0 -0
  47. package/lib/solution/BookingTicket/utils/resolveBestAddTimePlan.d.ts +9 -13
  48. package/lib/solution/BookingTicket/utils/resolveBestAddTimePlan.js +41 -28
  49. package/package.json +1 -1
@@ -1279,13 +1279,13 @@ var BaseSalesImpl = class extends import_BaseModule.BaseModule {
1279
1279
  throw new Error("BaseSales 解决方案需要 order 模块支持");
1280
1280
  }
1281
1281
  const inferredPaymentStatus = this.getSubmitPaymentStatus(params == null ? void 0 : params.paymentStatus);
1282
- debugger;
1283
1282
  const submitParams = {
1284
1283
  cacheId: this.cacheId,
1285
1284
  platform: (_a = this.otherParams) == null ? void 0 : _a.platform,
1286
1285
  businessCode: ((_b = this.otherParams) == null ? void 0 : _b.businessCode) || ((_c = this.otherParams) == null ? void 0 : _c.business_code),
1287
1286
  channel: this.getSubmitOrderSalesChannel(),
1288
1287
  type: (_d = this.otherParams) == null ? void 0 : _d.type,
1288
+ request_unique_idempotency_token: this.store.order.generateIdempotencyToken(),
1289
1289
  ...(params == null ? void 0 : params.payments) !== void 0 ? { payments: params.payments } : {},
1290
1290
  ...inferredPaymentStatus !== void 0 ? { paymentStatus: inferredPaymentStatus } : {},
1291
1291
  ...(params == null ? void 0 : params.smallTicketDataFlag) !== void 0 ? { smallTicketDataFlag: params.smallTicketDataFlag } : {},
@@ -1293,6 +1293,25 @@ var BaseSalesImpl = class extends import_BaseModule.BaseModule {
1293
1293
  };
1294
1294
  return this.store.order.submitTempOrder(submitParams);
1295
1295
  }
1296
+ async submitTempOrderAsync(params) {
1297
+ var _a, _b, _c, _d;
1298
+ if (!this.store.order) {
1299
+ throw new Error("BaseSales 解决方案需要 order 模块支持");
1300
+ }
1301
+ const inferredPaymentStatus = this.getSubmitPaymentStatus(params == null ? void 0 : params.paymentStatus);
1302
+ const submitParams = {
1303
+ cacheId: this.cacheId,
1304
+ platform: (_a = this.otherParams) == null ? void 0 : _a.platform,
1305
+ businessCode: ((_b = this.otherParams) == null ? void 0 : _b.businessCode) || ((_c = this.otherParams) == null ? void 0 : _c.business_code),
1306
+ channel: this.getSubmitOrderSalesChannel(),
1307
+ type: (_d = this.otherParams) == null ? void 0 : _d.type,
1308
+ ...(params == null ? void 0 : params.payments) !== void 0 ? { payments: params.payments } : {},
1309
+ ...inferredPaymentStatus !== void 0 ? { paymentStatus: inferredPaymentStatus } : {},
1310
+ ...(params == null ? void 0 : params.smallTicketDataFlag) !== void 0 ? { smallTicketDataFlag: params.smallTicketDataFlag } : {},
1311
+ ...(params == null ? void 0 : params.enhancePayload) !== void 0 ? { enhancePayload: params.enhancePayload } : {}
1312
+ };
1313
+ return this.store.order.submitTempOrderAsync(submitParams);
1314
+ }
1296
1315
  async printLocalOrderReceipt(lookup) {
1297
1316
  var _a, _b, _c, _d, _e, _f, _g;
1298
1317
  if (!this.store.order) {
@@ -311,7 +311,7 @@ export declare class BookingByStepImpl extends BaseModule implements Module {
311
311
  date: string;
312
312
  status: string;
313
313
  week: string;
314
- weekNum: 0 | 1 | 5 | 4 | 2 | 3 | 6;
314
+ weekNum: 0 | 2 | 1 | 5 | 3 | 4 | 6;
315
315
  }[]>;
316
316
  submitTimeSlot(timeSlots: TimeSliceItem): void;
317
317
  private getScheduleDataByIds;
@@ -1,13 +1,14 @@
1
1
  import { Module, ModuleOptions, PisellCore } from '../../types';
2
2
  import { BookingTicketCartView, BookingTicketProductCatalogView, BookingTicketState, IGetCustomerListParams, ICustomer, ILoadProductsParams, ILoadProductDetailParams, IScanResult } from './types';
3
3
  import type { ProductData } from '../../modules';
4
+ import type { OpenDataConfig } from '../../modules/OpenData';
4
5
  import { type BookingContextConfig, type BookingContextDateInput, type BookingContextResource, type BookingContextResourceMap, type BookingContextState, type InitBookingContextParams, type LoadBookingConfigParams, type LoadBookingResourcesParams, type ResourceError, type BookingCalcContext } from '../../modules/BookingContext';
5
6
  import type { OrderCustomerView } from '../../modules/Order/types';
6
7
  import { BaseSalesImpl } from '../BaseSales';
7
8
  import type { BaseSalesScanCodeResult } from '../BaseSales/types';
8
9
  import type { ISalesDetail } from '../BaseSales/utils/parseSalesResponse';
9
10
  import type { LoadSalesDetailParams } from '../../modules/Order/types';
10
- import type { GlobalScanHandleResult, GlobalScanHostBridge } from './types';
11
+ import type { AddAddTimeProductToBookingInput, AddAddTimeProductsToBookingInput, GlobalScanHandleResult, GlobalScanHostBridge } from './types';
11
12
  import { AddProductDecideContext, AddProductRequiresDetailPayload } from './utils/addProductDecision';
12
13
  import { type BuildCacheItemFromOrderLineInput } from '../../modules/BookingContext/utils/buildCacheItemFromOrderLine';
13
14
  import { type BuildNormalProductCacheItemFromOrderLineInput } from '../../modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine';
@@ -25,6 +26,7 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
25
26
  private platform;
26
27
  private scan;
27
28
  private productCatalog;
29
+ private addTimeProductsCatalog;
28
30
  private orderCustomerDiscountRefreshInFlight;
29
31
  private orderCustomerDiscountRefreshCustomerId;
30
32
  private loadOpenDataConfigInFlight;
@@ -43,6 +45,7 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
43
45
  private getBookingTicketBusinessCode;
44
46
  private getIdGeneratorPlugin;
45
47
  private loadOpenDataConfig;
48
+ getOpenData(): Promise<OpenDataConfig | null>;
46
49
  private getShortNumberOrDeviceId;
47
50
  private configureIdGeneratorFromOpenData;
48
51
  private normalizePositiveInteger;
@@ -319,7 +322,7 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
319
322
  * 获取当前的客户搜索条件
320
323
  * @returns 当前搜索条件
321
324
  */
322
- getCurrentCustomerSearchParams(): Omit<import("../../modules").ShopGetCustomerListParams, "skip" | "num">;
325
+ getCurrentCustomerSearchParams(): Omit<import("../../modules").ShopGetCustomerListParams, "num" | "skip">;
323
326
  /**
324
327
  * 获取客户列表状态(包含滚动加载相关状态)
325
328
  * @returns 客户状态
@@ -512,6 +515,39 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
512
515
  */
513
516
  handleGlobalScanCode(code: string, bridge?: GlobalScanHostBridge): Promise<GlobalScanHandleResult>;
514
517
  resolveBestAddTimePlan(addTimeProducts: any[], targetMinutes: number): any;
518
+ /**
519
+ * 构造一条绑定到已有 booking 的加时商品行。
520
+ *
521
+ * @example
522
+ * const line = this.buildAddTimeOrderLine({ product, product_add_schedule_time: 30 }, 'booking-1');
523
+ */
524
+ private buildAddTimeOrderLine;
525
+ /**
526
+ * 将加时商品作为独立商品行绑定到已有 booking。
527
+ *
528
+ * 与预约商品加车不同,本方法不会把当前 booking 作为 `addProductToOrder` 第二参数传入,
529
+ * 避免 OrderModule 新建 booking 或重写已有 booking.product_uid。
530
+ *
531
+ * @example
532
+ * await bookingTicket.addAddTimeProductToBooking({
533
+ * booking,
534
+ * product,
535
+ * num: 1,
536
+ * price: '10.00',
537
+ * product_add_schedule_time: 30,
538
+ * });
539
+ */
540
+ addAddTimeProductToBooking(input: AddAddTimeProductToBookingInput): Promise<OrderProduct[]>;
541
+ /**
542
+ * 批量将加时商品作为独立商品行绑定到已有 booking,并只触发一次购物车重算。
543
+ *
544
+ * @example
545
+ * await bookingTicket.addAddTimeProductsToBooking({
546
+ * booking,
547
+ * products: [{ product, product_add_schedule_time: 30 }],
548
+ * });
549
+ */
550
+ addAddTimeProductsToBooking(input: AddAddTimeProductsToBookingInput): Promise<OrderProduct[]>;
515
551
  /**
516
552
  * 销毁模块:先调用父类(销毁所有 store 内子模块 + emit destroy + super.destroy()),
517
553
  * 再清理 BookingTicket 自有资源(scan 监听 + scan 内存缓存)。
@@ -34,6 +34,7 @@ __export(BookingTicket_exports, {
34
34
  BookingTicketImpl: () => BookingTicketImpl
35
35
  });
36
36
  module.exports = __toCommonJS(BookingTicket_exports);
37
+ var import_dayjs = __toESM(require("dayjs"));
37
38
  var import_types = require("./types");
38
39
  var import_BookingContext = require("../../modules/BookingContext");
39
40
  var import_BaseSales = require("../BaseSales");
@@ -54,6 +55,57 @@ var import_resolveBestAddTimePlan = require("./utils/resolveBestAddTimePlan");
54
55
  __reExport(BookingTicket_exports, require("./types"), module.exports);
55
56
  var OPEN_DATA_SECTION_CODES = ["sale", "reservation", "fulfillment", "menu", "workflow"];
56
57
  var OPEN_DATA_CACHE_TTL = 5 * 60 * 1e3;
58
+ function resolveBookingUidForAddTime(booking) {
59
+ var _a;
60
+ const uid = (_a = booking == null ? void 0 : booking.metadata) == null ? void 0 : _a.unique_identification_number;
61
+ return uid === void 0 || uid === null ? "" : String(uid);
62
+ }
63
+ function normalizeAddTimeQuantity(value) {
64
+ const parsed = Math.floor(Number(value));
65
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 1;
66
+ }
67
+ function resolveAddTimeScheduleMinutes(explicitValue, product) {
68
+ const raw = explicitValue ?? (product == null ? void 0 : product.product_add_schedule_time);
69
+ if (raw === void 0 || raw === null || raw === "")
70
+ return void 0;
71
+ const parsed = Number(raw);
72
+ return Number.isFinite(parsed) ? parsed : void 0;
73
+ }
74
+ function resolveAddTimeProductPrice(explicitValue, product) {
75
+ return explicitValue ?? (product == null ? void 0 : product.price) ?? (product == null ? void 0 : product.selling_price) ?? (product == null ? void 0 : product.base_price);
76
+ }
77
+ function normalizeCoveredMinutes(value) {
78
+ const parsed = Math.ceil(Number(value));
79
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
80
+ }
81
+ function buildAddTimeBookingTimePatch(booking, coveredMinutes) {
82
+ if (coveredMinutes <= 0)
83
+ return null;
84
+ const endDate = booking == null ? void 0 : booking.end_date;
85
+ const endTime = booking == null ? void 0 : booking.end_time;
86
+ if (!endDate || !endTime) {
87
+ throw new Error("[BookingTicket] addAddTimeProductsToBooking: booking.end_date/end_time 缺失");
88
+ }
89
+ const currentEnd = (0, import_dayjs.default)(`${endDate} ${endTime}`);
90
+ if (!currentEnd.isValid()) {
91
+ throw new Error("[BookingTicket] addAddTimeProductsToBooking: booking.end_date/end_time 无效");
92
+ }
93
+ const nextEnd = currentEnd.add(coveredMinutes, "minute");
94
+ const currentDuration = Number(booking == null ? void 0 : booking.duration);
95
+ let duration = Number.isFinite(currentDuration) ? currentDuration + coveredMinutes : coveredMinutes;
96
+ if ((booking == null ? void 0 : booking.start_date) && (booking == null ? void 0 : booking.start_time)) {
97
+ const start = (0, import_dayjs.default)(`${booking.start_date} ${booking.start_time}`);
98
+ if (start.isValid()) {
99
+ const diffMinutes = nextEnd.diff(start, "minute");
100
+ duration = diffMinutes > 0 ? diffMinutes : duration;
101
+ }
102
+ }
103
+ return {
104
+ end_date: nextEnd.format("YYYY-MM-DD"),
105
+ end_time: nextEnd.format("HH:mm"),
106
+ duration
107
+ };
108
+ }
57
109
  function withProductOptionString(cacheItem) {
58
110
  var _a;
59
111
  const productOptionString = (0, import_orderLineDisplay.buildProductOptionStringFromOrderLine)(cacheItem);
@@ -79,6 +131,7 @@ var BookingTicketImpl = class extends import_BaseSales.BaseSalesImpl {
79
131
  */
80
132
  this.store = {};
81
133
  this.productCatalog = [];
134
+ this.addTimeProductsCatalog = [];
82
135
  this.orderCustomerDiscountRefreshInFlight = null;
83
136
  this.orderCustomerDiscountRefreshCustomerId = null;
84
137
  this.loadOpenDataConfigInFlight = null;
@@ -164,6 +217,10 @@ var BookingTicketImpl = class extends import_BaseSales.BaseSalesImpl {
164
217
  this.loadOpenDataConfigInFlight = null;
165
218
  }
166
219
  }
220
+ async getOpenData() {
221
+ var _a;
222
+ return (_a = this.store.openData) == null ? void 0 : _a.getOpenData();
223
+ }
167
224
  async getShortNumberOrDeviceId() {
168
225
  const getAsyncIotDeviceInfo = this.getAppData("async_iot_device_info");
169
226
  if (getAsyncIotDeviceInfo) {
@@ -520,10 +577,12 @@ var BookingTicketImpl = class extends import_BaseSales.BaseSalesImpl {
520
577
  * const products = await bookingTicket.getAddTimeProducts({ schedule_date: '2026-06-16' });
521
578
  */
522
579
  async getAddTimeProducts(params = {}) {
523
- return this.store.products.getAddTimeProducts({
580
+ const result = await this.store.products.getAddTimeProducts({
524
581
  ...params,
525
582
  cacheId: this.cacheId
526
583
  });
584
+ this.addTimeProductsCatalog = Array.isArray(result) ? [...result] : [];
585
+ return result;
527
586
  }
528
587
  /**
529
588
  * 只读查询单商品详情:走 ProductList 报价查询,但不写 productCatalog、
@@ -610,11 +669,23 @@ var BookingTicketImpl = class extends import_BaseSales.BaseSalesImpl {
610
669
  * tempOrder 仍是 checkout 协议对象;商品行直接透传 products,bookings 附带关联商品。
611
670
  */
612
671
  getCart() {
613
- var _a;
672
+ var _a, _b;
614
673
  const tempOrder = ((_a = this.store.order) == null ? void 0 : _a.getTempOrder()) ?? null;
615
674
  const salesDetail = this.getSalesOrder();
616
675
  const bookings = (salesDetail == null ? void 0 : salesDetail.bookings) ?? ((tempOrder == null ? void 0 : tempOrder.bookings) || []);
617
- return (0, import_cartView.buildCartView)((tempOrder == null ? void 0 : tempOrder.products) || [], bookings);
676
+ let operatingDayBoundary;
677
+ try {
678
+ operatingDayBoundary = this.getAppData("operating_day_boundary");
679
+ } catch {
680
+ operatingDayBoundary = void 0;
681
+ }
682
+ const shopOpeningHours = (operatingDayBoundary == null ? void 0 : operatingDayBoundary.type) === "start_time" ? "23:59" : operatingDayBoundary == null ? void 0 : operatingDayBoundary.time;
683
+ return (0, import_cartView.buildCartView)((tempOrder == null ? void 0 : tempOrder.products) || [], bookings, {
684
+ addTimeProducts: this.addTimeProductsCatalog,
685
+ bookingConfig: this.getBookingConfig(),
686
+ productsByUid: (_b = tempOrder == null ? void 0 : tempOrder._extend) == null ? void 0 : _b.productsByUid,
687
+ shopOpeningHours
688
+ });
618
689
  }
619
690
  /**
620
691
  * 获取当前下单客户(与 OrderModule.tempOrder snapshot 一致)。
@@ -1304,6 +1375,126 @@ var BookingTicketImpl = class extends import_BaseSales.BaseSalesImpl {
1304
1375
  resolveBestAddTimePlan(addTimeProducts, targetMinutes) {
1305
1376
  return (0, import_resolveBestAddTimePlan.resolveBestAddTimePlan)(addTimeProducts, targetMinutes);
1306
1377
  }
1378
+ /**
1379
+ * 构造一条绑定到已有 booking 的加时商品行。
1380
+ *
1381
+ * @example
1382
+ * const line = this.buildAddTimeOrderLine({ product, product_add_schedule_time: 30 }, 'booking-1');
1383
+ */
1384
+ buildAddTimeOrderLine(input, bookingUid) {
1385
+ var _a;
1386
+ const product = input == null ? void 0 : input.product;
1387
+ if (!product || typeof product !== "object") {
1388
+ throw new Error("[BookingTicket] addAddTimeProductToBooking: product 不能为空");
1389
+ }
1390
+ const quantity = normalizeAddTimeQuantity(input.num ?? product.num);
1391
+ const price = resolveAddTimeProductPrice(input.price, product);
1392
+ const addScheduleTime = resolveAddTimeScheduleMinutes(
1393
+ input.product_add_schedule_time,
1394
+ product
1395
+ );
1396
+ const payload = {
1397
+ id: product.id ?? product.product_id,
1398
+ product_id: product.product_id ?? product.id,
1399
+ product_variant_id: product.product_variant_id ?? product.variant_id ?? 0,
1400
+ variant_id: product.variant_id ?? product.product_variant_id ?? 0,
1401
+ quantity,
1402
+ num: quantity,
1403
+ price,
1404
+ selling_price: price,
1405
+ original_price: product.original_price ?? price,
1406
+ line_total: price,
1407
+ total: price,
1408
+ product_sku: product.product_sku,
1409
+ option: product.option ?? product.options ?? ((_a = product.product_sku) == null ? void 0 : _a.option) ?? [],
1410
+ bundle: product.bundle ?? product.bundles ?? product.product_bundle ?? []
1411
+ };
1412
+ const orderLine = this.transformBaseProductToOrderProduct({
1413
+ payload,
1414
+ fallbackProductId: product.id ?? product.product_id,
1415
+ sourceProduct: product,
1416
+ sourceItem: product
1417
+ });
1418
+ if (!orderLine) {
1419
+ throw new Error(
1420
+ "[BookingTicket] addAddTimeProductToBooking: product transform failed"
1421
+ );
1422
+ }
1423
+ const metadata = {
1424
+ ...orderLine.metadata || {},
1425
+ ...addScheduleTime !== void 0 ? { product_add_schedule_time: addScheduleTime } : {},
1426
+ booking_uid: bookingUid
1427
+ };
1428
+ return {
1429
+ ...orderLine,
1430
+ booking_uid: bookingUid,
1431
+ metadata
1432
+ };
1433
+ }
1434
+ /**
1435
+ * 将加时商品作为独立商品行绑定到已有 booking。
1436
+ *
1437
+ * 与预约商品加车不同,本方法不会把当前 booking 作为 `addProductToOrder` 第二参数传入,
1438
+ * 避免 OrderModule 新建 booking 或重写已有 booking.product_uid。
1439
+ *
1440
+ * @example
1441
+ * await bookingTicket.addAddTimeProductToBooking({
1442
+ * booking,
1443
+ * product,
1444
+ * num: 1,
1445
+ * price: '10.00',
1446
+ * product_add_schedule_time: 30,
1447
+ * });
1448
+ */
1449
+ async addAddTimeProductToBooking(input) {
1450
+ return this.addAddTimeProductsToBooking({
1451
+ booking: input.booking,
1452
+ products: [input],
1453
+ coveredMinutes: input.coveredMinutes,
1454
+ bookingPatch: input.bookingPatch
1455
+ });
1456
+ }
1457
+ /**
1458
+ * 批量将加时商品作为独立商品行绑定到已有 booking,并只触发一次购物车重算。
1459
+ *
1460
+ * @example
1461
+ * await bookingTicket.addAddTimeProductsToBooking({
1462
+ * booking,
1463
+ * products: [{ product, product_add_schedule_time: 30 }],
1464
+ * });
1465
+ */
1466
+ async addAddTimeProductsToBooking(input) {
1467
+ const bookingUid = resolveBookingUidForAddTime(input == null ? void 0 : input.booking);
1468
+ if (!bookingUid) {
1469
+ throw new Error(
1470
+ "[BookingTicket] addAddTimeProductToBooking: booking.metadata.unique_identification_number 缺失"
1471
+ );
1472
+ }
1473
+ const orderLines = (input.products || []).map((line) => ({
1474
+ product: this.buildAddTimeOrderLine(line, bookingUid)
1475
+ }));
1476
+ if (!this.store.order)
1477
+ throw new Error("order 模块未初始化");
1478
+ const coveredMinutes = normalizeCoveredMinutes(input.coveredMinutes);
1479
+ const bookingTimePatch = input.bookingPatch ?? buildAddTimeBookingTimePatch(input.booking, coveredMinutes);
1480
+ if (bookingTimePatch) {
1481
+ this.store.order.updateOrderBooking({
1482
+ unique_identification_number: bookingUid,
1483
+ updates: bookingTimePatch
1484
+ });
1485
+ }
1486
+ if (typeof this.store.order.addProductsToOrder === "function") {
1487
+ return this.store.order.addProductsToOrder(orderLines, {
1488
+ skipEditDiscountConfigRefresh: true,
1489
+ skipDiscountRecalculation: true
1490
+ });
1491
+ }
1492
+ const result = [];
1493
+ for (const line of orderLines) {
1494
+ result.splice(0, result.length, ...await this.addProductToOrder(line.product));
1495
+ }
1496
+ return result;
1497
+ }
1307
1498
  /**
1308
1499
  * 销毁模块:先调用父类(销毁所有 store 内子模块 + emit destroy + super.destroy()),
1309
1500
  * 再清理 BookingTicket 自有资源(scan 监听 + scan 内存缓存)。
@@ -112,6 +112,8 @@ export type BookingTicketCartItemView = BaseSalesOrderProduct;
112
112
  export interface BookingTicketCartBookingView extends ISalesBooking {
113
113
  _extend: {
114
114
  product: BookingTicketCartItemView | null;
115
+ addTimeProduct: BookingTicketCartItemView[];
116
+ canAddTime: boolean;
115
117
  };
116
118
  }
117
119
  /**
@@ -139,6 +141,66 @@ export interface BookingTicketCustomerView {
139
141
  export interface BookingTicketProductCatalogView {
140
142
  products: ProductData[];
141
143
  }
144
+ /**
145
+ * 添加加时商品到已有 booking 的输入。
146
+ *
147
+ * @example
148
+ * await bookingTicket.addAddTimeProductToBooking({
149
+ * booking,
150
+ * product,
151
+ * num: 1,
152
+ * price: '10.00',
153
+ * product_add_schedule_time: 30,
154
+ * });
155
+ */
156
+ export interface AddAddTimeProductToBookingInput {
157
+ /** 当前购物车中已存在的 booking,必须带 metadata.unique_identification_number。 */
158
+ booking: Record<string, any>;
159
+ /** 加时商品原始数据,保留完整商品信息供 OrderModule 缓存原始快照。 */
160
+ product: ProductData & Record<string, any>;
161
+ /** 本次添加数量,缺省为 1。 */
162
+ num?: number;
163
+ /** 本次加时商品价格,缺省回退 product.price。 */
164
+ price?: number | string;
165
+ /** 加时时长,单位分钟;最终写入 product.metadata.product_add_schedule_time。 */
166
+ product_add_schedule_time?: number;
167
+ /** 本次加时方案实际覆盖分钟数,用于同步更新 booking 结束时间与 duration。 */
168
+ coveredMinutes?: number;
169
+ /** 直接写入 booking 的时间补丁,restart 场景使用。 */
170
+ bookingPatch?: {
171
+ start_date?: string;
172
+ start_time?: string;
173
+ end_date?: string;
174
+ end_time?: string;
175
+ duration?: number;
176
+ };
177
+ }
178
+ /**
179
+ * 批量添加加时商品到已有 booking 的单行输入。
180
+ *
181
+ * @example
182
+ * const line: AddAddTimeProductLineInput = { product, product_add_schedule_time: 30 };
183
+ */
184
+ export type AddAddTimeProductLineInput = Omit<AddAddTimeProductToBookingInput, 'booking' | 'coveredMinutes'>;
185
+ /**
186
+ * 批量添加加时商品到已有 booking 的输入。
187
+ *
188
+ * @example
189
+ * await bookingTicket.addAddTimeProductsToBooking({
190
+ * booking,
191
+ * products: [{ product, product_add_schedule_time: 30 }],
192
+ * });
193
+ */
194
+ export interface AddAddTimeProductsToBookingInput {
195
+ /** 当前购物车中已存在的 booking,必须带 metadata.unique_identification_number。 */
196
+ booking: Record<string, any>;
197
+ /** 需要一次性写入购物车的加时商品行。 */
198
+ products: AddAddTimeProductLineInput[];
199
+ /** 本次加时方案实际覆盖分钟数,用于同步更新 booking 结束时间与 duration。 */
200
+ coveredMinutes?: number;
201
+ /** 直接写入 booking 的时间补丁,restart 场景使用。 */
202
+ bookingPatch?: AddAddTimeProductToBookingInput['bookingPatch'];
203
+ }
142
204
  /** 扫码宿主桥接:SalesSdk 在 pubsub 订阅回调里注入,OS 需要 UI 时直接调用 */
143
205
  export interface GlobalScanHostBridge {
144
206
  openProductDetail?: (payload: Record<string, any>) => Promise<any | null>;
@@ -0,0 +1,35 @@
1
+ import type { BaseSalesOrderProduct } from '../../BaseSales';
2
+ import type { ISalesBooking } from '../../BaseSales/utils/parseSalesResponse';
3
+ export interface BuildAddTimeCurrentItemParams {
4
+ booking: ISalesBooking;
5
+ product: BaseSalesOrderProduct | null;
6
+ productsByUid?: Record<string, {
7
+ origin?: Record<string, any>;
8
+ }>;
9
+ }
10
+ export interface ResolveBookingCanAddTimeParams extends BuildAddTimeCurrentItemParams {
11
+ addTimeProducts?: any[];
12
+ bookingConfig?: Record<string, any> | null;
13
+ shopOpeningHours?: string;
14
+ }
15
+ /**
16
+ * 从 booking 与其关联商品行构建旧 `isUsableProduct` 所需的当前商品快照。
17
+ *
18
+ * @example
19
+ * const currentItem = buildAddTimeCurrentItem({ booking, product, productsByUid });
20
+ */
21
+ export declare function buildAddTimeCurrentItem({ booking, product, productsByUid, }: BuildAddTimeCurrentItemParams): Record<string, any> | null;
22
+ /**
23
+ * 判断当前 booking 是否允许打开加时入口,保持与旧 ticketBooking 的商品适用规则一致。
24
+ *
25
+ * @example
26
+ * const canAddTime = resolveBookingCanAddTime({ booking, product, addTimeProducts, bookingConfig });
27
+ */
28
+ export declare function resolveBookingCanAddTime(params: ResolveBookingCanAddTimeParams): boolean;
29
+ /**
30
+ * 判断一个加时商品是否适用于当前预约商品。
31
+ *
32
+ * @example
33
+ * const usable = isUsableAddTimeProduct(addTime.add_time, currentItem, '23:59');
34
+ */
35
+ export declare function isUsableAddTimeProduct(addTime: any, currentItem: Record<string, any> | null | undefined, shopOpeningHours?: string): boolean;
@@ -0,0 +1,125 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+
29
+ // src/solution/BookingTicket/utils/addTimeAvailability.ts
30
+ var addTimeAvailability_exports = {};
31
+ __export(addTimeAvailability_exports, {
32
+ buildAddTimeCurrentItem: () => buildAddTimeCurrentItem,
33
+ isUsableAddTimeProduct: () => isUsableAddTimeProduct,
34
+ resolveBookingCanAddTime: () => resolveBookingCanAddTime
35
+ });
36
+ module.exports = __toCommonJS(addTimeAvailability_exports);
37
+ var import_dayjs = __toESM(require("dayjs"));
38
+ var import_lodash_es = require("lodash-es");
39
+ var import_serviceTimes = require("../../../modules/BookingContext/utils/serviceTimes");
40
+ var import_timeSlices = require("../../../modules/BookingContext/utils/timeSlices");
41
+ function buildAddTimeCurrentItem({
42
+ booking,
43
+ product,
44
+ productsByUid
45
+ }) {
46
+ var _a, _b, _c;
47
+ if (!booking || !product)
48
+ return null;
49
+ const uid = (_a = product.metadata) == null ? void 0 : _a.unique_identification_number;
50
+ const origin = uid ? (_b = productsByUid == null ? void 0 : productsByUid[String(uid)]) == null ? void 0 : _b.origin : void 0;
51
+ const sourceProduct = origin || booking.product || {};
52
+ const startAt = booking.start_date && booking.start_time ? `${booking.start_date} ${booking.start_time}` : booking.start_date;
53
+ const endAt = booking.end_date && booking.end_time ? `${booking.end_date} ${booking.end_time}` : booking.end_date;
54
+ return {
55
+ ...sourceProduct,
56
+ ...booking.product,
57
+ product_id: product.product_id ?? sourceProduct.product_id ?? sourceProduct.id,
58
+ id: sourceProduct.id ?? product.product_id,
59
+ collection: sourceProduct.collection || ((_c = booking.product) == null ? void 0 : _c.collection) || [],
60
+ duration: sourceProduct.duration || booking.duration,
61
+ _extend: {
62
+ ...sourceProduct._extend || {},
63
+ startDate: startAt ? (0, import_dayjs.default)(startAt) : void 0,
64
+ endDate: endAt ? (0, import_dayjs.default)(endAt) : void 0,
65
+ start_date: booking.start_date,
66
+ end_date: booking.end_date,
67
+ start_time: booking.start_time,
68
+ end_time: booking.end_time,
69
+ sub_type: booking.sub_type
70
+ }
71
+ };
72
+ }
73
+ function resolveBookingCanAddTime(params) {
74
+ var _a, _b, _c, _d;
75
+ const quickTime = (_d = (_c = (_b = (_a = params.bookingConfig) == null ? void 0 : _a.config) == null ? void 0 : _b.information_tab) == null ? void 0 : _c.appointment_card) == null ? void 0 : _d.quick_time;
76
+ if (quickTime !== void 0 && quickTime !== 1)
77
+ return false;
78
+ const currentItem = buildAddTimeCurrentItem(params);
79
+ if (!currentItem || (0, import_serviceTimes.isMultiDayProduct)(currentItem))
80
+ return false;
81
+ return (params.addTimeProducts || []).some(
82
+ (item) => isUsableAddTimeProduct(item == null ? void 0 : item.add_time, currentItem, params.shopOpeningHours)
83
+ );
84
+ }
85
+ function isUsableAddTimeProduct(addTime, currentItem, shopOpeningHours) {
86
+ var _a, _b;
87
+ if (!addTime || !currentItem)
88
+ return false;
89
+ if (addTime.additional_time_options === "unlimited") {
90
+ const startDate = (0, import_dayjs.default)((_a = currentItem._extend) == null ? void 0 : _a.startDate);
91
+ const endDate = (0, import_dayjs.default)((_b = currentItem._extend) == null ? void 0 : _b.endDate);
92
+ if (!startDate.isValid() || !endDate.isValid())
93
+ return false;
94
+ const maxEndDate = (0, import_timeSlices.getEndDate)(startDate, "flexible", "", shopOpeningHours);
95
+ if (endDate.isAfter(maxEndDate))
96
+ return false;
97
+ }
98
+ const relationProduct = addTime.relation_product || {};
99
+ const type = relationProduct.type;
100
+ const products = Array.isArray(relationProduct.products) ? relationProduct.products : [];
101
+ if (type === "product_all")
102
+ return true;
103
+ if (type === "product_collection") {
104
+ return Boolean(
105
+ (0, import_lodash_es.intersectionBy)(
106
+ products.map((item) => ({
107
+ ...item,
108
+ id: item.product_collection_id
109
+ })),
110
+ currentItem.collection || [],
111
+ "id"
112
+ ).length
113
+ );
114
+ }
115
+ if (type === "products") {
116
+ return products.some((item) => item.product_id == currentItem.product_id);
117
+ }
118
+ return false;
119
+ }
120
+ // Annotate the CommonJS export names for ESM import in node:
121
+ 0 && (module.exports = {
122
+ buildAddTimeCurrentItem,
123
+ isUsableAddTimeProduct,
124
+ resolveBookingCanAddTime
125
+ });
@@ -2,6 +2,14 @@ import type { BaseSalesOrderProduct } from '../../BaseSales';
2
2
  import type { ISalesBooking } from '../../BaseSales/utils/parseSalesResponse';
3
3
  import type { BookingTicketCartView, BookingTicketCustomerView, ICustomer } from '../types';
4
4
  import type { ScanOrderTempOrder } from '../../ScanOrder/types';
5
+ export interface BuildCartViewOptions {
6
+ addTimeProducts?: any[];
7
+ bookingConfig?: Record<string, any> | null;
8
+ productsByUid?: Record<string, {
9
+ origin?: Record<string, any>;
10
+ }>;
11
+ shopOpeningHours?: string;
12
+ }
5
13
  /**
6
14
  * 仅按 booking.product_uid 建索引,避免发明 booking 与商品行之间的其它等价关系。
7
15
  */
@@ -13,7 +21,7 @@ export declare function indexBookingsByProductUid(bookings: ISalesBooking[] | un
13
21
  * 关联规则(1:1):booking.product_uid ↔ line.metadata.unique_identification_number;
14
22
  * 同一 uid 已被更早的 booking 占用时,后续 booking 的 product 为 null。
15
23
  */
16
- export declare function buildCartView(lines: BaseSalesOrderProduct[] | undefined | null, bookings: ISalesBooking[] | undefined | null): BookingTicketCartView;
24
+ export declare function buildCartView(lines: BaseSalesOrderProduct[] | undefined | null, bookings: ISalesBooking[] | undefined | null, options?: BuildCartViewOptions): BookingTicketCartView;
17
25
  export declare function isWalkInCustomer(customer: Pick<ICustomer, 'id'> | null | undefined): boolean;
18
26
  /**
19
27
  * selected 优先,缺失时回退 tempOrder.customer_*,统一输出 UI 客户视图。