@pisell/pisellos 0.0.510 → 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 +69 -23
- 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 +29 -2
- package/dist/solution/ScanOrder/index.d.ts +7 -5
- package/dist/solution/ScanOrder/index.js +327 -274
- package/dist/solution/ScanOrder/types.d.ts +14 -0
- package/dist/solution/ScanOrder/types.js +13 -0
- package/dist/solution/ScanOrder/utils.d.ts +8 -10
- package/dist/solution/ScanOrder/utils.js +20 -54
- 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 +78 -17
- package/lib/modules/Order/types.d.ts +12 -5
- package/lib/modules/Order/utils.d.ts +1 -0
- package/lib/modules/Order/utils.js +22 -2
- package/lib/solution/ScanOrder/index.d.ts +7 -5
- package/lib/solution/ScanOrder/index.js +30 -0
- package/lib/solution/ScanOrder/types.d.ts +14 -0
- package/lib/solution/ScanOrder/utils.d.ts +8 -10
- package/lib/solution/ScanOrder/utils.js +10 -53
- package/lib/solution/VenueBooking/index.d.ts +2 -5
- package/package.json +1 -1
|
@@ -48,8 +48,22 @@ export interface ScanOrderOrderProduct extends ScanOrderOrderProductIdentity {
|
|
|
48
48
|
discount_list: any[];
|
|
49
49
|
product_bundle: any[];
|
|
50
50
|
metadata: Record<string, any>;
|
|
51
|
+
/** 商品行备注(如顾客对单品的特殊要求) */
|
|
52
|
+
note?: string;
|
|
51
53
|
_origin?: Record<string, any>;
|
|
52
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
|
+
*/
|
|
53
67
|
export interface ScanOrderSubmitProduct extends Omit<ScanOrderOrderProduct, '_origin' | 'identity_key'> {
|
|
54
68
|
/**
|
|
55
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 字段
|
|
@@ -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
|
/**
|
|
@@ -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 报错。
|
|
@@ -412,43 +414,6 @@ export function isSkuOnlyDeleteIdentity(x) {
|
|
|
412
414
|
if (x.identity_key) return false;
|
|
413
415
|
return !('product_option_item' in x) && !('product_bundle' in x);
|
|
414
416
|
}
|
|
415
|
-
|
|
416
|
-
/**
|
|
417
|
-
* 与常见 UI 约定一致:仅当存在**一条** `product_option_item` 时,
|
|
418
|
-
* `${product_id}_${option_group_id}_${product_option_item_id}_${num}` 可作为删除用 `identity_key`。
|
|
419
|
-
* 多小料行请使用加购时的 `identity_key` / `metadata.unique_identification_number` 或按 `product_option_item` 指纹删除。
|
|
420
|
-
*/
|
|
421
|
-
export function buildSyntheticSingleOptionIdentityKey(productId, productOptionItem) {
|
|
422
|
-
if (productId == null || !Number.isFinite(Number(productId))) return undefined;
|
|
423
|
-
var opts = Array.isArray(productOptionItem) ? productOptionItem : [];
|
|
424
|
-
if (opts.length !== 1) return undefined;
|
|
425
|
-
var o = opts[0];
|
|
426
|
-
var gid = Number(o === null || o === void 0 ? void 0 : o.option_group_id) || 0;
|
|
427
|
-
var oid = Number(o === null || o === void 0 ? void 0 : o.product_option_item_id) || 0;
|
|
428
|
-
var rawNum = o === null || o === void 0 ? void 0 : o.num;
|
|
429
|
-
var n = typeof rawNum === 'number' && !Number.isNaN(rawNum) ? Math.max(1, Math.floor(rawNum)) : 1;
|
|
430
|
-
return "".concat(productId, "_").concat(gid, "_").concat(oid, "_").concat(n);
|
|
431
|
-
}
|
|
432
|
-
function collectLineIdentityKeyCandidates(x) {
|
|
433
|
-
var _row$metadata;
|
|
434
|
-
var out = new Set();
|
|
435
|
-
var row = x;
|
|
436
|
-
if (typeof row.identity_key === 'string' && row.identity_key.length > 0) {
|
|
437
|
-
out.add(row.identity_key);
|
|
438
|
-
}
|
|
439
|
-
var uid = (_row$metadata = row.metadata) === null || _row$metadata === void 0 ? void 0 : _row$metadata.unique_identification_number;
|
|
440
|
-
if (typeof uid === 'string' && uid.length > 0) out.add(uid);
|
|
441
|
-
var synthetic = buildSyntheticSingleOptionIdentityKey(row.product_id, row.product_option_item);
|
|
442
|
-
if (synthetic) out.add(synthetic);
|
|
443
|
-
var opts = 'product_option_item' in row && Array.isArray(row.product_option_item) ? row.product_option_item : [];
|
|
444
|
-
var bundles = 'product_bundle' in row && Array.isArray(row.product_bundle) ? row.product_bundle : [];
|
|
445
|
-
// 无小料且无套餐时,与常见 UI 约定对齐:`${product_id}_${product_variant_id}_0`
|
|
446
|
-
if (opts.length === 0 && bundles.length === 0 && row.product_id != null && Number.isFinite(Number(row.product_id))) {
|
|
447
|
-
var vid = Number(row.product_variant_id) || 0;
|
|
448
|
-
out.add("".concat(row.product_id, "_").concat(vid, "_0"));
|
|
449
|
-
}
|
|
450
|
-
return out;
|
|
451
|
-
}
|
|
452
417
|
function fingerprintForIdentityWithOptionKeys(x) {
|
|
453
418
|
var row = x;
|
|
454
419
|
var opts = 'product_option_item' in row ? Array.isArray(row.product_option_item) ? row.product_option_item : [] : [];
|
|
@@ -457,23 +422,20 @@ function fingerprintForIdentityWithOptionKeys(x) {
|
|
|
457
422
|
}
|
|
458
423
|
|
|
459
424
|
/**
|
|
460
|
-
* 判断两个商品 identity
|
|
461
|
-
*
|
|
462
|
-
*
|
|
463
|
-
* -
|
|
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 通配 / 选项指纹删除」留兼容入口。
|
|
464
433
|
*/
|
|
465
434
|
export function isIdentityMatch(a, b) {
|
|
466
435
|
if (a.product_id !== b.product_id || a.product_variant_id !== b.product_variant_id) return false;
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
return collectLineIdentityKeyCandidates(b).has(a.identity_key);
|
|
471
|
-
}
|
|
472
|
-
if (!a.identity_key && b.identity_key) {
|
|
473
|
-
return collectLineIdentityKeyCandidates(a).has(b.identity_key);
|
|
474
|
-
}
|
|
475
|
-
return false;
|
|
476
|
-
}
|
|
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;
|
|
477
439
|
if (isSkuOnlyDeleteIdentity(a) || isSkuOnlyDeleteIdentity(b)) return true;
|
|
478
440
|
return fingerprintForIdentityWithOptionKeys(a) === fingerprintForIdentityWithOptionKeys(b);
|
|
479
441
|
}
|
|
@@ -497,8 +459,11 @@ export function getProductIdentityIndex(products, identity) {
|
|
|
497
459
|
export function normalizeOrderProduct(product) {
|
|
498
460
|
var _product$is_charge_ta;
|
|
499
461
|
var metadata = _objectSpread({}, product.metadata || {});
|
|
500
|
-
|
|
501
|
-
|
|
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;
|
|
502
467
|
}
|
|
503
468
|
|
|
504
469
|
// selling_price:券后成交单价;original_price:券前店铺售价。
|
|
@@ -530,7 +495,7 @@ export function normalizeOrderProduct(product) {
|
|
|
530
495
|
product_id: product.product_id,
|
|
531
496
|
num: getSafeProductNum(product.num),
|
|
532
497
|
product_variant_id: product.product_variant_id,
|
|
533
|
-
identity_key:
|
|
498
|
+
identity_key: resolvedIdentityKey,
|
|
534
499
|
product_option_item: product.product_option_item || [],
|
|
535
500
|
selling_price: resolvedSellingPrice,
|
|
536
501
|
original_price: resolvedOriginalPrice,
|
|
@@ -539,6 +504,7 @@ export function normalizeOrderProduct(product) {
|
|
|
539
504
|
discount_list: product.discount_list || [],
|
|
540
505
|
product_bundle: normalizedBundle,
|
|
541
506
|
metadata: metadata,
|
|
507
|
+
note: product.note != null ? String(product.note) : '',
|
|
542
508
|
_origin: product._origin
|
|
543
509
|
};
|
|
544
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';
|
|
@@ -298,6 +298,17 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
298
298
|
if (!Array.isArray(p.product_bundle)) {
|
|
299
299
|
p.product_bundle = [];
|
|
300
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
|
+
}
|
|
301
312
|
}
|
|
302
313
|
}
|
|
303
314
|
this.store.tempOrder = parsedData;
|
|
@@ -409,21 +420,57 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
409
420
|
// ─── TempOrder: 商品 CRUD ───
|
|
410
421
|
async addProductToOrder(product) {
|
|
411
422
|
const tempOrder = this.ensureTempOrder();
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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
|
+
}
|
|
419
441
|
} else {
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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
|
+
}
|
|
427
474
|
}
|
|
428
475
|
this.applyDiscount();
|
|
429
476
|
await this.recalculateSummary({ createIfMissing: true });
|
|
@@ -431,12 +478,26 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
431
478
|
return tempOrder.products;
|
|
432
479
|
}
|
|
433
480
|
async updateProductInOrder(params) {
|
|
434
|
-
const {
|
|
481
|
+
const {
|
|
482
|
+
product_id,
|
|
483
|
+
product_variant_id,
|
|
484
|
+
updates,
|
|
485
|
+
identity_key,
|
|
486
|
+
product_option_item,
|
|
487
|
+
product_bundle
|
|
488
|
+
} = params;
|
|
435
489
|
const tempOrder = this.ensureTempOrder();
|
|
436
|
-
const
|
|
490
|
+
const identityLookup = {
|
|
437
491
|
product_id,
|
|
438
492
|
product_variant_id
|
|
439
|
-
}
|
|
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);
|
|
440
501
|
if (productIndex === -1) {
|
|
441
502
|
throw new Error("[Order] 目标商品不存在,无法更新");
|
|
442
503
|
}
|
|
@@ -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?: {
|
|
@@ -62,6 +62,8 @@ function createDefaultOrderRulesHooks() {
|
|
|
62
62
|
id: product.product_id,
|
|
63
63
|
// Rules 引擎用 _id 作为 processedProductsMap 键;必须与购物车行级 identity 一致,
|
|
64
64
|
// 否则同 SKU 不同小料会在 calcDiscount 重组时互相覆盖。
|
|
65
|
+
// 不透明 identity 契约下,normalizeOrderProduct / restoreTempOrderFromStorage 已保证 identity_key 必存在;
|
|
66
|
+
// 留 fingerprint 分支仅作 Rules 单测里直接传裸 product 时的兜底。
|
|
65
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)(
|
|
66
68
|
product.product_option_item,
|
|
67
69
|
product.product_bundle
|
|
@@ -157,6 +159,23 @@ function formatDateTime(date) {
|
|
|
157
159
|
function normalizeSubmitPlatform(platform) {
|
|
158
160
|
return (platform == null ? void 0 : platform.toLowerCase()) === "pc" ? "PC" : "H5";
|
|
159
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
|
+
}
|
|
160
179
|
function normalizeSubmitProduct(product) {
|
|
161
180
|
const { _origin, identity_key, ...submitProduct } = product;
|
|
162
181
|
const rawMetadata = submitProduct.metadata || {};
|
|
@@ -184,9 +203,9 @@ function normalizeSubmitProduct(product) {
|
|
|
184
203
|
return {
|
|
185
204
|
...submitProduct,
|
|
186
205
|
...bookingUid ? { booking_uid: bookingUid } : {},
|
|
187
|
-
product_option_item: submitProduct.product_option_item
|
|
206
|
+
product_option_item: formatSubmitOptionItems(submitProduct.product_option_item),
|
|
188
207
|
discount_list: submitProduct.discount_list || [],
|
|
189
|
-
product_bundle: submitProduct.product_bundle
|
|
208
|
+
product_bundle: formatSubmitBundleItems(submitProduct.product_bundle),
|
|
190
209
|
metadata: cleanMetadata,
|
|
191
210
|
// 出站兼容:后端仍消费 payment_price 字段,从 selling_price(券后单价)派生。
|
|
192
211
|
payment_price: submitProduct.selling_price
|
|
@@ -387,6 +406,7 @@ function formatV1Product(products) {
|
|
|
387
406
|
product_id: product.product_id,
|
|
388
407
|
product_variant_id: product.product_variant_id,
|
|
389
408
|
num: product.num,
|
|
409
|
+
note: String(product.note ?? ""),
|
|
390
410
|
rowKey: product.product_id,
|
|
391
411
|
session: null,
|
|
392
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,
|
|
@@ -48,8 +48,22 @@ export interface ScanOrderOrderProduct extends ScanOrderOrderProductIdentity {
|
|
|
48
48
|
discount_list: any[];
|
|
49
49
|
product_bundle: any[];
|
|
50
50
|
metadata: Record<string, any>;
|
|
51
|
+
/** 商品行备注(如顾客对单品的特殊要求) */
|
|
52
|
+
note?: string;
|
|
51
53
|
_origin?: Record<string, any>;
|
|
52
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
|
+
*/
|
|
53
67
|
export interface ScanOrderSubmitProduct extends Omit<ScanOrderOrderProduct, '_origin' | 'identity_key'> {
|
|
54
68
|
/**
|
|
55
69
|
* 出站兼容字段:SDK 内部不再消费 payment_price,
|
|
@@ -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
|
/**
|