@pisell/pisellos 0.0.492 → 0.0.494
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 +9 -0
- package/dist/modules/Cart/utils/cartProduct.js +1 -0
- package/dist/modules/Order/index.d.ts +5 -0
- package/dist/modules/Order/index.js +70 -5
- package/dist/modules/Order/utils.js +12 -2
- package/dist/modules/Quotation/index.d.ts +8 -0
- package/dist/modules/Quotation/index.js +46 -13
- package/dist/modules/Schedule/getDateIsInSchedule.js +11 -18
- package/dist/solution/BookingByStep/index.js +44 -42
- package/dist/solution/BookingByStep/utils/timeslots.d.ts +3 -1
- package/dist/solution/BookingByStep/utils/timeslots.js +19 -12
- package/dist/solution/ScanOrder/utils.js +22 -3
- package/dist/solution/VenueBooking/index.d.ts +8 -9
- package/dist/solution/VenueBooking/index.js +612 -565
- package/dist/solution/VenueBooking/types.d.ts +7 -0
- package/dist/solution/VenueBooking/utils/slotMerge.d.ts +13 -2
- package/dist/solution/VenueBooking/utils/slotMerge.js +92 -15
- package/lib/model/strategy/adapter/promotion/index.js +49 -0
- package/lib/modules/Cart/utils/cartProduct.js +1 -0
- package/lib/modules/Order/index.d.ts +5 -0
- package/lib/modules/Order/index.js +37 -1
- package/lib/modules/Order/utils.js +13 -0
- package/lib/modules/Quotation/index.d.ts +8 -0
- package/lib/modules/Quotation/index.js +27 -6
- package/lib/modules/Schedule/getDateIsInSchedule.js +9 -11
- package/lib/solution/BookingByStep/index.js +4 -2
- package/lib/solution/BookingByStep/utils/timeslots.d.ts +3 -1
- package/lib/solution/BookingByStep/utils/timeslots.js +19 -11
- package/lib/solution/ScanOrder/utils.js +20 -3
- package/lib/solution/VenueBooking/index.d.ts +8 -9
- package/lib/solution/VenueBooking/index.js +79 -69
- package/lib/solution/VenueBooking/types.d.ts +7 -0
- package/lib/solution/VenueBooking/utils/slotMerge.d.ts +13 -2
- package/lib/solution/VenueBooking/utils/slotMerge.js +67 -13
- package/package.json +1 -1
|
@@ -92,6 +92,13 @@ export interface MergedSlotGroup {
|
|
|
92
92
|
slotCount: number;
|
|
93
93
|
slots: VenueSlotSelection[];
|
|
94
94
|
}
|
|
95
|
+
export interface PriceBreakdownEntry {
|
|
96
|
+
start_time: string;
|
|
97
|
+
end_time: string;
|
|
98
|
+
unit_price: number;
|
|
99
|
+
quotation_shelf_id: number;
|
|
100
|
+
quantity: number;
|
|
101
|
+
}
|
|
95
102
|
export interface VenueBookingState {
|
|
96
103
|
entryContext: VenueBookingEntryContext | null;
|
|
97
104
|
status: VenueBookingStatus;
|
|
@@ -1,7 +1,17 @@
|
|
|
1
|
-
import type { VenueSlotSelection, MergedSlotGroup, ResourceProductMapping, VenueResourceRawData } from '../types';
|
|
1
|
+
import type { VenueSlotSelection, MergedSlotGroup, PriceBreakdownEntry, ResourceProductMapping, VenueResourceRawData } from '../types';
|
|
2
2
|
import type { ScanOrderOrderProduct } from '../../ScanOrder/types';
|
|
3
3
|
export declare function buildVenueIdentityKey(resourceId: number | string, groupIndex: number): string;
|
|
4
4
|
export declare function mergeConsecutiveSlots(slots: VenueSlotSelection[]): MergedSlotGroup[];
|
|
5
|
+
export declare function buildPriceBreakdown(params: {
|
|
6
|
+
group: MergedSlotGroup;
|
|
7
|
+
productId: number;
|
|
8
|
+
quotation?: {
|
|
9
|
+
getQuotationShelfId: (p: {
|
|
10
|
+
productId: number;
|
|
11
|
+
datetime: string;
|
|
12
|
+
}) => number;
|
|
13
|
+
};
|
|
14
|
+
}): PriceBreakdownEntry[];
|
|
5
15
|
export interface BuildVenueBookingParams {
|
|
6
16
|
group: MergedSlotGroup;
|
|
7
17
|
resourceId: number | string;
|
|
@@ -13,6 +23,7 @@ export interface BuildVenueBookingParams {
|
|
|
13
23
|
export declare function buildVenueBookingEntry(params: BuildVenueBookingParams): Record<string, any>;
|
|
14
24
|
/**
|
|
15
25
|
* 从一条已合并的订单商品还原出独立的时段列表。
|
|
16
|
-
*
|
|
26
|
+
* 优先使用 metadata.price_breakdown 还原每个时段的精确价格,
|
|
27
|
+
* 回退到按 selling_price / slot_count 均分。
|
|
17
28
|
*/
|
|
18
29
|
export declare function expandMergedSlotToIndividual(product: ScanOrderOrderProduct, slotDurationMinutes: number): VenueSlotSelection[];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
|
|
1
2
|
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
|
|
2
3
|
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
3
4
|
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
|
|
@@ -49,8 +50,47 @@ export function mergeConsecutiveSlots(slots) {
|
|
|
49
50
|
});
|
|
50
51
|
return groups;
|
|
51
52
|
}
|
|
53
|
+
export function buildPriceBreakdown(params) {
|
|
54
|
+
var group = params.group,
|
|
55
|
+
productId = params.productId,
|
|
56
|
+
quotation = params.quotation;
|
|
57
|
+
if (!group.slots.length) return [];
|
|
58
|
+
var entries = [];
|
|
59
|
+
var _iterator = _createForOfIteratorHelper(group.slots),
|
|
60
|
+
_step;
|
|
61
|
+
try {
|
|
62
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
63
|
+
var slot = _step.value;
|
|
64
|
+
var unitPrice = Number(slot.price || '0');
|
|
65
|
+
var shelfId = quotation ? quotation.getQuotationShelfId({
|
|
66
|
+
productId: productId,
|
|
67
|
+
datetime: slot.startTime
|
|
68
|
+
}) : 0;
|
|
69
|
+
var startHm = dayjs(slot.startTime, 'YYYY-MM-DD HH:mm').format('HH:mm');
|
|
70
|
+
var endHm = dayjs(slot.endTime, 'YYYY-MM-DD HH:mm').format('HH:mm');
|
|
71
|
+
var last = entries[entries.length - 1];
|
|
72
|
+
if (last && last.unit_price === unitPrice && last.quotation_shelf_id === shelfId && last.end_time === startHm) {
|
|
73
|
+
last.end_time = endHm;
|
|
74
|
+
last.quantity += 1;
|
|
75
|
+
} else {
|
|
76
|
+
entries.push({
|
|
77
|
+
start_time: startHm,
|
|
78
|
+
end_time: endHm,
|
|
79
|
+
unit_price: unitPrice,
|
|
80
|
+
quotation_shelf_id: shelfId,
|
|
81
|
+
quantity: 1
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} catch (err) {
|
|
86
|
+
_iterator.e(err);
|
|
87
|
+
} finally {
|
|
88
|
+
_iterator.f();
|
|
89
|
+
}
|
|
90
|
+
return entries;
|
|
91
|
+
}
|
|
52
92
|
export function buildVenueBookingEntry(params) {
|
|
53
|
-
var _rawResource$form_id
|
|
93
|
+
var _rawResource$form_id;
|
|
54
94
|
var group = params.group,
|
|
55
95
|
resourceId = params.resourceId,
|
|
56
96
|
mapping = params.mapping,
|
|
@@ -82,7 +122,7 @@ export function buildVenueBookingEntry(params) {
|
|
|
82
122
|
main_field: mapping.resourceName,
|
|
83
123
|
form_id: (_rawResource$form_id = rawResource === null || rawResource === void 0 ? void 0 : rawResource.form_id) !== null && _rawResource$form_id !== void 0 ? _rawResource$form_id : mapping.formId,
|
|
84
124
|
relation_id: resourceId,
|
|
85
|
-
capacity:
|
|
125
|
+
capacity: 1,
|
|
86
126
|
metadata: {}
|
|
87
127
|
}],
|
|
88
128
|
relation_products: [],
|
|
@@ -99,7 +139,8 @@ export function buildVenueBookingEntry(params) {
|
|
|
99
139
|
|
|
100
140
|
/**
|
|
101
141
|
* 从一条已合并的订单商品还原出独立的时段列表。
|
|
102
|
-
*
|
|
142
|
+
* 优先使用 metadata.price_breakdown 还原每个时段的精确价格,
|
|
143
|
+
* 回退到按 selling_price / slot_count 均分。
|
|
103
144
|
*/
|
|
104
145
|
export function expandMergedSlotToIndividual(product, slotDurationMinutes) {
|
|
105
146
|
var meta = product.metadata || {};
|
|
@@ -108,21 +149,57 @@ export function expandMergedSlotToIndividual(product, slotDurationMinutes) {
|
|
|
108
149
|
var startTime = meta.start_time;
|
|
109
150
|
var endTime = meta.end_time;
|
|
110
151
|
if (!startTime || !endTime) return [];
|
|
111
|
-
var
|
|
112
|
-
var
|
|
113
|
-
var perSlotPrice = slotCount > 0 ? totalPrice.div(slotCount).toFixed(2) : totalPrice.toFixed(2);
|
|
152
|
+
var breakdown = meta.price_breakdown;
|
|
153
|
+
var datePrefix = dayjs(startTime, 'YYYY-MM-DD HH:mm').format('YYYY-MM-DD');
|
|
114
154
|
var result = [];
|
|
115
155
|
var cursor = dayjs(startTime, 'YYYY-MM-DD HH:mm');
|
|
116
156
|
var end = dayjs(endTime, 'YYYY-MM-DD HH:mm');
|
|
117
|
-
|
|
118
|
-
var
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
157
|
+
if (breakdown !== null && breakdown !== void 0 && breakdown.length) {
|
|
158
|
+
var priceMap = new Map();
|
|
159
|
+
var _iterator2 = _createForOfIteratorHelper(breakdown),
|
|
160
|
+
_step2;
|
|
161
|
+
try {
|
|
162
|
+
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
163
|
+
var entry = _step2.value;
|
|
164
|
+
var entryCursor = dayjs("".concat(datePrefix, " ").concat(entry.start_time), 'YYYY-MM-DD HH:mm');
|
|
165
|
+
var entryEnd = dayjs("".concat(datePrefix, " ").concat(entry.end_time), 'YYYY-MM-DD HH:mm');
|
|
166
|
+
while (entryCursor.isBefore(entryEnd)) {
|
|
167
|
+
priceMap.set(entryCursor.format('HH:mm'), entry.unit_price);
|
|
168
|
+
entryCursor = entryCursor.add(slotDurationMinutes, 'minute');
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
} catch (err) {
|
|
172
|
+
_iterator2.e(err);
|
|
173
|
+
} finally {
|
|
174
|
+
_iterator2.f();
|
|
175
|
+
}
|
|
176
|
+
while (cursor.isBefore(end)) {
|
|
177
|
+
var _priceMap$get;
|
|
178
|
+
var slotEnd = cursor.add(slotDurationMinutes, 'minute');
|
|
179
|
+
var hm = cursor.format('HH:mm');
|
|
180
|
+
var price = (_priceMap$get = priceMap.get(hm)) !== null && _priceMap$get !== void 0 ? _priceMap$get : 0;
|
|
181
|
+
result.push({
|
|
182
|
+
resourceId: resourceId,
|
|
183
|
+
startTime: cursor.format('YYYY-MM-DD HH:mm'),
|
|
184
|
+
endTime: slotEnd.format('YYYY-MM-DD HH:mm'),
|
|
185
|
+
price: new Decimal(price).toFixed(2)
|
|
186
|
+
});
|
|
187
|
+
cursor = slotEnd;
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
var slotCount = meta.slot_count || 1;
|
|
191
|
+
var totalPrice = new Decimal(product.selling_price || '0');
|
|
192
|
+
var perSlotPrice = slotCount > 0 ? totalPrice.div(slotCount).toFixed(2) : totalPrice.toFixed(2);
|
|
193
|
+
while (cursor.isBefore(end)) {
|
|
194
|
+
var _slotEnd = cursor.add(slotDurationMinutes, 'minute');
|
|
195
|
+
result.push({
|
|
196
|
+
resourceId: resourceId,
|
|
197
|
+
startTime: cursor.format('YYYY-MM-DD HH:mm'),
|
|
198
|
+
endTime: _slotEnd.format('YYYY-MM-DD HH:mm'),
|
|
199
|
+
price: perSlotPrice
|
|
200
|
+
});
|
|
201
|
+
cursor = _slotEnd;
|
|
202
|
+
}
|
|
126
203
|
}
|
|
127
204
|
return result;
|
|
128
205
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
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/model/strategy/adapter/promotion/index.ts
|
|
30
|
+
var promotion_exports = {};
|
|
31
|
+
__export(promotion_exports, {
|
|
32
|
+
BUY_X_GET_Y_FREE_STRATEGY: () => import_examples.BUY_X_GET_Y_FREE_STRATEGY,
|
|
33
|
+
PromotionAdapter: () => import_adapter.PromotionAdapter,
|
|
34
|
+
PromotionEvaluator: () => import_evaluator.PromotionEvaluator,
|
|
35
|
+
X_ITEMS_FOR_Y_PRICE_STRATEGY: () => import_examples.X_ITEMS_FOR_Y_PRICE_STRATEGY,
|
|
36
|
+
default: () => import_adapter2.default
|
|
37
|
+
});
|
|
38
|
+
module.exports = __toCommonJS(promotion_exports);
|
|
39
|
+
var import_evaluator = require("./evaluator");
|
|
40
|
+
var import_adapter = require("./adapter");
|
|
41
|
+
var import_adapter2 = __toESM(require("./adapter"));
|
|
42
|
+
var import_examples = require("./examples");
|
|
43
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
44
|
+
0 && (module.exports = {
|
|
45
|
+
BUY_X_GET_Y_FREE_STRATEGY,
|
|
46
|
+
PromotionAdapter,
|
|
47
|
+
PromotionEvaluator,
|
|
48
|
+
X_ITEMS_FOR_Y_PRICE_STRATEGY
|
|
49
|
+
});
|
|
@@ -341,6 +341,7 @@ var formatBundleToOrigin = (bundle) => {
|
|
|
341
341
|
option: formatOptionsToOrigin(getBundleValueByKey("option")),
|
|
342
342
|
discount_list: d.discount_list,
|
|
343
343
|
"bundle_selling_price": d == null ? void 0 : d.price,
|
|
344
|
+
"custom_price": d == null ? void 0 : d.price,
|
|
344
345
|
metadata: {
|
|
345
346
|
custom_product_bundle_map_id: d._id,
|
|
346
347
|
product_discount_difference
|
|
@@ -15,6 +15,11 @@ export declare class OrderModule extends BaseModule implements Module, OrderModu
|
|
|
15
15
|
private cacheId;
|
|
16
16
|
private salesSummaryModuleName;
|
|
17
17
|
private rulesHooksOverride?;
|
|
18
|
+
/**
|
|
19
|
+
* Populate `savedAmount` on each discount based on product-level discount details.
|
|
20
|
+
* Only selected discounts get a non-zero value.
|
|
21
|
+
*/
|
|
22
|
+
static populateSavedAmounts(productList: Array<Record<string, any>>, discountList: Array<Record<string, any>>): void;
|
|
18
23
|
constructor(name?: string, version?: string);
|
|
19
24
|
initialize(core: PisellCore, options: ModuleOptions): Promise<void>;
|
|
20
25
|
/**
|
|
@@ -47,6 +47,28 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
47
47
|
this.defaultName = "order";
|
|
48
48
|
this.defaultVersion = "1.0.0";
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Populate `savedAmount` on each discount based on product-level discount details.
|
|
52
|
+
* Only selected discounts get a non-zero value.
|
|
53
|
+
*/
|
|
54
|
+
static populateSavedAmounts(productList, discountList) {
|
|
55
|
+
var _a, _b;
|
|
56
|
+
const savedMap = /* @__PURE__ */ new Map();
|
|
57
|
+
for (const product of productList) {
|
|
58
|
+
const qty = product.num || 1;
|
|
59
|
+
for (const pd of product.discount_list || []) {
|
|
60
|
+
const discountKey = ((_a = pd.discount) == null ? void 0 : _a.resource_id) || pd.id;
|
|
61
|
+
if (discountKey == null)
|
|
62
|
+
continue;
|
|
63
|
+
const amount = new import_decimal.default(pd.amount || 0).times(qty).plus(((_b = pd.metadata) == null ? void 0 : _b.product_discount_difference) || 0);
|
|
64
|
+
savedMap.set(discountKey, (savedMap.get(discountKey) || new import_decimal.default(0)).plus(amount));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
for (const d of discountList) {
|
|
68
|
+
const key = d.id;
|
|
69
|
+
d.savedAmount = d.isSelected && key != null && savedMap.has(key) ? savedMap.get(key).toNumber() : 0;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
50
72
|
async initialize(core, options) {
|
|
51
73
|
var _a;
|
|
52
74
|
this.core = core;
|
|
@@ -151,7 +173,11 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
151
173
|
original_price: values.original_price !== void 0 ? String(values.original_price) : product.original_price,
|
|
152
174
|
discount_list: values.discount_list ?? product.discount_list,
|
|
153
175
|
num: values.quantity ?? product.num,
|
|
154
|
-
product_bundle: values.bundle ?? product.product_bundle
|
|
176
|
+
product_bundle: values.bundle ?? product.product_bundle,
|
|
177
|
+
metadata: {
|
|
178
|
+
...product.metadata || {},
|
|
179
|
+
...values.main_product_selling_price !== void 0 ? { main_product_selling_price: String(values.main_product_selling_price) } : {}
|
|
180
|
+
}
|
|
155
181
|
})
|
|
156
182
|
};
|
|
157
183
|
}
|
|
@@ -247,6 +273,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
247
273
|
tempOrder.products = result.productList;
|
|
248
274
|
}
|
|
249
275
|
if (result == null ? void 0 : result.discountList) {
|
|
276
|
+
OrderModule.populateSavedAmounts(result.productList || tempOrder.products, result.discountList);
|
|
250
277
|
(_f = this.store.discount) == null ? void 0 : _f.setDiscountList(result.discountList);
|
|
251
278
|
tempOrder.discount_list = result.discountList.filter((d) => d.isSelected);
|
|
252
279
|
}
|
|
@@ -425,9 +452,18 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
425
452
|
}
|
|
426
453
|
async removeProductFromOrder(identity) {
|
|
427
454
|
const tempOrder = this.ensureTempOrder();
|
|
455
|
+
const beforeProducts = tempOrder.products;
|
|
428
456
|
tempOrder.products = tempOrder.products.filter(
|
|
429
457
|
(item) => !(0, import_utils3.isIdentityMatch)(item, identity)
|
|
430
458
|
);
|
|
459
|
+
const removedByStrictIdentity = beforeProducts.length - tempOrder.products.length;
|
|
460
|
+
if (removedByStrictIdentity === 0 && identity.identity_key) {
|
|
461
|
+
tempOrder.products = tempOrder.products.filter((item) => {
|
|
462
|
+
const isSameProduct = String(item.product_id) === String(identity.product_id) && String(item.product_variant_id) === String(identity.product_variant_id);
|
|
463
|
+
const hasNoIdentityKey = !item.identity_key;
|
|
464
|
+
return !(isSameProduct && hasNoIdentityKey);
|
|
465
|
+
});
|
|
466
|
+
}
|
|
431
467
|
this.applyDiscount();
|
|
432
468
|
await this.recalculateSummary({ createIfMissing: true });
|
|
433
469
|
this.persistTempOrder();
|
|
@@ -110,6 +110,19 @@ function normalizeSubmitProduct(product) {
|
|
|
110
110
|
if (rawMetadata.unique_identification_number) {
|
|
111
111
|
cleanMetadata.unique_identification_number = rawMetadata.unique_identification_number;
|
|
112
112
|
}
|
|
113
|
+
const priceMetaKeys = [
|
|
114
|
+
"main_product_original_price",
|
|
115
|
+
"main_product_selling_price",
|
|
116
|
+
"source_product_price"
|
|
117
|
+
];
|
|
118
|
+
for (const key of priceMetaKeys) {
|
|
119
|
+
if (rawMetadata[key] !== void 0) {
|
|
120
|
+
cleanMetadata[key] = rawMetadata[key];
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (rawMetadata.price_breakdown) {
|
|
124
|
+
cleanMetadata.price_breakdown = rawMetadata.price_breakdown;
|
|
125
|
+
}
|
|
113
126
|
return {
|
|
114
127
|
...submitProduct,
|
|
115
128
|
...bookingUid ? { booking_uid: bookingUid } : {},
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Module, PisellCore, ModuleOptions } from '../../types';
|
|
2
2
|
import { BaseModule } from '../BaseModule';
|
|
3
|
+
import type { ScheduleItem } from '../Schedule/types';
|
|
3
4
|
import type { QuotationItem } from './types';
|
|
4
5
|
export type { QuotationItem, QuotationProductData, QuotationSchedule, QuotationState } from './types';
|
|
5
6
|
export declare class QuotationModule extends BaseModule implements Module {
|
|
@@ -7,6 +8,7 @@ export declare class QuotationModule extends BaseModule implements Module {
|
|
|
7
8
|
protected defaultVersion: string;
|
|
8
9
|
private request;
|
|
9
10
|
private store;
|
|
11
|
+
private scheduleResolver?;
|
|
10
12
|
constructor(name?: string, version?: string);
|
|
11
13
|
initialize(core: PisellCore, options: ModuleOptions): Promise<void>;
|
|
12
14
|
loadQuotations(params?: {
|
|
@@ -26,6 +28,11 @@ export declare class QuotationModule extends BaseModule implements Module {
|
|
|
26
28
|
variantId?: number;
|
|
27
29
|
datetime: string;
|
|
28
30
|
}): string | null;
|
|
31
|
+
getQuotationShelfId(params: {
|
|
32
|
+
productId: number;
|
|
33
|
+
variantId?: number;
|
|
34
|
+
datetime: string;
|
|
35
|
+
}): number;
|
|
29
36
|
/**
|
|
30
37
|
* Batch pre-compute quotation prices for a set of products across multiple time points.
|
|
31
38
|
* Key format: `${productId}:${timePoint}`
|
|
@@ -35,6 +42,7 @@ export declare class QuotationModule extends BaseModule implements Module {
|
|
|
35
42
|
productIds: number[];
|
|
36
43
|
timePoints: string[];
|
|
37
44
|
}): Map<string, string | null>;
|
|
45
|
+
setScheduleResolver(resolver: (id: number) => ScheduleItem | undefined): void;
|
|
38
46
|
private isQuotationActiveAt;
|
|
39
47
|
private findProductData;
|
|
40
48
|
}
|
|
@@ -77,6 +77,18 @@ var QuotationModule = class extends import_BaseModule.BaseModule {
|
|
|
77
77
|
}
|
|
78
78
|
return null;
|
|
79
79
|
}
|
|
80
|
+
getQuotationShelfId(params) {
|
|
81
|
+
const { productId, variantId, datetime } = params;
|
|
82
|
+
for (const quotation of this.store.list) {
|
|
83
|
+
if (!this.isQuotationActiveAt(quotation, datetime))
|
|
84
|
+
continue;
|
|
85
|
+
const match = this.findProductData(quotation.product_data, productId, variantId);
|
|
86
|
+
if (!match || match.value === 0)
|
|
87
|
+
continue;
|
|
88
|
+
return quotation.id;
|
|
89
|
+
}
|
|
90
|
+
return 0;
|
|
91
|
+
}
|
|
80
92
|
/**
|
|
81
93
|
* Batch pre-compute quotation prices for a set of products across multiple time points.
|
|
82
94
|
* Key format: `${productId}:${timePoint}`
|
|
@@ -97,16 +109,25 @@ var QuotationModule = class extends import_BaseModule.BaseModule {
|
|
|
97
109
|
}
|
|
98
110
|
return map;
|
|
99
111
|
}
|
|
112
|
+
setScheduleResolver(resolver) {
|
|
113
|
+
this.scheduleResolver = resolver;
|
|
114
|
+
}
|
|
100
115
|
isQuotationActiveAt(quotation, datetime) {
|
|
101
116
|
var _a;
|
|
102
117
|
if (!((_a = quotation.schedule) == null ? void 0 : _a.length))
|
|
103
118
|
return false;
|
|
104
|
-
const scheduleItems = quotation.schedule.map((s) =>
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
119
|
+
const scheduleItems = quotation.schedule.map((s) => {
|
|
120
|
+
var _a2;
|
|
121
|
+
const full = (_a2 = this.scheduleResolver) == null ? void 0 : _a2.call(this, s.id);
|
|
122
|
+
if (full)
|
|
123
|
+
return full;
|
|
124
|
+
return {
|
|
125
|
+
...s,
|
|
126
|
+
repeat_type: s.repeat_type || "none",
|
|
127
|
+
repeat_rule: s.repeat_rule || null,
|
|
128
|
+
time_slot: s.time_slot || []
|
|
129
|
+
};
|
|
130
|
+
});
|
|
110
131
|
return (0, import_getDateIsInSchedule.getDateIsInSchedule)(datetime, scheduleItems);
|
|
111
132
|
}
|
|
112
133
|
findProductData(productData, productId, variantId) {
|
|
@@ -70,7 +70,7 @@ var isInStandardSchedule = (targetDate, targetDateString, targetTimeString, sche
|
|
|
70
70
|
}
|
|
71
71
|
const startDate = (0, import_dayjs.default)(schedule.start_time);
|
|
72
72
|
const endDate = (0, import_dayjs.default)(schedule.end_time);
|
|
73
|
-
const isInBasicRange = targetDate.isSameOrAfter(startDate) && targetDate.
|
|
73
|
+
const isInBasicRange = targetDate.isSameOrAfter(startDate) && targetDate.isBefore(endDate);
|
|
74
74
|
if (schedule.repeat_type === "none") {
|
|
75
75
|
return isInBasicRange;
|
|
76
76
|
}
|
|
@@ -80,16 +80,14 @@ var isInTimeSlotsSchedule = (targetDate, targetDateString, targetTimeString, sch
|
|
|
80
80
|
if (!schedule.start_time) {
|
|
81
81
|
return false;
|
|
82
82
|
}
|
|
83
|
-
const
|
|
84
|
-
if (
|
|
83
|
+
const scheduleStartDate = (0, import_dayjs.default)(schedule.start_time.split(" ")[0]);
|
|
84
|
+
if (!isDateInTimeSlotsSchedule(targetDate, schedule, scheduleStartDate)) {
|
|
85
85
|
return false;
|
|
86
86
|
}
|
|
87
87
|
for (const timeSlot of schedule.time_slot) {
|
|
88
|
-
const slotStart = `${
|
|
89
|
-
const slotEnd = `${
|
|
90
|
-
|
|
91
|
-
const slotEndDate = (0, import_dayjs.default)(slotEnd);
|
|
92
|
-
if (targetDate.isSameOrAfter(slotStartDate) && targetDate.isSameOrBefore(slotEndDate)) {
|
|
88
|
+
const slotStart = (0, import_dayjs.default)(`${targetDateString} ${timeSlot.start_time}:00`);
|
|
89
|
+
const slotEnd = (0, import_dayjs.default)(`${targetDateString} ${timeSlot.end_time}:00`);
|
|
90
|
+
if (targetDate.isSameOrAfter(slotStart) && targetDate.isBefore(slotEnd)) {
|
|
93
91
|
return true;
|
|
94
92
|
}
|
|
95
93
|
}
|
|
@@ -104,7 +102,7 @@ var isInDesignationSchedule = (targetDate, targetDateString, targetTimeString, s
|
|
|
104
102
|
const endDateTime = `${designation.end_date} ${designation.end_time}:00`;
|
|
105
103
|
const startDate = (0, import_dayjs.default)(startDateTime);
|
|
106
104
|
const endDate = (0, import_dayjs.default)(endDateTime);
|
|
107
|
-
if (targetDate.isSameOrAfter(startDate) && targetDate.
|
|
105
|
+
if (targetDate.isSameOrAfter(startDate) && targetDate.isBefore(endDate)) {
|
|
108
106
|
return true;
|
|
109
107
|
}
|
|
110
108
|
}
|
|
@@ -158,7 +156,7 @@ var isInDailyRepeat = (targetDate, startDate, endDate, repeatRule) => {
|
|
|
158
156
|
const targetTimeOfDay = targetDate.hour() * 3600 + targetDate.minute() * 60 + targetDate.second();
|
|
159
157
|
const startTimeOfDay = startDate.hour() * 3600 + startDate.minute() * 60 + startDate.second();
|
|
160
158
|
const endTimeOfDay = endDate.hour() * 3600 + endDate.minute() * 60 + endDate.second();
|
|
161
|
-
return targetTimeOfDay >= startTimeOfDay && targetTimeOfDay
|
|
159
|
+
return targetTimeOfDay >= startTimeOfDay && targetTimeOfDay < endTimeOfDay;
|
|
162
160
|
};
|
|
163
161
|
var isInWeeklyRepeat = (targetDate, startDate, endDate, repeatRule) => {
|
|
164
162
|
const targetDayOfWeek = targetDate.day();
|
|
@@ -175,7 +173,7 @@ var isInWeeklyRepeat = (targetDate, startDate, endDate, repeatRule) => {
|
|
|
175
173
|
const targetTimeOfDay = targetDate.hour() * 3600 + targetDate.minute() * 60 + targetDate.second();
|
|
176
174
|
const startTimeOfDay = startDate.hour() * 3600 + startDate.minute() * 60 + startDate.second();
|
|
177
175
|
const endTimeOfDay = endDate.hour() * 3600 + endDate.minute() * 60 + endDate.second();
|
|
178
|
-
return targetTimeOfDay >= startTimeOfDay && targetTimeOfDay
|
|
176
|
+
return targetTimeOfDay >= startTimeOfDay && targetTimeOfDay < endTimeOfDay;
|
|
179
177
|
};
|
|
180
178
|
var getScheduleStartEndTimePoints = (date, scheduleList) => {
|
|
181
179
|
if (!date || !scheduleList || scheduleList.length === 0) {
|
|
@@ -1237,7 +1237,7 @@ var BookingByStepImpl = class extends import_BaseModule.BaseModule {
|
|
|
1237
1237
|
}
|
|
1238
1238
|
);
|
|
1239
1239
|
cartItems2.forEach((item, index) => {
|
|
1240
|
-
var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
1240
|
+
var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
|
|
1241
1241
|
const { currentCapacity } = (0, import_capacity.getCapacityInfoByCartItem)(item);
|
|
1242
1242
|
const hasOriginTimeSlot = Boolean(item._origin.start_time) && Boolean(item._origin.end_time) && !(timeSlots == null ? void 0 : timeSlots.start_time);
|
|
1243
1243
|
if (hasOriginTimeSlot) {
|
|
@@ -1370,7 +1370,9 @@ var BookingByStepImpl = class extends import_BaseModule.BaseModule {
|
|
|
1370
1370
|
const fastestResource = (0, import_timeslots.findFastestAvailableResource)({
|
|
1371
1371
|
resources: targetRenderList,
|
|
1372
1372
|
currentCapacity,
|
|
1373
|
-
countMap: selectResourcesMap
|
|
1373
|
+
countMap: selectResourcesMap,
|
|
1374
|
+
targetDate: (_k = dateRange == null ? void 0 : dateRange[0]) == null ? void 0 : _k.date,
|
|
1375
|
+
durationMinutes: ((_l = item == null ? void 0 : item.duration) == null ? void 0 : _l.value) || ((_n = (_m = item == null ? void 0 : item._productOrigin) == null ? void 0 : _m.duration) == null ? void 0 : _n.value) || 10
|
|
1374
1376
|
});
|
|
1375
1377
|
const targetResource = fastestResource || targetRenderList[0];
|
|
1376
1378
|
targetResource.capacity = currentCapacity;
|
|
@@ -19,10 +19,12 @@ export declare function calculateResourceAvailableTime({ resource, timeSlots, cu
|
|
|
19
19
|
* @param countMap 已预约数量映射
|
|
20
20
|
* @returns 最快可用的资源
|
|
21
21
|
*/
|
|
22
|
-
export declare function findFastestAvailableResource({ resources, currentCapacity, countMap, }: {
|
|
22
|
+
export declare function findFastestAvailableResource({ resources, currentCapacity, countMap, targetDate, durationMinutes, }: {
|
|
23
23
|
resources: ResourceItem[];
|
|
24
24
|
currentCapacity?: number;
|
|
25
25
|
countMap?: Record<number, number>;
|
|
26
|
+
targetDate?: string | Dayjs;
|
|
27
|
+
durationMinutes?: number;
|
|
26
28
|
}): ResourceItem | null;
|
|
27
29
|
/**
|
|
28
30
|
* 给定一个时间列表,通过开始和结束时间过滤出符合条件的时间段
|
|
@@ -77,10 +77,15 @@ function calculateResourceAvailableTime({
|
|
|
77
77
|
function findFastestAvailableResource({
|
|
78
78
|
resources,
|
|
79
79
|
currentCapacity = 1,
|
|
80
|
-
countMap = {}
|
|
80
|
+
countMap = {},
|
|
81
|
+
targetDate,
|
|
82
|
+
durationMinutes = 30
|
|
81
83
|
}) {
|
|
82
84
|
var _a, _b, _c;
|
|
83
85
|
const currentTime = (0, import_dayjs.default)();
|
|
86
|
+
const resolvedDuration = durationMinutes > 0 ? durationMinutes : 30;
|
|
87
|
+
const targetDay = targetDate ? (0, import_dayjs.default)(targetDate) : currentTime;
|
|
88
|
+
const isTargetDayToday = targetDay.isSame(currentTime, "day");
|
|
84
89
|
let fastestTime = null;
|
|
85
90
|
let fastestResources = [];
|
|
86
91
|
console.log("[TimeslotUtils] 查找最快可用资源:", {
|
|
@@ -90,19 +95,22 @@ function findFastestAvailableResource({
|
|
|
90
95
|
countMap
|
|
91
96
|
});
|
|
92
97
|
for (const resource of resources) {
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
98
|
+
const targetDayTimes = resource.times.filter((time) => {
|
|
99
|
+
const isSameTargetDay = (0, import_dayjs.default)(time.start_at).isSame(targetDay, "day");
|
|
100
|
+
if (!isSameTargetDay)
|
|
101
|
+
return false;
|
|
102
|
+
if (!isTargetDayToday)
|
|
103
|
+
return true;
|
|
104
|
+
return (0, import_dayjs.default)(time.end_at).isAfter(currentTime);
|
|
97
105
|
});
|
|
98
|
-
if (
|
|
106
|
+
if (targetDayTimes.length === 0) {
|
|
99
107
|
console.log(`[TimeslotUtils] 资源 ${resource.id}(${resource.main_field}) 今日无可用时间段`);
|
|
100
108
|
continue;
|
|
101
109
|
}
|
|
102
|
-
for (const time of
|
|
110
|
+
for (const time of targetDayTimes) {
|
|
103
111
|
const workStartTime = (0, import_dayjs.default)(time.start_at);
|
|
104
112
|
const workEndTime = (0, import_dayjs.default)(time.end_at);
|
|
105
|
-
let nextAvailableTime = currentTime.
|
|
113
|
+
let nextAvailableTime = isTargetDayToday && currentTime.isAfter(workStartTime) ? currentTime : workStartTime;
|
|
106
114
|
console.log(`[TimeslotUtils] 检查资源 ${resource.id}(${resource.main_field}):`, {
|
|
107
115
|
workTime: `${workStartTime.format("HH:mm")}-${workEndTime.format("HH:mm")}`,
|
|
108
116
|
checkStartTime: nextAvailableTime.format("HH:mm:ss"),
|
|
@@ -126,7 +134,7 @@ function findFastestAvailableResource({
|
|
|
126
134
|
pax: event.pax || 1
|
|
127
135
|
});
|
|
128
136
|
if (resource.resourceType === "single" || (resource.capacity || 1) === 1) {
|
|
129
|
-
if (finalAvailableTime.isBefore(eventEnd) && eventStart.isBefore(finalAvailableTime.add(
|
|
137
|
+
if (finalAvailableTime.isBefore(eventEnd) && eventStart.isBefore(finalAvailableTime.add(resolvedDuration, "minute"))) {
|
|
130
138
|
finalAvailableTime = eventEnd;
|
|
131
139
|
console.log(`[TimeslotUtils] 发现时间冲突,调整到: ${finalAvailableTime.format("HH:mm:ss")}`);
|
|
132
140
|
}
|
|
@@ -134,7 +142,7 @@ function findFastestAvailableResource({
|
|
|
134
142
|
const totalCapacity = resource.capacity || 0;
|
|
135
143
|
const currentUsedCapacity = countMap[resource.id] || 0;
|
|
136
144
|
const eventUsedCapacity = event.pax || 1;
|
|
137
|
-
if (finalAvailableTime.isBefore(eventEnd) && eventStart.isBefore(finalAvailableTime.add(
|
|
145
|
+
if (finalAvailableTime.isBefore(eventEnd) && eventStart.isBefore(finalAvailableTime.add(resolvedDuration, "minute"))) {
|
|
138
146
|
const totalRequiredCapacity = currentUsedCapacity + currentCapacity + eventUsedCapacity;
|
|
139
147
|
if (totalRequiredCapacity > totalCapacity) {
|
|
140
148
|
finalAvailableTime = eventEnd;
|
|
@@ -143,7 +151,7 @@ function findFastestAvailableResource({
|
|
|
143
151
|
}
|
|
144
152
|
}
|
|
145
153
|
}
|
|
146
|
-
if (finalAvailableTime.isAfter(workEndTime)) {
|
|
154
|
+
if (finalAvailableTime.add(resolvedDuration, "minute").isAfter(workEndTime)) {
|
|
147
155
|
console.log(`[TimeslotUtils] 资源 ${resource.id} 可用时间超出工作时间,跳过`);
|
|
148
156
|
continue;
|
|
149
157
|
}
|
|
@@ -294,10 +294,27 @@ function getProductIdentityIndex(products, identity) {
|
|
|
294
294
|
return products.findIndex((item) => isIdentityMatch(item, identity));
|
|
295
295
|
}
|
|
296
296
|
function normalizeOrderProduct(product) {
|
|
297
|
+
var _a;
|
|
297
298
|
const metadata = { ...product.metadata || {} };
|
|
298
299
|
if (product.identity_key && !metadata.unique_identification_number) {
|
|
299
300
|
metadata.unique_identification_number = product.identity_key;
|
|
300
301
|
}
|
|
302
|
+
const resolvedOriginalPrice = product.original_price || "0.00";
|
|
303
|
+
const resolvedSellingPrice = product.selling_price || "0.00";
|
|
304
|
+
if (metadata.main_product_original_price === void 0) {
|
|
305
|
+
metadata.main_product_original_price = resolvedOriginalPrice;
|
|
306
|
+
}
|
|
307
|
+
if (metadata.main_product_selling_price === void 0) {
|
|
308
|
+
metadata.main_product_selling_price = resolvedSellingPrice;
|
|
309
|
+
}
|
|
310
|
+
if (metadata.source_product_price === void 0) {
|
|
311
|
+
metadata.source_product_price = ((_a = product._origin) == null ? void 0 : _a.original_price) ?? resolvedOriginalPrice;
|
|
312
|
+
}
|
|
313
|
+
const normalizedBundle = (product.product_bundle || []).map((item) => ({
|
|
314
|
+
...item,
|
|
315
|
+
bundle_selling_price: item.bundle_selling_price ?? item.price ?? "0.00",
|
|
316
|
+
custom_price: item.custom_price ?? item.bundle_selling_price ?? item.price ?? "0.00"
|
|
317
|
+
}));
|
|
301
318
|
return {
|
|
302
319
|
order_detail_id: product.order_detail_id || null,
|
|
303
320
|
product_id: product.product_id,
|
|
@@ -305,13 +322,13 @@ function normalizeOrderProduct(product) {
|
|
|
305
322
|
product_variant_id: product.product_variant_id,
|
|
306
323
|
identity_key: product.identity_key,
|
|
307
324
|
product_option_item: product.product_option_item || [],
|
|
308
|
-
selling_price:
|
|
309
|
-
original_price:
|
|
325
|
+
selling_price: resolvedSellingPrice,
|
|
326
|
+
original_price: resolvedOriginalPrice,
|
|
310
327
|
payment_price: product.payment_price || "0.00",
|
|
311
328
|
tax_fee: product.tax_fee || "0.00",
|
|
312
329
|
is_charge_tax: product.is_charge_tax ?? 0,
|
|
313
330
|
discount_list: product.discount_list || [],
|
|
314
|
-
product_bundle:
|
|
331
|
+
product_bundle: normalizedBundle,
|
|
315
332
|
metadata,
|
|
316
333
|
_origin: product._origin
|
|
317
334
|
};
|