@pisell/pisellos 0.0.509 → 0.0.511
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 +114 -57
- package/dist/modules/Order/types.d.ts +12 -5
- package/dist/modules/Order/types.js +2 -0
- package/dist/modules/Order/utils.d.ts +1 -0
- package/dist/modules/Order/utils.js +33 -4
- package/dist/solution/ScanOrder/index.d.ts +7 -5
- package/dist/solution/ScanOrder/index.js +327 -274
- package/dist/solution/ScanOrder/types.d.ts +17 -0
- package/dist/solution/ScanOrder/types.js +13 -0
- package/dist/solution/ScanOrder/utils.d.ts +18 -3
- package/dist/solution/ScanOrder/utils.js +80 -13
- package/dist/solution/VenueBooking/index.d.ts +2 -5
- package/lib/model/strategy/adapter/promotion/index.js +49 -0
- package/lib/modules/Order/index.d.ts +3 -6
- package/lib/modules/Order/index.js +90 -26
- package/lib/modules/Order/types.d.ts +12 -5
- package/lib/modules/Order/utils.d.ts +1 -0
- package/lib/modules/Order/utils.js +28 -3
- package/lib/solution/ScanOrder/index.d.ts +7 -5
- package/lib/solution/ScanOrder/index.js +30 -0
- package/lib/solution/ScanOrder/types.d.ts +17 -0
- package/lib/solution/ScanOrder/utils.d.ts +18 -3
- package/lib/solution/ScanOrder/utils.js +58 -5
- package/lib/solution/VenueBooking/index.d.ts +2 -5
- package/package.json +1 -1
|
@@ -31,6 +31,9 @@ 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;
|
|
@@ -45,8 +48,22 @@ export interface ScanOrderOrderProduct extends ScanOrderOrderProductIdentity {
|
|
|
45
48
|
discount_list: any[];
|
|
46
49
|
product_bundle: any[];
|
|
47
50
|
metadata: Record<string, any>;
|
|
51
|
+
/** 商品行备注(如顾客对单品的特殊要求) */
|
|
52
|
+
note?: string;
|
|
48
53
|
_origin?: Record<string, any>;
|
|
49
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* 出站 payload 版本的商品行。
|
|
57
|
+
*
|
|
58
|
+
* 与 tempOrder 内部 `ScanOrderOrderProduct` 的字段差异:
|
|
59
|
+
* - `product_option_item[*]`:内部为 `{ product_option_item_id, option_group_id, num, price, ... }`,
|
|
60
|
+
* 出站由 `normalizeSubmitProduct` 重命名为 `{ option_group_item_id, option_group_id, num }`
|
|
61
|
+
* (后端 checkout 协议;丢弃 `price` 等运行时辅助字段)。
|
|
62
|
+
* - `product_bundle[*].option[*]`:同上重命名规则。
|
|
63
|
+
*
|
|
64
|
+
* 运行时(UI 显示、加购合并、指纹、持久化)全部消费内部字段名 `product_option_item_id`,
|
|
65
|
+
* 仅在 SDK 提交边界做一次出站映射,不影响 opaque identity 契约。
|
|
66
|
+
*/
|
|
50
67
|
export interface ScanOrderSubmitProduct extends Omit<ScanOrderOrderProduct, '_origin' | 'identity_key'> {
|
|
51
68
|
/**
|
|
52
69
|
* 出站兼容字段:SDK 内部不再消费 payment_price,
|
|
@@ -10,6 +10,19 @@ export var ScanOrderHooks = /*#__PURE__*/function (ScanOrderHooks) {
|
|
|
10
10
|
return ScanOrderHooks;
|
|
11
11
|
}({});
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* 出站 payload 版本的商品行。
|
|
15
|
+
*
|
|
16
|
+
* 与 tempOrder 内部 `ScanOrderOrderProduct` 的字段差异:
|
|
17
|
+
* - `product_option_item[*]`:内部为 `{ product_option_item_id, option_group_id, num, price, ... }`,
|
|
18
|
+
* 出站由 `normalizeSubmitProduct` 重命名为 `{ option_group_item_id, option_group_id, num }`
|
|
19
|
+
* (后端 checkout 协议;丢弃 `price` 等运行时辅助字段)。
|
|
20
|
+
* - `product_bundle[*].option[*]`:同上重命名规则。
|
|
21
|
+
*
|
|
22
|
+
* 运行时(UI 显示、加购合并、指纹、持久化)全部消费内部字段名 `product_option_item_id`,
|
|
23
|
+
* 仅在 SDK 提交边界做一次出站映射,不影响 opaque identity 契约。
|
|
24
|
+
*/
|
|
25
|
+
|
|
13
26
|
/**
|
|
14
27
|
* ScanOrder 临时订单(runtime-clean 版本)
|
|
15
28
|
* 只保留运行时字段,不包含任何 __comment 字段
|
|
@@ -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
|
/**
|
|
@@ -12,6 +12,8 @@ function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol
|
|
|
12
12
|
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); }
|
|
13
13
|
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
|
|
14
14
|
import dayjs from 'dayjs';
|
|
15
|
+
import { createUuidV4 } from "../../modules/Order/utils";
|
|
16
|
+
|
|
15
17
|
/**
|
|
16
18
|
* 构建金额全为 0 的空 summary。
|
|
17
19
|
* 作为尚未计算金额时的兜底默认值,避免下游因 undefined 报错。
|
|
@@ -367,14 +369,75 @@ export function attachItemRuleLimitsToTopLevelProducts(productList, limits) {
|
|
|
367
369
|
}
|
|
368
370
|
|
|
369
371
|
/**
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
|
|
372
|
+
* 将选项行 / 套餐行归一成稳定 JSON,用于「同 SKU 是否合并」判断。
|
|
373
|
+
* 数组元素先按主键排序再序列化,避免顺序差异导致误判为不同行。
|
|
374
|
+
*/
|
|
375
|
+
export function buildProductLineFingerprint(productOptionItem, productBundle) {
|
|
376
|
+
var optArr = Array.isArray(productOptionItem) ? productOptionItem : [];
|
|
377
|
+
var normalizedOpts = optArr.map(function (item) {
|
|
378
|
+
var _item$price;
|
|
379
|
+
return {
|
|
380
|
+
product_option_item_id: Number(item === null || item === void 0 ? void 0 : item.product_option_item_id) || 0,
|
|
381
|
+
option_group_id: Number(item === null || item === void 0 ? void 0 : item.option_group_id) || 0,
|
|
382
|
+
num: typeof (item === null || item === void 0 ? void 0 : item.num) === 'number' && !Number.isNaN(item.num) ? Math.max(1, Math.floor(item.num)) : 1,
|
|
383
|
+
price: String((_item$price = item === null || item === void 0 ? void 0 : item.price) !== null && _item$price !== void 0 ? _item$price : '')
|
|
384
|
+
};
|
|
385
|
+
}).sort(function (p, q) {
|
|
386
|
+
if (p.product_option_item_id !== q.product_option_item_id) {
|
|
387
|
+
return p.product_option_item_id - q.product_option_item_id;
|
|
388
|
+
}
|
|
389
|
+
if (p.option_group_id !== q.option_group_id) return p.option_group_id - q.option_group_id;
|
|
390
|
+
if (p.num !== q.num) return p.num - q.num;
|
|
391
|
+
return p.price.localeCompare(q.price);
|
|
392
|
+
});
|
|
393
|
+
var bundleArr = Array.isArray(productBundle) ? productBundle : [];
|
|
394
|
+
var normalizedBundles = bundleArr.map(function (item) {
|
|
395
|
+
var _ref, _item$bundle_id;
|
|
396
|
+
return {
|
|
397
|
+
bundle_id: Number((_ref = (_item$bundle_id = item === null || item === void 0 ? void 0 : item.bundle_id) !== null && _item$bundle_id !== void 0 ? _item$bundle_id : item === null || item === void 0 ? void 0 : item.id) !== null && _ref !== void 0 ? _ref : 0) || 0,
|
|
398
|
+
product_id: Number(item === null || item === void 0 ? void 0 : item.product_id) || 0,
|
|
399
|
+
num: typeof (item === null || item === void 0 ? void 0 : item.num) === 'number' && !Number.isNaN(item.num) ? Math.max(1, Math.floor(item.num)) : 1
|
|
400
|
+
};
|
|
401
|
+
}).sort(function (p, q) {
|
|
402
|
+
if (p.bundle_id !== q.bundle_id) return p.bundle_id - q.bundle_id;
|
|
403
|
+
if (p.product_id !== q.product_id) return p.product_id - q.product_id;
|
|
404
|
+
return p.num - q.num;
|
|
405
|
+
});
|
|
406
|
+
return JSON.stringify([normalizedOpts, normalizedBundles]);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* removeProductFromOrder 仅传 SKU 时的通配 identity(对象上未声明选项键)。
|
|
411
|
+
* 与显式 `product_option_item: []` 区分:后者只匹配「无选项」行。
|
|
412
|
+
*/
|
|
413
|
+
export function isSkuOnlyDeleteIdentity(x) {
|
|
414
|
+
if (x.identity_key) return false;
|
|
415
|
+
return !('product_option_item' in x) && !('product_bundle' in x);
|
|
416
|
+
}
|
|
417
|
+
function fingerprintForIdentityWithOptionKeys(x) {
|
|
418
|
+
var row = x;
|
|
419
|
+
var opts = 'product_option_item' in row ? Array.isArray(row.product_option_item) ? row.product_option_item : [] : [];
|
|
420
|
+
var buds = 'product_bundle' in row ? Array.isArray(row.product_bundle) ? row.product_bundle : [] : [];
|
|
421
|
+
return buildProductLineFingerprint(opts, buds);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* 判断两个商品 identity 是否匹配(不透明 identity 契约)。
|
|
426
|
+
*
|
|
427
|
+
* 调用约定:始终 `isIdentityMatch(line, callerIdentity)`。
|
|
428
|
+
* - 若 SKU(product_id + product_variant_id)不一致 → 不匹配。
|
|
429
|
+
* - **双方都带** `identity_key` → 严格字符串相等比较(不再猜测合成 key / metadata 桥接)。
|
|
430
|
+
* 这是 opaque identity 契约的标准路径:UI 删改时透传 SDK 回灌的 identity_key。
|
|
431
|
+
* - 否则(即至少一侧未声明 identity_key)→ 进入「SKU 通配 / 显式空选项 / 指纹」回退路径,
|
|
432
|
+
* 忽略线侧 identity_key,按内容语义匹配。这给「行已 opaque、调用方却想按 SKU 通配 / 选项指纹删除」留兼容入口。
|
|
373
433
|
*/
|
|
374
434
|
export function isIdentityMatch(a, b) {
|
|
375
435
|
if (a.product_id !== b.product_id || a.product_variant_id !== b.product_variant_id) return false;
|
|
376
|
-
|
|
377
|
-
|
|
436
|
+
var aHasKey = typeof a.identity_key === 'string' && a.identity_key.length > 0;
|
|
437
|
+
var bHasKey = typeof b.identity_key === 'string' && b.identity_key.length > 0;
|
|
438
|
+
if (aHasKey && bHasKey) return a.identity_key === b.identity_key;
|
|
439
|
+
if (isSkuOnlyDeleteIdentity(a) || isSkuOnlyDeleteIdentity(b)) return true;
|
|
440
|
+
return fingerprintForIdentityWithOptionKeys(a) === fingerprintForIdentityWithOptionKeys(b);
|
|
378
441
|
}
|
|
379
442
|
|
|
380
443
|
/**
|
|
@@ -396,8 +459,11 @@ export function getProductIdentityIndex(products, identity) {
|
|
|
396
459
|
export function normalizeOrderProduct(product) {
|
|
397
460
|
var _product$is_charge_ta;
|
|
398
461
|
var metadata = _objectSpread({}, product.metadata || {});
|
|
399
|
-
|
|
400
|
-
|
|
462
|
+
// 不透明 identity 契约:每条订单行必须带 identity_key。
|
|
463
|
+
// 调用方未传时由 SDK 自动生成 UUID,后续 update/remove 只做严格比对,避免猜测合成 key。
|
|
464
|
+
var resolvedIdentityKey = product.identity_key && String(product.identity_key).length > 0 ? String(product.identity_key) : createUuidV4();
|
|
465
|
+
if (!metadata.unique_identification_number) {
|
|
466
|
+
metadata.unique_identification_number = resolvedIdentityKey;
|
|
401
467
|
}
|
|
402
468
|
|
|
403
469
|
// selling_price:券后成交单价;original_price:券前店铺售价。
|
|
@@ -414,14 +480,14 @@ export function normalizeOrderProduct(product) {
|
|
|
414
480
|
metadata.main_product_selling_price = resolvedSellingPrice;
|
|
415
481
|
}
|
|
416
482
|
if (metadata.source_product_price === undefined) {
|
|
417
|
-
var
|
|
418
|
-
metadata.source_product_price = (
|
|
483
|
+
var _ref2, _product$_origin$pric, _product$_origin, _product$_origin2;
|
|
484
|
+
metadata.source_product_price = (_ref2 = (_product$_origin$pric = (_product$_origin = product._origin) === null || _product$_origin === void 0 ? void 0 : _product$_origin.price) !== null && _product$_origin$pric !== void 0 ? _product$_origin$pric : (_product$_origin2 = product._origin) === null || _product$_origin2 === void 0 ? void 0 : _product$_origin2.base_price) !== null && _ref2 !== void 0 ? _ref2 : resolvedSellingPrice;
|
|
419
485
|
}
|
|
420
486
|
var normalizedBundle = (product.product_bundle || []).map(function (item) {
|
|
421
|
-
var
|
|
487
|
+
var _ref3, _item$bundle_selling_, _ref4, _ref5, _item$custom_price;
|
|
422
488
|
return _objectSpread(_objectSpread({}, item), {}, {
|
|
423
|
-
bundle_selling_price: (
|
|
424
|
-
custom_price: (
|
|
489
|
+
bundle_selling_price: (_ref3 = (_item$bundle_selling_ = item.bundle_selling_price) !== null && _item$bundle_selling_ !== void 0 ? _item$bundle_selling_ : item.price) !== null && _ref3 !== void 0 ? _ref3 : '0.00',
|
|
490
|
+
custom_price: (_ref4 = (_ref5 = (_item$custom_price = item.custom_price) !== null && _item$custom_price !== void 0 ? _item$custom_price : item.bundle_selling_price) !== null && _ref5 !== void 0 ? _ref5 : item.price) !== null && _ref4 !== void 0 ? _ref4 : '0.00'
|
|
425
491
|
});
|
|
426
492
|
});
|
|
427
493
|
return {
|
|
@@ -429,7 +495,7 @@ export function normalizeOrderProduct(product) {
|
|
|
429
495
|
product_id: product.product_id,
|
|
430
496
|
num: getSafeProductNum(product.num),
|
|
431
497
|
product_variant_id: product.product_variant_id,
|
|
432
|
-
identity_key:
|
|
498
|
+
identity_key: resolvedIdentityKey,
|
|
433
499
|
product_option_item: product.product_option_item || [],
|
|
434
500
|
selling_price: resolvedSellingPrice,
|
|
435
501
|
original_price: resolvedOriginalPrice,
|
|
@@ -438,6 +504,7 @@ export function normalizeOrderProduct(product) {
|
|
|
438
504
|
discount_list: product.discount_list || [],
|
|
439
505
|
product_bundle: normalizedBundle,
|
|
440
506
|
metadata: metadata,
|
|
507
|
+
note: product.note != null ? String(product.note) : '',
|
|
441
508
|
_origin: product._origin
|
|
442
509
|
};
|
|
443
510
|
}
|
|
@@ -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;
|
|
@@ -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
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Module, PisellCore, ModuleOptions } from '../../types';
|
|
2
2
|
import { BaseModule } from '../BaseModule';
|
|
3
|
-
import { OrderModuleAPI, CommitOrderParams, SubmitScanOrderParams, ScanOrderMoreParams, CheckoutOrderParams } from './types';
|
|
3
|
+
import { OrderModuleAPI, CommitOrderParams, SubmitScanOrderParams, ScanOrderMoreParams, CheckoutOrderParams, UpdateProductInOrderParams } from './types';
|
|
4
4
|
import { CartItem } from '../Cart/types';
|
|
5
5
|
import { type SubmitPayloadEnhancer } from './utils';
|
|
6
6
|
import type { ScanOrderOrderProduct, ScanOrderOrderProductIdentity, ScanOrderSummary, ScanOrderTempOrder } from '../../solution/ScanOrder/types';
|
|
@@ -71,11 +71,7 @@ export declare class OrderModule extends BaseModule implements Module, OrderModu
|
|
|
71
71
|
updateTempOrderBuzzer(buzzer: string): string;
|
|
72
72
|
updateTempOrderContactsInfo(contactsInfo: any[]): any[];
|
|
73
73
|
addProductToOrder(product: Partial<ScanOrderOrderProduct> & ScanOrderOrderProductIdentity): Promise<ScanOrderOrderProduct[]>;
|
|
74
|
-
updateProductInOrder(params:
|
|
75
|
-
product_id: number | null;
|
|
76
|
-
product_variant_id: number;
|
|
77
|
-
updates: Partial<ScanOrderOrderProduct>;
|
|
78
|
-
}): Promise<ScanOrderOrderProduct[]>;
|
|
74
|
+
updateProductInOrder(params: UpdateProductInOrderParams): Promise<ScanOrderOrderProduct[]>;
|
|
79
75
|
removeProductFromOrder(identity: ScanOrderOrderProductIdentity): Promise<ScanOrderOrderProduct[]>;
|
|
80
76
|
submitTempOrder<T = any>(params?: {
|
|
81
77
|
cacheId?: string;
|
|
@@ -119,3 +115,4 @@ export declare class OrderModule extends BaseModule implements Module, OrderModu
|
|
|
119
115
|
getOrderInfoByRemote(order_id: number): Promise<any>;
|
|
120
116
|
getLastOrderInfo(): Record<string, any> | undefined;
|
|
121
117
|
}
|
|
118
|
+
export type { UpdateProductInOrderParams } from './types';
|
|
@@ -288,6 +288,29 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
288
288
|
this.window.localStorage.removeItem(key);
|
|
289
289
|
return;
|
|
290
290
|
}
|
|
291
|
+
if (Array.isArray(parsedData.products)) {
|
|
292
|
+
for (const p of parsedData.products) {
|
|
293
|
+
if (!p || typeof p !== "object")
|
|
294
|
+
continue;
|
|
295
|
+
if (!Array.isArray(p.product_option_item)) {
|
|
296
|
+
p.product_option_item = [];
|
|
297
|
+
}
|
|
298
|
+
if (!Array.isArray(p.product_bundle)) {
|
|
299
|
+
p.product_bundle = [];
|
|
300
|
+
}
|
|
301
|
+
const row = p;
|
|
302
|
+
if (typeof row.identity_key !== "string" || row.identity_key.length === 0) {
|
|
303
|
+
const newKey = (0, import_utils.createUuidV4)();
|
|
304
|
+
row.identity_key = newKey;
|
|
305
|
+
if (!row.metadata || typeof row.metadata !== "object") {
|
|
306
|
+
row.metadata = {};
|
|
307
|
+
}
|
|
308
|
+
if (!row.metadata.unique_identification_number) {
|
|
309
|
+
row.metadata.unique_identification_number = newKey;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
291
314
|
this.store.tempOrder = parsedData;
|
|
292
315
|
} catch {
|
|
293
316
|
(_b = (_a = this.window) == null ? void 0 : _a.localStorage) == null ? void 0 : _b.removeItem(key);
|
|
@@ -397,21 +420,57 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
397
420
|
// ─── TempOrder: 商品 CRUD ───
|
|
398
421
|
async addProductToOrder(product) {
|
|
399
422
|
const tempOrder = this.ensureTempOrder();
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
423
|
+
const hasExplicitIdentityKey = typeof product.identity_key === "string" && product.identity_key.length > 0;
|
|
424
|
+
if (hasExplicitIdentityKey) {
|
|
425
|
+
const normalizedProduct = (0, import_utils3.normalizeOrderProduct)(product);
|
|
426
|
+
const productIndex = (0, import_utils3.getProductIdentityIndex)(
|
|
427
|
+
tempOrder.products,
|
|
428
|
+
normalizedProduct
|
|
429
|
+
);
|
|
430
|
+
if (productIndex === -1) {
|
|
431
|
+
tempOrder.products.push(normalizedProduct);
|
|
432
|
+
} else {
|
|
433
|
+
const targetProduct = tempOrder.products[productIndex];
|
|
434
|
+
tempOrder.products[productIndex] = {
|
|
435
|
+
...targetProduct,
|
|
436
|
+
...normalizedProduct,
|
|
437
|
+
num: (0, import_utils3.getSafeProductNum)(targetProduct.num + normalizedProduct.num),
|
|
438
|
+
_origin: normalizedProduct._origin || targetProduct._origin
|
|
439
|
+
};
|
|
440
|
+
}
|
|
407
441
|
} else {
|
|
408
|
-
const
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
442
|
+
const incomingFingerprint = (0, import_utils3.buildProductLineFingerprint)(
|
|
443
|
+
product.product_option_item,
|
|
444
|
+
product.product_bundle
|
|
445
|
+
);
|
|
446
|
+
const matchedIndex = tempOrder.products.findIndex((item) => {
|
|
447
|
+
if (item.product_id !== product.product_id)
|
|
448
|
+
return false;
|
|
449
|
+
if (item.product_variant_id !== product.product_variant_id)
|
|
450
|
+
return false;
|
|
451
|
+
const existedFingerprint = (0, import_utils3.buildProductLineFingerprint)(
|
|
452
|
+
item.product_option_item,
|
|
453
|
+
item.product_bundle
|
|
454
|
+
);
|
|
455
|
+
return existedFingerprint === incomingFingerprint;
|
|
456
|
+
});
|
|
457
|
+
if (matchedIndex === -1) {
|
|
458
|
+
const normalizedProduct = (0, import_utils3.normalizeOrderProduct)(product);
|
|
459
|
+
tempOrder.products.push(normalizedProduct);
|
|
460
|
+
} else {
|
|
461
|
+
const targetProduct = tempOrder.products[matchedIndex];
|
|
462
|
+
const normalizedProduct = (0, import_utils3.normalizeOrderProduct)({
|
|
463
|
+
...product,
|
|
464
|
+
identity_key: targetProduct.identity_key
|
|
465
|
+
});
|
|
466
|
+
tempOrder.products[matchedIndex] = {
|
|
467
|
+
...targetProduct,
|
|
468
|
+
...normalizedProduct,
|
|
469
|
+
identity_key: targetProduct.identity_key,
|
|
470
|
+
num: (0, import_utils3.getSafeProductNum)(targetProduct.num + normalizedProduct.num),
|
|
471
|
+
_origin: normalizedProduct._origin || targetProduct._origin
|
|
472
|
+
};
|
|
473
|
+
}
|
|
415
474
|
}
|
|
416
475
|
this.applyDiscount();
|
|
417
476
|
await this.recalculateSummary({ createIfMissing: true });
|
|
@@ -419,12 +478,26 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
419
478
|
return tempOrder.products;
|
|
420
479
|
}
|
|
421
480
|
async updateProductInOrder(params) {
|
|
422
|
-
const {
|
|
481
|
+
const {
|
|
482
|
+
product_id,
|
|
483
|
+
product_variant_id,
|
|
484
|
+
updates,
|
|
485
|
+
identity_key,
|
|
486
|
+
product_option_item,
|
|
487
|
+
product_bundle
|
|
488
|
+
} = params;
|
|
423
489
|
const tempOrder = this.ensureTempOrder();
|
|
424
|
-
const
|
|
490
|
+
const identityLookup = {
|
|
425
491
|
product_id,
|
|
426
492
|
product_variant_id
|
|
427
|
-
}
|
|
493
|
+
};
|
|
494
|
+
if (identity_key !== void 0)
|
|
495
|
+
identityLookup.identity_key = identity_key;
|
|
496
|
+
if (product_option_item !== void 0)
|
|
497
|
+
identityLookup.product_option_item = product_option_item;
|
|
498
|
+
if (product_bundle !== void 0)
|
|
499
|
+
identityLookup.product_bundle = product_bundle;
|
|
500
|
+
const productIndex = (0, import_utils3.getProductIdentityIndex)(tempOrder.products, identityLookup);
|
|
428
501
|
if (productIndex === -1) {
|
|
429
502
|
throw new Error("[Order] 目标商品不存在,无法更新");
|
|
430
503
|
}
|
|
@@ -444,18 +517,9 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
444
517
|
}
|
|
445
518
|
async removeProductFromOrder(identity) {
|
|
446
519
|
const tempOrder = this.ensureTempOrder();
|
|
447
|
-
const beforeProducts = tempOrder.products;
|
|
448
520
|
tempOrder.products = tempOrder.products.filter(
|
|
449
521
|
(item) => !(0, import_utils3.isIdentityMatch)(item, identity)
|
|
450
522
|
);
|
|
451
|
-
const removedByStrictIdentity = beforeProducts.length - tempOrder.products.length;
|
|
452
|
-
if (removedByStrictIdentity === 0 && identity.identity_key) {
|
|
453
|
-
tempOrder.products = tempOrder.products.filter((item) => {
|
|
454
|
-
const isSameProduct = String(item.product_id) === String(identity.product_id) && String(item.product_variant_id) === String(identity.product_variant_id);
|
|
455
|
-
const hasNoIdentityKey = !item.identity_key;
|
|
456
|
-
return !(isSameProduct && hasNoIdentityKey);
|
|
457
|
-
});
|
|
458
|
-
}
|
|
459
523
|
this.applyDiscount();
|
|
460
524
|
await this.recalculateSummary({ createIfMissing: true });
|
|
461
525
|
this.persistTempOrder();
|
|
@@ -16,6 +16,15 @@ export interface OrderState {
|
|
|
16
16
|
discount: DiscountModule | null;
|
|
17
17
|
rules: RulesModule | null;
|
|
18
18
|
}
|
|
19
|
+
/** 更新购物车行:仅传 SKU 时命中同 SKU 第一行;多行同 SKU 须传 identity_key / 选项指纹等 */
|
|
20
|
+
export interface UpdateProductInOrderParams {
|
|
21
|
+
product_id: number | null;
|
|
22
|
+
product_variant_id: number;
|
|
23
|
+
updates: Partial<ScanOrderOrderProduct>;
|
|
24
|
+
identity_key?: string;
|
|
25
|
+
product_option_item?: any[];
|
|
26
|
+
product_bundle?: any[];
|
|
27
|
+
}
|
|
19
28
|
/**
|
|
20
29
|
* 订单信息
|
|
21
30
|
*/
|
|
@@ -43,6 +52,8 @@ export interface SubmitScanOrderProduct {
|
|
|
43
52
|
discount_list: any[];
|
|
44
53
|
product_bundle: any[];
|
|
45
54
|
metadata: Record<string, any>;
|
|
55
|
+
/** 商品行备注 */
|
|
56
|
+
note?: string;
|
|
46
57
|
}
|
|
47
58
|
export interface SubmitScanOrderSummary {
|
|
48
59
|
product_quantity: number;
|
|
@@ -210,11 +221,7 @@ export interface OrderModuleAPI {
|
|
|
210
221
|
updateTempOrderBuzzer: (buzzer: string) => string;
|
|
211
222
|
updateTempOrderContactsInfo: (contactsInfo: any[]) => any[];
|
|
212
223
|
addProductToOrder: (product: Partial<ScanOrderOrderProduct> & ScanOrderOrderProductIdentity) => Promise<ScanOrderOrderProduct[]>;
|
|
213
|
-
updateProductInOrder: (params:
|
|
214
|
-
product_id: number | null;
|
|
215
|
-
product_variant_id: number;
|
|
216
|
-
updates: Partial<ScanOrderOrderProduct>;
|
|
217
|
-
}) => Promise<ScanOrderOrderProduct[]>;
|
|
224
|
+
updateProductInOrder: (params: UpdateProductInOrderParams) => Promise<ScanOrderOrderProduct[]>;
|
|
218
225
|
removeProductFromOrder: (identity: ScanOrderOrderProductIdentity) => Promise<ScanOrderOrderProduct[]>;
|
|
219
226
|
persistTempOrder: () => void;
|
|
220
227
|
submitTempOrder: <T = any>(params?: {
|
|
@@ -60,7 +60,14 @@ function createDefaultOrderRulesHooks() {
|
|
|
60
60
|
var _a, _b, _c, _d;
|
|
61
61
|
return {
|
|
62
62
|
id: product.product_id,
|
|
63
|
-
_id
|
|
63
|
+
// Rules 引擎用 _id 作为 processedProductsMap 键;必须与购物车行级 identity 一致,
|
|
64
|
+
// 否则同 SKU 不同小料会在 calcDiscount 重组时互相覆盖。
|
|
65
|
+
// 不透明 identity 契约下,normalizeOrderProduct / restoreTempOrderFromStorage 已保证 identity_key 必存在;
|
|
66
|
+
// 留 fingerprint 分支仅作 Rules 单测里直接传裸 product 时的兜底。
|
|
67
|
+
_id: product.identity_key ? `${product.product_id}_${product.product_variant_id}_${product.identity_key}` : `${product.product_id}_${product.product_variant_id}_${(0, import_utils.buildProductLineFingerprint)(
|
|
68
|
+
product.product_option_item,
|
|
69
|
+
product.product_bundle
|
|
70
|
+
)}`,
|
|
64
71
|
price: product.selling_price,
|
|
65
72
|
total: new import_decimal.default(product.selling_price || 0).times(product.num || 1).toNumber(),
|
|
66
73
|
origin_total: new import_decimal.default(product.original_price || product.selling_price || 0).times(product.num || 1).toNumber(),
|
|
@@ -152,6 +159,23 @@ function formatDateTime(date) {
|
|
|
152
159
|
function normalizeSubmitPlatform(platform) {
|
|
153
160
|
return (platform == null ? void 0 : platform.toLowerCase()) === "pc" ? "PC" : "H5";
|
|
154
161
|
}
|
|
162
|
+
function formatSubmitOptionItems(options) {
|
|
163
|
+
if (!Array.isArray(options))
|
|
164
|
+
return [];
|
|
165
|
+
return options.map((d) => ({
|
|
166
|
+
num: d == null ? void 0 : d.num,
|
|
167
|
+
option_group_item_id: (d == null ? void 0 : d.option_group_item_id) ?? (d == null ? void 0 : d.product_option_item_id),
|
|
168
|
+
option_group_id: d == null ? void 0 : d.option_group_id
|
|
169
|
+
}));
|
|
170
|
+
}
|
|
171
|
+
function formatSubmitBundleItems(bundle) {
|
|
172
|
+
if (!Array.isArray(bundle))
|
|
173
|
+
return [];
|
|
174
|
+
return bundle.map((b) => ({
|
|
175
|
+
...b,
|
|
176
|
+
option: formatSubmitOptionItems(b == null ? void 0 : b.option)
|
|
177
|
+
}));
|
|
178
|
+
}
|
|
155
179
|
function normalizeSubmitProduct(product) {
|
|
156
180
|
const { _origin, identity_key, ...submitProduct } = product;
|
|
157
181
|
const rawMetadata = submitProduct.metadata || {};
|
|
@@ -179,9 +203,9 @@ function normalizeSubmitProduct(product) {
|
|
|
179
203
|
return {
|
|
180
204
|
...submitProduct,
|
|
181
205
|
...bookingUid ? { booking_uid: bookingUid } : {},
|
|
182
|
-
product_option_item: submitProduct.product_option_item
|
|
206
|
+
product_option_item: formatSubmitOptionItems(submitProduct.product_option_item),
|
|
183
207
|
discount_list: submitProduct.discount_list || [],
|
|
184
|
-
product_bundle: submitProduct.product_bundle
|
|
208
|
+
product_bundle: formatSubmitBundleItems(submitProduct.product_bundle),
|
|
185
209
|
metadata: cleanMetadata,
|
|
186
210
|
// 出站兼容:后端仍消费 payment_price 字段,从 selling_price(券后单价)派生。
|
|
187
211
|
payment_price: submitProduct.selling_price
|
|
@@ -382,6 +406,7 @@ function formatV1Product(products) {
|
|
|
382
406
|
product_id: product.product_id,
|
|
383
407
|
product_variant_id: product.product_variant_id,
|
|
384
408
|
num: product.num,
|
|
409
|
+
note: String(product.note ?? ""),
|
|
385
410
|
rowKey: product.product_id,
|
|
386
411
|
session: null,
|
|
387
412
|
unique: createUuidV4()
|
|
@@ -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';
|
|
@@ -85,11 +86,12 @@ export declare class ScanOrderImpl extends BaseModule implements Module {
|
|
|
85
86
|
private buildSubmitPayloadEnhancer;
|
|
86
87
|
submitScanOrder<T = any>(): Promise<T>;
|
|
87
88
|
addProductToOrder(product: Partial<ScanOrderOrderProduct> & ScanOrderOrderProductIdentity): Promise<ScanOrderOrderProduct[]>;
|
|
88
|
-
updateProductInOrder(params:
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
updateProductInOrder(params: UpdateProductInOrderParams): Promise<ScanOrderOrderProduct[]>;
|
|
90
|
+
/**
|
|
91
|
+
* 设置单行商品备注(与整单 `updateTempOrderNote` 区分)。
|
|
92
|
+
* 多行同 SKU 时必须传入与删除/更新一致的 identity(如 `identity_key`)。
|
|
93
|
+
*/
|
|
94
|
+
setOrderProductLineNote(identity: ScanOrderOrderProductIdentity, note: string): Promise<ScanOrderOrderProduct[]>;
|
|
93
95
|
removeProductFromOrder(identity: ScanOrderOrderProductIdentity): Promise<ScanOrderOrderProduct[]>;
|
|
94
96
|
private loadRuntimeConfigs;
|
|
95
97
|
private syncItemRuleConfigsFromDineInConfig;
|
|
@@ -892,6 +892,36 @@ var _ScanOrderImpl = class extends import_BaseModule.BaseModule {
|
|
|
892
892
|
throw error;
|
|
893
893
|
}
|
|
894
894
|
}
|
|
895
|
+
/**
|
|
896
|
+
* 设置单行商品备注(与整单 `updateTempOrderNote` 区分)。
|
|
897
|
+
* 多行同 SKU 时必须传入与删除/更新一致的 identity(如 `identity_key`)。
|
|
898
|
+
*/
|
|
899
|
+
async setOrderProductLineNote(identity, note) {
|
|
900
|
+
this.logMethodStart("setOrderProductLineNote", {
|
|
901
|
+
product_id: identity.product_id,
|
|
902
|
+
product_variant_id: identity.product_variant_id
|
|
903
|
+
});
|
|
904
|
+
try {
|
|
905
|
+
const params = {
|
|
906
|
+
product_id: identity.product_id,
|
|
907
|
+
product_variant_id: identity.product_variant_id,
|
|
908
|
+
updates: { note: String(note || "") }
|
|
909
|
+
};
|
|
910
|
+
if (identity.identity_key !== void 0)
|
|
911
|
+
params.identity_key = identity.identity_key;
|
|
912
|
+
if (identity.product_option_item !== void 0) {
|
|
913
|
+
params.product_option_item = identity.product_option_item;
|
|
914
|
+
}
|
|
915
|
+
if (identity.product_bundle !== void 0)
|
|
916
|
+
params.product_bundle = identity.product_bundle;
|
|
917
|
+
const products = await this.updateProductInOrder(params);
|
|
918
|
+
this.logMethodSuccess("setOrderProductLineNote", { productCount: products.length });
|
|
919
|
+
return products;
|
|
920
|
+
} catch (error) {
|
|
921
|
+
this.logMethodError("setOrderProductLineNote", error);
|
|
922
|
+
throw error;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
895
925
|
async removeProductFromOrder(identity) {
|
|
896
926
|
this.logMethodStart("removeProductFromOrder", {
|
|
897
927
|
product_id: identity.product_id,
|