@pisell/pisellos 2.3.144 → 2.3.146
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.
- package/dist/modules/BookingContext/utils/productExtend.d.ts +7 -0
- package/dist/modules/BookingContext/utils/productExtend.js +62 -18
- package/dist/modules/Order/index.d.ts +2 -1
- package/dist/modules/Order/index.js +380 -198
- package/dist/modules/Order/types.d.ts +9 -0
- package/dist/modules/ResourcePlanner/planner.d.ts +2 -0
- package/dist/modules/ResourcePlanner/planner.js +327 -22
- package/dist/modules/ResourcePlanner/types.d.ts +29 -0
- package/dist/solution/BookingByStep/index.d.ts +1 -1
- package/dist/solution/BookingTicket/utils/addProductDecision.d.ts +0 -1
- package/dist/solution/BookingTicket/utils/addProductDecision.js +1 -1
- package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.js +6 -2
- package/dist/solution/UnifiedBookingSales/index.d.ts +3 -0
- package/dist/solution/UnifiedBookingSales/index.js +432 -163
- package/dist/solution/UnifiedBookingSales/types.d.ts +14 -2
- package/lib/modules/BookingContext/utils/productExtend.d.ts +7 -0
- package/lib/modules/BookingContext/utils/productExtend.js +45 -2
- package/lib/modules/Order/index.d.ts +2 -1
- package/lib/modules/Order/index.js +92 -10
- package/lib/modules/Order/types.d.ts +9 -0
- package/lib/modules/ResourcePlanner/planner.d.ts +2 -0
- package/lib/modules/ResourcePlanner/planner.js +247 -7
- package/lib/modules/ResourcePlanner/types.d.ts +29 -0
- package/lib/solution/BookingByStep/index.d.ts +1 -1
- package/lib/solution/BookingTicket/utils/addProductDecision.d.ts +0 -1
- package/lib/solution/BookingTicket/utils/addProductDecision.js +1 -1
- package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.js +7 -1
- package/lib/solution/UnifiedBookingSales/index.d.ts +3 -0
- package/lib/solution/UnifiedBookingSales/index.js +220 -27
- package/lib/solution/UnifiedBookingSales/types.d.ts +14 -2
- package/package.json +1 -1
|
@@ -72,6 +72,11 @@ export interface UnifiedBookingSalesPrepareAvailabilityProjectionParams {
|
|
|
72
72
|
}
|
|
73
73
|
export interface AvailabilityContextRef {
|
|
74
74
|
contextId: string;
|
|
75
|
+
/** POS/Kiosk duration protocol capability and business-day default. */
|
|
76
|
+
durationTimeSelection?: {
|
|
77
|
+
version: 1;
|
|
78
|
+
currentBusinessDate: string;
|
|
79
|
+
};
|
|
75
80
|
}
|
|
76
81
|
export interface ContextualProductTimeRange extends ProductTimeRange {
|
|
77
82
|
availabilityContextId: string;
|
|
@@ -80,7 +85,7 @@ export interface ContextualProductTimeRange extends ProductTimeRange {
|
|
|
80
85
|
export interface ContextualProductTimeRangeGroup extends Omit<ProductTimeRangeGroup, 'ranges'> {
|
|
81
86
|
ranges: ContextualProductTimeRange[];
|
|
82
87
|
}
|
|
83
|
-
export type ContextualAvailabilityQuery = Omit<Extract<AvailabilityQuery, {
|
|
88
|
+
export type ContextualAvailabilityQuery = (Omit<Extract<AvailabilityQuery, {
|
|
84
89
|
target: 'product';
|
|
85
90
|
}>, never> | Omit<Extract<AvailabilityQuery, {
|
|
86
91
|
target: 'date';
|
|
@@ -92,7 +97,10 @@ export type ContextualAvailabilityQuery = Omit<Extract<AvailabilityQuery, {
|
|
|
92
97
|
target: 'resource';
|
|
93
98
|
}>, 'selectedRanges'> & {
|
|
94
99
|
selectedRanges: ContextualProductTimeRange[];
|
|
95
|
-
})
|
|
100
|
+
})) & {
|
|
101
|
+
/** Automatic remembered selection must not inherit POS manual exceptions. */
|
|
102
|
+
selectionIntent?: 'remembered' | 'manual';
|
|
103
|
+
};
|
|
96
104
|
export type ContextualTimeAvailabilityItem = Omit<TimeAvailabilityItem, keyof ProductTimeRange> & ContextualProductTimeRange;
|
|
97
105
|
export interface ContextualTimeAvailabilityGroup extends Omit<TimeAvailabilityGroup, 'times'> {
|
|
98
106
|
times: ContextualTimeAvailabilityItem[];
|
|
@@ -155,6 +163,10 @@ export interface UnifiedBookingSalesAddRetailProductParams {
|
|
|
155
163
|
}
|
|
156
164
|
export interface ContextualCommitAvailabilitySelectionParams {
|
|
157
165
|
mode: 'add' | 'edit';
|
|
166
|
+
/** Only POS duration add supports automatic remembered-time commits. */
|
|
167
|
+
selectionIntent?: 'remembered' | 'manual';
|
|
168
|
+
/** Local UI intent guard, checked again immediately before the Order mutation. */
|
|
169
|
+
availabilityCommitGuard?: () => void;
|
|
158
170
|
productLineUid?: string;
|
|
159
171
|
orderProduct: Record<string, any>;
|
|
160
172
|
candidate: ContextualProductTimeRange;
|
|
@@ -7,6 +7,13 @@ import { TimeSlicesResult, TimeSlice, DurationSlice } from './timeSlices';
|
|
|
7
7
|
* 让 OS 这层完全可在 Node 单测下运行(dayjs 在 Node 也是纯函数)。
|
|
8
8
|
*/
|
|
9
9
|
export interface BookingCalcContext {
|
|
10
|
+
/** Sales channel; remembered duration starts are accepted only by POS. */
|
|
11
|
+
channel?: string;
|
|
12
|
+
/** Start-only device preference for this decision; never reuse a cached end/resource. */
|
|
13
|
+
rememberedTime?: {
|
|
14
|
+
businessDate: string;
|
|
15
|
+
startAt: string;
|
|
16
|
+
};
|
|
10
17
|
/** 来自 `/core/board/management/config` 的整套配置;常用路径见 sliceTimeSlices / appointmentTimingPreference。 */
|
|
11
18
|
bookingConfig?: any;
|
|
12
19
|
/** 资源 origin map(id → resource origin)。 */
|
|
@@ -28,6 +28,44 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
|
|
28
28
|
* 让 OS 这层完全可在 Node 单测下运行(dayjs 在 Node 也是纯函数)。
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
+
/** Rebuild only the derived booking fields; SKU, quantity, notes and Holder stay intact. */
|
|
32
|
+
function seedRememberedBookingStart(cacheItem, ctx) {
|
|
33
|
+
const remembered = ctx.rememberedTime;
|
|
34
|
+
const invalid = () => Object.assign(new Error('Remembered time requires a valid POS duration start.'), {
|
|
35
|
+
code: 'INVALID_REMEMBERED_TIME'
|
|
36
|
+
});
|
|
37
|
+
const duration = cacheItem?.duration;
|
|
38
|
+
const durationType = typeof duration === 'object' && duration ? String(duration.type || '').toLowerCase() : '';
|
|
39
|
+
const durationValue = typeof duration === 'object' && duration ? duration.value : duration;
|
|
40
|
+
const isDuration = (0, _flexible.isProductFlexibleDuration)(duration) || ['', 'fixed', 'minute', 'minutes', 'hour', 'hours'].includes(durationType) && Number.isFinite(Number(durationValue)) && Number(durationValue) > 0;
|
|
41
|
+
const isCalendarDate = value => {
|
|
42
|
+
if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return false;
|
|
43
|
+
const date = new Date(`${value}T00:00:00.000Z`);
|
|
44
|
+
return Number.isFinite(date.getTime()) && date.toISOString().slice(0, 10) === value;
|
|
45
|
+
};
|
|
46
|
+
const parts = typeof remembered?.startAt === 'string' ? /^(\d{4}-\d{2}-\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.\d{1,3})?)?(?:Z|[+-](\d{2}):(\d{2}))?$/.exec(remembered.startAt) : null;
|
|
47
|
+
if (String(ctx.channel || '').trim().toLowerCase() !== 'pos' || !isDuration || (0, _serviceTimes.isMultiDayProduct)(cacheItem) || (0, _serviceTimes.isSessionProduct)(cacheItem) || cacheItem?.cartDetailValue?.session || cacheItem?._extend?.other?.session || !isCalendarDate(remembered?.businessDate) || !parts || !isCalendarDate(parts[1]) || Number(parts[2]) > 23 || Number(parts[3]) > 59 || Number(parts[4] || 0) > 59 || Number(parts[5] || 0) > 23 || Number(parts[6] || 0) > 59) throw invalid();
|
|
48
|
+
// Parse the actual instant with the same runtime clock used by the legacy booking helpers.
|
|
49
|
+
// The business-day label must not replace the calendar date of an overnight start.
|
|
50
|
+
const start = (0, _dayjs.default)(remembered.startAt);
|
|
51
|
+
if (!start.isValid()) throw invalid();
|
|
52
|
+
const extend = {
|
|
53
|
+
...(cacheItem?._extend || {})
|
|
54
|
+
};
|
|
55
|
+
for (const key of ['start_date', 'startDate', 'start_time', 'end_date', 'endDate', 'end_time', 'duration', 'resource', 'resources', 'capacity', 'booking', 'availabilitySelection']) delete extend[key];
|
|
56
|
+
return {
|
|
57
|
+
...cacheItem,
|
|
58
|
+
new: 1,
|
|
59
|
+
autoClose: false,
|
|
60
|
+
_data: {},
|
|
61
|
+
_extend: {
|
|
62
|
+
...extend,
|
|
63
|
+
start_date: start.format('YYYY-MM-DD'),
|
|
64
|
+
start_time: start.format(start.millisecond() ? 'HH:mm:ss.SSS' : start.second() ? 'HH:mm:ss' : 'HH:mm')
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
31
69
|
/** 从 BookingContext.date 提取 HH:mm(仅当日期带有效时分时)。 */
|
|
32
70
|
function derivePresetStartTimeFromDate(date) {
|
|
33
71
|
if (date == null) return undefined;
|
|
@@ -195,10 +233,15 @@ function getServiceStartTimeAndEndTime(params) {
|
|
|
195
233
|
*/
|
|
196
234
|
function getProductExtend(params) {
|
|
197
235
|
const {
|
|
198
|
-
cacheItem,
|
|
199
|
-
ctx,
|
|
200
236
|
isDisabledTime
|
|
201
237
|
} = params;
|
|
238
|
+
const hasRememberedTime = params.ctx.rememberedTime !== undefined;
|
|
239
|
+
const cacheItem = hasRememberedTime ? seedRememberedBookingStart(params.cacheItem, params.ctx) : params.cacheItem;
|
|
240
|
+
const ctx = hasRememberedTime ? {
|
|
241
|
+
...params.ctx,
|
|
242
|
+
previousService: undefined,
|
|
243
|
+
presetStartTime: undefined
|
|
244
|
+
} : params.ctx;
|
|
202
245
|
const isNew = cacheItem.new || !cacheItem._extend?.endDate;
|
|
203
246
|
const _cacheItem = {
|
|
204
247
|
...cacheItem
|
|
@@ -332,6 +332,7 @@ export declare class OrderModule extends BaseModule implements Module, OrderModu
|
|
|
332
332
|
private applyOrderProductsDepositSummary;
|
|
333
333
|
recalculateSummary(options?: {
|
|
334
334
|
createIfMissing?: boolean;
|
|
335
|
+
isCurrentOrder?: () => boolean;
|
|
335
336
|
}): Promise<OrderSummary | null>;
|
|
336
337
|
getOrderSummary(): Promise<OrderSummary>;
|
|
337
338
|
updateTempOrderNote(note: string): string;
|
|
@@ -592,7 +593,7 @@ export declare class OrderModule extends BaseModule implements Module, OrderModu
|
|
|
592
593
|
generateIdempotencyToken(): string;
|
|
593
594
|
syncPaymentsToOrder<T = any>(params: SyncPaymentsToOrderParams): Promise<SyncPaymentsToOrderResult<T>>;
|
|
594
595
|
createOrder(params: CommitOrderParams['query']): {
|
|
595
|
-
type: "
|
|
596
|
+
type: "virtual" | "appointment_booking";
|
|
596
597
|
platform: string;
|
|
597
598
|
sales_channel: string;
|
|
598
599
|
order_sales_channel: string;
|
|
@@ -913,7 +913,8 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
913
913
|
* - 不主动调用 applyDiscount / recalculateSummary,调用方负责跟进;
|
|
914
914
|
* - 返回的 payload 只能在 applyDiscount(商品写路径还需 summary/persist)完成后发布。
|
|
915
915
|
*/
|
|
916
|
-
async evaluatePromotion() {
|
|
916
|
+
async evaluatePromotion(options) {
|
|
917
|
+
if (options?.isCurrentOrder && !options.isCurrentOrder()) return null;
|
|
917
918
|
if (this.isApplyingPromotion || this.isEmittingPromotionApplied) {
|
|
918
919
|
return null;
|
|
919
920
|
}
|
|
@@ -974,6 +975,10 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
974
975
|
continue;
|
|
975
976
|
}
|
|
976
977
|
}
|
|
978
|
+
const giftMutationBaseline = options?.isCurrentOrder ? (0, _lodashEs.cloneDeep)({
|
|
979
|
+
products: tempOrder.products,
|
|
980
|
+
bookings: tempOrder.bookings
|
|
981
|
+
}) : null;
|
|
977
982
|
try {
|
|
978
983
|
const giftResult = await this.giftSelectResolver({
|
|
979
984
|
strategyId: addAction.strategyId,
|
|
@@ -982,6 +987,11 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
982
987
|
giftOptions: addAction.giftOptions,
|
|
983
988
|
sourceProductIds: addAction.sourceProductIds
|
|
984
989
|
});
|
|
990
|
+
if (options?.isCurrentOrder && !options.isCurrentOrder()) return null;
|
|
991
|
+
if (options?.isCurrentOrder && !(0, _lodashEs.isEqual)({
|
|
992
|
+
products: tempOrder.products,
|
|
993
|
+
bookings: tempOrder.bookings
|
|
994
|
+
}, giftMutationBaseline)) return null;
|
|
985
995
|
const giftProducts = Array.isArray(giftResult) ? giftResult : giftResult?.products;
|
|
986
996
|
const giftBookings = Array.isArray(giftResult) ? [] : giftResult?.bookings || [];
|
|
987
997
|
if (Array.isArray(giftProducts) && giftProducts.length > 0) {
|
|
@@ -1002,12 +1012,18 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
1002
1012
|
}
|
|
1003
1013
|
}
|
|
1004
1014
|
} catch (error) {
|
|
1015
|
+
if (options?.isCurrentOrder && !options.isCurrentOrder()) return null;
|
|
1016
|
+
if (options?.isCurrentOrder && !(0, _lodashEs.isEqual)({
|
|
1017
|
+
products: tempOrder.products,
|
|
1018
|
+
bookings: tempOrder.bookings
|
|
1019
|
+
}, giftMutationBaseline)) return null;
|
|
1005
1020
|
// 用户取消 / 选择失败 → 视为放弃本次新增
|
|
1006
1021
|
console.warn('[OrderModule] giftSelectResolver rejected, skip add gift', addAction.strategyId, error);
|
|
1007
1022
|
}
|
|
1008
1023
|
}
|
|
1009
1024
|
}
|
|
1010
1025
|
}
|
|
1026
|
+
if (options?.isCurrentOrder && !options.isCurrentOrder()) return null;
|
|
1011
1027
|
tempOrder.products = result.products;
|
|
1012
1028
|
this.lastUnfulfilledPromotions = result.unfulfilledPromotions;
|
|
1013
1029
|
this.lastGiftActions = result.giftActions;
|
|
@@ -2469,6 +2485,7 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
2469
2485
|
return shouldUseManualDepositOverride ? nextSummary : this.applyLastOrderDepositSnapshot(tempOrder, nextSummary);
|
|
2470
2486
|
}
|
|
2471
2487
|
async recalculateSummary(options) {
|
|
2488
|
+
if (options?.isCurrentOrder && !options.isCurrentOrder()) return null;
|
|
2472
2489
|
const tempOrder = options?.createIfMissing ? this.ensureTempOrder() : this.store.tempOrder;
|
|
2473
2490
|
if (!tempOrder) return null;
|
|
2474
2491
|
this.sanitizeTempOrderProducts(tempOrder);
|
|
@@ -2478,7 +2495,7 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
2478
2495
|
const snapshotSummary = shouldReuseOriginalSnapshot ? this.restoreOriginalSnapshotIfProductsUnchanged(tempOrder, originalSnapshot) : null;
|
|
2479
2496
|
if (snapshotSummary) return snapshotSummary;
|
|
2480
2497
|
const shouldFullRecalculateFromSnapshot = Boolean(originalSnapshot);
|
|
2481
|
-
const summaryProducts = shouldFullRecalculateFromSnapshot ? (0, _lodashEs.cloneDeep)(tempOrder.products || []) : tempOrder.products;
|
|
2498
|
+
const summaryProducts = shouldFullRecalculateFromSnapshot || options?.isCurrentOrder ? (0, _lodashEs.cloneDeep)(tempOrder.products || []) : tempOrder.products;
|
|
2482
2499
|
if (shouldFullRecalculateFromSnapshot) {
|
|
2483
2500
|
this.clearPersistedAmountFieldsForFullRecalc(summaryProducts || []);
|
|
2484
2501
|
}
|
|
@@ -2516,6 +2533,10 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
2516
2533
|
});
|
|
2517
2534
|
return this.store.summary;
|
|
2518
2535
|
}
|
|
2536
|
+
const summaryMutationBaseline = options?.isCurrentOrder ? (0, _lodashEs.cloneDeep)({
|
|
2537
|
+
tempOrder,
|
|
2538
|
+
summary: this.store.summary
|
|
2539
|
+
}) : null;
|
|
2519
2540
|
const salesSummaryResult = await salesSummary.getSummary({
|
|
2520
2541
|
products: summaryProducts,
|
|
2521
2542
|
isPriceIncludeTax: tempOrder.is_price_include_tax,
|
|
@@ -2525,7 +2546,12 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
2525
2546
|
channel: this.otherParams.channel
|
|
2526
2547
|
} : {})
|
|
2527
2548
|
});
|
|
2528
|
-
if (
|
|
2549
|
+
if (options?.isCurrentOrder && !options.isCurrentOrder()) return null;
|
|
2550
|
+
if (options?.isCurrentOrder && !(0, _lodashEs.isEqual)({
|
|
2551
|
+
tempOrder,
|
|
2552
|
+
summary: this.store.summary
|
|
2553
|
+
}, summaryMutationBaseline)) return null;
|
|
2554
|
+
if (shouldFullRecalculateFromSnapshot || options?.isCurrentOrder) {
|
|
2529
2555
|
tempOrder.products = summaryProducts;
|
|
2530
2556
|
}
|
|
2531
2557
|
this.syncSummaryProductMetadata(tempOrder.products || []);
|
|
@@ -3454,20 +3480,30 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
3454
3480
|
* await this.finalizeProductOrderMutation(tempOrder);
|
|
3455
3481
|
*/
|
|
3456
3482
|
async finalizeProductOrderMutation(tempOrder, options) {
|
|
3483
|
+
const isCurrentOrder = options?.isCurrentOrder;
|
|
3484
|
+
if (isCurrentOrder && !isCurrentOrder()) return;
|
|
3457
3485
|
this.syncTempOrderHolderFromBookings(tempOrder);
|
|
3458
3486
|
this.prepareVirtualSettlementTempOrder(tempOrder);
|
|
3459
3487
|
const isVirtualSettlement = this.isActiveVirtualSettlementOrder(tempOrder);
|
|
3460
3488
|
if (!options?.skipEditDiscountConfigRefresh && !isVirtualSettlement) {
|
|
3461
3489
|
await this.ensureEditDiscountConfigForProductChange(tempOrder);
|
|
3462
3490
|
}
|
|
3463
|
-
|
|
3491
|
+
if (isCurrentOrder && !isCurrentOrder()) return;
|
|
3492
|
+
const promotionPayload = !options?.skipDiscountRecalculation && !isVirtualSettlement ? await this.evaluatePromotion(isCurrentOrder ? {
|
|
3493
|
+
isCurrentOrder
|
|
3494
|
+
} : undefined) : null;
|
|
3495
|
+
if (isCurrentOrder && !isCurrentOrder()) return;
|
|
3464
3496
|
if (!options?.skipDiscountRecalculation && !isVirtualSettlement) {
|
|
3465
3497
|
this.applyDiscount();
|
|
3466
3498
|
}
|
|
3467
3499
|
this.sanitizeTempOrderProducts(tempOrder);
|
|
3468
|
-
await this.recalculateSummary({
|
|
3469
|
-
createIfMissing: true
|
|
3500
|
+
const summary = await this.recalculateSummary({
|
|
3501
|
+
createIfMissing: true,
|
|
3502
|
+
...(isCurrentOrder ? {
|
|
3503
|
+
isCurrentOrder
|
|
3504
|
+
} : {})
|
|
3470
3505
|
});
|
|
3506
|
+
if (isCurrentOrder && (!isCurrentOrder() || !summary)) return;
|
|
3471
3507
|
this.persistTempOrder();
|
|
3472
3508
|
this.emitPromotionApplied(promotionPayload);
|
|
3473
3509
|
}
|
|
@@ -3704,8 +3740,16 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
3704
3740
|
* // → tempOrder.products 新增 3 行,每行 num=1,bookings 3 条
|
|
3705
3741
|
*/
|
|
3706
3742
|
async addProductToOrder(product, booking, options) {
|
|
3707
|
-
const
|
|
3708
|
-
|
|
3743
|
+
const availabilityCommitGuard = options?.availabilityCommitGuard;
|
|
3744
|
+
availabilityCommitGuard?.();
|
|
3745
|
+
const liveTempOrder = this.ensureTempOrder();
|
|
3746
|
+
const beforeCommit = this.createCartIntentCommitGuard('add_product', liveTempOrder);
|
|
3747
|
+
// 自动预约在草稿上准备完整批次,避免拆票期间暴露半成品,或在失败时
|
|
3748
|
+
// 用整单快照回滚而抹掉其他入口的并发修改。人工路径保持原有写入行为。
|
|
3749
|
+
const tempOrder = availabilityCommitGuard ? (0, _lodashEs.cloneDeep)(liveTempOrder) : liveTempOrder;
|
|
3750
|
+
const guardedMutationFields = ['products', 'bookings', 'notes', '_extend', 'metadata', 'currency_code', 'currency_symbol', 'currency_format'];
|
|
3751
|
+
const readGuardedMutationFields = order => Object.fromEntries(guardedMutationFields.map(key => [key, order[key]]));
|
|
3752
|
+
const guardedMutationBaseline = availabilityCommitGuard ? (0, _lodashEs.cloneDeep)(readGuardedMutationFields(liveTempOrder)) : null;
|
|
3709
3753
|
const salesNoteDrafts = (options?.salesNotes || []).map(draft => ({
|
|
3710
3754
|
note_type: String(draft.note_type || '').trim(),
|
|
3711
3755
|
text: {
|
|
@@ -3779,6 +3823,7 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
3779
3823
|
disableMerge: options?.disableMerge,
|
|
3780
3824
|
beforeCommit
|
|
3781
3825
|
});
|
|
3826
|
+
availabilityCommitGuard?.();
|
|
3782
3827
|
attachSalesNoteDrafts(committedProduct);
|
|
3783
3828
|
if (insertAtIndex !== undefined) insertAtIndex += 1;
|
|
3784
3829
|
}
|
|
@@ -3788,6 +3833,7 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
3788
3833
|
disableMerge: options?.disableMerge,
|
|
3789
3834
|
beforeCommit
|
|
3790
3835
|
});
|
|
3836
|
+
availabilityCommitGuard?.();
|
|
3791
3837
|
attachSalesNoteDrafts(committedProduct);
|
|
3792
3838
|
}
|
|
3793
3839
|
} else {
|
|
@@ -3796,18 +3842,54 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
3796
3842
|
disableMerge: options?.disableMerge,
|
|
3797
3843
|
beforeCommit
|
|
3798
3844
|
});
|
|
3845
|
+
availabilityCommitGuard?.();
|
|
3799
3846
|
attachSalesNoteDrafts(committedProduct);
|
|
3800
3847
|
}
|
|
3848
|
+
if (availabilityCommitGuard) {
|
|
3849
|
+
availabilityCommitGuard();
|
|
3850
|
+
beforeCommit();
|
|
3851
|
+
// 调用方的结构 revision 可能不包括备注、价格或结算元数据。
|
|
3852
|
+
// 本次会覆盖的字段也要保持原值,不能让草稿覆盖并发编辑。
|
|
3853
|
+
if (!(0, _lodashEs.isEqual)(readGuardedMutationFields(liveTempOrder), guardedMutationBaseline)) {
|
|
3854
|
+
throw Object.assign(new Error('预约加购期间订单已变化,请重新选择'), {
|
|
3855
|
+
code: 'AVAILABILITY_SELECTION_STALE'
|
|
3856
|
+
});
|
|
3857
|
+
}
|
|
3858
|
+
// 从最后一次门禁到真正新增之间不再 await。只发布本次新增会改写的
|
|
3859
|
+
// 字段;其余订单字段继续使用 live 对象,外部持有的订单引用也不变。
|
|
3860
|
+
Object.assign(liveTempOrder, readGuardedMutationFields(tempOrder));
|
|
3861
|
+
}
|
|
3801
3862
|
} catch (error) {
|
|
3802
3863
|
rollbackProductAndNotesMutation();
|
|
3803
3864
|
throw error;
|
|
3804
3865
|
}
|
|
3805
|
-
|
|
3866
|
+
// 发布后预约已加购;后续促销/summary 沿用既有收尾,不用自身新增后的
|
|
3867
|
+
// revision 再执行提交 CAS,也不会把已加购预约因收尾耗时而撤回。
|
|
3868
|
+
if (availabilityCommitGuard) {
|
|
3869
|
+
const committedIdentity = [liveTempOrder.order_id, liveTempOrder.external_sale_number];
|
|
3870
|
+
const isCurrentOrder = () => this.store.tempOrder === liveTempOrder && liveTempOrder.order_id === committedIdentity[0] && liveTempOrder.external_sale_number === committedIdentity[1] && (options?.availabilityCommitTargetGuard?.() ?? true);
|
|
3871
|
+
try {
|
|
3872
|
+
// 新增已发布,即使后续报价/促销收尾失败,也不能向调用方报告未加购。
|
|
3873
|
+
// 先保存已提交行;异步结果只允许写回仍属于本次提交的订单。
|
|
3874
|
+
if (isCurrentOrder()) this.persistTempOrder();
|
|
3875
|
+
await this.finalizeProductOrderMutation(liveTempOrder, {
|
|
3876
|
+
isCurrentOrder
|
|
3877
|
+
});
|
|
3878
|
+
} catch (error) {
|
|
3879
|
+
this.logWarning('addProductToOrder committed; finalization failed', {
|
|
3880
|
+
product_id: product.product_id,
|
|
3881
|
+
committed: true,
|
|
3882
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3883
|
+
});
|
|
3884
|
+
}
|
|
3885
|
+
} else {
|
|
3886
|
+
await this.finalizeProductOrderMutation(liveTempOrder);
|
|
3887
|
+
}
|
|
3806
3888
|
this.logInfo('addProductToOrder success', {
|
|
3807
3889
|
product_id: product.product_id,
|
|
3808
3890
|
with_booking: !!booking
|
|
3809
3891
|
});
|
|
3810
|
-
return
|
|
3892
|
+
return liveTempOrder.products;
|
|
3811
3893
|
}
|
|
3812
3894
|
|
|
3813
3895
|
/**
|
|
@@ -445,6 +445,15 @@ export interface AddProductToOrderOptions {
|
|
|
445
445
|
disableMerge?: boolean;
|
|
446
446
|
/** 商品行 UID 落定后,由 OrderModule 原子绑定到该行的 Sales Notes 草稿。 */
|
|
447
447
|
salesNotes?: SalesNoteDraftInput[];
|
|
448
|
+
/**
|
|
449
|
+
* 自动预约加购的同步提交门禁;抛错时不发布本次新增。
|
|
450
|
+
* 带门禁时先在私有草稿中准备全部商品/booking,异步等待后及原子发布前重新调用。
|
|
451
|
+
* 门禁须校验当前订单身份/版本和真实时间;发布前不会因本次拆行改变订单版本。
|
|
452
|
+
* 不传时保留原有人工加购行为。
|
|
453
|
+
*/
|
|
454
|
+
availabilityCommitGuard?: () => void;
|
|
455
|
+
/** 发布后的订单/业务归属检查;只用于阻止旧异步收尾写入新单,不再校验截止或 revision。 */
|
|
456
|
+
availabilityCommitTargetGuard?: () => boolean;
|
|
448
457
|
}
|
|
449
458
|
/**
|
|
450
459
|
* 删除商品后的可选位置保持策略。
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { AvailabilityProjection, AvailabilityProjectionInput, AvailabilityQuery, AvailabilityQueryResult, AvailabilitySelectionValidationParams, AvailabilitySelectionValidationResult, GetProductTimeRangesRequest, GetProductTimeRangesRuntimeOptions, ProductTimeRangeGroup } from './types';
|
|
2
2
|
export declare function createAvailabilityProjection(input: AvailabilityProjectionInput): AvailabilityProjection;
|
|
3
|
+
/** Resolve the label without changing the real calendar timestamp. */
|
|
4
|
+
export declare function getDurationBusinessDate(projection: AvailabilityProjection, startAt: string): string;
|
|
3
5
|
export declare function getProductTimeRanges(projection: AvailabilityProjection, request?: GetProductTimeRangesRequest, runtimeOptions?: GetProductTimeRangesRuntimeOptions): ProductTimeRangeGroup[];
|
|
4
6
|
export declare function queryAvailabilityProjection(projection: AvailabilityProjection, request: AvailabilityQuery, runtimeOptions?: GetProductTimeRangesRuntimeOptions): AvailabilityQueryResult;
|
|
5
7
|
export declare function validateAvailabilitySelection(projection: AvailabilityProjection, params: AvailabilitySelectionValidationParams, runtimeOptions?: GetProductTimeRangesRuntimeOptions): AvailabilitySelectionValidationResult;
|