@pisell/pisellos 2.2.174 → 2.2.176
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 +52 -1
- package/dist/modules/Order/index.js +730 -348
- package/dist/modules/Order/types.d.ts +140 -1
- package/dist/modules/Order/types.js +29 -0
- package/dist/modules/Order/utils.d.ts +11 -0
- package/dist/modules/Order/utils.js +43 -2
- package/dist/modules/SalesSummary/utils.js +3 -3
- package/dist/server/index.js +77 -23
- package/dist/solution/BaseSales/index.d.ts +37 -1
- package/dist/solution/BaseSales/index.js +751 -457
- package/dist/solution/BaseSales/utils/cartPromotion.d.ts +275 -0
- package/dist/solution/BaseSales/utils/cartPromotion.js +1258 -0
- package/dist/solution/BookingTicket/index.js +4 -0
- package/dist/solution/BookingTicket/utils/cartView.js +4 -2
- package/lib/model/strategy/adapter/promotion/index.js +0 -49
- package/lib/modules/Order/index.d.ts +52 -1
- package/lib/modules/Order/index.js +195 -6
- package/lib/modules/Order/types.d.ts +140 -1
- package/lib/modules/Order/types.js +1 -0
- package/lib/modules/Order/utils.d.ts +11 -0
- package/lib/modules/Order/utils.js +34 -2
- package/lib/modules/SalesSummary/utils.js +3 -3
- package/lib/server/index.js +39 -4
- package/lib/solution/BaseSales/index.d.ts +37 -1
- package/lib/solution/BaseSales/index.js +207 -1
- package/lib/solution/BaseSales/utils/cartPromotion.d.ts +275 -0
- package/lib/solution/BaseSales/utils/cartPromotion.js +836 -0
- package/lib/solution/BookingTicket/index.js +3 -0
- package/lib/solution/BookingTicket/utils/cartView.js +4 -2
- package/package.json +1 -1
|
@@ -883,6 +883,10 @@ export var BookingTicketImpl = /*#__PURE__*/function (_BaseSalesImpl) {
|
|
|
883
883
|
var next = this.buildOrderCustomerPayload();
|
|
884
884
|
this.core.effects.emit("".concat(this.name, ":onOrderCustomerChange"), next);
|
|
885
885
|
this.refreshDiscountConfigAfterOrderCustomerChange(patch.customer_id);
|
|
886
|
+
// 客户切换可能改变可用促销/赠品;触发一次重算(recursion-safe)。
|
|
887
|
+
void this.store.order.applyPromotion().catch(function (error) {
|
|
888
|
+
console.error('[BookingTicket] applyPromotion after customer change failed', error);
|
|
889
|
+
});
|
|
886
890
|
}
|
|
887
891
|
|
|
888
892
|
/**
|
|
@@ -67,8 +67,10 @@ export function buildCartView(lines, bookings) {
|
|
|
67
67
|
});
|
|
68
68
|
});
|
|
69
69
|
var items = productLines.filter(function (line) {
|
|
70
|
-
var _line$metadata2;
|
|
71
|
-
|
|
70
|
+
var _line$metadata2, _line$metadata3;
|
|
71
|
+
// 促销赠品行仅通过主商品 Gift 行展示,不在 items 单独占一行(对齐 legacy `isGift: !!_giftInfo`)
|
|
72
|
+
if ((_line$metadata2 = line.metadata) !== null && _line$metadata2 !== void 0 && _line$metadata2._giftInfo) return false;
|
|
73
|
+
var uid = (_line$metadata3 = line.metadata) === null || _line$metadata3 === void 0 ? void 0 : _line$metadata3.unique_identification_number;
|
|
72
74
|
if (uid === undefined || uid === null || uid === '') return true;
|
|
73
75
|
return !claimedUids.has(String(uid));
|
|
74
76
|
});
|
|
@@ -1,49 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -3,7 +3,7 @@ import { BaseModule } from '../BaseModule';
|
|
|
3
3
|
import { OrderModuleAPI, CommitOrderParams, UpdateOrderBookingParams, UpdateOrderProductParams, UpdateOrderProductQuantityParams, CheckoutOrderParams, CancelOrderParams, ChangeBookingStatusParams } from './types';
|
|
4
4
|
import { CartItem } from '../Cart/types';
|
|
5
5
|
import { type SubmitPayloadEnhancer } from './utils';
|
|
6
|
-
import type { OrderAmountSnapshot, AddProductBookingInput, OrderIdentitySnapshot, OrderPaymentSource, OrderSnapshot, OrderSyncState, SyncPaymentsToOrderParams, SyncPaymentsToOrderResult, SubmitSalesOrderParams, OrderProduct, OrderProductIdentity, OrderSummary, OrderTempOrder, OrderPaymentData, SendCustomerPayLinkParams, UpdateTempOrderCustomerInput, OrderCustomerPatch, OrderCustomerView, OrderScanCodeResult, ReplaceTempOrderOptions } from './types';
|
|
6
|
+
import type { OrderAmountSnapshot, AddProductBookingInput, OrderIdentitySnapshot, OrderPaymentSource, OrderSnapshot, OrderSyncState, SyncPaymentsToOrderParams, SyncPaymentsToOrderResult, SubmitSalesOrderParams, OrderProduct, OrderProductIdentity, OrderSummary, OrderTempOrder, OrderPaymentData, SendCustomerPayLinkParams, UpdateTempOrderCustomerInput, OrderCustomerPatch, OrderCustomerView, OrderScanCodeResult, ReplaceTempOrderOptions, OrderPromotionEvaluator, OrderGiftSelectResolver, OrderUnfulfilledPromotion, OrderLastGiftActions } from './types';
|
|
7
7
|
import type { ICustomer } from '../AccountList/types';
|
|
8
8
|
import type { Discount } from '../Discount/types';
|
|
9
9
|
export declare class OrderModule extends BaseModule implements Module, OrderModuleAPI {
|
|
@@ -19,6 +19,16 @@ export declare class OrderModule extends BaseModule implements Module, OrderModu
|
|
|
19
19
|
private rulesHooksOverride?;
|
|
20
20
|
private orderHooks?;
|
|
21
21
|
private otherParams?;
|
|
22
|
+
/** 评估器引用;由 SalesSdkProvider 注入,未注入时 applyPromotion 静默跳过 */
|
|
23
|
+
private promotionEvaluator;
|
|
24
|
+
/** 赠品选择 resolver;由 SDK host bridge 注入 */
|
|
25
|
+
private giftSelectResolver;
|
|
26
|
+
/** applyPromotion 递归保护 flag(写路径同步调 applyPromotion,禁止重入) */
|
|
27
|
+
private isApplyingPromotion;
|
|
28
|
+
/** 最近一次 applyPromotion 的未满足促销提示 */
|
|
29
|
+
private lastUnfulfilledPromotions;
|
|
30
|
+
/** 最近一次 applyPromotion 的赠品操作 diff(debug / SDK 读取使用) */
|
|
31
|
+
private lastGiftActions;
|
|
22
32
|
/**
|
|
23
33
|
* Populate `savedAmount` on each discount based on product-level discount details.
|
|
24
34
|
* Only selected discounts get a non-zero value.
|
|
@@ -50,6 +60,46 @@ export declare class OrderModule extends BaseModule implements Module, OrderModu
|
|
|
50
60
|
getDiscountList(): Discount[];
|
|
51
61
|
scanCode(code: string, customerId?: number): Promise<OrderScanCodeResult>;
|
|
52
62
|
applyDiscount(): void;
|
|
63
|
+
/**
|
|
64
|
+
* 注入促销评估器。
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* order.setPromotionEvaluator(appHelper.utils.promotionEvaluator);
|
|
68
|
+
*/
|
|
69
|
+
setPromotionEvaluator(evaluator: OrderPromotionEvaluator | null): void;
|
|
70
|
+
/**
|
|
71
|
+
* 注入赠品选择 resolver。OS 需要补赠品时会 await 调用 resolver;缺省时跳过新增并 console.warn。
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* order.setGiftSelectResolver(async (ctx) => {
|
|
75
|
+
* // SDK 内部决定弹窗 or 自动选第一项,返回完整 OrderProduct[]
|
|
76
|
+
* return giftSelectBridge.request(ctx);
|
|
77
|
+
* });
|
|
78
|
+
*/
|
|
79
|
+
setGiftSelectResolver(resolver: OrderGiftSelectResolver | null): void;
|
|
80
|
+
/**
|
|
81
|
+
* 为商品目录追加促销标签,供上层 Sales 门面复用。
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* const products = order.appendPromotionTags(catalogProducts);
|
|
85
|
+
*/
|
|
86
|
+
appendPromotionTags<T extends Record<string, any>>(products: T[]): T[];
|
|
87
|
+
/**
|
|
88
|
+
* 应用购物车促销:计算 → 差异化赠品 → 写回 tempOrder.products → emit onPromotionApplied。
|
|
89
|
+
*
|
|
90
|
+
* 调用时机:写路径(add/update/remove/clear/setCustomer)末尾自动触发;外部一般无需手动调。
|
|
91
|
+
*
|
|
92
|
+
* 关键约束:
|
|
93
|
+
* - 不调用任何公开 CRUD API(如 removeProductFromOrder),全部内部 mutate tempOrder.products,避免事件风暴;
|
|
94
|
+
* - 用 `isApplyingPromotion` 防递归;
|
|
95
|
+
* - 多选项赠品依赖 giftSelectResolver,未注入则跳过且 console.warn;
|
|
96
|
+
* - 不主动调用 applyDiscount / recalculateSummary,调用方负责跟进。
|
|
97
|
+
*/
|
|
98
|
+
applyPromotion(): Promise<void>;
|
|
99
|
+
/** 最近一次 applyPromotion 输出的未满足促销列表 */
|
|
100
|
+
getUnfulfilledPromotions(): OrderUnfulfilledPromotion[];
|
|
101
|
+
/** 最近一次 applyPromotion 输出的赠品操作 diff */
|
|
102
|
+
getLastGiftActions(): OrderLastGiftActions | null;
|
|
53
103
|
initTempOrder(params: {
|
|
54
104
|
cacheId?: string;
|
|
55
105
|
salesSummaryModuleName?: string;
|
|
@@ -64,6 +114,7 @@ export declare class OrderModule extends BaseModule implements Module, OrderModu
|
|
|
64
114
|
private createExternalSaleNumber;
|
|
65
115
|
private ensureExternalSaleNumber;
|
|
66
116
|
private ensureRequestUniqueIdempotencyToken;
|
|
117
|
+
private buildPaymentSyncIdempotencyToken;
|
|
67
118
|
hasLocalDatabase(): boolean;
|
|
68
119
|
private buildLocalOrderRecord;
|
|
69
120
|
getLocalOrderByOrderId(orderId: number | string): Promise<Record<string, any> | null>;
|
|
@@ -37,6 +37,7 @@ var import_types = require("./types");
|
|
|
37
37
|
var import_utils = require("./utils");
|
|
38
38
|
var import_utils2 = require("../Product/utils");
|
|
39
39
|
var import_dayjs = __toESM(require("dayjs"));
|
|
40
|
+
var import_cartPromotion = require("../../solution/BaseSales/utils/cartPromotion");
|
|
40
41
|
var import_utils3 = require("../../solution/ScanOrder/utils");
|
|
41
42
|
var import_manualProductDiscount = require("./utils/manualProductDiscount");
|
|
42
43
|
var import_Discount = require("../Discount");
|
|
@@ -50,6 +51,17 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
50
51
|
super(name || "order", version);
|
|
51
52
|
this.defaultName = "order";
|
|
52
53
|
this.defaultVersion = "1.0.0";
|
|
54
|
+
// ─── 促销/赠品 ────────────────────────────────────
|
|
55
|
+
/** 评估器引用;由 SalesSdkProvider 注入,未注入时 applyPromotion 静默跳过 */
|
|
56
|
+
this.promotionEvaluator = null;
|
|
57
|
+
/** 赠品选择 resolver;由 SDK host bridge 注入 */
|
|
58
|
+
this.giftSelectResolver = null;
|
|
59
|
+
/** applyPromotion 递归保护 flag(写路径同步调 applyPromotion,禁止重入) */
|
|
60
|
+
this.isApplyingPromotion = false;
|
|
61
|
+
/** 最近一次 applyPromotion 的未满足促销提示 */
|
|
62
|
+
this.lastUnfulfilledPromotions = [];
|
|
63
|
+
/** 最近一次 applyPromotion 的赠品操作 diff(debug / SDK 读取使用) */
|
|
64
|
+
this.lastGiftActions = null;
|
|
53
65
|
}
|
|
54
66
|
/**
|
|
55
67
|
* Populate `savedAmount` on each discount based on product-level discount details.
|
|
@@ -81,10 +93,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
81
93
|
var _a, _b, _c;
|
|
82
94
|
if ((_a = product.metadata) == null ? void 0 : _a.is_manual_discount)
|
|
83
95
|
return product;
|
|
84
|
-
const totalPerUnitDiscount = (
|
|
85
|
-
(sum, discount) => sum + Number(discount.amount || 0),
|
|
86
|
-
0
|
|
87
|
-
);
|
|
96
|
+
const totalPerUnitDiscount = (0, import_utils.resolveEffectivePerUnitDiscount)(product);
|
|
88
97
|
const optionSum = (0, import_utils.sumOptionUnitPrice)(product.product_option_item);
|
|
89
98
|
const sourcePrice = ((_b = product.metadata) == null ? void 0 : _b.source_product_price) ?? (((_c = product.metadata) == null ? void 0 : _c.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");
|
|
90
99
|
const newSourceSellingPrice = new import_decimal.default(Number(sourcePrice) || 0).minus(totalPerUnitDiscount).toDecimalPlaces(2).toString();
|
|
@@ -317,6 +326,160 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
317
326
|
(_e = this.store.discount) == null ? void 0 : _e.setDiscountList(result.discountList);
|
|
318
327
|
}
|
|
319
328
|
}
|
|
329
|
+
// ─── 促销/赠品 ──────────────────────────────────────
|
|
330
|
+
/**
|
|
331
|
+
* 注入促销评估器。
|
|
332
|
+
*
|
|
333
|
+
* @example
|
|
334
|
+
* order.setPromotionEvaluator(appHelper.utils.promotionEvaluator);
|
|
335
|
+
*/
|
|
336
|
+
setPromotionEvaluator(evaluator) {
|
|
337
|
+
this.promotionEvaluator = evaluator || null;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* 注入赠品选择 resolver。OS 需要补赠品时会 await 调用 resolver;缺省时跳过新增并 console.warn。
|
|
341
|
+
*
|
|
342
|
+
* @example
|
|
343
|
+
* order.setGiftSelectResolver(async (ctx) => {
|
|
344
|
+
* // SDK 内部决定弹窗 or 自动选第一项,返回完整 OrderProduct[]
|
|
345
|
+
* return giftSelectBridge.request(ctx);
|
|
346
|
+
* });
|
|
347
|
+
*/
|
|
348
|
+
setGiftSelectResolver(resolver) {
|
|
349
|
+
this.giftSelectResolver = resolver || null;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* 为商品目录追加促销标签,供上层 Sales 门面复用。
|
|
353
|
+
*
|
|
354
|
+
* @example
|
|
355
|
+
* const products = order.appendPromotionTags(catalogProducts);
|
|
356
|
+
*/
|
|
357
|
+
appendPromotionTags(products) {
|
|
358
|
+
return (0, import_cartPromotion.appendPromotionTags)(products, this.promotionEvaluator);
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* 应用购物车促销:计算 → 差异化赠品 → 写回 tempOrder.products → emit onPromotionApplied。
|
|
362
|
+
*
|
|
363
|
+
* 调用时机:写路径(add/update/remove/clear/setCustomer)末尾自动触发;外部一般无需手动调。
|
|
364
|
+
*
|
|
365
|
+
* 关键约束:
|
|
366
|
+
* - 不调用任何公开 CRUD API(如 removeProductFromOrder),全部内部 mutate tempOrder.products,避免事件风暴;
|
|
367
|
+
* - 用 `isApplyingPromotion` 防递归;
|
|
368
|
+
* - 多选项赠品依赖 giftSelectResolver,未注入则跳过且 console.warn;
|
|
369
|
+
* - 不主动调用 applyDiscount / recalculateSummary,调用方负责跟进。
|
|
370
|
+
*/
|
|
371
|
+
async applyPromotion() {
|
|
372
|
+
if (this.isApplyingPromotion)
|
|
373
|
+
return;
|
|
374
|
+
if (!this.promotionEvaluator)
|
|
375
|
+
return;
|
|
376
|
+
const tempOrder = this.store.tempOrder;
|
|
377
|
+
if (!tempOrder)
|
|
378
|
+
return;
|
|
379
|
+
this.isApplyingPromotion = true;
|
|
380
|
+
try {
|
|
381
|
+
const result = (0, import_cartPromotion.processCartPromotion)(
|
|
382
|
+
tempOrder.products,
|
|
383
|
+
this.promotionEvaluator
|
|
384
|
+
);
|
|
385
|
+
if (result.giftActions.toRemove.length > 0) {
|
|
386
|
+
const removeSet = new Set(result.giftActions.toRemove.map(String));
|
|
387
|
+
result.products = result.products.filter((p) => {
|
|
388
|
+
const uid = (0, import_cartPromotion.getOrderProductUid)(p);
|
|
389
|
+
return uid ? !removeSet.has(String(uid)) : true;
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
for (const reduce of result.giftActions.toReduce) {
|
|
393
|
+
for (const item of reduce.items) {
|
|
394
|
+
const product = result.products.find(
|
|
395
|
+
(p) => (0, import_cartPromotion.getOrderProductUid)(p) === item.uid
|
|
396
|
+
);
|
|
397
|
+
if (product) {
|
|
398
|
+
product.num = item.newQuantity;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
if (result.giftActions.toAdd.length > 0) {
|
|
403
|
+
if (!this.giftSelectResolver) {
|
|
404
|
+
if (result.giftActions.toAdd.some((g) => g.giftOptions.length > 1)) {
|
|
405
|
+
console.warn(
|
|
406
|
+
"[OrderModule] giftSelectResolver not set, skip applying gifts",
|
|
407
|
+
result.giftActions.toAdd
|
|
408
|
+
);
|
|
409
|
+
} else {
|
|
410
|
+
console.warn(
|
|
411
|
+
"[OrderModule] giftSelectResolver not set, skip applying single-option gifts",
|
|
412
|
+
result.giftActions.toAdd
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
} else {
|
|
416
|
+
for (const addAction of result.giftActions.toAdd) {
|
|
417
|
+
if (addAction.giftOptions.length === 1) {
|
|
418
|
+
const sameStrategy = result.products.find(
|
|
419
|
+
(p) => {
|
|
420
|
+
var _a, _b;
|
|
421
|
+
return ((_b = (_a = p.metadata) == null ? void 0 : _a._giftInfo) == null ? void 0 : _b.strategyId) === addAction.strategyId;
|
|
422
|
+
}
|
|
423
|
+
);
|
|
424
|
+
if (sameStrategy) {
|
|
425
|
+
sameStrategy.num = (Number(sameStrategy.num) || 0) + (addAction.giftCount || 1);
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
try {
|
|
430
|
+
const giftProducts = await this.giftSelectResolver({
|
|
431
|
+
strategyId: addAction.strategyId,
|
|
432
|
+
strategyName: addAction.strategyName,
|
|
433
|
+
giftCount: addAction.giftCount,
|
|
434
|
+
giftOptions: addAction.giftOptions,
|
|
435
|
+
sourceProductIds: addAction.sourceProductIds
|
|
436
|
+
});
|
|
437
|
+
if (Array.isArray(giftProducts) && giftProducts.length > 0) {
|
|
438
|
+
for (const gp of giftProducts) {
|
|
439
|
+
if (!gp)
|
|
440
|
+
continue;
|
|
441
|
+
result.products.push(gp);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
} catch (error) {
|
|
445
|
+
console.warn(
|
|
446
|
+
"[OrderModule] giftSelectResolver rejected, skip add gift",
|
|
447
|
+
addAction.strategyId,
|
|
448
|
+
error
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
tempOrder.products = result.products;
|
|
455
|
+
this.lastUnfulfilledPromotions = result.unfulfilledPromotions;
|
|
456
|
+
this.lastGiftActions = result.giftActions;
|
|
457
|
+
const payload = {
|
|
458
|
+
unfulfilledPromotions: result.unfulfilledPromotions,
|
|
459
|
+
giftActions: result.giftActions,
|
|
460
|
+
gifts: result.gifts.map((g) => ({
|
|
461
|
+
strategyId: g.strategyId,
|
|
462
|
+
strategyName: g.strategyName,
|
|
463
|
+
giftCount: g.giftCount,
|
|
464
|
+
giftOptions: g.giftOptions
|
|
465
|
+
})),
|
|
466
|
+
products: tempOrder.products
|
|
467
|
+
};
|
|
468
|
+
this.effectsEmit("onPromotionApplied", payload);
|
|
469
|
+
} catch (error) {
|
|
470
|
+
console.error("[OrderModule] applyPromotion failed", error);
|
|
471
|
+
} finally {
|
|
472
|
+
this.isApplyingPromotion = false;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
/** 最近一次 applyPromotion 输出的未满足促销列表 */
|
|
476
|
+
getUnfulfilledPromotions() {
|
|
477
|
+
return this.lastUnfulfilledPromotions;
|
|
478
|
+
}
|
|
479
|
+
/** 最近一次 applyPromotion 输出的赠品操作 diff */
|
|
480
|
+
getLastGiftActions() {
|
|
481
|
+
return this.lastGiftActions;
|
|
482
|
+
}
|
|
320
483
|
// ─── TempOrder: 初始化入口 ───
|
|
321
484
|
initTempOrder(params) {
|
|
322
485
|
if (params.cacheId !== void 0)
|
|
@@ -376,6 +539,14 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
376
539
|
}
|
|
377
540
|
return tempOrder.request_unique_idempotency_token;
|
|
378
541
|
}
|
|
542
|
+
buildPaymentSyncIdempotencyToken(tempOrder, payments) {
|
|
543
|
+
const baseToken = this.ensureRequestUniqueIdempotencyToken(tempOrder);
|
|
544
|
+
const paidPaymentsCount = (payments || []).filter((payment) => {
|
|
545
|
+
return String((payment == null ? void 0 : payment.status) || "").toLowerCase() === "paid";
|
|
546
|
+
}).length;
|
|
547
|
+
const paidCount = paidPaymentsCount > 0 ? paidPaymentsCount : payments.length;
|
|
548
|
+
return `${baseToken}_payment_${paidCount}`;
|
|
549
|
+
}
|
|
379
550
|
hasLocalDatabase() {
|
|
380
551
|
return !!this.dbManager;
|
|
381
552
|
}
|
|
@@ -1207,6 +1378,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
1207
1378
|
if (linked) {
|
|
1208
1379
|
tempOrder.bookings = [...tempOrder.bookings || [], linked.booking];
|
|
1209
1380
|
}
|
|
1381
|
+
await this.applyPromotion();
|
|
1210
1382
|
this.applyDiscount();
|
|
1211
1383
|
await this.recalculateSummary({ createIfMissing: true });
|
|
1212
1384
|
this.persistTempOrder();
|
|
@@ -1337,6 +1509,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
1337
1509
|
tempOrder.products[productIndex],
|
|
1338
1510
|
(_g = tempOrder.products[productIndex].metadata) == null ? void 0 : _g.unique_identification_number
|
|
1339
1511
|
);
|
|
1512
|
+
await this.applyPromotion();
|
|
1340
1513
|
this.applyDiscount();
|
|
1341
1514
|
await this.recalculateSummary({ createIfMissing: true });
|
|
1342
1515
|
this.persistTempOrder();
|
|
@@ -1399,6 +1572,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
1399
1572
|
normalizedProduct.discount_list
|
|
1400
1573
|
);
|
|
1401
1574
|
tempOrder.products[productIndex] = normalizedProduct;
|
|
1575
|
+
await this.applyPromotion();
|
|
1402
1576
|
this.applyDiscount();
|
|
1403
1577
|
await this.recalculateSummary({ createIfMissing: true });
|
|
1404
1578
|
this.persistTempOrder();
|
|
@@ -1443,6 +1617,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
1443
1617
|
for (const product of removedProducts) {
|
|
1444
1618
|
this.removeLinkedBookingsForProduct(tempOrder, product);
|
|
1445
1619
|
}
|
|
1620
|
+
await this.applyPromotion();
|
|
1446
1621
|
this.applyDiscount();
|
|
1447
1622
|
await this.recalculateSummary({ createIfMissing: true });
|
|
1448
1623
|
this.persistTempOrder();
|
|
@@ -1462,6 +1637,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
1462
1637
|
productsByUid: {}
|
|
1463
1638
|
};
|
|
1464
1639
|
}
|
|
1640
|
+
await this.applyPromotion();
|
|
1465
1641
|
this.applyDiscount();
|
|
1466
1642
|
await this.recalculateSummary({ createIfMissing: true });
|
|
1467
1643
|
this.persistTempOrder();
|
|
@@ -1493,7 +1669,8 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
1493
1669
|
...hasPaymentStatusOverride ? { payment_status: params == null ? void 0 : params.paymentStatus } : {},
|
|
1494
1670
|
...hasSmallTicketDataFlagOverride ? { small_ticket_data_flag: params == null ? void 0 : params.smallTicketDataFlag } : {}
|
|
1495
1671
|
};
|
|
1496
|
-
|
|
1672
|
+
const enhancedPayload = (params == null ? void 0 : params.enhancePayload) ? params.enhancePayload(nextPayload, ctx) : nextPayload;
|
|
1673
|
+
return enhancedPayload;
|
|
1497
1674
|
} : void 0;
|
|
1498
1675
|
const payload = (0, import_utils.buildSubmitPayload)({
|
|
1499
1676
|
tempOrder,
|
|
@@ -1510,6 +1687,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
1510
1687
|
this.logInfo("submitTempOrder calling sales checkout", {
|
|
1511
1688
|
orderId: payload.order_id,
|
|
1512
1689
|
externalSaleNumber: payload.external_sale_number,
|
|
1690
|
+
requestUniqueIdempotencyToken: payload.request_unique_idempotency_token,
|
|
1513
1691
|
paymentStatus: payload.payment_status,
|
|
1514
1692
|
paymentsCount: ((_b = payload.payments) == null ? void 0 : _b.length) || 0,
|
|
1515
1693
|
smallTicketDataFlag: payload.small_ticket_data_flag
|
|
@@ -1611,11 +1789,22 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
1611
1789
|
return { isFullyPaid };
|
|
1612
1790
|
}
|
|
1613
1791
|
this.store.syncState = "submitting";
|
|
1792
|
+
const paymentSyncIdempotencyToken = this.buildPaymentSyncIdempotencyToken(
|
|
1793
|
+
tempOrder,
|
|
1794
|
+
mappedPayments
|
|
1795
|
+
);
|
|
1614
1796
|
const submitResult = await this.submitTempOrder({
|
|
1615
1797
|
payments: mappedPayments,
|
|
1616
1798
|
paymentStatus,
|
|
1617
1799
|
smallTicketDataFlag: params.smallTicketDataFlag,
|
|
1618
|
-
channel: params.channel
|
|
1800
|
+
channel: params.channel,
|
|
1801
|
+
enhancePayload: (payload) => {
|
|
1802
|
+
const nextPayload = {
|
|
1803
|
+
...payload,
|
|
1804
|
+
request_unique_idempotency_token: paymentSyncIdempotencyToken
|
|
1805
|
+
};
|
|
1806
|
+
return nextPayload;
|
|
1807
|
+
}
|
|
1619
1808
|
});
|
|
1620
1809
|
return { isFullyPaid, submitResult };
|
|
1621
1810
|
}
|
|
@@ -13,7 +13,106 @@ export declare enum OrderHooks {
|
|
|
13
13
|
OnOrderUpdate = "order:onOrderUpdate",
|
|
14
14
|
OnOrderCancel = "order:onOrderCancel",
|
|
15
15
|
OnOrderStatusChange = "order:onOrderStatusChange",
|
|
16
|
-
OnOrderCustomerChange = "order:onOrderCustomerChange"
|
|
16
|
+
OnOrderCustomerChange = "order:onOrderCustomerChange",
|
|
17
|
+
OnPromotionApplied = "order:onPromotionApplied"
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 促销评估器协议(OS 与 PromotionEvaluator 之间的桥接接口)。
|
|
21
|
+
*
|
|
22
|
+
* 与 `pisell_os/src/model/strategy/adapter/promotion/evaluator.ts` 中的
|
|
23
|
+
* `PromotionEvaluator` 实例结构保持兼容。SalesSdkProvider 会从 `appHelper.utils.promotionEvaluator`
|
|
24
|
+
* 读取实例并通过 `setPromotionEvaluator` 注入到 OrderModule。
|
|
25
|
+
*
|
|
26
|
+
* 这里只声明 OrderModule 需要调用的子集,避免反向依赖具体 evaluator 实现。
|
|
27
|
+
*/
|
|
28
|
+
export interface OrderPromotionEvaluator {
|
|
29
|
+
evaluateCartWithPricing(input: {
|
|
30
|
+
products: any[];
|
|
31
|
+
channel?: string;
|
|
32
|
+
}): any;
|
|
33
|
+
getProductsApplicableStrategies(input: {
|
|
34
|
+
products: any[];
|
|
35
|
+
channel?: string;
|
|
36
|
+
}, matchVariant?: boolean): any[];
|
|
37
|
+
}
|
|
38
|
+
/** 赠品解析回调 context(OS → SDK 的请求体) */
|
|
39
|
+
export interface OrderGiftSelectContext {
|
|
40
|
+
strategyId: string;
|
|
41
|
+
strategyName: string | Record<string, string>;
|
|
42
|
+
giftCount: number;
|
|
43
|
+
giftOptions: Array<{
|
|
44
|
+
product_id: number;
|
|
45
|
+
product_variant_id: number;
|
|
46
|
+
}>;
|
|
47
|
+
/** 触发此赠品的主商品 metadata.unique_identification_number 列表 */
|
|
48
|
+
sourceProductIds: string[];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 赠品解析回调签名(SDK → OS 的返回值)。
|
|
52
|
+
*
|
|
53
|
+
* - 单选项:SDK 内部自动构造商品,不弹窗
|
|
54
|
+
* - 多选项:SDK 弹窗等用户确认后构造
|
|
55
|
+
* - 用户取消:抛错或返回空数组(OS 视为放弃本次添加)
|
|
56
|
+
*
|
|
57
|
+
* 返回的 OrderProduct 必须是完整的购物车行(含 metadata._giftInfo 标记),OS 会直接 push 入 tempOrder.products。
|
|
58
|
+
*/
|
|
59
|
+
export type OrderGiftSelectResolver = (ctx: OrderGiftSelectContext) => Promise<Partial<OrderProduct>[] | null>;
|
|
60
|
+
/** 未满足的促销策略(购物车底部 alert 渲染源) */
|
|
61
|
+
export interface OrderUnfulfilledPromotion {
|
|
62
|
+
strategyId: string;
|
|
63
|
+
strategyName: string | Record<string, string>;
|
|
64
|
+
actionType: string;
|
|
65
|
+
needQuantity: number;
|
|
66
|
+
currentQuantity: number;
|
|
67
|
+
requiredQuantity: number;
|
|
68
|
+
eligibleProducts: Array<{
|
|
69
|
+
product_id: number;
|
|
70
|
+
product_variant_id: number;
|
|
71
|
+
}>;
|
|
72
|
+
strategyMetadata?: any;
|
|
73
|
+
display?: {
|
|
74
|
+
text: string | Record<string, string>;
|
|
75
|
+
type: string;
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/** 上一次 applyPromotion 计算出的赠品操作(仅供调试/读取使用) */
|
|
79
|
+
export interface OrderLastGiftActions {
|
|
80
|
+
toAdd: Array<{
|
|
81
|
+
strategyId: string;
|
|
82
|
+
strategyName: string | Record<string, string>;
|
|
83
|
+
giftCount: number;
|
|
84
|
+
giftOptions: Array<{
|
|
85
|
+
product_id: number;
|
|
86
|
+
product_variant_id: number;
|
|
87
|
+
}>;
|
|
88
|
+
sourceProductIds: string[];
|
|
89
|
+
}>;
|
|
90
|
+
toReduce: Array<{
|
|
91
|
+
strategyId: string;
|
|
92
|
+
items: Array<{
|
|
93
|
+
uid: string;
|
|
94
|
+
currentQuantity: number;
|
|
95
|
+
newQuantity: number;
|
|
96
|
+
}>;
|
|
97
|
+
}>;
|
|
98
|
+
toRemove: string[];
|
|
99
|
+
}
|
|
100
|
+
/** onPromotionApplied 事件 payload */
|
|
101
|
+
export interface OrderPromotionAppliedPayload {
|
|
102
|
+
unfulfilledPromotions: OrderUnfulfilledPromotion[];
|
|
103
|
+
giftActions: OrderLastGiftActions;
|
|
104
|
+
/** evaluator 返回的赠品摘要 */
|
|
105
|
+
gifts: Array<{
|
|
106
|
+
strategyId: string;
|
|
107
|
+
strategyName: string | Record<string, string>;
|
|
108
|
+
giftCount: number;
|
|
109
|
+
giftOptions: Array<{
|
|
110
|
+
product_id: number;
|
|
111
|
+
product_variant_id: number;
|
|
112
|
+
}>;
|
|
113
|
+
}>;
|
|
114
|
+
/** 处理后的商品列表 */
|
|
115
|
+
products: OrderProduct[];
|
|
17
116
|
}
|
|
18
117
|
/**
|
|
19
118
|
* tempOrder 上的下单客户协议字段视图。
|
|
@@ -561,6 +660,46 @@ export interface OrderModuleAPI {
|
|
|
561
660
|
}) => Promise<Discount[]>;
|
|
562
661
|
getDiscountList: () => Discount[];
|
|
563
662
|
applyDiscount: () => void;
|
|
663
|
+
/**
|
|
664
|
+
* 注入促销评估器。
|
|
665
|
+
*
|
|
666
|
+
* SalesSdkProvider 在初始化 bookingTicket 后会自动从 `appHelper.utils.promotionEvaluator`
|
|
667
|
+
* 读取实例并调用本方法注入。host 透明,业务 UI 不感知。
|
|
668
|
+
*
|
|
669
|
+
* @example
|
|
670
|
+
* order.setPromotionEvaluator(appHelper.utils.promotionEvaluator);
|
|
671
|
+
*/
|
|
672
|
+
setPromotionEvaluator: (evaluator: OrderPromotionEvaluator | null) => void;
|
|
673
|
+
/**
|
|
674
|
+
* 注入赠品选择 resolver。OS 在 applyPromotion 内部当需要新增赠品时会 await 调用 resolver。
|
|
675
|
+
*
|
|
676
|
+
* 缺省时 OS 会 `console.warn` 并跳过新增(既有赠品的 reduce/remove 仍会执行)。
|
|
677
|
+
*
|
|
678
|
+
* @example
|
|
679
|
+
* order.setGiftSelectResolver((ctx) => giftSelectBridge.request(ctx));
|
|
680
|
+
*/
|
|
681
|
+
setGiftSelectResolver: (resolver: OrderGiftSelectResolver | null) => void;
|
|
682
|
+
/**
|
|
683
|
+
* 为商品目录追加促销标签。
|
|
684
|
+
*
|
|
685
|
+
* @example
|
|
686
|
+
* const taggedProducts = order.appendPromotionTags(products);
|
|
687
|
+
*/
|
|
688
|
+
appendPromotionTags: <T extends Record<string, any>>(products: T[]) => T[];
|
|
689
|
+
/**
|
|
690
|
+
* 应用购物车促销(评估 → 差异化赠品 → 写回 tempOrder.products → emit onPromotionApplied)。
|
|
691
|
+
*
|
|
692
|
+
* 自动在 addProductToOrder / updateOrderProductQuantity / removeProductFromOrder /
|
|
693
|
+
* clearOrderCartLines / setOrderCustomer 等写路径末尾触发,业务一般无需手动调用。
|
|
694
|
+
*
|
|
695
|
+
* 多选项赠品依赖 giftSelectResolver;如未注入则跳过且 console.warn。
|
|
696
|
+
* 内部以 `_isApplyingPromotion` flag 防递归。
|
|
697
|
+
*/
|
|
698
|
+
applyPromotion: () => Promise<void>;
|
|
699
|
+
/** 读取上次 applyPromotion 产出的未满足促销提示 */
|
|
700
|
+
getUnfulfilledPromotions: () => OrderUnfulfilledPromotion[];
|
|
701
|
+
/** 读取上次 applyPromotion 产出的赠品操作 diff(toAdd / toReduce / toRemove) */
|
|
702
|
+
getLastGiftActions: () => OrderLastGiftActions | null;
|
|
564
703
|
/**
|
|
565
704
|
* 取消订单
|
|
566
705
|
* @param params 取消订单参数
|
|
@@ -28,6 +28,7 @@ var OrderHooks = /* @__PURE__ */ ((OrderHooks2) => {
|
|
|
28
28
|
OrderHooks2["OnOrderCancel"] = "order:onOrderCancel";
|
|
29
29
|
OrderHooks2["OnOrderStatusChange"] = "order:onOrderStatusChange";
|
|
30
30
|
OrderHooks2["OnOrderCustomerChange"] = "order:onOrderCustomerChange";
|
|
31
|
+
OrderHooks2["OnPromotionApplied"] = "order:onPromotionApplied";
|
|
31
32
|
return OrderHooks2;
|
|
32
33
|
})(OrderHooks || {});
|
|
33
34
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -85,6 +85,17 @@ export declare function resolveManualOverrideLineOriginalPrice(params: {
|
|
|
85
85
|
/** 已有行级 original_price(origin 被裁剪时兜底划线价) */
|
|
86
86
|
lineOriginalPrice?: string | number | null;
|
|
87
87
|
}, fallbackCompositeOriginal: string): string;
|
|
88
|
+
/**
|
|
89
|
+
* 计算商品行「每单位有效折扣」:discount_list 合计 + 被 Rules 剥离但仍 inPromotion 的差额。
|
|
90
|
+
*
|
|
91
|
+
* Rules.calcDiscount 会把 type=promotion 从 discount_list 去掉,但 metadata._promotion 仍保留;
|
|
92
|
+
* applyProductDiscountPrices 必须回读 _promotion,否则抬价/降价都会在第二步被 source 原价覆盖。
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* resolveEffectivePerUnitDiscount({ metadata: { _promotion: { inPromotion: true, originalPrice: 1, finalPrice: 5 } }, discount_list: [] });
|
|
96
|
+
* // => -4
|
|
97
|
+
*/
|
|
98
|
+
export declare function resolveEffectivePerUnitDiscount(product: Record<string, any>): number;
|
|
88
99
|
export declare function createDefaultOrderRulesHooks(): RulesParamsHooks;
|
|
89
100
|
/**
|
|
90
101
|
* 通过 session 类商品的开始时间结束时间生成商品的时长
|