@pisell/pisellos 0.0.510 → 0.0.512
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/Order/index.d.ts +3 -6
- package/dist/modules/Order/index.js +129 -62
- package/dist/modules/Order/types.d.ts +23 -5
- package/dist/modules/Order/types.js +2 -0
- package/dist/modules/Order/utils.d.ts +64 -11
- package/dist/modules/Order/utils.js +269 -43
- package/dist/modules/SalesSummary/utils.js +33 -68
- package/dist/modules/Summary/utils.js +6 -21
- package/dist/solution/ScanOrder/index.d.ts +10 -5
- package/dist/solution/ScanOrder/index.js +605 -492
- package/dist/solution/ScanOrder/types.d.ts +43 -2
- package/dist/solution/ScanOrder/types.js +13 -0
- package/dist/solution/ScanOrder/utils.d.ts +26 -12
- package/dist/solution/ScanOrder/utils.js +130 -82
- 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 +98 -24
- package/lib/modules/Order/types.d.ts +23 -5
- package/lib/modules/Order/utils.d.ts +64 -11
- package/lib/modules/Order/utils.js +167 -15
- package/lib/modules/SalesSummary/utils.js +13 -47
- package/lib/modules/Summary/utils.js +4 -18
- package/lib/solution/ScanOrder/index.d.ts +10 -5
- package/lib/solution/ScanOrder/index.js +60 -4
- package/lib/solution/ScanOrder/types.d.ts +43 -2
- package/lib/solution/ScanOrder/utils.d.ts +26 -12
- package/lib/solution/ScanOrder/utils.js +76 -68
- package/lib/solution/VenueBooking/index.d.ts +2 -5
- package/lib/solution/VenueBooking/index.js +13 -6
- package/package.json +1 -1
|
@@ -48,30 +48,6 @@ function getSafeNum(num) {
|
|
|
48
48
|
return 1;
|
|
49
49
|
return Math.floor(num);
|
|
50
50
|
}
|
|
51
|
-
function getVariantPrice(product) {
|
|
52
|
-
var _a, _b;
|
|
53
|
-
const variantId = Number(product.product_variant_id || 0);
|
|
54
|
-
if (!variantId)
|
|
55
|
-
return null;
|
|
56
|
-
const metadataVariantList = (_b = (_a = product.metadata) == null ? void 0 : _a.origin) == null ? void 0 : _b.variant;
|
|
57
|
-
if (Array.isArray(metadataVariantList)) {
|
|
58
|
-
const variant = metadataVariantList.find(
|
|
59
|
-
(item) => Number(item.id) === variantId
|
|
60
|
-
);
|
|
61
|
-
if ((variant == null ? void 0 : variant.price) !== void 0 && (variant == null ? void 0 : variant.price) !== null) {
|
|
62
|
-
return toDecimal(variant.price);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
function getOptionUnitPrice(product) {
|
|
68
|
-
const optionItems = product.product_option_item || [];
|
|
69
|
-
return optionItems.reduce((sum, item) => {
|
|
70
|
-
const quantity = getSafeNum(item == null ? void 0 : item.num);
|
|
71
|
-
const itemPrice = toDecimal(item == null ? void 0 : item.price);
|
|
72
|
-
return sum.plus(itemPrice.times(quantity));
|
|
73
|
-
}, new import_decimal.default(0));
|
|
74
|
-
}
|
|
75
51
|
function getBundleUnitPrice(product, useOriginal = false) {
|
|
76
52
|
const bundleItems = product.product_bundle || [];
|
|
77
53
|
return bundleItems.reduce((sum, item) => {
|
|
@@ -81,13 +57,18 @@ function getBundleUnitPrice(product, useOriginal = false) {
|
|
|
81
57
|
}, new import_decimal.default(0));
|
|
82
58
|
}
|
|
83
59
|
function getUnitPaymentTotal(product) {
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
60
|
+
var _a, _b;
|
|
61
|
+
const mainSelling = (_a = product.metadata) == null ? void 0 : _a.main_product_selling_price;
|
|
62
|
+
const mainOriginal = (_b = product.metadata) == null ? void 0 : _b.main_product_original_price;
|
|
63
|
+
const basePrice = mainSelling !== void 0 ? toDecimal(mainSelling) : mainOriginal !== void 0 ? toDecimal(mainOriginal) : toDecimal(product.selling_price);
|
|
64
|
+
return basePrice.plus(getBundleUnitPrice(product));
|
|
87
65
|
}
|
|
88
66
|
function getUnitOriginalTotal(product) {
|
|
89
|
-
|
|
90
|
-
|
|
67
|
+
var _a, _b;
|
|
68
|
+
const mainOriginal = (_a = product.metadata) == null ? void 0 : _a.main_product_original_price;
|
|
69
|
+
const mainSelling = (_b = product.metadata) == null ? void 0 : _b.main_product_selling_price;
|
|
70
|
+
const basePrice = mainOriginal !== void 0 ? toDecimal(mainOriginal) : mainSelling !== void 0 ? toDecimal(mainSelling) : toDecimal(product.original_price || product.selling_price);
|
|
71
|
+
return basePrice.plus(getBundleUnitPrice(product, true));
|
|
91
72
|
}
|
|
92
73
|
function buildSurchargeServiceItems(products) {
|
|
93
74
|
return products.map((product) => {
|
|
@@ -207,12 +188,6 @@ function isBundleMarkupOrDiscount(item) {
|
|
|
207
188
|
const isDiscount = (item == null ? void 0 : item.price_type) === "markdown" && ((item == null ? void 0 : item.price_type_ext) === "" || !(item == null ? void 0 : item.price_type_ext));
|
|
208
189
|
return isMarkup || isDiscount;
|
|
209
190
|
}
|
|
210
|
-
function getDiscountListAmount(discountList) {
|
|
211
|
-
return (discountList || []).reduce(
|
|
212
|
-
(total, d) => total.plus(toDecimal(d.amount)),
|
|
213
|
-
new import_decimal.default(0)
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
191
|
function calculateSingleItemTax(params) {
|
|
217
192
|
const { price, taxRate, isPriceIncludeTax, isChargeTax } = params;
|
|
218
193
|
if (price.lte(0))
|
|
@@ -229,18 +204,9 @@ function calculateSingleItemTax(params) {
|
|
|
229
204
|
return price.dividedBy(divisor).times(rate);
|
|
230
205
|
}
|
|
231
206
|
function getMainProductPaymentTotal(product) {
|
|
232
|
-
var _a, _b
|
|
233
|
-
const
|
|
234
|
-
let total =
|
|
235
|
-
const mainDiscountList = ((_c = (_b = product._origin) == null ? void 0 : _b.product) == null ? void 0 : _c.discount_list) || ((_d = product._origin) == null ? void 0 : _d.discount_list) || [];
|
|
236
|
-
const mainDiscountAmount = getDiscountListAmount(
|
|
237
|
-
mainDiscountList.filter((d) => {
|
|
238
|
-
var _a2;
|
|
239
|
-
return !((_a2 = d == null ? void 0 : d.metadata) == null ? void 0 : _a2.custom_product_bundle_map_id);
|
|
240
|
-
})
|
|
241
|
-
);
|
|
242
|
-
total = total.minus(mainDiscountAmount);
|
|
243
|
-
total = total.plus(getOptionUnitPrice(product));
|
|
207
|
+
var _a, _b;
|
|
208
|
+
const mainSelling = ((_a = product.metadata) == null ? void 0 : _a.main_product_selling_price) ?? ((_b = product.metadata) == null ? void 0 : _b.main_product_original_price) ?? 0;
|
|
209
|
+
let total = toDecimal(mainSelling);
|
|
244
210
|
const bundleItems = product.product_bundle || [];
|
|
245
211
|
for (const bundleItem of bundleItems) {
|
|
246
212
|
if (isBundleMarkupOrDiscount(bundleItem)) {
|
|
@@ -437,25 +437,11 @@ var getBundleItemIsDiscountPrice = (item) => {
|
|
|
437
437
|
var getBundleItemIsMarkupOrDiscountPrice = (item) => {
|
|
438
438
|
return getBundleItemIsMarkupPrice(item) || getBundleItemIsDiscountPrice(item);
|
|
439
439
|
};
|
|
440
|
-
var getDiscountAmount = (discounts) => {
|
|
441
|
-
return (discounts || []).reduce((total, discount) => {
|
|
442
|
-
return total.add(new import_decimal.default(discount.amount || 0));
|
|
443
|
-
}, new import_decimal.default(0)).toNumber();
|
|
444
|
-
};
|
|
445
440
|
var getMainProductTotal = (item) => {
|
|
446
|
-
var _a, _b
|
|
447
|
-
let total = new import_decimal.default(
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
return !((_a2 = item2 == null ? void 0 : item2.metadata) == null ? void 0 : _a2.custom_product_bundle_map_id);
|
|
451
|
-
})) || [];
|
|
452
|
-
const mainProductDiscountAmount = getDiscountAmount(discount);
|
|
453
|
-
total = total.minus(mainProductDiscountAmount);
|
|
454
|
-
if ((item == null ? void 0 : item.option) && Array.isArray(item == null ? void 0 : item.option)) {
|
|
455
|
-
total = total.add(item == null ? void 0 : item.option.reduce((t, option) => {
|
|
456
|
-
return t.add(new import_decimal.default(option.price || 0).mul(option.num || 1));
|
|
457
|
-
}, new import_decimal.default(0)));
|
|
458
|
-
}
|
|
441
|
+
var _a, _b;
|
|
442
|
+
let total = new import_decimal.default(
|
|
443
|
+
(item == null ? void 0 : item.main_product_selling_price) ?? ((_a = item == null ? void 0 : item.metadata) == null ? void 0 : _a.main_product_selling_price) ?? ((_b = item == null ? void 0 : item.metadata) == null ? void 0 : _b.main_product_original_price) ?? 0
|
|
444
|
+
);
|
|
459
445
|
for (let bundleItem of (item == null ? void 0 : item.bundle) || []) {
|
|
460
446
|
if (getBundleItemIsMarkupOrDiscountPrice(bundleItem)) {
|
|
461
447
|
const bundleItemTotal = new import_decimal.default(bundleItem.bundle_selling_price ?? bundleItem.price ?? 0);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Module, ModuleOptions, PisellCore } from '../../types';
|
|
2
2
|
import { BaseModule } from '../../modules/BaseModule';
|
|
3
3
|
import { ScanOrderAddLogParams, ScanOrderAvailabilityInfo, ScanOrderOrderProduct, ScanOrderOrderProductIdentity, ScanOrderScanCodeResult } from './types';
|
|
4
|
+
import type { UpdateProductInOrderParams } from '../../modules/Order/types';
|
|
4
5
|
import type { Discount } from '../../modules/Discount/types';
|
|
5
6
|
import { type CartItemSummary, type PaxInfo, type QuantityCheckResult, type QuantityLimitResult } from '../../model/strategy/adapter/itemRule';
|
|
6
7
|
import type { StrategyConfig } from '../../model/strategy/type';
|
|
@@ -48,6 +49,9 @@ export declare class ScanOrderImpl extends BaseModule implements Module {
|
|
|
48
49
|
private registerCustomerLoginListeners;
|
|
49
50
|
private refreshOrderMarketingAfterLogin;
|
|
50
51
|
constructor(name?: string, version?: string);
|
|
52
|
+
/** 与 `otherParams.cacheId` 一致,供宿主在 URL 变化时判断是否需要重新注册模块 */
|
|
53
|
+
getCacheId(): string | undefined;
|
|
54
|
+
private destroyRegisteredChildModules;
|
|
51
55
|
initialize(core: PisellCore, options?: ModuleOptions): Promise<void>;
|
|
52
56
|
destroy(): Promise<void>;
|
|
53
57
|
retryInit(): Promise<void>;
|
|
@@ -85,11 +89,12 @@ export declare class ScanOrderImpl extends BaseModule implements Module {
|
|
|
85
89
|
private buildSubmitPayloadEnhancer;
|
|
86
90
|
submitScanOrder<T = any>(): Promise<T>;
|
|
87
91
|
addProductToOrder(product: Partial<ScanOrderOrderProduct> & ScanOrderOrderProductIdentity): Promise<ScanOrderOrderProduct[]>;
|
|
88
|
-
updateProductInOrder(params:
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
updateProductInOrder(params: UpdateProductInOrderParams): Promise<ScanOrderOrderProduct[]>;
|
|
93
|
+
/**
|
|
94
|
+
* 设置单行商品备注(与整单 `updateTempOrderNote` 区分)。
|
|
95
|
+
* 多行同 SKU 时必须传入与删除/更新一致的 identity(如 `identity_key`)。
|
|
96
|
+
*/
|
|
97
|
+
setOrderProductLineNote(identity: ScanOrderOrderProductIdentity, note: string): Promise<ScanOrderOrderProduct[]>;
|
|
93
98
|
removeProductFromOrder(identity: ScanOrderOrderProductIdentity): Promise<ScanOrderOrderProduct[]>;
|
|
94
99
|
private loadRuntimeConfigs;
|
|
95
100
|
private syncItemRuleConfigsFromDineInConfig;
|
|
@@ -260,6 +260,23 @@ var _ScanOrderImpl = class extends import_BaseModule.BaseModule {
|
|
|
260
260
|
}
|
|
261
261
|
}
|
|
262
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
|
+
}
|
|
263
280
|
async initialize(core, options = {}) {
|
|
264
281
|
var _a, _b, _c, _d, _e, _f;
|
|
265
282
|
this.logMethodStart("initialize");
|
|
@@ -375,6 +392,8 @@ var _ScanOrderImpl = class extends import_BaseModule.BaseModule {
|
|
|
375
392
|
this.logMethodStart("destroy");
|
|
376
393
|
this.clearLoginEffectListeners();
|
|
377
394
|
await this.core.effects.emit(import_types.ScanOrderHooks.onDestroy, {});
|
|
395
|
+
await this.destroyRegisteredChildModules();
|
|
396
|
+
super.destroy();
|
|
378
397
|
console.log("[ScanOrder] 已销毁");
|
|
379
398
|
this.logMethodSuccess("destroy");
|
|
380
399
|
}
|
|
@@ -626,7 +645,7 @@ var _ScanOrderImpl = class extends import_BaseModule.BaseModule {
|
|
|
626
645
|
}
|
|
627
646
|
}
|
|
628
647
|
async setDiscountSelected(params) {
|
|
629
|
-
var _a, _b, _c;
|
|
648
|
+
var _a, _b, _c, _d, _e;
|
|
630
649
|
this.logMethodStart("setDiscountSelected", params);
|
|
631
650
|
try {
|
|
632
651
|
if (!this.store.order)
|
|
@@ -691,11 +710,18 @@ var _ScanOrderImpl = class extends import_BaseModule.BaseModule {
|
|
|
691
710
|
(sum, pd) => sum + (pd.amount || 0),
|
|
692
711
|
0
|
|
693
712
|
);
|
|
694
|
-
const
|
|
695
|
-
product.
|
|
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);
|
|
696
717
|
if (product.metadata) {
|
|
697
|
-
product.metadata.main_product_selling_price =
|
|
718
|
+
product.metadata.main_product_selling_price = newMainSellingPrice;
|
|
719
|
+
product.metadata.price_schema_version = 2;
|
|
698
720
|
}
|
|
721
|
+
product.selling_price = (0, import_utils2.composeLinePrice)({
|
|
722
|
+
mainPrice: newMainSellingPrice,
|
|
723
|
+
bundle: product.product_bundle
|
|
724
|
+
});
|
|
699
725
|
}
|
|
700
726
|
import_Order.OrderModule.populateSavedAmounts(tempOrder.products, nextDiscountList);
|
|
701
727
|
await (discountModule == null ? void 0 : discountModule.setDiscountList(nextDiscountList));
|
|
@@ -892,6 +918,36 @@ var _ScanOrderImpl = class extends import_BaseModule.BaseModule {
|
|
|
892
918
|
throw error;
|
|
893
919
|
}
|
|
894
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
|
+
}
|
|
895
951
|
async removeProductFromOrder(identity) {
|
|
896
952
|
this.logMethodStart("removeProductFromOrder", {
|
|
897
953
|
product_id: identity.product_id,
|
|
@@ -39,21 +39,62 @@ export interface ScanOrderOrderProduct extends ScanOrderOrderProductIdentity {
|
|
|
39
39
|
order_detail_id: number | null;
|
|
40
40
|
num: number;
|
|
41
41
|
product_option_item: any[];
|
|
42
|
-
/**
|
|
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
|
+
*/
|
|
43
50
|
selling_price: string;
|
|
44
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* 券前 **行 composite 单价** = `metadata.main_product_original_price`
|
|
53
|
+
* + Σ(bundle 原价 × num)
|
|
54
|
+
*
|
|
55
|
+
* 新语义 v2 下 `metadata.main_product_original_price` 已经**含 option**、不含折扣。
|
|
56
|
+
* 不承载后台划线价语义。
|
|
57
|
+
*/
|
|
45
58
|
original_price: string;
|
|
46
59
|
tax_fee: string;
|
|
47
60
|
is_charge_tax: number;
|
|
48
61
|
discount_list: any[];
|
|
49
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
|
+
*/
|
|
50
75
|
metadata: Record<string, any>;
|
|
76
|
+
/** 商品行备注(如顾客对单品的特殊要求) */
|
|
77
|
+
note?: string;
|
|
51
78
|
_origin?: Record<string, any>;
|
|
52
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
|
+
*/
|
|
53
92
|
export interface ScanOrderSubmitProduct extends Omit<ScanOrderOrderProduct, '_origin' | 'identity_key'> {
|
|
54
93
|
/**
|
|
55
94
|
* 出站兼容字段:SDK 内部不再消费 payment_price,
|
|
56
95
|
* 仅在提交后端时由 selling_price 派生,保持原有后端契约。
|
|
96
|
+
* 新语义 v2 下 selling_price 是 composite(含 option、含 bundle、含主商品折扣),
|
|
97
|
+
* 因此 payment_price 同样是 composite。
|
|
57
98
|
*/
|
|
58
99
|
payment_price: string;
|
|
59
100
|
}
|
|
@@ -92,16 +92,14 @@ export declare function buildProductLineFingerprint(productOptionItem?: unknown,
|
|
|
92
92
|
*/
|
|
93
93
|
export declare function isSkuOnlyDeleteIdentity(x: ScanOrderOrderProductIdentity): boolean;
|
|
94
94
|
/**
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* - 否则若任一方为「仅 SKU」删除通配(未声明 product_option_item / product_bundle 键),只比 SKU。
|
|
104
|
-
* - 否则比较选项 + 套餐指纹(同 SKU 且指纹相同才合并数量)。
|
|
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 通配 / 选项指纹删除」留兼容入口。
|
|
105
103
|
*/
|
|
106
104
|
export declare function isIdentityMatch(a: ScanOrderOrderProductIdentity, b: ScanOrderOrderProductIdentity): boolean;
|
|
107
105
|
/**
|
|
@@ -110,10 +108,26 @@ export declare function isIdentityMatch(a: ScanOrderOrderProductIdentity, b: Sca
|
|
|
110
108
|
*/
|
|
111
109
|
export declare function getProductIdentityIndex(products: ScanOrderOrderProduct[], identity: ScanOrderOrderProductIdentity): number;
|
|
112
110
|
/**
|
|
113
|
-
*
|
|
111
|
+
* 对外部传入的商品对象做归一化(v2 composite 语义):
|
|
114
112
|
* - 补全可选字段默认值(未传则使用兜底值,避免后续计算时因 undefined 导致异常)
|
|
115
113
|
* - 对 num 调用 getSafeProductNum 做安全处理
|
|
116
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。
|
|
117
131
|
*/
|
|
118
132
|
export declare function normalizeOrderProduct(product: Partial<ScanOrderOrderProduct> & ScanOrderOrderProductIdentity): ScanOrderOrderProduct;
|
|
119
133
|
/**
|
|
@@ -129,7 +143,7 @@ export declare function hasCustomCapacityProduct(products: ProductData[]): boole
|
|
|
129
143
|
/**
|
|
130
144
|
* 根据预约规则商品的 resource.type 计算桌台是否已被"占满"。
|
|
131
145
|
* - single:只要有 `lastOrderId` 即视为占用
|
|
132
|
-
* - multiple:当前时间落在 `capacity_list[i]` 的 `[start_at, end_at]`(inclusive)内的 pax 之和
|
|
146
|
+
* - multiple:当前时间落在 `capacity_list[i]` 的 `[start_at, end_at]`(inclusive)内的 pax 之和 >= 总容量
|
|
133
147
|
* - 其他('capacity' / undefined):返回 false,不施加限制
|
|
134
148
|
*/
|
|
135
149
|
export declare function computeResourceIsFull(params: {
|
|
@@ -35,7 +35,6 @@ __export(utils_exports, {
|
|
|
35
35
|
buildProductKey: () => buildProductKey,
|
|
36
36
|
buildProductLineFingerprint: () => buildProductLineFingerprint,
|
|
37
37
|
buildQuantityLimitIndex: () => buildQuantityLimitIndex,
|
|
38
|
-
buildSyntheticSingleOptionIdentityKey: () => buildSyntheticSingleOptionIdentityKey,
|
|
39
38
|
collectLinkProductIdsFromReservationRules: () => collectLinkProductIdsFromReservationRules,
|
|
40
39
|
computeResourceIsFull: () => computeResourceIsFull,
|
|
41
40
|
createEmptySummary: () => createEmptySummary,
|
|
@@ -62,6 +61,8 @@ __export(utils_exports, {
|
|
|
62
61
|
});
|
|
63
62
|
module.exports = __toCommonJS(utils_exports);
|
|
64
63
|
var import_dayjs = __toESM(require("dayjs"));
|
|
64
|
+
var import_decimal = __toESM(require("decimal.js"));
|
|
65
|
+
var import_utils = require("../../modules/Order/utils");
|
|
65
66
|
function createEmptySummary() {
|
|
66
67
|
return {
|
|
67
68
|
product_quantity: 0,
|
|
@@ -378,43 +379,6 @@ function isSkuOnlyDeleteIdentity(x) {
|
|
|
378
379
|
return false;
|
|
379
380
|
return !("product_option_item" in x) && !("product_bundle" in x);
|
|
380
381
|
}
|
|
381
|
-
function buildSyntheticSingleOptionIdentityKey(productId, productOptionItem) {
|
|
382
|
-
if (productId == null || !Number.isFinite(Number(productId)))
|
|
383
|
-
return void 0;
|
|
384
|
-
const opts = Array.isArray(productOptionItem) ? productOptionItem : [];
|
|
385
|
-
if (opts.length !== 1)
|
|
386
|
-
return void 0;
|
|
387
|
-
const o = opts[0];
|
|
388
|
-
const gid = Number(o == null ? void 0 : o.option_group_id) || 0;
|
|
389
|
-
const oid = Number(o == null ? void 0 : o.product_option_item_id) || 0;
|
|
390
|
-
const rawNum = o == null ? void 0 : o.num;
|
|
391
|
-
const n = typeof rawNum === "number" && !Number.isNaN(rawNum) ? Math.max(1, Math.floor(rawNum)) : 1;
|
|
392
|
-
return `${productId}_${gid}_${oid}_${n}`;
|
|
393
|
-
}
|
|
394
|
-
function collectLineIdentityKeyCandidates(x) {
|
|
395
|
-
var _a;
|
|
396
|
-
const out = /* @__PURE__ */ new Set();
|
|
397
|
-
const row = x;
|
|
398
|
-
if (typeof row.identity_key === "string" && row.identity_key.length > 0) {
|
|
399
|
-
out.add(row.identity_key);
|
|
400
|
-
}
|
|
401
|
-
const uid = (_a = row.metadata) == null ? void 0 : _a.unique_identification_number;
|
|
402
|
-
if (typeof uid === "string" && uid.length > 0)
|
|
403
|
-
out.add(uid);
|
|
404
|
-
const synthetic = buildSyntheticSingleOptionIdentityKey(
|
|
405
|
-
row.product_id,
|
|
406
|
-
row.product_option_item
|
|
407
|
-
);
|
|
408
|
-
if (synthetic)
|
|
409
|
-
out.add(synthetic);
|
|
410
|
-
const opts = "product_option_item" in row && Array.isArray(row.product_option_item) ? row.product_option_item : [];
|
|
411
|
-
const bundles = "product_bundle" in row && Array.isArray(row.product_bundle) ? row.product_bundle : [];
|
|
412
|
-
if (opts.length === 0 && bundles.length === 0 && row.product_id != null && Number.isFinite(Number(row.product_id))) {
|
|
413
|
-
const vid = Number(row.product_variant_id) || 0;
|
|
414
|
-
out.add(`${row.product_id}_${vid}_0`);
|
|
415
|
-
}
|
|
416
|
-
return out;
|
|
417
|
-
}
|
|
418
382
|
function fingerprintForIdentityWithOptionKeys(x) {
|
|
419
383
|
const row = x;
|
|
420
384
|
const opts = "product_option_item" in row ? Array.isArray(row.product_option_item) ? row.product_option_item : [] : [];
|
|
@@ -424,17 +388,10 @@ function fingerprintForIdentityWithOptionKeys(x) {
|
|
|
424
388
|
function isIdentityMatch(a, b) {
|
|
425
389
|
if (a.product_id !== b.product_id || a.product_variant_id !== b.product_variant_id)
|
|
426
390
|
return false;
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
return collectLineIdentityKeyCandidates(b).has(a.identity_key);
|
|
432
|
-
}
|
|
433
|
-
if (!a.identity_key && b.identity_key) {
|
|
434
|
-
return collectLineIdentityKeyCandidates(a).has(b.identity_key);
|
|
435
|
-
}
|
|
436
|
-
return false;
|
|
437
|
-
}
|
|
391
|
+
const aHasKey = typeof a.identity_key === "string" && a.identity_key.length > 0;
|
|
392
|
+
const bHasKey = typeof b.identity_key === "string" && b.identity_key.length > 0;
|
|
393
|
+
if (aHasKey && bHasKey)
|
|
394
|
+
return a.identity_key === b.identity_key;
|
|
438
395
|
if (isSkuOnlyDeleteIdentity(a) || isSkuOnlyDeleteIdentity(b))
|
|
439
396
|
return true;
|
|
440
397
|
return fingerprintForIdentityWithOptionKeys(a) === fingerprintForIdentityWithOptionKeys(b);
|
|
@@ -445,39 +402,91 @@ function getProductIdentityIndex(products, identity) {
|
|
|
445
402
|
function normalizeOrderProduct(product) {
|
|
446
403
|
var _a, _b;
|
|
447
404
|
const metadata = { ...product.metadata || {} };
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
const resolvedSellingPrice = product.selling_price || "0.00";
|
|
452
|
-
const resolvedOriginalPrice = product.original_price || resolvedSellingPrice;
|
|
453
|
-
if (metadata.main_product_original_price === void 0) {
|
|
454
|
-
metadata.main_product_original_price = resolvedOriginalPrice;
|
|
455
|
-
}
|
|
456
|
-
if (metadata.main_product_selling_price === void 0) {
|
|
457
|
-
metadata.main_product_selling_price = resolvedSellingPrice;
|
|
458
|
-
}
|
|
459
|
-
if (metadata.source_product_price === void 0) {
|
|
460
|
-
metadata.source_product_price = ((_a = product._origin) == null ? void 0 : _a.price) ?? ((_b = product._origin) == null ? void 0 : _b.base_price) ?? resolvedSellingPrice;
|
|
405
|
+
const resolvedIdentityKey = product.identity_key && String(product.identity_key).length > 0 ? String(product.identity_key) : (0, import_utils.createUuidV4)();
|
|
406
|
+
if (!metadata.unique_identification_number) {
|
|
407
|
+
metadata.unique_identification_number = resolvedIdentityKey;
|
|
461
408
|
}
|
|
462
409
|
const normalizedBundle = (product.product_bundle || []).map((item) => ({
|
|
463
410
|
...item,
|
|
464
411
|
bundle_selling_price: item.bundle_selling_price ?? item.price ?? "0.00",
|
|
465
412
|
custom_price: item.custom_price ?? item.bundle_selling_price ?? item.price ?? "0.00"
|
|
466
413
|
}));
|
|
414
|
+
const normalizedOptions = product.product_option_item || [];
|
|
415
|
+
const optionSum = (0, import_utils.sumOptionUnitPrice)(normalizedOptions);
|
|
416
|
+
const isV2 = metadata.price_schema_version === 2 || metadata.price_schema_version === "2";
|
|
417
|
+
const variantId = Number(product.product_variant_id || 0);
|
|
418
|
+
const variantList = variantId ? (_a = metadata == null ? void 0 : metadata.origin) == null ? void 0 : _a.variant : null;
|
|
419
|
+
const variantPrice = Array.isArray(variantList) ? (_b = variantList.find((v) => Number(v == null ? void 0 : v.id) === variantId)) == null ? void 0 : _b.price : void 0;
|
|
420
|
+
const resolvedSource = (() => {
|
|
421
|
+
var _a2, _b2;
|
|
422
|
+
if (metadata.source_product_price !== void 0) {
|
|
423
|
+
return String(metadata.source_product_price);
|
|
424
|
+
}
|
|
425
|
+
if (variantPrice !== void 0 && variantPrice !== null) {
|
|
426
|
+
return String(variantPrice);
|
|
427
|
+
}
|
|
428
|
+
const originPrice = (_a2 = product._origin) == null ? void 0 : _a2.price;
|
|
429
|
+
if (originPrice !== void 0 && originPrice !== null) {
|
|
430
|
+
return String(originPrice);
|
|
431
|
+
}
|
|
432
|
+
const originBasePrice = (_b2 = product._origin) == null ? void 0 : _b2.base_price;
|
|
433
|
+
if (originBasePrice !== void 0 && originBasePrice !== null) {
|
|
434
|
+
return String(originBasePrice);
|
|
435
|
+
}
|
|
436
|
+
if (!isV2 && metadata.main_product_original_price !== void 0) {
|
|
437
|
+
return String(metadata.main_product_original_price);
|
|
438
|
+
}
|
|
439
|
+
return product.original_price ?? product.selling_price ?? "0.00";
|
|
440
|
+
})();
|
|
441
|
+
const mainOriginalDec = new import_decimal.default(Number(resolvedSource) || 0).plus(optionSum);
|
|
442
|
+
const mainOriginalStr = mainOriginalDec.toDecimalPlaces(2).toFixed(2);
|
|
443
|
+
let mainSellingDec;
|
|
444
|
+
if (isV2 && metadata.main_product_selling_price != null) {
|
|
445
|
+
mainSellingDec = new import_decimal.default(Number(metadata.main_product_selling_price) || 0);
|
|
446
|
+
} else if (metadata.main_product_selling_price != null && metadata.main_product_original_price != null) {
|
|
447
|
+
const legacyOriginal = new import_decimal.default(Number(metadata.main_product_original_price) || 0);
|
|
448
|
+
const legacySelling = new import_decimal.default(Number(metadata.main_product_selling_price) || 0);
|
|
449
|
+
const legacyDiscount = legacyOriginal.minus(legacySelling);
|
|
450
|
+
mainSellingDec = mainOriginalDec.minus(legacyDiscount);
|
|
451
|
+
} else if (product.original_price != null && product.selling_price != null && new import_decimal.default(Number(product.original_price) || 0).greaterThan(
|
|
452
|
+
new import_decimal.default(Number(product.selling_price) || 0)
|
|
453
|
+
)) {
|
|
454
|
+
const topOriginal = new import_decimal.default(Number(product.original_price) || 0);
|
|
455
|
+
const topSelling = new import_decimal.default(Number(product.selling_price) || 0);
|
|
456
|
+
const legacyDiscount = topOriginal.minus(topSelling);
|
|
457
|
+
mainSellingDec = mainOriginalDec.minus(legacyDiscount);
|
|
458
|
+
} else {
|
|
459
|
+
mainSellingDec = mainOriginalDec;
|
|
460
|
+
}
|
|
461
|
+
const mainSellingStr = mainSellingDec.toDecimalPlaces(2).toFixed(2);
|
|
462
|
+
metadata.source_product_price = resolvedSource;
|
|
463
|
+
metadata.main_product_original_price = mainOriginalStr;
|
|
464
|
+
metadata.main_product_selling_price = mainSellingStr;
|
|
465
|
+
metadata.price_schema_version = 2;
|
|
466
|
+
const composedSellingPrice = (0, import_utils.composeLinePrice)({
|
|
467
|
+
mainPrice: mainSellingStr,
|
|
468
|
+
bundle: normalizedBundle
|
|
469
|
+
});
|
|
470
|
+
const composedOriginalPrice = (0, import_utils.composeLinePrice)({
|
|
471
|
+
mainPrice: mainOriginalStr,
|
|
472
|
+
bundle: normalizedBundle,
|
|
473
|
+
useOriginalBundle: true
|
|
474
|
+
});
|
|
467
475
|
return {
|
|
468
476
|
order_detail_id: product.order_detail_id || null,
|
|
469
477
|
product_id: product.product_id,
|
|
470
478
|
num: getSafeProductNum(product.num),
|
|
471
479
|
product_variant_id: product.product_variant_id,
|
|
472
|
-
identity_key:
|
|
473
|
-
product_option_item:
|
|
474
|
-
selling_price:
|
|
475
|
-
original_price:
|
|
480
|
+
identity_key: resolvedIdentityKey,
|
|
481
|
+
product_option_item: normalizedOptions,
|
|
482
|
+
selling_price: composedSellingPrice,
|
|
483
|
+
original_price: composedOriginalPrice,
|
|
476
484
|
tax_fee: product.tax_fee || "0.00",
|
|
477
485
|
is_charge_tax: product.is_charge_tax ?? 0,
|
|
478
486
|
discount_list: product.discount_list || [],
|
|
479
487
|
product_bundle: normalizedBundle,
|
|
480
488
|
metadata,
|
|
489
|
+
note: product.note != null ? String(product.note) : "",
|
|
481
490
|
_origin: product._origin
|
|
482
491
|
};
|
|
483
492
|
}
|
|
@@ -539,7 +548,7 @@ function computeResourceIsFull(params) {
|
|
|
539
548
|
occupied += pax;
|
|
540
549
|
}
|
|
541
550
|
}
|
|
542
|
-
return occupied
|
|
551
|
+
return occupied >= totalCapacity;
|
|
543
552
|
}
|
|
544
553
|
function pickFirstCustomCapacityPaxBounds(products) {
|
|
545
554
|
for (const p of products) {
|
|
@@ -600,7 +609,6 @@ function pickFirstCustomCapacityDimensionId(products) {
|
|
|
600
609
|
buildProductKey,
|
|
601
610
|
buildProductLineFingerprint,
|
|
602
611
|
buildQuantityLimitIndex,
|
|
603
|
-
buildSyntheticSingleOptionIdentityKey,
|
|
604
612
|
collectLinkProductIdsFromReservationRules,
|
|
605
613
|
computeResourceIsFull,
|
|
606
614
|
createEmptySummary,
|
|
@@ -2,6 +2,7 @@ import { Module, ModuleOptions, PisellCore } from '../../types';
|
|
|
2
2
|
import { BaseModule } from '../../modules/BaseModule';
|
|
3
3
|
import { VenueBookingAddLogParams, VenueBookingSlotConfig, VenueDateSummaryItem, VenueSlotSelection, VenueTimeSlotGrid } from './types';
|
|
4
4
|
import type { ScanOrderOrderProduct, ScanOrderOrderProductIdentity } from '../ScanOrder/types';
|
|
5
|
+
import type { UpdateProductInOrderParams } from '../../modules/Order/types';
|
|
5
6
|
import type { OpenDataAvailabilityResult } from '../../modules/OpenData';
|
|
6
7
|
import type { ProductData } from '../../modules/Product/types';
|
|
7
8
|
import { type CartItemSummary, type PaxInfo, type QuantityCheckResult, type QuantityLimitResult } from '../../model/strategy/adapter/itemRule';
|
|
@@ -165,11 +166,7 @@ export declare class VenueBookingImpl extends BaseModule implements Module {
|
|
|
165
166
|
getSummary(): Promise<import("./types").ScanOrderSummary>;
|
|
166
167
|
submitOrder<T = any>(): Promise<T>;
|
|
167
168
|
addProductToOrder(product: Partial<ScanOrderOrderProduct> & ScanOrderOrderProductIdentity): Promise<ScanOrderOrderProduct[]>;
|
|
168
|
-
updateProductInOrder(params:
|
|
169
|
-
product_id: number | null;
|
|
170
|
-
product_variant_id: number;
|
|
171
|
-
updates: Partial<ScanOrderOrderProduct>;
|
|
172
|
-
}): Promise<ScanOrderOrderProduct[]>;
|
|
169
|
+
updateProductInOrder(params: UpdateProductInOrderParams): Promise<ScanOrderOrderProduct[]>;
|
|
173
170
|
removeProductFromOrder(identity: ScanOrderOrderProductIdentity): Promise<ScanOrderOrderProduct[]>;
|
|
174
171
|
getProductList(): Promise<ProductData[]>;
|
|
175
172
|
private loadOpenDataConfig;
|