@pisell/pisellos 0.10.49 → 0.10.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/modules/Discount/types.d.ts +2 -0
- 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 +19 -1
- package/dist/modules/Order/index.js +362 -217
- 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 +45 -26
- package/dist/modules/Rules/types.d.ts +4 -0
- 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 +173 -95
- package/dist/solution/BaseSales/index.d.ts +23 -0
- package/dist/solution/BaseSales/index.js +1216 -1031
- package/dist/solution/BaseSales/pickupTiming.d.ts +37 -0
- package/dist/solution/BaseSales/pickupTiming.js +482 -0
- package/dist/solution/BaseSales/utils/parseSalesResponse.js +3 -0
- package/dist/solution/BookingByStep/index.d.ts +1 -1
- package/dist/solution/BookingTicket/utils/cartView.js +3 -1
- package/dist/solution/UnifiedBookingSales/index.d.ts +2 -0
- package/dist/solution/UnifiedBookingSales/index.js +583 -531
- package/lib/model/strategy/adapter/promotion/index.js +46 -1
- package/lib/modules/Discount/types.d.ts +2 -0
- 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 +19 -1
- package/lib/modules/Order/index.js +120 -7
- 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 +24 -13
- package/lib/modules/Rules/types.d.ts +4 -0
- 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 +93 -22
- package/lib/solution/BaseSales/index.d.ts +23 -0
- package/lib/solution/BaseSales/index.js +104 -1
- package/lib/solution/BaseSales/pickupTiming.d.ts +37 -0
- package/lib/solution/BaseSales/pickupTiming.js +222 -0
- package/lib/solution/BaseSales/utils/parseSalesResponse.js +3 -0
- package/lib/solution/BookingByStep/index.d.ts +1 -1
- package/lib/solution/BookingTicket/utils/cartView.js +3 -1
- package/lib/solution/UnifiedBookingSales/index.d.ts +2 -0
- package/lib/solution/UnifiedBookingSales/index.js +20 -0
- package/package.json +1 -1
|
@@ -0,0 +1,222 @@
|
|
|
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
|
+
/** Coordinates fresh configuration with the independent appointment; UI keeps edit drafts. */
|
|
21
|
+
class PickupTimingController {
|
|
22
|
+
latest;
|
|
23
|
+
requests = new Map();
|
|
24
|
+
writeSequence = 0;
|
|
25
|
+
constructor(host) {
|
|
26
|
+
this.host = host;
|
|
27
|
+
}
|
|
28
|
+
now() {
|
|
29
|
+
return this.host.now?.() ?? new Date();
|
|
30
|
+
}
|
|
31
|
+
async snapshot(force, includeSchedule, tolerateScheduleError = false) {
|
|
32
|
+
const scope = this.host.getScope();
|
|
33
|
+
const key = `${scope}:${force}:${includeSchedule}:${tolerateScheduleError}`;
|
|
34
|
+
let request = this.requests.get(key);
|
|
35
|
+
if (!request) {
|
|
36
|
+
request = (async () => {
|
|
37
|
+
const config = (0, _FulfillmentTiming.normalizePickupTimingConfig)(await withTimeout(this.host.loadOpenData(force)));
|
|
38
|
+
const timezone = this.host.getTimezone();
|
|
39
|
+
let schedules = [];
|
|
40
|
+
let error;
|
|
41
|
+
if (includeSchedule && config.handoverModes.includes('pick_up') && config.timingMode !== 'asap_only' && config.scheduleIds.length) {
|
|
42
|
+
try {
|
|
43
|
+
const schedule = this.host.getSchedule();
|
|
44
|
+
if (!schedule) throw new Error('Pickup schedule is unavailable. Please try again.');
|
|
45
|
+
await withTimeout(schedule.loadAllSchedule({
|
|
46
|
+
useCache: false
|
|
47
|
+
}));
|
|
48
|
+
const ids = [...new Set([...config.scheduleIds, ...(config.operatingHoursScheduleIds || [])])];
|
|
49
|
+
schedules = (0, _lodashEs.cloneDeep)(schedule.getScheduleListByIds(ids) || []);
|
|
50
|
+
} catch (reason) {
|
|
51
|
+
if (!tolerateScheduleError) throw reason;
|
|
52
|
+
error = reason instanceof Error ? reason.message : 'Pickup schedule is unavailable. Please try again.';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (scope !== this.host.getScope()) throw new Error('Store settings changed. Please try again.');
|
|
56
|
+
return {
|
|
57
|
+
config,
|
|
58
|
+
timezone,
|
|
59
|
+
schedules,
|
|
60
|
+
scope,
|
|
61
|
+
error
|
|
62
|
+
};
|
|
63
|
+
})();
|
|
64
|
+
this.requests.set(key, request);
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const result = await request;
|
|
68
|
+
if (scope !== this.host.getScope()) throw new Error('Store settings changed. Please try again.');
|
|
69
|
+
this.latest = result;
|
|
70
|
+
return result;
|
|
71
|
+
} finally {
|
|
72
|
+
if (this.requests.get(key) === request) this.requests.delete(key);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async getAvailability(params = {}) {
|
|
76
|
+
const snapshot = await this.snapshot(params.force !== false, true, true);
|
|
77
|
+
const now = this.now();
|
|
78
|
+
try {
|
|
79
|
+
return {
|
|
80
|
+
...(0, _FulfillmentTiming.computePickupTimingAvailability)({
|
|
81
|
+
...snapshot,
|
|
82
|
+
date: params.date,
|
|
83
|
+
now
|
|
84
|
+
}),
|
|
85
|
+
error: snapshot.error
|
|
86
|
+
};
|
|
87
|
+
} catch (reason) {
|
|
88
|
+
// Keep the handover selector usable when only Pickup's timezone is unavailable.
|
|
89
|
+
return {
|
|
90
|
+
config: snapshot.config,
|
|
91
|
+
timezone: snapshot.timezone,
|
|
92
|
+
now: now.toISOString(),
|
|
93
|
+
today: '',
|
|
94
|
+
maxDate: '',
|
|
95
|
+
date: params.date || '',
|
|
96
|
+
dates: [],
|
|
97
|
+
slots: [],
|
|
98
|
+
error: reason instanceof Error ? reason.message : 'Pickup times are unavailable. Please try again.'
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
getSelection() {
|
|
103
|
+
const order = this.host.getOrder();
|
|
104
|
+
const tempOrder = order.getTempOrder();
|
|
105
|
+
const booking = order.getPickupTimingBooking();
|
|
106
|
+
const config = this.latest?.scope === this.host.getScope() ? this.latest.config : undefined;
|
|
107
|
+
const storedServiceType = String(tempOrder?.metadata?.shop_service_data?.service_type || '');
|
|
108
|
+
const serviceType = config?.handoverModes.length ? config.handoverModes.includes(storedServiceType) ? storedServiceType : config.handoverModes[0] : storedServiceType;
|
|
109
|
+
const details = serviceType === 'pick_up' ? booking?.metadata.fulfillment_timing : undefined;
|
|
110
|
+
const mode = config?.timingMode === 'asap_only' ? 'asap' : config?.timingMode === 'scheduled_only' ? 'scheduled' : details?.mode ?? 'asap';
|
|
111
|
+
const sameMode = details?.mode === mode;
|
|
112
|
+
return {
|
|
113
|
+
serviceType,
|
|
114
|
+
mode,
|
|
115
|
+
source: mode === 'asap' ? 'asap' : sameMode && details?.source || 'schedule',
|
|
116
|
+
...(sameMode && booking ? {
|
|
117
|
+
date: booking.start_date,
|
|
118
|
+
time: booking.start_time,
|
|
119
|
+
instant: details?.instant
|
|
120
|
+
} : {})
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
async validateSnapshot(selection) {
|
|
124
|
+
const snapshot = await this.snapshot(true, selection.serviceType === 'pick_up' && selection.mode === 'scheduled' && selection.source !== 'pos_manual');
|
|
125
|
+
const now = this.now();
|
|
126
|
+
const result = (0, _FulfillmentTiming.validatePickupTimingSelection)(selection, {
|
|
127
|
+
...snapshot,
|
|
128
|
+
now,
|
|
129
|
+
allowManualOverride: this.host.isPos()
|
|
130
|
+
});
|
|
131
|
+
return {
|
|
132
|
+
snapshot,
|
|
133
|
+
now,
|
|
134
|
+
result
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
async validate(selection) {
|
|
138
|
+
return (await this.validateSnapshot(selection)).result;
|
|
139
|
+
}
|
|
140
|
+
async setSelection(selection, options = {}) {
|
|
141
|
+
const order = this.host.getOrder();
|
|
142
|
+
const tempOrder = order.ensureTempOrder();
|
|
143
|
+
const sequence = ++this.writeSequence;
|
|
144
|
+
const {
|
|
145
|
+
snapshot,
|
|
146
|
+
now,
|
|
147
|
+
result
|
|
148
|
+
} = await this.validateSnapshot(selection);
|
|
149
|
+
if (sequence !== this.writeSequence || order.getTempOrder() !== tempOrder || snapshot.scope !== this.host.getScope()) {
|
|
150
|
+
throw new Error('The order changed. Please choose the pickup time again.');
|
|
151
|
+
}
|
|
152
|
+
if (!result.valid) throw new Error(result.message || 'Please choose a valid pickup time.');
|
|
153
|
+
if (!snapshot.config.handoverModes.length) {
|
|
154
|
+
order.clearPickupTimingBooking();
|
|
155
|
+
return this.getSelection();
|
|
156
|
+
}
|
|
157
|
+
if (selection.serviceType !== 'pick_up') {
|
|
158
|
+
order.updateTempOrderServiceType(selection.serviceType);
|
|
159
|
+
return this.getSelection();
|
|
160
|
+
}
|
|
161
|
+
const slot = selection.mode === 'asap' ? (0, _FulfillmentTiming.getAsapPickupTiming)({
|
|
162
|
+
...snapshot,
|
|
163
|
+
now
|
|
164
|
+
}) : result.slot;
|
|
165
|
+
if (!slot) throw new Error('Please choose a pickup time.');
|
|
166
|
+
// Validate everything before either field is changed; notify once after the combined update.
|
|
167
|
+
tempOrder.metadata = {
|
|
168
|
+
...(tempOrder.metadata || {}),
|
|
169
|
+
shop_service_data: {
|
|
170
|
+
...(tempOrder.metadata?.shop_service_data || {}),
|
|
171
|
+
service_type: 'pick_up'
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
order.setPickupTimingBooking({
|
|
175
|
+
mode: selection.mode,
|
|
176
|
+
source: selection.mode === 'asap' ? 'asap' : selection.source === 'pos_manual' ? 'pos_manual' : 'schedule',
|
|
177
|
+
date: slot.date,
|
|
178
|
+
time: slot.time,
|
|
179
|
+
instant: slot.instant,
|
|
180
|
+
timezone: snapshot.timezone,
|
|
181
|
+
frozen: options.frozen ?? false
|
|
182
|
+
});
|
|
183
|
+
return this.getSelection();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Called before a new checkout/payment; payment callbacks keep their already prepared appointment. */
|
|
187
|
+
async prepare(options = {}) {
|
|
188
|
+
const order = this.host.getOrder();
|
|
189
|
+
const tempOrder = order.getTempOrder();
|
|
190
|
+
const sequence = this.writeSequence;
|
|
191
|
+
if (order.getOrderIdentity()?.isSubmitted) return this.getSelection();
|
|
192
|
+
const booking = order.getPickupTimingBooking();
|
|
193
|
+
if (booking?.metadata.fulfillment_timing?.frozen) return this.getSelection();
|
|
194
|
+
// An actual payment callback must never revalidate or move the time after funds have been taken.
|
|
195
|
+
const paymentStatus = order.getTempOrder()?.payment_status;
|
|
196
|
+
if (paymentStatus === 'paid' || paymentStatus === 'partially_paid') {
|
|
197
|
+
if (booking?.metadata.fulfillment_timing) {
|
|
198
|
+
const detail = booking.metadata.fulfillment_timing;
|
|
199
|
+
order.setPickupTimingBooking({
|
|
200
|
+
...detail,
|
|
201
|
+
date: booking.start_date,
|
|
202
|
+
time: booking.start_time,
|
|
203
|
+
frozen: true
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return this.getSelection();
|
|
207
|
+
}
|
|
208
|
+
const snapshot = await this.snapshot(true, false);
|
|
209
|
+
if (order !== this.host.getOrder() || order.getTempOrder() !== tempOrder || sequence !== this.writeSequence) {
|
|
210
|
+
throw new Error('The order changed. Please choose the pickup time again.');
|
|
211
|
+
}
|
|
212
|
+
const selection = this.getSelection();
|
|
213
|
+
if (!snapshot.config.handoverModes.length) {
|
|
214
|
+
order.clearPickupTimingBooking();
|
|
215
|
+
return selection;
|
|
216
|
+
}
|
|
217
|
+
return this.setSelection(selection, {
|
|
218
|
+
frozen: options.freeze ?? false
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
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 |
|
|
329
|
+
weekNum: 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
|
330
330
|
}[]>;
|
|
331
331
|
submitTimeSlot(timeSlots: TimeSliceItem): void;
|
|
332
332
|
private getScheduleDataByIds;
|
|
@@ -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,7 @@ 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;
|
|
62
63
|
private unifiedProductCatalog;
|
|
63
64
|
private availabilityRuntimeOffsetMinutes;
|
|
64
65
|
private availabilityScheduleCatalog;
|
|
@@ -148,6 +149,7 @@ export declare class UnifiedBookingSalesImpl extends BookingTicket {
|
|
|
148
149
|
*/
|
|
149
150
|
loadOpenData(params: UnifiedBookingSalesLoadOpenDataParams): Promise<OpenDataConfig>;
|
|
150
151
|
getOpenData(): Promise<OpenDataConfig | null>;
|
|
152
|
+
protected loadPickupTimingOpenData(force: boolean): Promise<Record<string, unknown>>;
|
|
151
153
|
private resetAllergyItemRuleState;
|
|
152
154
|
private fetchAllergyItemRuleConfigs;
|
|
153
155
|
private ensureAllergyItemRuleConfigsLoaded;
|
|
@@ -1199,6 +1199,7 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1199
1199
|
defaultName = 'unifiedBookingSales';
|
|
1200
1200
|
defaultVersion = '1.0.0';
|
|
1201
1201
|
store = {};
|
|
1202
|
+
pickupTimingEnabled = true;
|
|
1202
1203
|
unifiedProductCatalog = [];
|
|
1203
1204
|
availabilityRuntimeOffsetMinutes = null;
|
|
1204
1205
|
availabilityScheduleCatalog = null;
|
|
@@ -1747,6 +1748,25 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1747
1748
|
channel
|
|
1748
1749
|
});
|
|
1749
1750
|
}
|
|
1751
|
+
async loadPickupTimingOpenData(force) {
|
|
1752
|
+
const scope = this.getPickupTimingScope();
|
|
1753
|
+
// A fresh validation must not borrow a request started for a previous store/context.
|
|
1754
|
+
if (force && this.loadOpenDataInFlight) await this.loadOpenDataInFlight;
|
|
1755
|
+
if (scope !== this.getPickupTimingScope()) {
|
|
1756
|
+
throw new Error('Store settings changed. Please try again.');
|
|
1757
|
+
}
|
|
1758
|
+
const businessCode = String(this.otherParams?.businessCode ?? this.otherParams?.business_code ?? '').trim();
|
|
1759
|
+
const channel = String(this.getSubmitOrderSalesChannel() ?? '').trim();
|
|
1760
|
+
const config = await this.loadOpenData({
|
|
1761
|
+
businessCode,
|
|
1762
|
+
channel,
|
|
1763
|
+
force
|
|
1764
|
+
});
|
|
1765
|
+
if (scope !== this.getPickupTimingScope()) {
|
|
1766
|
+
throw new Error('Store settings changed. Please try again.');
|
|
1767
|
+
}
|
|
1768
|
+
return config;
|
|
1769
|
+
}
|
|
1750
1770
|
resetAllergyItemRuleState() {
|
|
1751
1771
|
this.allergyItemRuleConfigs = [];
|
|
1752
1772
|
this.allergyItemRuleConfigsPromise = null;
|