@pisell/pisellos 2.1.130 → 2.1.131
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/Order/index.d.ts +3 -6
- package/dist/modules/Order/index.js +119 -41
- package/dist/modules/Order/types.d.ts +23 -5
- package/dist/modules/Order/types.js +2 -0
- package/dist/modules/Order/utils.d.ts +66 -11
- package/dist/modules/Order/utils.js +281 -45
- package/dist/modules/SalesSummary/utils.js +33 -68
- package/dist/modules/ScanOrderLogger/providers/feishu.js +168 -60
- package/dist/modules/ScanOrderLogger/types.d.ts +6 -0
- package/dist/modules/Summary/utils.js +6 -21
- package/dist/solution/ScanOrder/index.d.ts +31 -6
- package/dist/solution/ScanOrder/index.js +1062 -498
- package/dist/solution/ScanOrder/types.d.ts +52 -2
- package/dist/solution/ScanOrder/types.js +16 -1
- package/dist/solution/ScanOrder/utils.d.ts +41 -5
- package/dist/solution/ScanOrder/utils.js +214 -33
- package/dist/solution/VenueBooking/index.d.ts +2 -5
- package/dist/solution/VenueBooking/index.js +35 -27
- package/lib/modules/Order/index.d.ts +3 -6
- package/lib/modules/Order/index.js +109 -30
- package/lib/modules/Order/types.d.ts +23 -5
- package/lib/modules/Order/utils.d.ts +66 -11
- package/lib/modules/Order/utils.js +181 -16
- package/lib/modules/SalesSummary/utils.js +13 -47
- package/lib/modules/ScanOrderLogger/providers/feishu.js +100 -34
- package/lib/modules/ScanOrderLogger/types.d.ts +6 -0
- package/lib/modules/Summary/utils.js +4 -18
- package/lib/solution/ScanOrder/index.d.ts +31 -6
- package/lib/solution/ScanOrder/index.js +315 -14
- package/lib/solution/ScanOrder/types.d.ts +52 -2
- package/lib/solution/ScanOrder/utils.d.ts +41 -5
- package/lib/solution/ScanOrder/utils.js +150 -20
- package/lib/solution/VenueBooking/index.d.ts +2 -5
- package/lib/solution/VenueBooking/index.js +13 -6
- package/package.json +1 -1
|
@@ -35,8 +35,12 @@ __export(ScanOrder_exports, {
|
|
|
35
35
|
module.exports = __toCommonJS(ScanOrder_exports);
|
|
36
36
|
var import_BaseModule = require("../../modules/BaseModule");
|
|
37
37
|
var import_types = require("./types");
|
|
38
|
+
var import_Order = require("../../modules/Order");
|
|
39
|
+
var import_types2 = require("../../modules/Account/types");
|
|
40
|
+
var import_types3 = require("../RegisterAndLogin/types");
|
|
41
|
+
var import_decimal = __toESM(require("decimal.js"));
|
|
38
42
|
var import_utils = require("./utils");
|
|
39
|
-
var
|
|
43
|
+
var import_types4 = require("../BookingByStep/types");
|
|
40
44
|
var import_ProductList = require("../../modules/ProductList");
|
|
41
45
|
var import_Schedule = require("../../modules/Schedule");
|
|
42
46
|
var import_getDateIsInSchedule = require("../../modules/Schedule/getDateIsInSchedule");
|
|
@@ -74,6 +78,9 @@ var _ScanOrderImpl = class extends import_BaseModule.BaseModule {
|
|
|
74
78
|
this.itemRuleRuntimeConfig = {};
|
|
75
79
|
/** 最近一次 checkResourceAvailable 从预约规则 link 拉取的商品快照 */
|
|
76
80
|
this.enabledReservationRuleProducts = [];
|
|
81
|
+
this.loginEffectDisposers = [];
|
|
82
|
+
this.customerLoginRefreshInFlight = null;
|
|
83
|
+
this.customerLoginRefreshIdInFlight = null;
|
|
77
84
|
}
|
|
78
85
|
getScanOrderLoggerContext() {
|
|
79
86
|
return {
|
|
@@ -157,6 +164,119 @@ var _ScanOrderImpl = class extends import_BaseModule.BaseModule {
|
|
|
157
164
|
}
|
|
158
165
|
});
|
|
159
166
|
}
|
|
167
|
+
normalizeCustomerId(value) {
|
|
168
|
+
const customerId = Number(value);
|
|
169
|
+
if (!Number.isFinite(customerId) || customerId <= 0)
|
|
170
|
+
return null;
|
|
171
|
+
return customerId;
|
|
172
|
+
}
|
|
173
|
+
resolveCustomerIdFromLoginPayload(payload) {
|
|
174
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
175
|
+
const candidates = [
|
|
176
|
+
payload == null ? void 0 : payload.customerId,
|
|
177
|
+
payload == null ? void 0 : payload.customer_id,
|
|
178
|
+
payload == null ? void 0 : payload.id,
|
|
179
|
+
(_a = payload == null ? void 0 : payload.user) == null ? void 0 : _a.customerId,
|
|
180
|
+
(_b = payload == null ? void 0 : payload.user) == null ? void 0 : _b.customer_id,
|
|
181
|
+
(_c = payload == null ? void 0 : payload.user) == null ? void 0 : _c.id,
|
|
182
|
+
(_e = (_d = payload == null ? void 0 : payload.user) == null ? void 0 : _d._origin) == null ? void 0 : _e.customer_id,
|
|
183
|
+
(_f = payload == null ? void 0 : payload.account) == null ? void 0 : _f.customerId,
|
|
184
|
+
(_g = payload == null ? void 0 : payload.account) == null ? void 0 : _g.customer_id,
|
|
185
|
+
(_h = payload == null ? void 0 : payload.account) == null ? void 0 : _h.id,
|
|
186
|
+
(_i = payload == null ? void 0 : payload._origin) == null ? void 0 : _i.customer_id
|
|
187
|
+
];
|
|
188
|
+
for (const candidate of candidates) {
|
|
189
|
+
const customerId = this.normalizeCustomerId(candidate);
|
|
190
|
+
if (customerId)
|
|
191
|
+
return customerId;
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
clearLoginEffectListeners() {
|
|
196
|
+
for (const dispose of this.loginEffectDisposers) {
|
|
197
|
+
dispose();
|
|
198
|
+
}
|
|
199
|
+
this.loginEffectDisposers = [];
|
|
200
|
+
}
|
|
201
|
+
registerLoginEffect(event, callback) {
|
|
202
|
+
var _a;
|
|
203
|
+
const effects = (_a = this.core) == null ? void 0 : _a.effects;
|
|
204
|
+
if (!(effects == null ? void 0 : effects.on))
|
|
205
|
+
return;
|
|
206
|
+
effects.on(event, callback);
|
|
207
|
+
this.loginEffectDisposers.push(() => {
|
|
208
|
+
if (typeof effects.off === "function") {
|
|
209
|
+
effects.off(event, callback);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
registerCustomerLoginListeners() {
|
|
214
|
+
this.clearLoginEffectListeners();
|
|
215
|
+
const createHandleLogin = () => async (payload) => {
|
|
216
|
+
const customerId = this.resolveCustomerIdFromLoginPayload(payload);
|
|
217
|
+
if (!customerId)
|
|
218
|
+
return;
|
|
219
|
+
await this.refreshOrderMarketingAfterLogin({ customerId });
|
|
220
|
+
};
|
|
221
|
+
this.registerLoginEffect(
|
|
222
|
+
_ScanOrderImpl.PISELL1_LOGIN_SUCCESS,
|
|
223
|
+
createHandleLogin()
|
|
224
|
+
);
|
|
225
|
+
this.registerLoginEffect(import_types2.AccountHooks.OnLogin, createHandleLogin());
|
|
226
|
+
this.registerLoginEffect(
|
|
227
|
+
import_types3.RegisterAndLoginHooks.onLoginSuccess,
|
|
228
|
+
createHandleLogin()
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
async refreshOrderMarketingAfterLogin(params) {
|
|
232
|
+
if (!this.store.order)
|
|
233
|
+
throw new Error("order 模块未初始化");
|
|
234
|
+
if (this.customerLoginRefreshInFlight) {
|
|
235
|
+
if (this.customerLoginRefreshIdInFlight === params.customerId) {
|
|
236
|
+
await this.customerLoginRefreshInFlight;
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
await this.customerLoginRefreshInFlight;
|
|
240
|
+
}
|
|
241
|
+
this.customerLoginRefreshIdInFlight = params.customerId;
|
|
242
|
+
const refreshTask = (async () => {
|
|
243
|
+
await this.store.order.loadDiscountConfig({
|
|
244
|
+
customerId: params.customerId
|
|
245
|
+
});
|
|
246
|
+
await this.store.order.recalculateSummary({ createIfMissing: true });
|
|
247
|
+
this.store.order.persistTempOrder();
|
|
248
|
+
await this.refreshItemRuleQuantityLimits();
|
|
249
|
+
await this.refreshCartValidationPassed();
|
|
250
|
+
})();
|
|
251
|
+
this.customerLoginRefreshInFlight = refreshTask;
|
|
252
|
+
try {
|
|
253
|
+
await refreshTask;
|
|
254
|
+
} catch (error) {
|
|
255
|
+
throw error;
|
|
256
|
+
} finally {
|
|
257
|
+
if (this.customerLoginRefreshInFlight === refreshTask) {
|
|
258
|
+
this.customerLoginRefreshInFlight = null;
|
|
259
|
+
this.customerLoginRefreshIdInFlight = null;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/** 与 `otherParams.cacheId` 一致,供宿主在 URL 变化时判断是否需要重新注册模块 */
|
|
264
|
+
getCacheId() {
|
|
265
|
+
return this.cacheId;
|
|
266
|
+
}
|
|
267
|
+
async destroyRegisteredChildModules() {
|
|
268
|
+
const modules = [
|
|
269
|
+
this.store.schedule,
|
|
270
|
+
this.store.salesSummary,
|
|
271
|
+
this.store.order,
|
|
272
|
+
this.store.products,
|
|
273
|
+
this.store.scanOrderLogger
|
|
274
|
+
];
|
|
275
|
+
for (const mod of modules) {
|
|
276
|
+
if (mod && typeof mod.destroy === "function")
|
|
277
|
+
await Promise.resolve(mod.destroy());
|
|
278
|
+
}
|
|
279
|
+
}
|
|
160
280
|
async initialize(core, options = {}) {
|
|
161
281
|
var _a, _b, _c, _d, _e, _f;
|
|
162
282
|
this.logMethodStart("initialize");
|
|
@@ -195,7 +315,7 @@ var _ScanOrderImpl = class extends import_BaseModule.BaseModule {
|
|
|
195
315
|
];
|
|
196
316
|
moduleArr.forEach((step) => {
|
|
197
317
|
var _a2, _b2, _c2, _d2, _e2, _f2;
|
|
198
|
-
const targetModule = (0,
|
|
318
|
+
const targetModule = (0, import_types4.createModule)(step, this.name);
|
|
199
319
|
if (targetModule) {
|
|
200
320
|
this.store[step] = targetModule;
|
|
201
321
|
const initialState = step === "salesSummary" ? {
|
|
@@ -236,6 +356,7 @@ var _ScanOrderImpl = class extends import_BaseModule.BaseModule {
|
|
|
236
356
|
fatherModule: this.name
|
|
237
357
|
}
|
|
238
358
|
});
|
|
359
|
+
this.registerCustomerLoginListeners();
|
|
239
360
|
console.log("[ScanOrder] 初始化开始");
|
|
240
361
|
try {
|
|
241
362
|
await ((_e = this.store.order) == null ? void 0 : _e.recalculateSummary({ createIfMissing: false }));
|
|
@@ -269,7 +390,10 @@ var _ScanOrderImpl = class extends import_BaseModule.BaseModule {
|
|
|
269
390
|
}
|
|
270
391
|
async destroy() {
|
|
271
392
|
this.logMethodStart("destroy");
|
|
393
|
+
this.clearLoginEffectListeners();
|
|
272
394
|
await this.core.effects.emit(import_types.ScanOrderHooks.onDestroy, {});
|
|
395
|
+
await this.destroyRegisteredChildModules();
|
|
396
|
+
super.destroy();
|
|
273
397
|
console.log("[ScanOrder] 已销毁");
|
|
274
398
|
this.logMethodSuccess("destroy");
|
|
275
399
|
}
|
|
@@ -494,6 +618,134 @@ var _ScanOrderImpl = class extends import_BaseModule.BaseModule {
|
|
|
494
618
|
throw error;
|
|
495
619
|
}
|
|
496
620
|
}
|
|
621
|
+
getDiscountList() {
|
|
622
|
+
if (!this.store.order)
|
|
623
|
+
return [];
|
|
624
|
+
return this.store.order.getDiscountList();
|
|
625
|
+
}
|
|
626
|
+
async scanCode(code, customerId) {
|
|
627
|
+
this.logMethodStart("scanCode", { code });
|
|
628
|
+
try {
|
|
629
|
+
if (!this.store.order)
|
|
630
|
+
throw new Error("order 模块未初始化");
|
|
631
|
+
const raw = await this.store.order.scanCode(code, customerId);
|
|
632
|
+
if (raw.isAvailable) {
|
|
633
|
+
await this.store.order.recalculateSummary({ createIfMissing: true });
|
|
634
|
+
this.store.order.persistTempOrder();
|
|
635
|
+
}
|
|
636
|
+
this.logMethodSuccess("scanCode", { isAvailable: raw.isAvailable });
|
|
637
|
+
return {
|
|
638
|
+
isAvailable: raw.isAvailable,
|
|
639
|
+
type: raw.type,
|
|
640
|
+
unavailableReason: raw.unavailableReason
|
|
641
|
+
};
|
|
642
|
+
} catch (error) {
|
|
643
|
+
this.logMethodError("scanCode", error);
|
|
644
|
+
throw error;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
async setDiscountSelected(params) {
|
|
648
|
+
var _a, _b, _c, _d, _e;
|
|
649
|
+
this.logMethodStart("setDiscountSelected", params);
|
|
650
|
+
try {
|
|
651
|
+
if (!this.store.order)
|
|
652
|
+
throw new Error("order 模块未初始化");
|
|
653
|
+
const list = this.store.order.getDiscountList();
|
|
654
|
+
const updated = list.map(
|
|
655
|
+
(d) => d.id === params.discountId ? {
|
|
656
|
+
...d,
|
|
657
|
+
isSelected: params.isSelected,
|
|
658
|
+
isManualSelect: !params.isSelected
|
|
659
|
+
} : d
|
|
660
|
+
);
|
|
661
|
+
const tempOrder = this.store.order.ensureTempOrder();
|
|
662
|
+
const orderStore = this.store.order.store || {};
|
|
663
|
+
const discountModule = orderStore.discount;
|
|
664
|
+
const rulesModule = orderStore.rules;
|
|
665
|
+
const holders = ((_a = tempOrder.holder) == null ? void 0 : _a.form_record_id) ? [{ form_record_id: tempOrder.holder.form_record_id }] : [];
|
|
666
|
+
let nextDiscountList = updated;
|
|
667
|
+
await (discountModule == null ? void 0 : discountModule.setDiscountList(updated));
|
|
668
|
+
if (rulesModule) {
|
|
669
|
+
const result = rulesModule.calcDiscount(
|
|
670
|
+
{
|
|
671
|
+
productList: tempOrder.products,
|
|
672
|
+
discountList: updated,
|
|
673
|
+
holders,
|
|
674
|
+
isFormSubject: !!((_b = tempOrder.holder) == null ? void 0 : _b.type) && tempOrder.holder.type === "form"
|
|
675
|
+
},
|
|
676
|
+
{
|
|
677
|
+
discountId: params.discountId,
|
|
678
|
+
isSelected: params.isSelected
|
|
679
|
+
}
|
|
680
|
+
) || { productList: tempOrder.products, discountList: updated };
|
|
681
|
+
if (result == null ? void 0 : result.productList) {
|
|
682
|
+
tempOrder.products = result.productList;
|
|
683
|
+
}
|
|
684
|
+
if (result == null ? void 0 : result.discountList) {
|
|
685
|
+
nextDiscountList = result.discountList;
|
|
686
|
+
if (!params.isSelected) {
|
|
687
|
+
const beforeSelectedIds = new Set(
|
|
688
|
+
updated.filter((d) => d.isSelected).map((d) => d.id)
|
|
689
|
+
);
|
|
690
|
+
for (const d of nextDiscountList) {
|
|
691
|
+
if (d.isSelected && !beforeSelectedIds.has(d.id)) {
|
|
692
|
+
d.isSelected = false;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
const selectedResourceIds = new Set(
|
|
699
|
+
nextDiscountList.filter((d) => d.isSelected).map((d) => d.id)
|
|
700
|
+
);
|
|
701
|
+
for (const product of tempOrder.products) {
|
|
702
|
+
if ((_c = product._origin) == null ? void 0 : _c.isManualDiscount)
|
|
703
|
+
continue;
|
|
704
|
+
product.discount_list = (product.discount_list || []).filter((pd) => {
|
|
705
|
+
var _a2;
|
|
706
|
+
const rid = ((_a2 = pd.discount) == null ? void 0 : _a2.resource_id) ?? pd.id;
|
|
707
|
+
return rid != null && selectedResourceIds.has(rid);
|
|
708
|
+
});
|
|
709
|
+
const totalPerUnitDiscount = (product.discount_list || []).reduce(
|
|
710
|
+
(sum, pd) => sum + (pd.amount || 0),
|
|
711
|
+
0
|
|
712
|
+
);
|
|
713
|
+
const optionSum = (0, import_utils2.sumOptionUnitPrice)(product.product_option_item);
|
|
714
|
+
const sourcePrice = ((_d = product.metadata) == null ? void 0 : _d.source_product_price) ?? (((_e = product.metadata) == null ? void 0 : _e.main_product_original_price) != null ? new import_decimal.default(Number(product.metadata.main_product_original_price) || 0).minus(optionSum).toFixed(2) : product.original_price ?? "0");
|
|
715
|
+
const newSourceSellingPrice = new import_decimal.default(Number(sourcePrice) || 0).minus(totalPerUnitDiscount).toDecimalPlaces(2).toString();
|
|
716
|
+
const newMainSellingPrice = new import_decimal.default(Number(newSourceSellingPrice) || 0).plus(optionSum).toDecimalPlaces(2).toFixed(2);
|
|
717
|
+
if (product.metadata) {
|
|
718
|
+
product.metadata.main_product_selling_price = newMainSellingPrice;
|
|
719
|
+
product.metadata.price_schema_version = 2;
|
|
720
|
+
}
|
|
721
|
+
product.selling_price = (0, import_utils2.composeLinePrice)({
|
|
722
|
+
mainPrice: newMainSellingPrice,
|
|
723
|
+
bundle: product.product_bundle
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
import_Order.OrderModule.populateSavedAmounts(tempOrder.products, nextDiscountList);
|
|
727
|
+
await (discountModule == null ? void 0 : discountModule.setDiscountList(nextDiscountList));
|
|
728
|
+
tempOrder.discount_list = (nextDiscountList || []).filter((d) => d.isSelected);
|
|
729
|
+
await this.store.order.recalculateSummary({ createIfMissing: true });
|
|
730
|
+
this.store.order.persistTempOrder();
|
|
731
|
+
this.logMethodSuccess("setDiscountSelected", params);
|
|
732
|
+
} catch (error) {
|
|
733
|
+
this.logMethodError("setDiscountSelected", error);
|
|
734
|
+
throw error;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
async onCustomerLogin(params) {
|
|
738
|
+
this.logMethodStart("onCustomerLogin", { customerId: params.customerId });
|
|
739
|
+
try {
|
|
740
|
+
await this.refreshOrderMarketingAfterLogin({
|
|
741
|
+
customerId: params.customerId
|
|
742
|
+
});
|
|
743
|
+
this.logMethodSuccess("onCustomerLogin", { customerId: params.customerId });
|
|
744
|
+
} catch (error) {
|
|
745
|
+
this.logMethodError("onCustomerLogin", error);
|
|
746
|
+
throw error;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
497
749
|
// ScanOrder 提交 payload enhancer:
|
|
498
750
|
// - 给所有 booking 注入 appointment_status: 'started'(扫码点餐语义)
|
|
499
751
|
// - 给所有 booking 的 metadata 注入 resource_select_type(来自预约规则商品的 resource.type)
|
|
@@ -515,7 +767,7 @@ var _ScanOrderImpl = class extends import_BaseModule.BaseModule {
|
|
|
515
767
|
return 1;
|
|
516
768
|
};
|
|
517
769
|
return (payload, { bookingUuid, tempOrder }) => {
|
|
518
|
-
var _a;
|
|
770
|
+
var _a, _b;
|
|
519
771
|
const resourceId = tempOrder.resource_id ?? (resourceState == null ? void 0 : resourceState.relationId);
|
|
520
772
|
const pickOriginal = (value) => {
|
|
521
773
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
@@ -536,20 +788,31 @@ var _ScanOrderImpl = class extends import_BaseModule.BaseModule {
|
|
|
536
788
|
metadata: {}
|
|
537
789
|
} : void 0;
|
|
538
790
|
const ruleProductUid = ruleProduct ? (0, import_utils2.createUuidV4)() : void 0;
|
|
539
|
-
const
|
|
791
|
+
const rawCollectPax = (_b = tempOrder.metadata) == null ? void 0 : _b.collect_pax;
|
|
792
|
+
const hasCollectPaxForCapacity = rawCollectPax !== null && rawCollectPax !== void 0 && rawCollectPax !== "";
|
|
793
|
+
const bookingCapacityValue = hasCollectPaxForCapacity ? (0, import_utils2.normalizeSubmitCollectPaxValue)(rawCollectPax) : resolveResourceCapacity();
|
|
794
|
+
const bookingCapacityDimensionId = (0, import_utils.pickFirstCustomCapacityDimensionId)(this.enabledReservationRuleProducts) ?? 0;
|
|
540
795
|
const nextBookings = (payload.bookings || []).map((booking, idx) => ({
|
|
541
796
|
...booking,
|
|
542
797
|
appointment_status: "started",
|
|
543
798
|
metadata: {
|
|
544
799
|
...booking.metadata || {},
|
|
545
800
|
...resourceSelectType ? { resource_select_type: resourceSelectType } : {},
|
|
546
|
-
...resourceSelectType ? {
|
|
801
|
+
...resourceSelectType ? {
|
|
802
|
+
capacity: [
|
|
803
|
+
{
|
|
804
|
+
id: bookingCapacityDimensionId,
|
|
805
|
+
value: bookingCapacityValue,
|
|
806
|
+
name: ""
|
|
807
|
+
}
|
|
808
|
+
]
|
|
809
|
+
} : {}
|
|
547
810
|
},
|
|
548
811
|
...idx === 0 && resourceEntry ? { resources: [resourceEntry] } : {},
|
|
549
812
|
...idx === 0 && ruleProductUid ? { product_uid: ruleProductUid } : {}
|
|
550
813
|
}));
|
|
551
814
|
const nextProducts = [...payload.products || []];
|
|
552
|
-
if (ruleProduct && ruleProductUid) {
|
|
815
|
+
if (ruleProduct && ruleProductUid && !tempOrder.order_id) {
|
|
553
816
|
const sellingPrice = String(ruleProduct.price ?? "0.00");
|
|
554
817
|
const originalPrice = String(
|
|
555
818
|
ruleProduct.original_price ?? ruleProduct.price ?? "0.00"
|
|
@@ -655,6 +918,36 @@ var _ScanOrderImpl = class extends import_BaseModule.BaseModule {
|
|
|
655
918
|
throw error;
|
|
656
919
|
}
|
|
657
920
|
}
|
|
921
|
+
/**
|
|
922
|
+
* 设置单行商品备注(与整单 `updateTempOrderNote` 区分)。
|
|
923
|
+
* 多行同 SKU 时必须传入与删除/更新一致的 identity(如 `identity_key`)。
|
|
924
|
+
*/
|
|
925
|
+
async setOrderProductLineNote(identity, note) {
|
|
926
|
+
this.logMethodStart("setOrderProductLineNote", {
|
|
927
|
+
product_id: identity.product_id,
|
|
928
|
+
product_variant_id: identity.product_variant_id
|
|
929
|
+
});
|
|
930
|
+
try {
|
|
931
|
+
const params = {
|
|
932
|
+
product_id: identity.product_id,
|
|
933
|
+
product_variant_id: identity.product_variant_id,
|
|
934
|
+
updates: { note: String(note || "") }
|
|
935
|
+
};
|
|
936
|
+
if (identity.identity_key !== void 0)
|
|
937
|
+
params.identity_key = identity.identity_key;
|
|
938
|
+
if (identity.product_option_item !== void 0) {
|
|
939
|
+
params.product_option_item = identity.product_option_item;
|
|
940
|
+
}
|
|
941
|
+
if (identity.product_bundle !== void 0)
|
|
942
|
+
params.product_bundle = identity.product_bundle;
|
|
943
|
+
const products = await this.updateProductInOrder(params);
|
|
944
|
+
this.logMethodSuccess("setOrderProductLineNote", { productCount: products.length });
|
|
945
|
+
return products;
|
|
946
|
+
} catch (error) {
|
|
947
|
+
this.logMethodError("setOrderProductLineNote", error);
|
|
948
|
+
throw error;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
658
951
|
async removeProductFromOrder(identity) {
|
|
659
952
|
this.logMethodStart("removeProductFromOrder", {
|
|
660
953
|
product_id: identity.product_id,
|
|
@@ -1035,7 +1328,7 @@ var _ScanOrderImpl = class extends import_BaseModule.BaseModule {
|
|
|
1035
1328
|
const relationId = (0, import_utils.toPositiveString)(detail == null ? void 0 : detail.form_record_id);
|
|
1036
1329
|
const tableFormId = (0, import_utils.toPositiveString)(detail == null ? void 0 : detail.form_id);
|
|
1037
1330
|
const formRecord = (detail == null ? void 0 : detail.form_record) ?? null;
|
|
1038
|
-
const allowSnack = ((_b = (_a = this.otherParams) == null ? void 0 : _a.dineInConfig) == null ? void 0 : _b["
|
|
1331
|
+
const allowSnack = ((_b = (_a = this.otherParams) == null ? void 0 : _a.dineInConfig) == null ? void 0 : _b["sale.allow_add_items"]) || false;
|
|
1039
1332
|
const deskmateValid = false;
|
|
1040
1333
|
const isExclusive = resourceSelectType === "single";
|
|
1041
1334
|
const isFull = (0, import_utils.computeResourceIsFull)({
|
|
@@ -1394,10 +1687,10 @@ var _ScanOrderImpl = class extends import_BaseModule.BaseModule {
|
|
|
1394
1687
|
readUIStateBucket() {
|
|
1395
1688
|
var _a;
|
|
1396
1689
|
const key = this.getUIStateBucketKey();
|
|
1397
|
-
if (!key || !((_a = this.window) == null ? void 0 : _a.
|
|
1690
|
+
if (!key || !((_a = this.window) == null ? void 0 : _a.localStorage))
|
|
1398
1691
|
return {};
|
|
1399
1692
|
try {
|
|
1400
|
-
const raw = this.window.
|
|
1693
|
+
const raw = this.window.localStorage.getItem(key) || "{}";
|
|
1401
1694
|
const parsed = JSON.parse(raw);
|
|
1402
1695
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
1403
1696
|
} catch {
|
|
@@ -1407,10 +1700,10 @@ var _ScanOrderImpl = class extends import_BaseModule.BaseModule {
|
|
|
1407
1700
|
writeUIStateBucket(bucket) {
|
|
1408
1701
|
var _a;
|
|
1409
1702
|
const key = this.getUIStateBucketKey();
|
|
1410
|
-
if (!key || !((_a = this.window) == null ? void 0 : _a.
|
|
1703
|
+
if (!key || !((_a = this.window) == null ? void 0 : _a.localStorage))
|
|
1411
1704
|
return;
|
|
1412
1705
|
try {
|
|
1413
|
-
this.window.
|
|
1706
|
+
this.window.localStorage.setItem(key, JSON.stringify(bucket));
|
|
1414
1707
|
} catch (error) {
|
|
1415
1708
|
console.warn("[ScanOrder] writeUIStateBucket failed", error);
|
|
1416
1709
|
}
|
|
@@ -1441,10 +1734,10 @@ var _ScanOrderImpl = class extends import_BaseModule.BaseModule {
|
|
|
1441
1734
|
clearUIState() {
|
|
1442
1735
|
var _a;
|
|
1443
1736
|
const key = this.getUIStateBucketKey();
|
|
1444
|
-
if (!key || !((_a = this.window) == null ? void 0 : _a.
|
|
1737
|
+
if (!key || !((_a = this.window) == null ? void 0 : _a.localStorage))
|
|
1445
1738
|
return;
|
|
1446
1739
|
try {
|
|
1447
|
-
this.window.
|
|
1740
|
+
this.window.localStorage.removeItem(key);
|
|
1448
1741
|
} catch (error) {
|
|
1449
1742
|
console.warn("[ScanOrder] clearUIState failed", error);
|
|
1450
1743
|
}
|
|
@@ -1499,11 +1792,19 @@ var _ScanOrderImpl = class extends import_BaseModule.BaseModule {
|
|
|
1499
1792
|
}
|
|
1500
1793
|
};
|
|
1501
1794
|
var ScanOrderImpl = _ScanOrderImpl;
|
|
1502
|
-
|
|
1795
|
+
ScanOrderImpl.PISELL1_LOGIN_SUCCESS = "pisell1.login.success";
|
|
1796
|
+
// ─── UI 状态缓存(按 cacheId 分桶,localStorage) ───
|
|
1503
1797
|
//
|
|
1504
1798
|
// 用于物料层持久化 UI 层面的轻量状态(如当前步骤、gate 标记、登录回跳意图等)。
|
|
1505
1799
|
// 桶键:pisell.scanOrder.uiState:<cacheId>;内部以 JSON object 形式存储多个字段。
|
|
1506
1800
|
// 无 cacheId 时所有方法自动降级为 no-op,上层不用判空。
|
|
1801
|
+
//
|
|
1802
|
+
// 之所以用 localStorage 而不是 sessionStorage:
|
|
1803
|
+
// 1. 与 Order 模块 openCache 下的 tempOrder 同栈,购物车在 / UIState 在,行为对齐;
|
|
1804
|
+
// 2. pisell1.login 整页 OAuth 跳转在 H5 壳 / iOS Safari 场景下可能丢 sessionStorage,
|
|
1805
|
+
// 导致登录回跳后 gate 标记失效、pax 弹窗重开、落点错误;localStorage 稳定不受影响。
|
|
1806
|
+
// 3. 失效由上层显式控制:提交成功 clearStepCache / entryContext 不一致 clearUIState /
|
|
1807
|
+
// pendingStep 登录回调 deleteUIState,足够约束生命周期。
|
|
1507
1808
|
ScanOrderImpl.UI_STATE_KEY_PREFIX = "pisell.scanOrder.uiState:";
|
|
1508
1809
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1509
1810
|
0 && (module.exports = {
|
|
@@ -31,26 +31,70 @@ export interface ScanOrderOrderProductIdentity {
|
|
|
31
31
|
product_id: number | null;
|
|
32
32
|
product_variant_id: number;
|
|
33
33
|
identity_key?: string;
|
|
34
|
+
/** 参与合并/匹配;与 remove 通配语义见 isSkuOnlyDeleteIdentity */
|
|
35
|
+
product_option_item?: any[];
|
|
36
|
+
product_bundle?: any[];
|
|
34
37
|
}
|
|
35
38
|
export interface ScanOrderOrderProduct extends ScanOrderOrderProductIdentity {
|
|
36
39
|
order_detail_id: number | null;
|
|
37
40
|
num: number;
|
|
38
41
|
product_option_item: any[];
|
|
39
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* 券后 **行 composite 单价** = `metadata.main_product_selling_price`
|
|
44
|
+
* + Σ((bundle.bundle_selling_price ?? bundle.price) × (bundle.num ?? 1))
|
|
45
|
+
*
|
|
46
|
+
* 新语义 v2 下 `metadata.main_product_selling_price` 已经**含 option**、含主商品折扣,
|
|
47
|
+
* 因此本字段不再叠加 option。未应用券时等同 `original_price`。
|
|
48
|
+
* Rules 钩子、主商品计税、Summary 统一读 metadata,不以此字段反推。
|
|
49
|
+
*/
|
|
40
50
|
selling_price: string;
|
|
41
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* 券前 **行 composite 单价** = `metadata.main_product_original_price`
|
|
53
|
+
* + Σ(bundle 原价 × num)
|
|
54
|
+
*
|
|
55
|
+
* 新语义 v2 下 `metadata.main_product_original_price` 已经**含 option**、不含折扣。
|
|
56
|
+
* 不承载后台划线价语义。
|
|
57
|
+
*/
|
|
42
58
|
original_price: string;
|
|
43
59
|
tax_fee: string;
|
|
44
60
|
is_charge_tax: number;
|
|
45
61
|
discount_list: any[];
|
|
46
62
|
product_bundle: any[];
|
|
63
|
+
/**
|
|
64
|
+
* 行级扩展 metadata。价格相关的权威字段:
|
|
65
|
+
*
|
|
66
|
+
* - `source_product_price`:主商品/variant 基础价(已应用报价单),**不含 option、不含折扣**。
|
|
67
|
+
* 是派生 `main_product_*` 的唯一源。variant 分支优先读 `metadata.origin.variant[vid].price`。
|
|
68
|
+
* - `main_product_original_price` = `source_product_price + Σ(option.price × option.num)`,
|
|
69
|
+
* **含 option、不含折扣**。
|
|
70
|
+
* - `main_product_selling_price` = `main_product_original_price − 主商品券 per-unit amount`,
|
|
71
|
+
* **含 option、含主商品折扣**。Rules 钩子 / Summary / 计税均以此为主商品权威源。
|
|
72
|
+
* - `price_schema_version`(当前 = 2):schema 版本 sentinel,用于跨端协商价格口径、
|
|
73
|
+
* 区分 v1 旧缓存以便 `normalizeOrderProduct` 触发迁移。
|
|
74
|
+
*/
|
|
47
75
|
metadata: Record<string, any>;
|
|
76
|
+
/** 商品行备注(如顾客对单品的特殊要求) */
|
|
77
|
+
note?: string;
|
|
48
78
|
_origin?: Record<string, any>;
|
|
49
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* 出站 payload 版本的商品行。
|
|
82
|
+
*
|
|
83
|
+
* 与 tempOrder 内部 `ScanOrderOrderProduct` 的字段差异:
|
|
84
|
+
* - `product_option_item[*]`:内部为 `{ product_option_item_id, option_group_id, num, price, ... }`,
|
|
85
|
+
* 出站由 `normalizeSubmitProduct` 重命名为 `{ option_group_item_id, option_group_id, num }`
|
|
86
|
+
* (后端 checkout 协议;丢弃 `price` 等运行时辅助字段)。
|
|
87
|
+
* - `product_bundle[*].option[*]`:同上重命名规则。
|
|
88
|
+
*
|
|
89
|
+
* 运行时(UI 显示、加购合并、指纹、持久化)全部消费内部字段名 `product_option_item_id`,
|
|
90
|
+
* 仅在 SDK 提交边界做一次出站映射,不影响 opaque identity 契约。
|
|
91
|
+
*/
|
|
50
92
|
export interface ScanOrderSubmitProduct extends Omit<ScanOrderOrderProduct, '_origin' | 'identity_key'> {
|
|
51
93
|
/**
|
|
52
94
|
* 出站兼容字段:SDK 内部不再消费 payment_price,
|
|
53
95
|
* 仅在提交后端时由 selling_price 派生,保持原有后端契约。
|
|
96
|
+
* 新语义 v2 下 selling_price 是 composite(含 option、含 bundle、含主商品折扣),
|
|
97
|
+
* 因此 payment_price 同样是 composite。
|
|
54
98
|
*/
|
|
55
99
|
payment_price: string;
|
|
56
100
|
}
|
|
@@ -235,3 +279,9 @@ export interface ScanOrderLoggerRuntimeConfig {
|
|
|
235
279
|
}
|
|
236
280
|
export interface ScanOrderAddLogParams extends ScanOrderLogInput {
|
|
237
281
|
}
|
|
282
|
+
/** ScanOrder.scanCode 对外的轻量结果(不含 discountList,UI 在 resolve 后调 getDiscountList) */
|
|
283
|
+
export interface ScanOrderScanCodeResult {
|
|
284
|
+
isAvailable: boolean;
|
|
285
|
+
type?: 'server' | string;
|
|
286
|
+
unavailableReason?: 'time_limit' | string;
|
|
287
|
+
}
|
|
@@ -82,9 +82,24 @@ export declare function buildItemRuleBusinessData(params: {
|
|
|
82
82
|
}): ItemRuleBusinessData;
|
|
83
83
|
export declare function attachItemRuleLimitsToTopLevelProducts<T>(productList: T, limits: QuantityLimitResult[]): T;
|
|
84
84
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
|
|
85
|
+
* 将选项行 / 套餐行归一成稳定 JSON,用于「同 SKU 是否合并」判断。
|
|
86
|
+
* 数组元素先按主键排序再序列化,避免顺序差异导致误判为不同行。
|
|
87
|
+
*/
|
|
88
|
+
export declare function buildProductLineFingerprint(productOptionItem?: unknown, productBundle?: unknown): string;
|
|
89
|
+
/**
|
|
90
|
+
* removeProductFromOrder 仅传 SKU 时的通配 identity(对象上未声明选项键)。
|
|
91
|
+
* 与显式 `product_option_item: []` 区分:后者只匹配「无选项」行。
|
|
92
|
+
*/
|
|
93
|
+
export declare function isSkuOnlyDeleteIdentity(x: ScanOrderOrderProductIdentity): boolean;
|
|
94
|
+
/**
|
|
95
|
+
* 判断两个商品 identity 是否匹配(不透明 identity 契约)。
|
|
96
|
+
*
|
|
97
|
+
* 调用约定:始终 `isIdentityMatch(line, callerIdentity)`。
|
|
98
|
+
* - 若 SKU(product_id + product_variant_id)不一致 → 不匹配。
|
|
99
|
+
* - **双方都带** `identity_key` → 严格字符串相等比较(不再猜测合成 key / metadata 桥接)。
|
|
100
|
+
* 这是 opaque identity 契约的标准路径:UI 删改时透传 SDK 回灌的 identity_key。
|
|
101
|
+
* - 否则(即至少一侧未声明 identity_key)→ 进入「SKU 通配 / 显式空选项 / 指纹」回退路径,
|
|
102
|
+
* 忽略线侧 identity_key,按内容语义匹配。这给「行已 opaque、调用方却想按 SKU 通配 / 选项指纹删除」留兼容入口。
|
|
88
103
|
*/
|
|
89
104
|
export declare function isIdentityMatch(a: ScanOrderOrderProductIdentity, b: ScanOrderOrderProductIdentity): boolean;
|
|
90
105
|
/**
|
|
@@ -93,10 +108,26 @@ export declare function isIdentityMatch(a: ScanOrderOrderProductIdentity, b: Sca
|
|
|
93
108
|
*/
|
|
94
109
|
export declare function getProductIdentityIndex(products: ScanOrderOrderProduct[], identity: ScanOrderOrderProductIdentity): number;
|
|
95
110
|
/**
|
|
96
|
-
*
|
|
111
|
+
* 对外部传入的商品对象做归一化(v2 composite 语义):
|
|
97
112
|
* - 补全可选字段默认值(未传则使用兜底值,避免后续计算时因 undefined 导致异常)
|
|
98
113
|
* - 对 num 调用 getSafeProductNum 做安全处理
|
|
99
114
|
* - 保留 _origin 供后续业务流程(如促销规则)使用
|
|
115
|
+
*
|
|
116
|
+
* 价格字段语义(metadata 权威源 + composite 派生):
|
|
117
|
+
* - `metadata.source_product_price`:主商品/variant 基础价(已应用报价单),**不含 option**、
|
|
118
|
+
* **不含折扣**。是推导 main_product_* 的起点。variant 分支优先读 `metadata.origin.variant[vid].price`。
|
|
119
|
+
* - `metadata.main_product_original_price`:`source + Σ(option.price × option.num)`,**含 option**、
|
|
120
|
+
* **不含折扣**。
|
|
121
|
+
* - `metadata.main_product_selling_price`:`main_product_original_price - 主商品券 per-unit amount`,
|
|
122
|
+
* **含 option**、**含折扣**。
|
|
123
|
+
* - 行级 `selling_price` = `main_product_selling_price + Σ(bundle_selling_price × num)`。
|
|
124
|
+
* - 行级 `original_price` = `main_product_original_price + Σ(bundle 原价 × num)`。
|
|
125
|
+
*
|
|
126
|
+
* 迁移与幂等:
|
|
127
|
+
* - `metadata.price_schema_version === 2` → 已新语义归一化,保留 main_product_* 原值(保留折扣)。
|
|
128
|
+
* - 其它情况(v1 / 缺字段 / 无 metadata)→ 按"main_product_selling_price 曾是 main-only"的旧约定
|
|
129
|
+
* 反推 legacyDiscount,再以新 source + options 基准重算。最终统一打上 `price_schema_version: 2`。
|
|
130
|
+
* - 因此多次 normalize 不会重复叠加 option/bundle。
|
|
100
131
|
*/
|
|
101
132
|
export declare function normalizeOrderProduct(product: Partial<ScanOrderOrderProduct> & ScanOrderOrderProductIdentity): ScanOrderOrderProduct;
|
|
102
133
|
/**
|
|
@@ -112,7 +143,7 @@ export declare function hasCustomCapacityProduct(products: ProductData[]): boole
|
|
|
112
143
|
/**
|
|
113
144
|
* 根据预约规则商品的 resource.type 计算桌台是否已被"占满"。
|
|
114
145
|
* - single:只要有 `lastOrderId` 即视为占用
|
|
115
|
-
* - multiple:当前时间落在 `capacity_list[i]` 的 `[start_at, end_at]`(inclusive)内的 pax 之和
|
|
146
|
+
* - multiple:当前时间落在 `capacity_list[i]` 的 `[start_at, end_at]`(inclusive)内的 pax 之和 >= 总容量
|
|
116
147
|
* - 其他('capacity' / undefined):返回 false,不施加限制
|
|
117
148
|
*/
|
|
118
149
|
export declare function computeResourceIsFull(params: {
|
|
@@ -129,3 +160,8 @@ export declare function pickFirstCustomCapacityPaxBounds(products: ProductData[]
|
|
|
129
160
|
min?: number;
|
|
130
161
|
max?: number;
|
|
131
162
|
} | undefined;
|
|
163
|
+
/**
|
|
164
|
+
* 第一个 `capacity.type === 'custom'` 的商品,取其 `custom[0].id`(提交 booking metadata.capacity 维度 id)。
|
|
165
|
+
* 无匹配时返回 `undefined`,调用方应回退为 `0`。
|
|
166
|
+
*/
|
|
167
|
+
export declare function pickFirstCustomCapacityDimensionId(products: ProductData[]): string | number | undefined;
|