@pisell/pisellos 2.2.273 → 2.2.275
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/BookingContext/utils/buildCacheItemFromOrderLine.js +12 -10
- package/dist/modules/Order/index.d.ts +7 -6
- package/dist/modules/Order/index.js +217 -115
- package/dist/modules/Order/types.d.ts +23 -3
- package/dist/modules/Order/types.js +11 -0
- package/dist/modules/Payment/index.js +1 -1
- package/dist/modules/Payment/types.d.ts +81 -4
- package/dist/modules/Payment/types.js +18 -3
- package/dist/modules/Payment/walletpass.d.ts +38 -1
- package/dist/modules/Payment/walletpass.js +917 -114
- package/dist/modules/Rules/index.d.ts +2 -0
- package/dist/modules/Rules/index.js +78 -64
- package/dist/modules/SalesSummary/utils.js +7 -1
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.js +27 -18
- package/dist/server/modules/index.d.ts +1 -1
- package/dist/server/modules/order/index.d.ts +16 -0
- package/dist/server/modules/order/index.js +964 -776
- package/dist/server/modules/order/types.d.ts +14 -0
- package/dist/server/modules/order/types.js +6 -0
- package/dist/solution/BaseSales/index.d.ts +80 -15
- package/dist/solution/BaseSales/index.js +1498 -444
- package/dist/solution/BaseSales/types.d.ts +55 -1
- package/dist/solution/BaseSales/types.js +2 -1
- package/dist/solution/BaseSales/utils/cartPromotion.js +8 -3
- package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +6 -3
- package/dist/solution/BookingByStep/index.d.ts +1 -1
- package/dist/solution/BookingTicket/index.d.ts +1 -1
- package/dist/solution/BookingTicket/index.js +12 -3
- package/dist/solution/BookingTicket/types.d.ts +4 -0
- package/dist/solution/BookingTicket/utils/cartView.js +44 -32
- package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +8 -0
- package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.js +173 -83
- package/dist/solution/Sales/index.d.ts +1 -0
- package/dist/solution/Sales/index.js +16 -2
- package/dist/solution/VenueBooking/index.d.ts +1 -0
- package/lib/model/strategy/adapter/promotion/index.js +0 -51
- package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +3 -1
- package/lib/modules/Order/index.d.ts +7 -6
- package/lib/modules/Order/index.js +127 -20
- package/lib/modules/Order/types.d.ts +23 -3
- package/lib/modules/Payment/index.js +1 -1
- package/lib/modules/Payment/types.d.ts +81 -4
- package/lib/modules/Payment/types.js +4 -3
- package/lib/modules/Payment/walletpass.d.ts +38 -1
- package/lib/modules/Payment/walletpass.js +730 -21
- package/lib/modules/Rules/index.d.ts +2 -0
- package/lib/modules/Rules/index.js +24 -14
- package/lib/modules/SalesSummary/utils.js +5 -1
- package/lib/server/index.d.ts +4 -0
- package/lib/server/index.js +14 -6
- package/lib/server/modules/index.d.ts +1 -1
- package/lib/server/modules/order/index.d.ts +16 -0
- package/lib/server/modules/order/index.js +140 -4
- package/lib/server/modules/order/types.d.ts +14 -0
- package/lib/server/modules/order/types.js +1 -0
- package/lib/solution/BaseSales/index.d.ts +80 -15
- package/lib/solution/BaseSales/index.js +712 -48
- package/lib/solution/BaseSales/types.d.ts +55 -1
- package/lib/solution/BaseSales/types.js +2 -1
- package/lib/solution/BaseSales/utils/cartPromotion.js +8 -2
- package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +3 -2
- package/lib/solution/BookingByStep/index.d.ts +1 -1
- package/lib/solution/BookingTicket/index.d.ts +1 -1
- package/lib/solution/BookingTicket/index.js +4 -1
- package/lib/solution/BookingTicket/types.d.ts +4 -0
- package/lib/solution/BookingTicket/utils/cartView.js +43 -30
- package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +8 -0
- package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.js +37 -5
- package/lib/solution/Sales/index.d.ts +1 -0
- package/lib/solution/Sales/index.js +15 -3
- package/lib/solution/VenueBooking/index.d.ts +1 -0
- package/package.json +1 -1
|
@@ -27,6 +27,8 @@ export declare class RulesModule extends BaseModule implements Module, RulesModu
|
|
|
27
27
|
}): {
|
|
28
28
|
isAvailable: boolean;
|
|
29
29
|
discountList: Discount[];
|
|
30
|
+
/** 完整规则计算后的列表;调用方可用于保留暂时无适用商品的扫码卡券。 */
|
|
31
|
+
evaluatedDiscountList?: Discount[];
|
|
30
32
|
productList: any[];
|
|
31
33
|
unavailableReason?: UnavailableReason;
|
|
32
34
|
};
|
|
@@ -94,7 +94,7 @@ var RulesModule = class extends import_BaseModule.BaseModule {
|
|
|
94
94
|
productList
|
|
95
95
|
};
|
|
96
96
|
}
|
|
97
|
-
if (productList.every((item) => {
|
|
97
|
+
if (productList.length > 0 && productList.every((item) => {
|
|
98
98
|
var _a;
|
|
99
99
|
const product = this.hooks.getProduct(item);
|
|
100
100
|
return product.booking_id && (((_a = product.discount_list) == null ? void 0 : _a.length) || product.total == 0);
|
|
@@ -152,6 +152,7 @@ var RulesModule = class extends import_BaseModule.BaseModule {
|
|
|
152
152
|
return {
|
|
153
153
|
isAvailable: hasApplicableDiscount,
|
|
154
154
|
discountList: hasApplicableDiscount ? result.discountList : oldDiscountList,
|
|
155
|
+
evaluatedDiscountList: result.discountList,
|
|
155
156
|
productList: hasApplicableDiscount ? result.productList : productList,
|
|
156
157
|
unavailableReason
|
|
157
158
|
};
|
|
@@ -181,9 +182,13 @@ var RulesModule = class extends import_BaseModule.BaseModule {
|
|
|
181
182
|
getUnavailableReason(discountList, productList) {
|
|
182
183
|
var _a;
|
|
183
184
|
for (const discount of discountList) {
|
|
184
|
-
|
|
185
|
+
const bookingTimes = productList.length ? productList.map((item) => {
|
|
185
186
|
const product = this.hooks.getProduct(item);
|
|
186
|
-
|
|
187
|
+
return ((product == null ? void 0 : product.startDate) || (0, import_dayjs.default)()).format(
|
|
188
|
+
"YYYY-MM-DD HH:mm:ss"
|
|
189
|
+
);
|
|
190
|
+
}) : [(0, import_dayjs.default)().format("YYYY-MM-DD HH:mm:ss")];
|
|
191
|
+
for (const bookingTime of bookingTimes) {
|
|
187
192
|
const filteredList = (0, import_utils.filterDiscountListByBookingTime)([discount], bookingTime);
|
|
188
193
|
if (filteredList.length === 0) {
|
|
189
194
|
return import_types.UnavailableReason.TimeLimit;
|
|
@@ -238,7 +243,7 @@ var RulesModule = class extends import_BaseModule.BaseModule {
|
|
|
238
243
|
const discountType = selectedDiscount.tag || selectedDiscount.type;
|
|
239
244
|
return selectedDiscount.isEditMode === true && selectedDiscount.isSelected === true && ["good_pass", "discount_card", "product_discount_card"].includes(discountType);
|
|
240
245
|
};
|
|
241
|
-
const
|
|
246
|
+
const resolveReapplyBookingDiscountPercent = (originProduct, selectedDiscount) => {
|
|
242
247
|
var _a2;
|
|
243
248
|
if (!shouldReapplyBookingDiscount(originProduct, selectedDiscount))
|
|
244
249
|
return void 0;
|
|
@@ -312,6 +317,9 @@ var RulesModule = class extends import_BaseModule.BaseModule {
|
|
|
312
317
|
bundleIndex,
|
|
313
318
|
// 虚拟商品属性
|
|
314
319
|
price: Number(bundleItem.price || 0),
|
|
320
|
+
// 新增商品运行态通常带 _bundle_product_id;订单 hydrate 后只有
|
|
321
|
+
// bundle_product_id。Change time 必须兼容两种结构,否则 bundle
|
|
322
|
+
// 子项无法命中折扣卡的 limited_relation_product_data.product_ids。
|
|
315
323
|
id: bundleItem.bundle_product_id ?? bundleItem._bundle_product_id ?? bundleItem.product_id,
|
|
316
324
|
_id: `${product._id}_bundle_${bundleIndex}`,
|
|
317
325
|
parentId: product._id,
|
|
@@ -772,13 +780,6 @@ var RulesModule = class extends import_BaseModule.BaseModule {
|
|
|
772
780
|
if (isPersistedProduct && hasExistingWalletDiscount) {
|
|
773
781
|
if (flatItem.type === "main") {
|
|
774
782
|
processedProductsMap.set(product._id, [originProduct]);
|
|
775
|
-
} else {
|
|
776
|
-
processedFlatItemsMap.set(flatItem._id, [
|
|
777
|
-
{
|
|
778
|
-
...flatItem,
|
|
779
|
-
processed: true
|
|
780
|
-
}
|
|
781
|
-
]);
|
|
782
783
|
}
|
|
783
784
|
return;
|
|
784
785
|
}
|
|
@@ -1084,8 +1085,8 @@ var RulesModule = class extends import_BaseModule.BaseModule {
|
|
|
1084
1085
|
amount = productAllocation.discountAmount;
|
|
1085
1086
|
productDiscountDifference = productAllocation.difference;
|
|
1086
1087
|
mainProductSellingPrice = Math.max(new import_decimal.default(product.price).minus(amount).toNumber(), 0);
|
|
1087
|
-
} else if (
|
|
1088
|
-
const percent =
|
|
1088
|
+
} else if (resolveReapplyBookingDiscountPercent(originProduct, selectedDiscount2) !== void 0) {
|
|
1089
|
+
const percent = resolveReapplyBookingDiscountPercent(originProduct, selectedDiscount2);
|
|
1089
1090
|
reapplyDiscountPercent = new import_decimal.default(percent).toFixed(2);
|
|
1090
1091
|
mainProductSellingPrice = new import_decimal.default(product.price || 0).mul(new import_decimal.default(100).minus(percent)).div(100).toDecimalPlaces(2).toNumber();
|
|
1091
1092
|
amount = new import_decimal.default(product.price).minus(mainProductSellingPrice).toNumber();
|
|
@@ -1258,10 +1259,19 @@ var RulesModule = class extends import_BaseModule.BaseModule {
|
|
|
1258
1259
|
let targetProductTotal;
|
|
1259
1260
|
let fixedAmountPerItem;
|
|
1260
1261
|
let productDiscountDifference;
|
|
1262
|
+
let reapplyDiscountPercent;
|
|
1261
1263
|
if (isOrderLevel && productAllocation) {
|
|
1262
1264
|
fixedAmountPerItem = productAllocation.discountAmount;
|
|
1263
1265
|
productDiscountDifference = productAllocation.difference;
|
|
1264
1266
|
targetProductTotal = Math.max(new import_decimal.default(productOriginTotal).minus(fixedAmountPerItem).toNumber(), 0);
|
|
1267
|
+
} else if (resolveReapplyBookingDiscountPercent(originProduct, selectedDiscount2) !== void 0) {
|
|
1268
|
+
const percent = resolveReapplyBookingDiscountPercent(
|
|
1269
|
+
originProduct,
|
|
1270
|
+
selectedDiscount2
|
|
1271
|
+
);
|
|
1272
|
+
reapplyDiscountPercent = new import_decimal.default(percent).toFixed(2);
|
|
1273
|
+
targetProductTotal = new import_decimal.default(productOriginTotal).mul(new import_decimal.default(100).minus(percent)).div(100).toDecimalPlaces(2).toNumber();
|
|
1274
|
+
fixedAmountPerItem = new import_decimal.default(productOriginTotal).minus(targetProductTotal).toNumber();
|
|
1265
1275
|
} else {
|
|
1266
1276
|
targetProductTotal = (0, import_utils.getDiscountAmount)(
|
|
1267
1277
|
selectedDiscount2,
|
|
@@ -1283,7 +1293,7 @@ var RulesModule = class extends import_BaseModule.BaseModule {
|
|
|
1283
1293
|
title: selectedDiscount2.format_title,
|
|
1284
1294
|
original_amount: product.original_price,
|
|
1285
1295
|
product_id: product.id || product.product_id,
|
|
1286
|
-
percent: selectedDiscount2.par_value,
|
|
1296
|
+
percent: reapplyDiscountPercent ?? selectedDiscount2.par_value,
|
|
1287
1297
|
discount_product_id: selectedDiscount2.product_id
|
|
1288
1298
|
},
|
|
1289
1299
|
metadata: {
|
|
@@ -99,7 +99,11 @@ function getUnitOriginalTotal(product) {
|
|
|
99
99
|
function isGeneratedVoucherProduct(product) {
|
|
100
100
|
const record = product;
|
|
101
101
|
const metadata = (record == null ? void 0 : record.metadata) || {};
|
|
102
|
-
|
|
102
|
+
const isGeneratedChild = metadata.parent_order_detail_id !== void 0 || metadata.gift_card_type !== void 0;
|
|
103
|
+
if (isGeneratedChild)
|
|
104
|
+
return true;
|
|
105
|
+
const isPersisted = (record == null ? void 0 : record.order_detail_id) !== void 0 && (record == null ? void 0 : record.order_detail_id) !== null;
|
|
106
|
+
return (record == null ? void 0 : record.extension_type) === "product_voucher" && !isPersisted;
|
|
103
107
|
}
|
|
104
108
|
function buildSurchargeServiceItems(products) {
|
|
105
109
|
return products.map((product) => {
|
package/lib/server/index.d.ts
CHANGED
|
@@ -498,6 +498,10 @@ declare class Server {
|
|
|
498
498
|
private handleOrderDraftSubmit;
|
|
499
499
|
private handlePendingSyncCheckoutOrder;
|
|
500
500
|
private buildPendingSyncCheckoutOrder;
|
|
501
|
+
/**
|
|
502
|
+
* Android 结账自动小票由当前设备设置控制;其它平台保持原有行为。
|
|
503
|
+
*/
|
|
504
|
+
private shouldAutoPrintCheckoutReceipt;
|
|
501
505
|
private shouldBuildSmallTicketData;
|
|
502
506
|
private shouldPrintSyncedOrder;
|
|
503
507
|
private hasPaymentStatus;
|
package/lib/server/index.js
CHANGED
|
@@ -65,6 +65,7 @@ var PRODUCT_TITLE_SNAPSHOT_KEYS = [
|
|
|
65
65
|
"kitchen",
|
|
66
66
|
"original"
|
|
67
67
|
];
|
|
68
|
+
var AUTO_PRINT_STORAGE_KEY = "auto_print";
|
|
68
69
|
var Server = class {
|
|
69
70
|
constructor(core) {
|
|
70
71
|
this.quotationAvailableCache = /* @__PURE__ */ new Map();
|
|
@@ -3698,7 +3699,6 @@ var Server = class {
|
|
|
3698
3699
|
});
|
|
3699
3700
|
}
|
|
3700
3701
|
async handleOrderCheckoutSubmit(params) {
|
|
3701
|
-
var _a;
|
|
3702
3702
|
const { backendPath, data, title } = params;
|
|
3703
3703
|
this.logInfo(`${title}: 开始处理`, {
|
|
3704
3704
|
backendPath,
|
|
@@ -3723,15 +3723,14 @@ var Server = class {
|
|
|
3723
3723
|
});
|
|
3724
3724
|
if ((pendingResult == null ? void 0 : pendingResult.status) === true) {
|
|
3725
3725
|
const pendingOrder = pendingResult.data;
|
|
3726
|
-
|
|
3727
|
-
const isAndroid = platform === "android";
|
|
3728
|
-
if (pendingOrder && this.shouldBuildSmallTicketData(checkoutData) && this.isSalesOrderFullyPaid(pendingOrder) && // TODO 这里先加个兼容,如果是安卓,则需要 type=virtual 才走打印,如果是苹果则无限制
|
|
3729
|
-
(isAndroid ? checkoutData.type === "virtual" : true)) {
|
|
3726
|
+
if (pendingOrder && this.shouldBuildSmallTicketData(checkoutData) && this.isSalesOrderFullyPaid(pendingOrder) && this.shouldAutoPrintCheckoutReceipt()) {
|
|
3730
3727
|
await this.dispatchLocalReceiptPrint({
|
|
3731
3728
|
checkoutData,
|
|
3732
3729
|
order: pendingOrder,
|
|
3733
3730
|
response: pendingResult,
|
|
3734
|
-
requireFullyPaid: true
|
|
3731
|
+
requireFullyPaid: true,
|
|
3732
|
+
// 自动小票固定使用 print_all 的收据类型 0,不随订单 type 改成券/手环类型 99。
|
|
3733
|
+
isManualPrint: true
|
|
3735
3734
|
});
|
|
3736
3735
|
}
|
|
3737
3736
|
await this.dispatchCheckoutSyncTask({
|
|
@@ -3949,6 +3948,15 @@ var Server = class {
|
|
|
3949
3948
|
{ requireFullyPaid: true, productMap }
|
|
3950
3949
|
);
|
|
3951
3950
|
}
|
|
3951
|
+
/**
|
|
3952
|
+
* Android 结账自动小票由当前设备设置控制;其它平台保持原有行为。
|
|
3953
|
+
*/
|
|
3954
|
+
shouldAutoPrintCheckoutReceipt() {
|
|
3955
|
+
var _a, _b, _c, _d, _e;
|
|
3956
|
+
if (((_b = (_a = this.core.context) == null ? void 0 : _a.getPlatform) == null ? void 0 : _b.call(_a)) !== "android")
|
|
3957
|
+
return true;
|
|
3958
|
+
return ((_e = (_d = (_c = this.app) == null ? void 0 : _c.storage) == null ? void 0 : _d.getStorage) == null ? void 0 : _e.call(_d, AUTO_PRINT_STORAGE_KEY)) === "true";
|
|
3959
|
+
}
|
|
3952
3960
|
shouldBuildSmallTicketData(order) {
|
|
3953
3961
|
if (!order || typeof order !== "object")
|
|
3954
3962
|
return false;
|
|
@@ -14,7 +14,7 @@ export { QuotationHooks } from './quotation/types';
|
|
|
14
14
|
export { ScheduleModuleEx } from './schedule';
|
|
15
15
|
export type { ScheduleState, ScheduleItem } from './schedule/types';
|
|
16
16
|
export { OrderModule } from './order';
|
|
17
|
-
export type { OrderState, OrderData, OrderId, OrderSummary, OrderBookingItem, OrderProductLineItem, OrderPaymentItem, OrderSurchargeItem, OrderProductDiscountItem, OrderWithoutBookings, BookingData, OrderFilters, BookingFilters, OrderFilterResult, OrderFilterRejectDetail, OrderFilterRejectedEntry, BookingFilterResult, OrderModulePagedResult, } from './order/types';
|
|
17
|
+
export type { OrderState, OrderData, OrderId, OrderSummary, OrderBookingItem, OrderProductLineItem, OrderPaymentItem, OrderSurchargeItem, OrderProductDiscountItem, OrderWithoutBookings, BookingData, OrderFilters, BookingFilters, OrderFilterResult, OrderFilterRejectDetail, OrderFilterRejectedEntry, BookingFilterResult, OrderModulePagedResult, RemoteProductMembershipChangedPayload, RemoteProductMembershipChangeSource, } from './order/types';
|
|
18
18
|
export { OrderHooks } from './order/types';
|
|
19
19
|
export { PaymentServerModule } from './payment';
|
|
20
20
|
export type { PaymentMethod, PaymentMethodsChangedEventData, PaymentServerState } from './payment/types';
|
|
@@ -30,6 +30,10 @@ export declare class OrderModule extends BaseModule implements Module {
|
|
|
30
30
|
* await this.emitOrdersChanged([freshOrder], 'pubsub.bodyUpsert')
|
|
31
31
|
*/
|
|
32
32
|
private emitOrdersChanged;
|
|
33
|
+
/**
|
|
34
|
+
* 广播远端商品成员增删。监听器异常必须与订单落库和原有订单广播隔离。
|
|
35
|
+
*/
|
|
36
|
+
private emitRemoteProductMembershipChanges;
|
|
33
37
|
initialize(core: PisellCore, options?: ModuleOptions): Promise<void>;
|
|
34
38
|
/**
|
|
35
39
|
* 记录信息日志
|
|
@@ -231,6 +235,18 @@ export declare class OrderModule extends BaseModule implements Module {
|
|
|
231
235
|
*/
|
|
232
236
|
private lookupOrderIndexByIdentityKeys;
|
|
233
237
|
private isRemoteEchoMergeSource;
|
|
238
|
+
private getProductMembershipKey;
|
|
239
|
+
private doProductsHaveStableMembershipIdentity;
|
|
240
|
+
/**
|
|
241
|
+
* 冲突提示关注逻辑商品行,而不是服务端 checkout 过程中可能被重建的 detail 记录。
|
|
242
|
+
* 通用集合合并仍以 order_detail_id 为事实主键;这里只允许相同稳定 UID 跨 detail id 对齐,
|
|
243
|
+
* 避免本机 checkout 回声被误判成“一删一增”。
|
|
244
|
+
*/
|
|
245
|
+
private isSameProductMembershipItem;
|
|
246
|
+
/**
|
|
247
|
+
* 对比合并前后的商品成员集合。商品字段修改与数组重排不会产生 diff。
|
|
248
|
+
*/
|
|
249
|
+
private getRemoteProductMembershipChange;
|
|
234
250
|
/**
|
|
235
251
|
* 将订单的全部身份键登记到索引并指向其在列表中的下标。
|
|
236
252
|
* 合并后订单新增的身份字段(如挂单同步后拿到 order_id)也会被登记。
|
|
@@ -89,6 +89,24 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
89
89
|
};
|
|
90
90
|
await this.core.effects.emit(import_types.OrderHooks.onOrdersChanged, payload);
|
|
91
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* 广播远端商品成员增删。监听器异常必须与订单落库和原有订单广播隔离。
|
|
94
|
+
*/
|
|
95
|
+
async emitRemoteProductMembershipChanges(changes) {
|
|
96
|
+
for (const payload of changes) {
|
|
97
|
+
try {
|
|
98
|
+
await this.core.effects.emit(import_types.OrderHooks.onRemoteProductMembershipChanged, payload);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
this.logError("广播远端订单商品成员变更失败", {
|
|
101
|
+
order_id: payload.order_id,
|
|
102
|
+
source: payload.source,
|
|
103
|
+
added_product_count: payload.added_product_keys.length,
|
|
104
|
+
removed_product_count: payload.removed_product_keys.length,
|
|
105
|
+
error: error instanceof Error ? error.message : String(error)
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
92
110
|
async initialize(core, options) {
|
|
93
111
|
var _a;
|
|
94
112
|
this.core = core;
|
|
@@ -314,7 +332,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
314
332
|
* @example
|
|
315
333
|
* const { toWrite, skipped } = this.filterRedundantPatchOrders('pubsub.bodyUpsert', orders, mergeActions)
|
|
316
334
|
*/
|
|
317
|
-
filterRedundantPatchOrders(source, orders, mergeActions) {
|
|
335
|
+
filterRedundantPatchOrders(source, orders, mergeActions, forceWriteStorageKeys = /* @__PURE__ */ new Set()) {
|
|
318
336
|
const eligibleSkipSources = /* @__PURE__ */ new Set(["pubsub.bodyUpsert", "pubsub.httpRefresh"]);
|
|
319
337
|
if (!eligibleSkipSources.has(source)) {
|
|
320
338
|
return { toWrite: orders, skipped: [] };
|
|
@@ -334,6 +352,10 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
334
352
|
toWrite.push(order);
|
|
335
353
|
continue;
|
|
336
354
|
}
|
|
355
|
+
if (forceWriteStorageKeys.has(storageKey)) {
|
|
356
|
+
toWrite.push(order);
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
337
359
|
const recent = this.recentSqliteWriteByStorageKey.get(storageKey);
|
|
338
360
|
if (recent && ORDER_BUSINESS_WRITE_SOURCES.has(recent.source) && now - recent.ts < ORDER_SQLITE_DEDUPE_MS) {
|
|
339
361
|
skipped.push({
|
|
@@ -1484,10 +1506,20 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
1484
1506
|
});
|
|
1485
1507
|
this.logDuplicateOrders(`${source}.beforeMerge`, this.store.list);
|
|
1486
1508
|
const normalizedFreshOrders = [];
|
|
1509
|
+
const incomingProductsHaveStableIdentity = /* @__PURE__ */ new Map();
|
|
1487
1510
|
for (const order of freshOrders) {
|
|
1488
1511
|
const id = order == null ? void 0 : order.order_id;
|
|
1489
1512
|
if (id === void 0 || id === null)
|
|
1490
1513
|
continue;
|
|
1514
|
+
const rawProducts = order.products;
|
|
1515
|
+
if (Array.isArray(rawProducts)) {
|
|
1516
|
+
const allProductsHaveStableIdentity = this.doProductsHaveStableMembershipIdentity(rawProducts);
|
|
1517
|
+
const orderIdKey = this.getIdKey(id);
|
|
1518
|
+
incomingProductsHaveStableIdentity.set(
|
|
1519
|
+
orderIdKey,
|
|
1520
|
+
(incomingProductsHaveStableIdentity.get(orderIdKey) ?? true) && allProductsHaveStableIdentity
|
|
1521
|
+
);
|
|
1522
|
+
}
|
|
1491
1523
|
normalizedFreshOrders.push(this.normalizeRemoteSyncedOrder(order));
|
|
1492
1524
|
}
|
|
1493
1525
|
const uniqueFreshOrders = this.compactOrderListByIdentity(
|
|
@@ -1496,7 +1528,14 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
1496
1528
|
).list;
|
|
1497
1529
|
const uniqueFreshCount = uniqueFreshOrders.length;
|
|
1498
1530
|
const patchedOrders = [];
|
|
1531
|
+
const remoteProductMembershipChanges = [];
|
|
1532
|
+
const forceWriteStorageKeys = /* @__PURE__ */ new Set();
|
|
1499
1533
|
const mergeActions = {};
|
|
1534
|
+
const existingProductsHaveStableIdentity = this.store.list.map(
|
|
1535
|
+
(order) => this.doProductsHaveStableMembershipIdentity(
|
|
1536
|
+
order.products
|
|
1537
|
+
)
|
|
1538
|
+
);
|
|
1500
1539
|
const updatedList = this.store.list.map(
|
|
1501
1540
|
(order) => this.normalizeOrderForMemoryList(order, `${source}.existingMemory`)
|
|
1502
1541
|
);
|
|
@@ -1540,6 +1579,17 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
1540
1579
|
continue;
|
|
1541
1580
|
}
|
|
1542
1581
|
let mergedOrder = this.mergeOrderRecords(existingOrder, fresh);
|
|
1582
|
+
const freshOrderId = fresh.order_id;
|
|
1583
|
+
const incomingProductsAreStable = freshOrderId !== void 0 && freshOrderId !== null && incomingProductsHaveStableIdentity.get(this.getIdKey(freshOrderId)) !== false;
|
|
1584
|
+
let membershipChange = null;
|
|
1585
|
+
if (Array.isArray(fresh.products) && freshOrderId !== void 0 && freshOrderId !== null && existingProductsHaveStableIdentity[matchIndex] !== false && incomingProductsAreStable) {
|
|
1586
|
+
membershipChange = this.getRemoteProductMembershipChange(
|
|
1587
|
+
existingOrder,
|
|
1588
|
+
mergedOrder,
|
|
1589
|
+
source
|
|
1590
|
+
);
|
|
1591
|
+
}
|
|
1592
|
+
let mergedProductsHaveStableIdentity = existingProductsHaveStableIdentity[matchIndex] !== false && incomingProductsAreStable;
|
|
1543
1593
|
let adjustedMatchIndex = matchIndex;
|
|
1544
1594
|
const duplicateIndexes = /* @__PURE__ */ new Set();
|
|
1545
1595
|
for (const key of freshIdentityKeys) {
|
|
@@ -1551,7 +1601,9 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
1551
1601
|
if (duplicateIndexes.size > 0) {
|
|
1552
1602
|
for (const duplicateIndex of [...duplicateIndexes].sort((left, right) => right - left)) {
|
|
1553
1603
|
mergedOrder = this.mergeOrderRecords(mergedOrder, updatedList[duplicateIndex]);
|
|
1604
|
+
mergedProductsHaveStableIdentity = mergedProductsHaveStableIdentity && existingProductsHaveStableIdentity[duplicateIndex] !== false;
|
|
1554
1605
|
updatedList.splice(duplicateIndex, 1);
|
|
1606
|
+
existingProductsHaveStableIdentity.splice(duplicateIndex, 1);
|
|
1555
1607
|
if (duplicateIndex < adjustedMatchIndex)
|
|
1556
1608
|
adjustedMatchIndex -= 1;
|
|
1557
1609
|
}
|
|
@@ -1564,6 +1616,13 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
1564
1616
|
}
|
|
1565
1617
|
}
|
|
1566
1618
|
updatedList[adjustedMatchIndex] = mergedOrder;
|
|
1619
|
+
existingProductsHaveStableIdentity[adjustedMatchIndex] = mergedProductsHaveStableIdentity;
|
|
1620
|
+
if (membershipChange) {
|
|
1621
|
+
remoteProductMembershipChanges.push(membershipChange);
|
|
1622
|
+
const mergedStorageKey = this.getOrderStorageKey(mergedOrder);
|
|
1623
|
+
if (mergedStorageKey)
|
|
1624
|
+
forceWriteStorageKeys.add(mergedStorageKey);
|
|
1625
|
+
}
|
|
1567
1626
|
this.registerOrderIdentityKeys(identityKeyToIndex, mergedOrder, adjustedMatchIndex);
|
|
1568
1627
|
patchedOrders.push(mergedOrder);
|
|
1569
1628
|
if (mergeKey)
|
|
@@ -1572,6 +1631,9 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
1572
1631
|
}
|
|
1573
1632
|
const insertIndex = updatedList.length;
|
|
1574
1633
|
updatedList.push(fresh);
|
|
1634
|
+
existingProductsHaveStableIdentity.push(
|
|
1635
|
+
fresh.order_id !== void 0 && fresh.order_id !== null && incomingProductsHaveStableIdentity.get(this.getIdKey(fresh.order_id)) !== false
|
|
1636
|
+
);
|
|
1575
1637
|
this.registerOrderIdentityKeys(identityKeyToIndex, fresh, insertIndex);
|
|
1576
1638
|
patchedOrders.push(fresh);
|
|
1577
1639
|
if (mergeKey)
|
|
@@ -1586,13 +1648,20 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
1586
1648
|
});
|
|
1587
1649
|
this.store.list = this.runOrderStoreSelfCheck(updatedList, `${source}.store`);
|
|
1588
1650
|
this.syncOrdersMap();
|
|
1589
|
-
await this.patchOrdersInSQLite(
|
|
1651
|
+
await this.patchOrdersInSQLite(
|
|
1652
|
+
patchedOrders,
|
|
1653
|
+
source,
|
|
1654
|
+
mergeActions,
|
|
1655
|
+
{},
|
|
1656
|
+
forceWriteStorageKeys
|
|
1657
|
+
);
|
|
1590
1658
|
this.logInfo("mergeOrdersToStore-结束", {
|
|
1591
1659
|
uniqueFreshCount,
|
|
1592
1660
|
storeOrderCountAfter: this.store.list.length,
|
|
1593
1661
|
insertCount,
|
|
1594
1662
|
updateCount
|
|
1595
1663
|
});
|
|
1664
|
+
await this.emitRemoteProductMembershipChanges(remoteProductMembershipChanges);
|
|
1596
1665
|
await this.emitOrdersChanged(patchedOrders, source);
|
|
1597
1666
|
}
|
|
1598
1667
|
/**
|
|
@@ -1616,6 +1685,72 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
1616
1685
|
isRemoteEchoMergeSource(source) {
|
|
1617
1686
|
return source === "pubsub.bodyUpsert" || source === "pubsub.httpRefresh";
|
|
1618
1687
|
}
|
|
1688
|
+
getProductMembershipKey(product) {
|
|
1689
|
+
const persistentId = (0, import_orderCollectionIdentity.getOrderCollectionPersistentId)("product", product);
|
|
1690
|
+
if (persistentId)
|
|
1691
|
+
return `order_detail_id:${persistentId}`;
|
|
1692
|
+
const uid = (0, import_orderCollectionIdentity.getOrderCollectionUid)("product", product);
|
|
1693
|
+
return uid ? `unique_identification_number:${uid}` : "";
|
|
1694
|
+
}
|
|
1695
|
+
doProductsHaveStableMembershipIdentity(products) {
|
|
1696
|
+
if (!Array.isArray(products))
|
|
1697
|
+
return true;
|
|
1698
|
+
return products.every(
|
|
1699
|
+
(product) => Boolean(
|
|
1700
|
+
(0, import_orderCollectionIdentity.getOrderCollectionPersistentId)("product", product) || (0, import_orderCollectionIdentity.getOrderCollectionUid)("product", product)
|
|
1701
|
+
)
|
|
1702
|
+
);
|
|
1703
|
+
}
|
|
1704
|
+
/**
|
|
1705
|
+
* 冲突提示关注逻辑商品行,而不是服务端 checkout 过程中可能被重建的 detail 记录。
|
|
1706
|
+
* 通用集合合并仍以 order_detail_id 为事实主键;这里只允许相同稳定 UID 跨 detail id 对齐,
|
|
1707
|
+
* 避免本机 checkout 回声被误判成“一删一增”。
|
|
1708
|
+
*/
|
|
1709
|
+
isSameProductMembershipItem(left, right) {
|
|
1710
|
+
if ((0, import_orderCollectionIdentity.isSameOrderCollectionItem)("product", left, right))
|
|
1711
|
+
return true;
|
|
1712
|
+
const leftUid = (0, import_orderCollectionIdentity.getOrderCollectionUid)("product", left);
|
|
1713
|
+
const rightUid = (0, import_orderCollectionIdentity.getOrderCollectionUid)("product", right);
|
|
1714
|
+
return Boolean(leftUid && rightUid && leftUid === rightUid);
|
|
1715
|
+
}
|
|
1716
|
+
/**
|
|
1717
|
+
* 对比合并前后的商品成员集合。商品字段修改与数组重排不会产生 diff。
|
|
1718
|
+
*/
|
|
1719
|
+
getRemoteProductMembershipChange(existingOrder, mergedOrder, source) {
|
|
1720
|
+
if (!this.isRemoteEchoMergeSource(source))
|
|
1721
|
+
return null;
|
|
1722
|
+
const previousProducts = Array.isArray(existingOrder.products) ? existingOrder.products : [];
|
|
1723
|
+
const currentProducts = Array.isArray(mergedOrder.products) ? mergedOrder.products : [];
|
|
1724
|
+
const unmatchedCurrentIndexes = new Set(currentProducts.map((_, index) => index));
|
|
1725
|
+
const removedProductKeys = [];
|
|
1726
|
+
for (const previousProduct of previousProducts) {
|
|
1727
|
+
const matchedIndex = currentProducts.findIndex(
|
|
1728
|
+
(currentProduct, index) => unmatchedCurrentIndexes.has(index) && this.isSameProductMembershipItem(previousProduct, currentProduct)
|
|
1729
|
+
);
|
|
1730
|
+
if (matchedIndex >= 0) {
|
|
1731
|
+
unmatchedCurrentIndexes.delete(matchedIndex);
|
|
1732
|
+
continue;
|
|
1733
|
+
}
|
|
1734
|
+
const productKey = this.getProductMembershipKey(previousProduct);
|
|
1735
|
+
if (productKey)
|
|
1736
|
+
removedProductKeys.push(productKey);
|
|
1737
|
+
}
|
|
1738
|
+
const addedProductKeys = [...unmatchedCurrentIndexes].map((index) => this.getProductMembershipKey(currentProducts[index])).filter(Boolean);
|
|
1739
|
+
if (addedProductKeys.length === 0 && removedProductKeys.length === 0) {
|
|
1740
|
+
return null;
|
|
1741
|
+
}
|
|
1742
|
+
const orderId = mergedOrder.order_id ?? existingOrder.order_id;
|
|
1743
|
+
if (orderId === void 0 || orderId === null)
|
|
1744
|
+
return null;
|
|
1745
|
+
return {
|
|
1746
|
+
order_id: orderId,
|
|
1747
|
+
source,
|
|
1748
|
+
added_product_keys: addedProductKeys,
|
|
1749
|
+
removed_product_keys: removedProductKeys,
|
|
1750
|
+
previous_product_count: previousProducts.length,
|
|
1751
|
+
current_product_count: currentProducts.length
|
|
1752
|
+
};
|
|
1753
|
+
}
|
|
1619
1754
|
/**
|
|
1620
1755
|
* 将订单的全部身份键登记到索引并指向其在列表中的下标。
|
|
1621
1756
|
* 合并后订单新增的身份字段(如挂单同步后拿到 order_id)也会被登记。
|
|
@@ -2140,7 +2275,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
2140
2275
|
* @example
|
|
2141
2276
|
* await this.patchOrdersInSQLite(freshOrders)
|
|
2142
2277
|
*/
|
|
2143
|
-
async patchOrdersInSQLite(orders, source, mergeActions = {}, options = {}) {
|
|
2278
|
+
async patchOrdersInSQLite(orders, source, mergeActions = {}, options = {}, forceWriteStorageKeys = /* @__PURE__ */ new Set()) {
|
|
2144
2279
|
if (orders.length === 0) {
|
|
2145
2280
|
return;
|
|
2146
2281
|
}
|
|
@@ -2156,7 +2291,8 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
2156
2291
|
const { toWrite, skipped } = this.filterRedundantPatchOrders(
|
|
2157
2292
|
source,
|
|
2158
2293
|
ordersSnapshot,
|
|
2159
|
-
mergeActions
|
|
2294
|
+
mergeActions,
|
|
2295
|
+
forceWriteStorageKeys
|
|
2160
2296
|
);
|
|
2161
2297
|
if (toWrite.length === 0) {
|
|
2162
2298
|
return;
|
|
@@ -568,6 +568,19 @@ export interface OrderChangedPayload {
|
|
|
568
568
|
/** 触发来源,便于订阅者区分 pubsub / 远端覆盖 / 清空等场景 */
|
|
569
569
|
source?: string;
|
|
570
570
|
}
|
|
571
|
+
export type RemoteProductMembershipChangeSource = 'pubsub.bodyUpsert' | 'pubsub.httpRefresh';
|
|
572
|
+
/**
|
|
573
|
+
* 远端订单快照相对本地订单产生商品成员增删时的广播载荷。
|
|
574
|
+
* 只表达商品行成员关系变化,不包含数量、价格、备注等字段级修改。
|
|
575
|
+
*/
|
|
576
|
+
export interface RemoteProductMembershipChangedPayload {
|
|
577
|
+
order_id: OrderId;
|
|
578
|
+
source: RemoteProductMembershipChangeSource;
|
|
579
|
+
added_product_keys: string[];
|
|
580
|
+
removed_product_keys: string[];
|
|
581
|
+
previous_product_count: number;
|
|
582
|
+
current_product_count: number;
|
|
583
|
+
}
|
|
571
584
|
/**
|
|
572
585
|
* OrderSyncMessage - pubsub 同步消息结构
|
|
573
586
|
*/
|
|
@@ -605,5 +618,6 @@ export interface OrderState {
|
|
|
605
618
|
export declare enum OrderHooks {
|
|
606
619
|
onOrdersLoaded = "order:onOrdersLoaded",
|
|
607
620
|
onOrdersChanged = "order:onOrdersChanged",
|
|
621
|
+
onRemoteProductMembershipChanged = "order:onRemoteProductMembershipChanged",
|
|
608
622
|
onOrdersSyncCompleted = "order:onOrdersSyncCompleted"
|
|
609
623
|
}
|
|
@@ -25,6 +25,7 @@ module.exports = __toCommonJS(types_exports);
|
|
|
25
25
|
var OrderHooks = /* @__PURE__ */ ((OrderHooks2) => {
|
|
26
26
|
OrderHooks2["onOrdersLoaded"] = "order:onOrdersLoaded";
|
|
27
27
|
OrderHooks2["onOrdersChanged"] = "order:onOrdersChanged";
|
|
28
|
+
OrderHooks2["onRemoteProductMembershipChanged"] = "order:onRemoteProductMembershipChanged";
|
|
28
29
|
OrderHooks2["onOrdersSyncCompleted"] = "order:onOrdersSyncCompleted";
|
|
29
30
|
return OrderHooks2;
|
|
30
31
|
})(OrderHooks || {});
|