@pisell/pisellos 0.10.50 → 0.10.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/model/strategy/adapter/promotion/index.js +0 -9
- package/dist/modules/FulfillmentTiming/index.d.ts +11 -0
- package/dist/modules/FulfillmentTiming/index.js +407 -0
- package/dist/modules/FulfillmentTiming/types.d.ts +67 -0
- package/dist/modules/FulfillmentTiming/types.js +1 -0
- package/dist/modules/OpenData/types.d.ts +6 -0
- package/dist/modules/Order/index.d.ts +18 -1
- package/dist/modules/Order/index.js +322 -195
- package/dist/modules/Order/pickupTiming.d.ts +46 -0
- package/dist/modules/Order/pickupTiming.js +53 -0
- package/dist/modules/Order/types.d.ts +11 -1
- package/dist/modules/Order/utils.d.ts +10 -0
- package/dist/modules/Order/utils.js +145 -85
- package/dist/modules/Rules/index.js +3 -2
- package/dist/modules/Schedule/index.d.ts +8 -0
- package/dist/modules/Schedule/index.js +134 -37
- package/dist/modules/index.d.ts +1 -0
- package/dist/modules/index.js +1 -0
- package/dist/server/modules/order/index.d.ts +8 -0
- package/dist/server/modules/order/index.js +506 -453
- package/dist/server/modules/order/types.d.ts +1 -0
- package/dist/server/utils/small-ticket.js +165 -94
- package/dist/solution/BaseSales/index.d.ts +24 -0
- package/dist/solution/BaseSales/index.js +1231 -1037
- package/dist/solution/BaseSales/pickupTiming.d.ts +43 -0
- package/dist/solution/BaseSales/pickupTiming.js +589 -0
- package/dist/solution/BaseSales/utils/parseSalesResponse.js +3 -0
- package/dist/solution/BookingByStep/index.d.ts +1 -1
- package/dist/solution/BookingTicket/index.d.ts +2 -2
- package/dist/solution/BookingTicket/utils/cartView.js +3 -1
- package/dist/solution/UnifiedBookingSales/index.d.ts +13 -4
- package/dist/solution/UnifiedBookingSales/index.js +873 -708
- package/lib/modules/FulfillmentTiming/index.d.ts +11 -0
- package/lib/modules/FulfillmentTiming/index.js +359 -0
- package/lib/modules/FulfillmentTiming/types.d.ts +67 -0
- package/lib/modules/FulfillmentTiming/types.js +5 -0
- package/lib/modules/OpenData/types.d.ts +6 -0
- package/lib/modules/Order/index.d.ts +18 -1
- package/lib/modules/Order/index.js +101 -3
- package/lib/modules/Order/pickupTiming.d.ts +46 -0
- package/lib/modules/Order/pickupTiming.js +61 -0
- package/lib/modules/Order/types.d.ts +11 -1
- package/lib/modules/Order/utils.d.ts +10 -0
- package/lib/modules/Order/utils.js +58 -4
- package/lib/modules/Rules/index.js +3 -2
- package/lib/modules/Schedule/index.d.ts +8 -0
- package/lib/modules/Schedule/index.js +64 -11
- package/lib/modules/index.d.ts +1 -0
- package/lib/modules/index.js +11 -0
- package/lib/server/modules/order/index.d.ts +8 -0
- package/lib/server/modules/order/index.js +19 -0
- package/lib/server/modules/order/types.d.ts +1 -0
- package/lib/server/utils/small-ticket.js +86 -22
- package/lib/solution/BaseSales/index.d.ts +24 -0
- package/lib/solution/BaseSales/index.js +109 -1
- package/lib/solution/BaseSales/pickupTiming.d.ts +43 -0
- package/lib/solution/BaseSales/pickupTiming.js +269 -0
- package/lib/solution/BaseSales/utils/parseSalesResponse.js +3 -0
- package/lib/solution/BookingByStep/index.d.ts +1 -1
- package/lib/solution/BookingTicket/index.d.ts +2 -2
- package/lib/solution/BookingTicket/utils/cartView.js +3 -1
- package/lib/solution/UnifiedBookingSales/index.d.ts +13 -4
- package/lib/solution/UnifiedBookingSales/index.js +130 -46
- package/package.json +1 -1
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.PickupTimingController = void 0;
|
|
7
|
+
var _lodashEs = require("lodash-es");
|
|
8
|
+
var _FulfillmentTiming = require("../../modules/FulfillmentTiming");
|
|
9
|
+
async function withTimeout(request) {
|
|
10
|
+
let timer;
|
|
11
|
+
try {
|
|
12
|
+
return await Promise.race([request, new Promise((_, reject) => {
|
|
13
|
+
timer = setTimeout(() => reject(new Error('Unable to load pickup times. Please try again.')), 30000);
|
|
14
|
+
})]);
|
|
15
|
+
} finally {
|
|
16
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Reuses runtime configuration and schedules; every calculation still uses the live clock. */
|
|
21
|
+
class PickupTimingController {
|
|
22
|
+
latest;
|
|
23
|
+
runtime;
|
|
24
|
+
writeSequence = 0;
|
|
25
|
+
constructor(host) {
|
|
26
|
+
this.host = host;
|
|
27
|
+
}
|
|
28
|
+
now() {
|
|
29
|
+
return this.host.now?.() ?? new Date();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** A new solution context or an explicit settings refresh invalidates pending work too. */
|
|
33
|
+
invalidate() {
|
|
34
|
+
this.runtime = undefined;
|
|
35
|
+
this.latest = undefined;
|
|
36
|
+
this.writeSequence += 1;
|
|
37
|
+
}
|
|
38
|
+
assertCurrentRuntime(runtime) {
|
|
39
|
+
if (this.runtime !== runtime || runtime.scope !== this.host.getScope()) {
|
|
40
|
+
throw new Error('Store settings changed. Please try again.');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
getRuntime() {
|
|
44
|
+
const scope = this.host.getScope();
|
|
45
|
+
if (this.runtime?.scope !== scope) {
|
|
46
|
+
this.latest = undefined;
|
|
47
|
+
this.runtime = {
|
|
48
|
+
scope
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return this.runtime;
|
|
52
|
+
}
|
|
53
|
+
async loadConfig(runtime) {
|
|
54
|
+
if (runtime.config) return runtime.config;
|
|
55
|
+
if (!runtime.configRequest) {
|
|
56
|
+
const request = (async () => {
|
|
57
|
+
const config = (0, _FulfillmentTiming.normalizePickupTimingConfig)(await withTimeout(this.host.loadOpenData(false)));
|
|
58
|
+
this.assertCurrentRuntime(runtime);
|
|
59
|
+
runtime.config = config;
|
|
60
|
+
return config;
|
|
61
|
+
})();
|
|
62
|
+
runtime.configRequest = request;
|
|
63
|
+
}
|
|
64
|
+
const request = runtime.configRequest;
|
|
65
|
+
try {
|
|
66
|
+
return await request;
|
|
67
|
+
} finally {
|
|
68
|
+
if (runtime.configRequest === request) runtime.configRequest = undefined;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async loadSchedules(runtime, config) {
|
|
72
|
+
if (runtime.schedules) return runtime.schedules;
|
|
73
|
+
if (!runtime.scheduleRequest) {
|
|
74
|
+
const request = (async () => {
|
|
75
|
+
const schedule = this.host.getSchedule();
|
|
76
|
+
if (!schedule) throw new Error('Pickup schedule is unavailable. Please try again.');
|
|
77
|
+
// Reuse the catalog loaded by solution initialization, including its in-flight request.
|
|
78
|
+
await withTimeout(schedule.loadAllSchedule({
|
|
79
|
+
useCache: true
|
|
80
|
+
}));
|
|
81
|
+
this.assertCurrentRuntime(runtime);
|
|
82
|
+
const ids = [...new Set([...config.scheduleIds, ...(config.operatingHoursScheduleIds || [])])];
|
|
83
|
+
const schedules = (0, _lodashEs.cloneDeep)(schedule.getScheduleListByIds(ids) || []);
|
|
84
|
+
runtime.schedules = schedules;
|
|
85
|
+
return schedules;
|
|
86
|
+
})();
|
|
87
|
+
runtime.scheduleRequest = request;
|
|
88
|
+
}
|
|
89
|
+
const request = runtime.scheduleRequest;
|
|
90
|
+
try {
|
|
91
|
+
return await request;
|
|
92
|
+
} finally {
|
|
93
|
+
if (runtime.scheduleRequest === request) runtime.scheduleRequest = undefined;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async snapshot(includeSchedule, tolerateScheduleError = false) {
|
|
97
|
+
const runtime = this.getRuntime();
|
|
98
|
+
const config = await this.loadConfig(runtime);
|
|
99
|
+
this.assertCurrentRuntime(runtime);
|
|
100
|
+
let schedules = [];
|
|
101
|
+
let error;
|
|
102
|
+
if (includeSchedule && config.handoverModes.includes('pick_up') && config.timingMode !== 'asap_only' && config.scheduleIds.length) {
|
|
103
|
+
try {
|
|
104
|
+
schedules = await this.loadSchedules(runtime, config);
|
|
105
|
+
} catch (reason) {
|
|
106
|
+
if (!tolerateScheduleError) throw reason;
|
|
107
|
+
error = reason instanceof Error ? reason.message : 'Pickup schedule is unavailable. Please try again.';
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
this.assertCurrentRuntime(runtime);
|
|
111
|
+
const result = {
|
|
112
|
+
config,
|
|
113
|
+
timezone: this.host.getTimezone(),
|
|
114
|
+
schedules,
|
|
115
|
+
scope: runtime.scope,
|
|
116
|
+
error
|
|
117
|
+
};
|
|
118
|
+
this.latest = result;
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
async getAvailability(params = {}) {
|
|
122
|
+
// `force` remains accepted for older callers; refresh recomputes time using the runtime catalog.
|
|
123
|
+
const snapshot = await this.snapshot(true, true);
|
|
124
|
+
const now = this.now();
|
|
125
|
+
try {
|
|
126
|
+
return {
|
|
127
|
+
...(0, _FulfillmentTiming.computePickupTimingAvailability)({
|
|
128
|
+
...snapshot,
|
|
129
|
+
date: params.date,
|
|
130
|
+
now
|
|
131
|
+
}),
|
|
132
|
+
error: snapshot.error
|
|
133
|
+
};
|
|
134
|
+
} catch (reason) {
|
|
135
|
+
// Keep the handover selector usable when only Pickup's timezone is unavailable.
|
|
136
|
+
return {
|
|
137
|
+
config: snapshot.config,
|
|
138
|
+
timezone: snapshot.timezone,
|
|
139
|
+
now: now.toISOString(),
|
|
140
|
+
today: '',
|
|
141
|
+
maxDate: '',
|
|
142
|
+
date: params.date || '',
|
|
143
|
+
dates: [],
|
|
144
|
+
slots: [],
|
|
145
|
+
error: reason instanceof Error ? reason.message : 'Pickup times are unavailable. Please try again.'
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
getSelection() {
|
|
150
|
+
const order = this.host.getOrder();
|
|
151
|
+
const tempOrder = order.getTempOrder();
|
|
152
|
+
const booking = order.getPickupTimingBooking();
|
|
153
|
+
const config = this.latest?.scope === this.host.getScope() ? this.latest.config : undefined;
|
|
154
|
+
const storedServiceType = String(tempOrder?.metadata?.shop_service_data?.service_type || '');
|
|
155
|
+
const serviceType = config?.handoverModes.length ? config.handoverModes.includes(storedServiceType) ? storedServiceType : config.handoverModes[0] : storedServiceType;
|
|
156
|
+
const details = serviceType === 'pick_up' ? booking?.metadata.fulfillment_timing : undefined;
|
|
157
|
+
const mode = config?.timingMode === 'asap_only' ? 'asap' : config?.timingMode === 'scheduled_only' ? 'scheduled' : details?.mode ?? 'asap';
|
|
158
|
+
const sameMode = details?.mode === mode;
|
|
159
|
+
return {
|
|
160
|
+
serviceType,
|
|
161
|
+
mode,
|
|
162
|
+
source: mode === 'asap' ? 'asap' : sameMode && details?.source || 'schedule',
|
|
163
|
+
...(sameMode && booking ? {
|
|
164
|
+
date: booking.start_date,
|
|
165
|
+
time: booking.start_time,
|
|
166
|
+
instant: details?.instant
|
|
167
|
+
} : {})
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
async validateSnapshot(selection) {
|
|
171
|
+
const snapshot = await this.snapshot(selection.serviceType === 'pick_up' && selection.mode === 'scheduled' && selection.source !== 'pos_manual');
|
|
172
|
+
const now = this.now();
|
|
173
|
+
const result = (0, _FulfillmentTiming.validatePickupTimingSelection)(selection, {
|
|
174
|
+
...snapshot,
|
|
175
|
+
now,
|
|
176
|
+
allowManualOverride: this.host.isPos()
|
|
177
|
+
});
|
|
178
|
+
return {
|
|
179
|
+
snapshot,
|
|
180
|
+
now,
|
|
181
|
+
result
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
async validate(selection) {
|
|
185
|
+
return (await this.validateSnapshot(selection)).result;
|
|
186
|
+
}
|
|
187
|
+
async setSelection(selection, options = {}) {
|
|
188
|
+
const order = this.host.getOrder();
|
|
189
|
+
const tempOrder = order.ensureTempOrder();
|
|
190
|
+
const sequence = ++this.writeSequence;
|
|
191
|
+
const {
|
|
192
|
+
snapshot,
|
|
193
|
+
now,
|
|
194
|
+
result
|
|
195
|
+
} = await this.validateSnapshot(selection);
|
|
196
|
+
if (sequence !== this.writeSequence || order.getTempOrder() !== tempOrder || snapshot.scope !== this.host.getScope()) {
|
|
197
|
+
throw new Error('The order changed. Please choose the pickup time again.');
|
|
198
|
+
}
|
|
199
|
+
if (!result.valid) throw new Error(result.message || 'Please choose a valid pickup time.');
|
|
200
|
+
if (!snapshot.config.handoverModes.length) {
|
|
201
|
+
order.clearPickupTimingBooking();
|
|
202
|
+
return this.getSelection();
|
|
203
|
+
}
|
|
204
|
+
if (selection.serviceType !== 'pick_up') {
|
|
205
|
+
order.updateTempOrderServiceType(selection.serviceType);
|
|
206
|
+
return this.getSelection();
|
|
207
|
+
}
|
|
208
|
+
const slot = selection.mode === 'asap' ? (0, _FulfillmentTiming.getAsapPickupTiming)({
|
|
209
|
+
...snapshot,
|
|
210
|
+
now
|
|
211
|
+
}) : result.slot;
|
|
212
|
+
if (!slot) throw new Error('Please choose a pickup time.');
|
|
213
|
+
// Validate everything before either field is changed; notify once after the combined update.
|
|
214
|
+
tempOrder.metadata = {
|
|
215
|
+
...(tempOrder.metadata || {}),
|
|
216
|
+
shop_service_data: {
|
|
217
|
+
...(tempOrder.metadata?.shop_service_data || {}),
|
|
218
|
+
service_type: 'pick_up'
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
order.setPickupTimingBooking({
|
|
222
|
+
mode: selection.mode,
|
|
223
|
+
source: selection.mode === 'asap' ? 'asap' : selection.source === 'pos_manual' ? 'pos_manual' : 'schedule',
|
|
224
|
+
date: slot.date,
|
|
225
|
+
time: slot.time,
|
|
226
|
+
instant: slot.instant,
|
|
227
|
+
timezone: snapshot.timezone,
|
|
228
|
+
frozen: options.frozen ?? false
|
|
229
|
+
});
|
|
230
|
+
return this.getSelection();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Called before a new checkout/payment; payment callbacks keep their already prepared appointment. */
|
|
234
|
+
async prepare(options = {}) {
|
|
235
|
+
const order = this.host.getOrder();
|
|
236
|
+
const tempOrder = order.getTempOrder();
|
|
237
|
+
const sequence = this.writeSequence;
|
|
238
|
+
if (order.getOrderIdentity()?.isSubmitted) return this.getSelection();
|
|
239
|
+
const booking = order.getPickupTimingBooking();
|
|
240
|
+
if (booking?.metadata.fulfillment_timing?.frozen) return this.getSelection();
|
|
241
|
+
// An actual payment callback must never revalidate or move the time after funds have been taken.
|
|
242
|
+
const paymentStatus = order.getTempOrder()?.payment_status;
|
|
243
|
+
if (paymentStatus === 'paid' || paymentStatus === 'partially_paid') {
|
|
244
|
+
if (booking?.metadata.fulfillment_timing) {
|
|
245
|
+
const detail = booking.metadata.fulfillment_timing;
|
|
246
|
+
order.setPickupTimingBooking({
|
|
247
|
+
...detail,
|
|
248
|
+
date: booking.start_date,
|
|
249
|
+
time: booking.start_time,
|
|
250
|
+
frozen: true
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
return this.getSelection();
|
|
254
|
+
}
|
|
255
|
+
const snapshot = await this.snapshot(false);
|
|
256
|
+
if (order !== this.host.getOrder() || order.getTempOrder() !== tempOrder || sequence !== this.writeSequence) {
|
|
257
|
+
throw new Error('The order changed. Please choose the pickup time again.');
|
|
258
|
+
}
|
|
259
|
+
const selection = this.getSelection();
|
|
260
|
+
if (!snapshot.config.handoverModes.length) {
|
|
261
|
+
order.clearPickupTimingBooking();
|
|
262
|
+
return selection;
|
|
263
|
+
}
|
|
264
|
+
return this.setSelection(selection, {
|
|
265
|
+
frozen: options.freeze ?? false
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
exports.PickupTimingController = PickupTimingController;
|
|
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.parseSalesResponse = parseSalesResponse;
|
|
7
7
|
var _orderCollectionIdentity = require("../../../modules/Order/utils/orderCollectionIdentity");
|
|
8
|
+
var _pickupTiming = require("../../../modules/Order/pickupTiming");
|
|
8
9
|
/**
|
|
9
10
|
* 把 `/order/sales/{id}` 响应(OrderData)解析为 ISalesDetail。
|
|
10
11
|
*
|
|
@@ -64,6 +65,7 @@ function pickCustomer(raw) {
|
|
|
64
65
|
*/
|
|
65
66
|
function isRootBooking(booking) {
|
|
66
67
|
if (!booking || typeof booking !== 'object') return false;
|
|
68
|
+
if ((0, _pickupTiming.isFulfillmentTimingBooking)(booking)) return false;
|
|
67
69
|
const parentId = booking.parent_id;
|
|
68
70
|
if (parentId === 0 || parentId === null || parentId === undefined) return true;
|
|
69
71
|
return false;
|
|
@@ -71,6 +73,7 @@ function isRootBooking(booking) {
|
|
|
71
73
|
function indexBookingsByProductUid(bookings) {
|
|
72
74
|
const result = {};
|
|
73
75
|
for (const b of bookings) {
|
|
76
|
+
if ((0, _pickupTiming.isFulfillmentTimingBooking)(b)) continue;
|
|
74
77
|
const uid = b && (b.product_uid || b.metadata?.product_uid) || '';
|
|
75
78
|
if (!uid) continue;
|
|
76
79
|
const key = String(uid);
|
|
@@ -326,7 +326,7 @@ export declare class BookingByStepImpl extends BaseModule implements Module {
|
|
|
326
326
|
date: string;
|
|
327
327
|
status: string;
|
|
328
328
|
week: string;
|
|
329
|
-
weekNum: 0 | 2 | 1 |
|
|
329
|
+
weekNum: 0 | 2 | 1 | 5 | 4 | 3 | 6;
|
|
330
330
|
}[]>;
|
|
331
331
|
submitTimeSlot(timeSlots: TimeSliceItem): void;
|
|
332
332
|
private getScheduleDataByIds;
|
|
@@ -52,10 +52,10 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
|
|
|
52
52
|
* 在解决方案对外 ready 前准备 Invoice Document;失败只降级模板,不阻断销售。
|
|
53
53
|
* 已由页面取得的 OpenData 直接交给模块接管,没有注入数据时再走 target-scoped 加载。
|
|
54
54
|
*/
|
|
55
|
-
|
|
55
|
+
protected prepareInvoiceTemplateAtSolutionReady(injectedOpenData?: OpenDataConfig): Promise<void>;
|
|
56
56
|
protected getBookingTicketBusinessCode(): string | null;
|
|
57
57
|
private getIdGeneratorPlugin;
|
|
58
|
-
|
|
58
|
+
protected loadOpenDataConfig(): Promise<OpenDataConfig | null>;
|
|
59
59
|
getOpenData(): Promise<OpenDataConfig | null>;
|
|
60
60
|
getInvoiceLayoutConfig(): Promise<InvoiceLayoutConfig>;
|
|
61
61
|
getInvoiceTemplateSettings(): Promise<InvoiceTemplateSettings>;
|
|
@@ -8,12 +8,14 @@ exports.buildCustomerView = buildCustomerView;
|
|
|
8
8
|
exports.indexBookingsByProductUid = indexBookingsByProductUid;
|
|
9
9
|
exports.isWalkInCustomer = isWalkInCustomer;
|
|
10
10
|
var _addTimeAvailability = require("./addTimeAvailability");
|
|
11
|
+
var _pickupTiming = require("../../../modules/Order/pickupTiming");
|
|
11
12
|
/**
|
|
12
13
|
* 仅按 booking.product_uid 建索引,避免发明 booking 与商品行之间的其它等价关系。
|
|
13
14
|
*/
|
|
14
15
|
function indexBookingsByProductUid(bookings) {
|
|
15
16
|
if (!Array.isArray(bookings) || bookings.length === 0) return {};
|
|
16
17
|
return bookings.reduce((acc, booking) => {
|
|
18
|
+
if ((0, _pickupTiming.isFulfillmentTimingBooking)(booking)) return acc;
|
|
17
19
|
const uid = booking?.product_uid;
|
|
18
20
|
if (uid === undefined || uid === null || uid === '') return acc;
|
|
19
21
|
const key = String(uid);
|
|
@@ -105,7 +107,7 @@ function isGiftProductLine(line) {
|
|
|
105
107
|
function buildCartView(lines, bookings, options = {}) {
|
|
106
108
|
const productLines = Array.isArray(lines) ? lines : [];
|
|
107
109
|
const bookingList = Array.isArray(bookings) ? bookings : [];
|
|
108
|
-
const childBookings = bookingList.filter(booking => booking.parent_id !== 0);
|
|
110
|
+
const childBookings = bookingList.filter(booking => booking.parent_id !== 0 && !(0, _pickupTiming.isFulfillmentTimingBooking)(booking));
|
|
109
111
|
const bookingsByProductUid = indexBookingsByProductUid(childBookings);
|
|
110
112
|
const linesByUid = indexProductsByUid(productLines);
|
|
111
113
|
const addTimeLinesByBookingUid = indexAddTimeProductsByBookingUid(productLines);
|
|
@@ -59,6 +59,13 @@ export declare class UnifiedBookingSalesImpl extends BookingTicket {
|
|
|
59
59
|
protected defaultName: string;
|
|
60
60
|
protected defaultVersion: string;
|
|
61
61
|
protected store: UnifiedBookingSalesState;
|
|
62
|
+
protected pickupTimingEnabled: boolean;
|
|
63
|
+
private runtimeOpenDataScope;
|
|
64
|
+
private runtimeOpenDataSnapshot;
|
|
65
|
+
private runtimeOpenDataModule;
|
|
66
|
+
private runtimeOpenDataCore;
|
|
67
|
+
private runtimeOpenDataGeneration;
|
|
68
|
+
private runtimeOpenDataDestroyed;
|
|
62
69
|
private unifiedProductCatalog;
|
|
63
70
|
private availabilityRuntimeOffsetMinutes;
|
|
64
71
|
private availabilityScheduleCatalog;
|
|
@@ -142,12 +149,14 @@ export declare class UnifiedBookingSalesImpl extends BookingTicket {
|
|
|
142
149
|
private resolveDiscountAction;
|
|
143
150
|
private resetCachedCustomerDiscountState;
|
|
144
151
|
private refreshLegacySessionCustomerDiscounts;
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
152
|
+
private getRuntimeOpenDataScope;
|
|
153
|
+
private resetRuntimeOpenData;
|
|
154
|
+
protected prepareInvoiceTemplateAtSolutionReady(injectedOpenData?: OpenDataConfig): Promise<void>;
|
|
155
|
+
protected loadOpenDataConfig(): Promise<OpenDataConfig | null>;
|
|
156
|
+
/** A successful configuration belongs to this business runtime until an explicit refresh. */
|
|
149
157
|
loadOpenData(params: UnifiedBookingSalesLoadOpenDataParams): Promise<OpenDataConfig>;
|
|
150
158
|
getOpenData(): Promise<OpenDataConfig | null>;
|
|
159
|
+
protected loadPickupTimingOpenData(_force: boolean): Promise<Record<string, unknown>>;
|
|
151
160
|
private resetAllergyItemRuleState;
|
|
152
161
|
private fetchAllergyItemRuleConfigs;
|
|
153
162
|
private ensureAllergyItemRuleConfigsLoaded;
|
|
@@ -55,7 +55,6 @@ var _platform = require("../../utils/platform");
|
|
|
55
55
|
var _itemRule = require("../../model/strategy/adapter/itemRule");
|
|
56
56
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
57
57
|
const OPEN_DATA_SECTION_CODES = ['sale', 'reservation', 'fulfillment', 'menu', 'workflow', 'basic', 'checkout', 'availability', 'documents'];
|
|
58
|
-
const OPEN_DATA_CACHE_TTL = 5 * 60 * 1000;
|
|
59
58
|
function normalizePositiveInteger(value) {
|
|
60
59
|
const normalized = Number(value);
|
|
61
60
|
if (!Number.isInteger(normalized) || normalized <= 0) return undefined;
|
|
@@ -1199,6 +1198,13 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1199
1198
|
defaultName = 'unifiedBookingSales';
|
|
1200
1199
|
defaultVersion = '1.0.0';
|
|
1201
1200
|
store = {};
|
|
1201
|
+
pickupTimingEnabled = true;
|
|
1202
|
+
runtimeOpenDataScope = null;
|
|
1203
|
+
runtimeOpenDataSnapshot = null;
|
|
1204
|
+
runtimeOpenDataModule = null;
|
|
1205
|
+
runtimeOpenDataCore;
|
|
1206
|
+
runtimeOpenDataGeneration = 0;
|
|
1207
|
+
runtimeOpenDataDestroyed = false;
|
|
1202
1208
|
unifiedProductCatalog = [];
|
|
1203
1209
|
availabilityRuntimeOffsetMinutes = null;
|
|
1204
1210
|
availabilityScheduleCatalog = null;
|
|
@@ -1218,16 +1224,16 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1218
1224
|
allergyItemRuleConfigs = [];
|
|
1219
1225
|
allergyItemRuleConfigsPromise = null;
|
|
1220
1226
|
async initialize(core, options = {}) {
|
|
1227
|
+
this.resetRuntimeOpenData();
|
|
1228
|
+
this.runtimeOpenDataDestroyed = false;
|
|
1229
|
+
const openDataGeneration = this.runtimeOpenDataGeneration;
|
|
1221
1230
|
this.availabilityScheduleCatalog = null;
|
|
1222
|
-
this.openDataTarget = null;
|
|
1223
1231
|
this.availabilityRuntimeOffsetMinutes = (0, _localClock.captureRuntimeOffsetMinutes)();
|
|
1224
1232
|
this.clearAvailabilityRemoteResourceState();
|
|
1225
1233
|
this.resetAllergyItemRuleState();
|
|
1226
1234
|
await super.initialize(core, options);
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
if (this.otherParams?.openData && openDataBusinessCode && openDataChannel) {
|
|
1230
|
-
this.openDataTarget = `${openDataBusinessCode}+${openDataChannel}`;
|
|
1235
|
+
if (this.runtimeOpenDataDestroyed || openDataGeneration !== this.runtimeOpenDataGeneration) {
|
|
1236
|
+
throw new Error('Store settings changed. Please try again.');
|
|
1231
1237
|
}
|
|
1232
1238
|
this.captureAvailabilityScheduleCatalog();
|
|
1233
1239
|
}
|
|
@@ -1305,7 +1311,7 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1305
1311
|
const businessCode = String(this.otherParams?.businessCode ?? this.otherParams?.business_code ?? '').trim();
|
|
1306
1312
|
const channel = String(this.otherParams?.channel ?? '').trim();
|
|
1307
1313
|
const currentTarget = businessCode && channel ? `${businessCode}+${channel}` : null;
|
|
1308
|
-
let openData = !currentTarget || this.openDataTarget === currentTarget ? this.otherParams?.openData : null;
|
|
1314
|
+
let openData = (!currentTarget || this.openDataTarget === currentTarget) && (this.runtimeOpenDataScope === null || this.runtimeOpenDataScope === this.getRuntimeOpenDataScope()) ? this.otherParams?.openData : null;
|
|
1309
1315
|
if (!openData && this.store.openData) {
|
|
1310
1316
|
try {
|
|
1311
1317
|
openData = await this.getOpenData();
|
|
@@ -1369,6 +1375,13 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1369
1375
|
const previousScope = this.getAvailabilityScopeDescriptor(this.otherParams || {});
|
|
1370
1376
|
const previousItemRuleConfig = this.otherParams?.itemRule;
|
|
1371
1377
|
const explicitlySetsOpenData = Object.prototype.hasOwnProperty.call(params, 'openData');
|
|
1378
|
+
const nextParams = cover ? params : {
|
|
1379
|
+
...this.otherParams,
|
|
1380
|
+
...params
|
|
1381
|
+
};
|
|
1382
|
+
if (explicitlySetsOpenData || this.getRuntimeOpenDataScope() !== this.getRuntimeOpenDataScope(nextParams)) {
|
|
1383
|
+
this.clearOpenDataCache();
|
|
1384
|
+
}
|
|
1372
1385
|
await super.setOtherParams(params, {
|
|
1373
1386
|
cover
|
|
1374
1387
|
});
|
|
@@ -1385,6 +1398,8 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1385
1398
|
}
|
|
1386
1399
|
}
|
|
1387
1400
|
async destroy() {
|
|
1401
|
+
this.runtimeOpenDataDestroyed = true;
|
|
1402
|
+
this.clearOpenDataCache();
|
|
1388
1403
|
this.availabilityScheduleCatalog = null;
|
|
1389
1404
|
this.availabilityRuntimeOffsetMinutes = null;
|
|
1390
1405
|
await this.resetAvailabilityProjectionState('destroy');
|
|
@@ -1685,52 +1700,117 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1685
1700
|
action: this.resolveDiscountAction(this.resolveDiscountOrderIdentity(tempOrder))
|
|
1686
1701
|
});
|
|
1687
1702
|
}
|
|
1703
|
+
getRuntimeOpenDataScope(otherParams = this.otherParams) {
|
|
1704
|
+
return JSON.stringify({
|
|
1705
|
+
shop: this.getPickupTimingContextData('shop_id'),
|
|
1706
|
+
appShop: this.appPlugin ? this.getAppData('shop_id') : undefined,
|
|
1707
|
+
tenant: this.appPlugin ? this.getAppData('tenant_id') : undefined,
|
|
1708
|
+
businessCode: String(otherParams?.businessCode ?? otherParams?.business_code ?? '').trim(),
|
|
1709
|
+
channel: String(otherParams?.channel || super.getSubmitOrderSalesChannel()).trim(),
|
|
1710
|
+
platform: (0, _platform.normalizeRuntimePlatform)(otherParams?.platform),
|
|
1711
|
+
timezone: this.getPickupTimingContextData('timezone'),
|
|
1712
|
+
businessTimezone: otherParams?.businessTimezone,
|
|
1713
|
+
cacheId: otherParams === this.otherParams ? this.cacheId : otherParams?.cacheId
|
|
1714
|
+
});
|
|
1715
|
+
}
|
|
1716
|
+
resetRuntimeOpenData() {
|
|
1717
|
+
this.runtimeOpenDataGeneration += 1;
|
|
1718
|
+
this.runtimeOpenDataScope = null;
|
|
1719
|
+
this.runtimeOpenDataSnapshot = null;
|
|
1720
|
+
this.runtimeOpenDataModule = null;
|
|
1721
|
+
this.runtimeOpenDataCore = undefined;
|
|
1722
|
+
this.openDataTarget = null;
|
|
1723
|
+
this.loadOpenDataInFlight = null;
|
|
1724
|
+
this.loadOpenDataInFlightTarget = null;
|
|
1725
|
+
this.otherParams = {
|
|
1726
|
+
...this.otherParams,
|
|
1727
|
+
openData: null
|
|
1728
|
+
};
|
|
1729
|
+
this.store.openData?.clearCache?.();
|
|
1730
|
+
}
|
|
1731
|
+
async prepareInvoiceTemplateAtSolutionReady(injectedOpenData) {
|
|
1732
|
+
const businessCode = this.getBookingTicketBusinessCode();
|
|
1733
|
+
if (!businessCode || !this.store.openData) return;
|
|
1734
|
+
if (injectedOpenData) {
|
|
1735
|
+
this.otherParams.openData = injectedOpenData;
|
|
1736
|
+
this.openDataTarget = `${businessCode}+${this.getSubmitOrderSalesChannel()}`;
|
|
1737
|
+
}
|
|
1738
|
+
try {
|
|
1739
|
+
await this.loadOpenDataConfig();
|
|
1740
|
+
} catch (error) {
|
|
1741
|
+
console.warn('[UnifiedBookingSales] Invoice 模板预加载失败,使用默认配置', error);
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
async loadOpenDataConfig() {
|
|
1745
|
+
const businessCode = this.getBookingTicketBusinessCode();
|
|
1746
|
+
if (!businessCode) return null;
|
|
1747
|
+
return this.loadOpenData({
|
|
1748
|
+
businessCode,
|
|
1749
|
+
channel: this.getSubmitOrderSalesChannel()
|
|
1750
|
+
});
|
|
1751
|
+
}
|
|
1688
1752
|
|
|
1689
|
-
/**
|
|
1690
|
-
* Loads OpenData without borrowing BookingTicket. Cache ownership is tied to the exact
|
|
1691
|
-
* business/channel target so a reused solution instance cannot leak another entry's menus.
|
|
1692
|
-
*/
|
|
1753
|
+
/** A successful configuration belongs to this business runtime until an explicit refresh. */
|
|
1693
1754
|
async loadOpenData(params) {
|
|
1694
|
-
|
|
1755
|
+
const module = this.store.openData;
|
|
1756
|
+
if (!module) {
|
|
1695
1757
|
throw new Error('[UnifiedBookingSales] openData 模块未初始化');
|
|
1696
1758
|
}
|
|
1759
|
+
if (this.runtimeOpenDataDestroyed) {
|
|
1760
|
+
throw new Error('Store settings changed. Please try again.');
|
|
1761
|
+
}
|
|
1697
1762
|
const businessCode = String(params.businessCode || '').trim();
|
|
1698
1763
|
const channel = String(params.channel || '').trim();
|
|
1699
1764
|
if (!businessCode || !channel) {
|
|
1700
1765
|
throw new Error('[UnifiedBookingSales] OpenData 需要 businessCode 与 channel');
|
|
1701
1766
|
}
|
|
1702
1767
|
const target = `${businessCode}+${channel}`;
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1768
|
+
this.otherParams.businessCode = businessCode;
|
|
1769
|
+
this.otherParams.channel = channel;
|
|
1770
|
+
const scope = this.getRuntimeOpenDataScope();
|
|
1771
|
+
if (params.force) {
|
|
1772
|
+
this.clearOpenDataCache();
|
|
1773
|
+
}
|
|
1774
|
+
if (this.runtimeOpenDataScope !== scope || this.runtimeOpenDataModule !== module || this.runtimeOpenDataCore !== this.core) {
|
|
1775
|
+
// Only the first, explicitly injected target may seed a new runtime snapshot.
|
|
1776
|
+
const injectedOpenData = this.runtimeOpenDataScope === null && (!this.openDataTarget || this.openDataTarget === target) && isRecord(this.otherParams.openData) ? this.otherParams.openData : null;
|
|
1777
|
+
if (this.runtimeOpenDataScope !== null) {
|
|
1778
|
+
// The caller's Pickup Timing scope already changes with this runtime scope.
|
|
1779
|
+
this.resetRuntimeOpenData();
|
|
1708
1780
|
}
|
|
1709
|
-
|
|
1781
|
+
this.runtimeOpenDataScope = scope;
|
|
1782
|
+
this.runtimeOpenDataModule = module;
|
|
1783
|
+
this.runtimeOpenDataCore = this.core;
|
|
1784
|
+
this.runtimeOpenDataSnapshot = injectedOpenData;
|
|
1710
1785
|
}
|
|
1711
1786
|
if (this.loadOpenDataInFlight) {
|
|
1712
|
-
|
|
1713
|
-
|
|
1787
|
+
return this.loadOpenDataInFlight;
|
|
1788
|
+
}
|
|
1789
|
+
const generation = this.runtimeOpenDataGeneration;
|
|
1790
|
+
const core = this.core;
|
|
1791
|
+
const request = (async () => {
|
|
1792
|
+
let openData = this.runtimeOpenDataSnapshot;
|
|
1793
|
+
if (openData) {
|
|
1794
|
+
await module.ensureInvoiceTemplateSettings?.(target, openData);
|
|
1795
|
+
} else {
|
|
1796
|
+
openData = await module.fetchOpenData({
|
|
1797
|
+
scope: 'board',
|
|
1798
|
+
target,
|
|
1799
|
+
section_code: [...OPEN_DATA_SECTION_CODES]
|
|
1800
|
+
});
|
|
1714
1801
|
}
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1802
|
+
if (this.runtimeOpenDataDestroyed || generation !== this.runtimeOpenDataGeneration || scope !== this.getRuntimeOpenDataScope() || module !== this.store.openData || core !== this.core) {
|
|
1803
|
+
throw new Error('Store settings changed. Please try again.');
|
|
1804
|
+
}
|
|
1805
|
+
this.runtimeOpenDataSnapshot = openData;
|
|
1806
|
+
this.openDataTarget = target;
|
|
1807
|
+
this.otherParams.openData = openData;
|
|
1808
|
+
return openData;
|
|
1809
|
+
})();
|
|
1718
1810
|
this.loadOpenDataInFlightTarget = target;
|
|
1719
|
-
const request = this.store.openData.fetchOpenData({
|
|
1720
|
-
scope: 'board',
|
|
1721
|
-
target,
|
|
1722
|
-
section_code: [...OPEN_DATA_SECTION_CODES]
|
|
1723
|
-
});
|
|
1724
1811
|
this.loadOpenDataInFlight = request;
|
|
1725
1812
|
try {
|
|
1726
|
-
|
|
1727
|
-
if (this.loadOpenDataInFlight === request) {
|
|
1728
|
-
this.openDataTarget = target;
|
|
1729
|
-
this.otherParams.openData = openData;
|
|
1730
|
-
this.otherParams.businessCode = businessCode;
|
|
1731
|
-
this.otherParams.channel = channel;
|
|
1732
|
-
}
|
|
1733
|
-
return openData;
|
|
1813
|
+
return await request;
|
|
1734
1814
|
} finally {
|
|
1735
1815
|
if (this.loadOpenDataInFlight === request) {
|
|
1736
1816
|
this.loadOpenDataInFlight = null;
|
|
@@ -1739,13 +1819,19 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1739
1819
|
}
|
|
1740
1820
|
}
|
|
1741
1821
|
async getOpenData() {
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1822
|
+
return this.loadOpenDataConfig();
|
|
1823
|
+
}
|
|
1824
|
+
async loadPickupTimingOpenData(_force) {
|
|
1825
|
+
const scope = this.getPickupTimingScope();
|
|
1826
|
+
const businessCode = String(this.otherParams?.businessCode ?? this.otherParams?.business_code ?? '').trim();
|
|
1827
|
+
const config = await this.loadOpenData({
|
|
1746
1828
|
businessCode,
|
|
1747
|
-
channel
|
|
1829
|
+
channel: this.getSubmitOrderSalesChannel()
|
|
1748
1830
|
});
|
|
1831
|
+
if (scope !== this.getPickupTimingScope()) {
|
|
1832
|
+
throw new Error('Store settings changed. Please try again.');
|
|
1833
|
+
}
|
|
1834
|
+
return config;
|
|
1749
1835
|
}
|
|
1750
1836
|
resetAllergyItemRuleState() {
|
|
1751
1837
|
this.allergyItemRuleConfigs = [];
|
|
@@ -1840,10 +1926,8 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1840
1926
|
});
|
|
1841
1927
|
}
|
|
1842
1928
|
clearOpenDataCache() {
|
|
1843
|
-
this.
|
|
1844
|
-
this.
|
|
1845
|
-
this.loadOpenDataInFlightTarget = null;
|
|
1846
|
-
super.clearOpenDataCache();
|
|
1929
|
+
this.resetRuntimeOpenData();
|
|
1930
|
+
this.resetPickupTimingCache();
|
|
1847
1931
|
}
|
|
1848
1932
|
async getInvoiceLayoutConfig() {
|
|
1849
1933
|
const openData = await this.getOpenData();
|
|
@@ -1863,7 +1947,7 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1863
1947
|
const businessCode = String(this.otherParams?.businessCode || '').trim();
|
|
1864
1948
|
const channel = String(this.otherParams?.channel || '').trim();
|
|
1865
1949
|
if (!businessCode || !channel) return false;
|
|
1866
|
-
return this.openDataTarget === `${businessCode}+${channel}
|
|
1950
|
+
return this.openDataTarget === `${businessCode}+${channel}` && this.runtimeOpenDataScope === this.getRuntimeOpenDataScope();
|
|
1867
1951
|
}
|
|
1868
1952
|
|
|
1869
1953
|
/**
|