@pisell/pisellos 2.2.256 → 2.2.258
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 +17 -13
- package/dist/modules/Order/index.d.ts +29 -1
- package/dist/modules/Order/index.js +508 -241
- package/dist/modules/Order/types.d.ts +20 -2
- package/dist/modules/Order/utils/bundleDiscountHydration.d.ts +5 -0
- package/dist/modules/Order/utils/bundleDiscountHydration.js +144 -0
- package/dist/modules/Order/utils.d.ts +4 -0
- package/dist/modules/Order/utils.js +114 -28
- package/dist/modules/Product/index.d.ts +1 -1
- package/dist/modules/Product/types.d.ts +6 -0
- package/dist/modules/Rules/index.d.ts +3 -3
- package/dist/modules/Rules/index.js +8 -3
- package/dist/modules/Rules/types.d.ts +2 -1
- package/dist/server/index.d.ts +12 -2
- package/dist/server/index.js +1227 -923
- package/dist/server/modules/order/index.d.ts +22 -3
- package/dist/server/modules/order/index.js +397 -314
- package/dist/solution/BaseSales/index.d.ts +16 -2
- package/dist/solution/BaseSales/index.js +484 -314
- package/dist/solution/BaseSales/types.d.ts +1 -0
- package/dist/solution/BaseSales/types.js +2 -1
- package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +4 -2
- package/dist/solution/BookingByStep/index.d.ts +1 -1
- package/dist/solution/BookingTicket/index.d.ts +9 -1
- package/dist/solution/BookingTicket/index.js +162 -6
- package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +11 -1
- package/lib/modules/Order/index.d.ts +29 -1
- package/lib/modules/Order/index.js +340 -57
- package/lib/modules/Order/types.d.ts +20 -2
- package/lib/modules/Order/utils/bundleDiscountHydration.d.ts +5 -0
- package/lib/modules/Order/utils/bundleDiscountHydration.js +139 -0
- package/lib/modules/Order/utils.d.ts +4 -0
- package/lib/modules/Order/utils.js +84 -2
- package/lib/modules/Product/index.d.ts +1 -1
- package/lib/modules/Product/types.d.ts +6 -0
- package/lib/modules/Rules/index.d.ts +3 -3
- package/lib/modules/Rules/index.js +4 -3
- package/lib/modules/Rules/types.d.ts +2 -1
- package/lib/server/index.d.ts +12 -2
- package/lib/server/index.js +218 -12
- package/lib/server/modules/order/index.d.ts +22 -3
- package/lib/server/modules/order/index.js +102 -51
- package/lib/solution/BaseSales/index.d.ts +16 -2
- package/lib/solution/BaseSales/index.js +143 -31
- package/lib/solution/BaseSales/types.d.ts +1 -0
- package/lib/solution/BaseSales/types.js +2 -1
- package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +4 -5
- package/lib/solution/BookingByStep/index.d.ts +1 -1
- package/lib/solution/BookingTicket/index.d.ts +9 -1
- package/lib/solution/BookingTicket/index.js +112 -0
- package/package.json +1 -1
package/lib/server/index.js
CHANGED
|
@@ -639,7 +639,8 @@ var Server = class {
|
|
|
639
639
|
checkoutData,
|
|
640
640
|
order: printableOrder,
|
|
641
641
|
response: null,
|
|
642
|
-
requireFullyPaid: false
|
|
642
|
+
requireFullyPaid: false,
|
|
643
|
+
isManualPrint: true
|
|
643
644
|
});
|
|
644
645
|
return {
|
|
645
646
|
code: 200,
|
|
@@ -661,6 +662,86 @@ var Server = class {
|
|
|
661
662
|
};
|
|
662
663
|
}
|
|
663
664
|
};
|
|
665
|
+
this.handleMachinecodeBatchMetadata = async ({ data }) => {
|
|
666
|
+
var _a, _b;
|
|
667
|
+
const title = "handleMachinecodeBatchMetadata";
|
|
668
|
+
const backendPath = "/shop/machinecode/batch-metadata";
|
|
669
|
+
const batchMetadata = Array.isArray(data == null ? void 0 : data.data) ? data.data : [];
|
|
670
|
+
this.logInfo(`${title}: 开始处理`, {
|
|
671
|
+
backendPath,
|
|
672
|
+
batchSize: batchMetadata.length,
|
|
673
|
+
order_lookup: this.resolveMachinecodeBatchMetadataLookup(data) ?? null
|
|
674
|
+
});
|
|
675
|
+
if (batchMetadata.length === 0) {
|
|
676
|
+
return {
|
|
677
|
+
code: 200,
|
|
678
|
+
status: true,
|
|
679
|
+
message: "",
|
|
680
|
+
data: []
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
try {
|
|
684
|
+
await this.syncMachinecodeBatchMetadataToLocalOrder(data, batchMetadata);
|
|
685
|
+
} catch (error) {
|
|
686
|
+
const message = this.getUnknownErrorMessage(error);
|
|
687
|
+
this.logError(`${title}: 本地订单 metadata 更新失败`, {
|
|
688
|
+
error: message,
|
|
689
|
+
error_detail: this.normalizeUnknownError(error)
|
|
690
|
+
});
|
|
691
|
+
return {
|
|
692
|
+
code: 500,
|
|
693
|
+
status: false,
|
|
694
|
+
message,
|
|
695
|
+
data: null
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
if (!((_b = (_a = this.app) == null ? void 0 : _a.request) == null ? void 0 : _b.post)) {
|
|
699
|
+
this.logError(`${title}: app.request 不可用`, { backendPath });
|
|
700
|
+
return {
|
|
701
|
+
code: 500,
|
|
702
|
+
status: false,
|
|
703
|
+
message: "app.request 不可用",
|
|
704
|
+
data: null
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
try {
|
|
708
|
+
const response = await this.app.request.post(backendPath, {
|
|
709
|
+
data: batchMetadata
|
|
710
|
+
}, {
|
|
711
|
+
isShopApi: true,
|
|
712
|
+
customToast: () => {
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
return {
|
|
716
|
+
code: (response == null ? void 0 : response.code) ?? 200,
|
|
717
|
+
status: (response == null ? void 0 : response.status) ?? true,
|
|
718
|
+
message: (response == null ? void 0 : response.message) ?? "",
|
|
719
|
+
data: (response == null ? void 0 : response.data) ?? response
|
|
720
|
+
};
|
|
721
|
+
} catch (error) {
|
|
722
|
+
const backendError = this.extractBackendErrorResponse(error);
|
|
723
|
+
if (backendError) {
|
|
724
|
+
return {
|
|
725
|
+
code: backendError.code ?? 500,
|
|
726
|
+
status: backendError.status ?? false,
|
|
727
|
+
message: backendError.message ?? "后端拒绝 batch-metadata",
|
|
728
|
+
data: backendError.data ?? null
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
const message = this.getUnknownErrorMessage(error);
|
|
732
|
+
this.logError(`${title}: 请求失败`, {
|
|
733
|
+
backendPath,
|
|
734
|
+
error: message,
|
|
735
|
+
error_detail: this.normalizeUnknownError(error)
|
|
736
|
+
});
|
|
737
|
+
return {
|
|
738
|
+
code: 500,
|
|
739
|
+
status: false,
|
|
740
|
+
message,
|
|
741
|
+
data: null
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
};
|
|
664
745
|
this.handleOrderSalesDetail = async ({ url, data, path }) => {
|
|
665
746
|
var _a;
|
|
666
747
|
const requestPath = this.parseRequestPath(url, path);
|
|
@@ -737,7 +818,7 @@ var Server = class {
|
|
|
737
818
|
};
|
|
738
819
|
} catch (error) {
|
|
739
820
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
740
|
-
this.
|
|
821
|
+
this.logInfo("handleOrderSalesDetail: 请求失败", {
|
|
741
822
|
lookup,
|
|
742
823
|
backendPath,
|
|
743
824
|
error: errorMessage
|
|
@@ -1743,6 +1824,16 @@ var Server = class {
|
|
|
1743
1824
|
method: "post",
|
|
1744
1825
|
path: "/shop/local/print-order",
|
|
1745
1826
|
handler: this.handleLocalPrintOrder.bind(this)
|
|
1827
|
+
},
|
|
1828
|
+
{
|
|
1829
|
+
method: "post",
|
|
1830
|
+
path: "/shop/machinecode/batch-metadata",
|
|
1831
|
+
handler: this.handleMachinecodeBatchMetadata.bind(this)
|
|
1832
|
+
},
|
|
1833
|
+
{
|
|
1834
|
+
method: "post",
|
|
1835
|
+
path: "/machinecode/batch-metadata",
|
|
1836
|
+
handler: this.handleMachinecodeBatchMetadata.bind(this)
|
|
1746
1837
|
}
|
|
1747
1838
|
]);
|
|
1748
1839
|
this.registerPrefixRoutes([
|
|
@@ -1988,6 +2079,20 @@ var Server = class {
|
|
|
1988
2079
|
return null;
|
|
1989
2080
|
return text.split("T")[0].split(" ")[0] || null;
|
|
1990
2081
|
}
|
|
2082
|
+
/**
|
|
2083
|
+
* 判断时间范围是否完整落在今天。
|
|
2084
|
+
*
|
|
2085
|
+
* @example
|
|
2086
|
+
* this.isRangeFullyToday('2026-04-15', '2026-04-15') // => true
|
|
2087
|
+
*/
|
|
2088
|
+
isRangeFullyToday(startRaw, endRaw) {
|
|
2089
|
+
const startDate = this.extractQueryDate(startRaw);
|
|
2090
|
+
const endDate = this.extractQueryDate(endRaw);
|
|
2091
|
+
if (!startDate || !endDate)
|
|
2092
|
+
return false;
|
|
2093
|
+
const today = (0, import_dayjs.default)().format("YYYY-MM-DD");
|
|
2094
|
+
return startDate === today && endDate === today;
|
|
2095
|
+
}
|
|
1991
2096
|
/**
|
|
1992
2097
|
* 判断时间范围是否「开始和结束都不是今天」。
|
|
1993
2098
|
*
|
|
@@ -2004,11 +2109,11 @@ var Server = class {
|
|
|
2004
2109
|
}
|
|
2005
2110
|
/**
|
|
2006
2111
|
* 订单查询是否走本地:
|
|
2007
|
-
* -
|
|
2008
|
-
* -
|
|
2112
|
+
* - 仅当 start_time 与 end_time 都是今天时,走本地;
|
|
2113
|
+
* - 任意一端不是今天时,走云端,确保跨天查询能拉取非本地窗口数据。
|
|
2009
2114
|
*/
|
|
2010
2115
|
shouldUseLocalOrderQuery(data) {
|
|
2011
|
-
return
|
|
2116
|
+
return this.isRangeFullyToday(data == null ? void 0 : data.start_time, data == null ? void 0 : data.end_time);
|
|
2012
2117
|
}
|
|
2013
2118
|
/**
|
|
2014
2119
|
* 预约查询是否走本地:
|
|
@@ -2642,10 +2747,12 @@ var Server = class {
|
|
|
2642
2747
|
action: "print_all",
|
|
2643
2748
|
device_id: params.deviceId ?? deviceId,
|
|
2644
2749
|
payload: {
|
|
2645
|
-
type: params.
|
|
2750
|
+
type: params.isManualPrint ? "0" : "99",
|
|
2646
2751
|
data: params.receiptOnly ? {
|
|
2647
2752
|
order_id: printIdentity,
|
|
2648
|
-
small_ticket_data: (_b = (_a = params.syncedOrder) == null ? void 0 : _a.payment_info) == null ? void 0 : _b.small_ticket_data
|
|
2753
|
+
small_ticket_data: (_b = (_a = params.syncedOrder) == null ? void 0 : _a.payment_info) == null ? void 0 : _b.small_ticket_data,
|
|
2754
|
+
skip_wristband: true,
|
|
2755
|
+
skip_voucher: true
|
|
2649
2756
|
} : {
|
|
2650
2757
|
order_id: printIdentity,
|
|
2651
2758
|
machine_code_print_info: (_d = (_c = params.syncedOrder) == null ? void 0 : _c.payment_info) == null ? void 0 : _d.machine_code_print_info,
|
|
@@ -2656,7 +2763,7 @@ var Server = class {
|
|
|
2656
2763
|
idempotency_key: this.buildPrintOtherReceiptIdempotencyKey(params.syncedOrder, {
|
|
2657
2764
|
receiptOnly: params.receiptOnly
|
|
2658
2765
|
}),
|
|
2659
|
-
name: "
|
|
2766
|
+
name: "PRINT_V1",
|
|
2660
2767
|
source: "server",
|
|
2661
2768
|
source_type: "order",
|
|
2662
2769
|
source_id: printIdentity
|
|
@@ -2696,7 +2803,8 @@ var Server = class {
|
|
|
2696
2803
|
checkoutData: params.checkoutData,
|
|
2697
2804
|
syncedOrder: printableOrder,
|
|
2698
2805
|
response: params.response,
|
|
2699
|
-
receiptOnly: true
|
|
2806
|
+
receiptOnly: true,
|
|
2807
|
+
isManualPrint: params.isManualPrint
|
|
2700
2808
|
});
|
|
2701
2809
|
return { printed: true, order: printableOrder };
|
|
2702
2810
|
}
|
|
@@ -2714,6 +2822,107 @@ var Server = class {
|
|
|
2714
2822
|
}
|
|
2715
2823
|
return void 0;
|
|
2716
2824
|
}
|
|
2825
|
+
resolveMachinecodeBatchMetadataLookup(data) {
|
|
2826
|
+
const candidates = [
|
|
2827
|
+
data == null ? void 0 : data.order_lookup,
|
|
2828
|
+
data == null ? void 0 : data.orderLookup,
|
|
2829
|
+
data == null ? void 0 : data.external_sale_number,
|
|
2830
|
+
data == null ? void 0 : data.externalSaleNumber,
|
|
2831
|
+
data == null ? void 0 : data.order_id,
|
|
2832
|
+
data == null ? void 0 : data.orderId,
|
|
2833
|
+
data == null ? void 0 : data.order_number,
|
|
2834
|
+
data == null ? void 0 : data.orderNumber,
|
|
2835
|
+
data == null ? void 0 : data.shop_order_number,
|
|
2836
|
+
data == null ? void 0 : data.shopOrderNumber,
|
|
2837
|
+
data == null ? void 0 : data.shop_full_order_number,
|
|
2838
|
+
data == null ? void 0 : data.shopFullOrderNumber
|
|
2839
|
+
];
|
|
2840
|
+
return candidates.find((candidate) => candidate !== void 0 && candidate !== null && candidate !== "");
|
|
2841
|
+
}
|
|
2842
|
+
async syncMachinecodeBatchMetadataToLocalOrder(data, batchMetadata) {
|
|
2843
|
+
var _a;
|
|
2844
|
+
const title = "handleMachinecodeBatchMetadata";
|
|
2845
|
+
const lookup = this.resolveMachinecodeBatchMetadataLookup(data);
|
|
2846
|
+
if (lookup === void 0) {
|
|
2847
|
+
this.logWarning(`${title}: order lookup 缺失,跳过本地订单 metadata 更新`, {
|
|
2848
|
+
batchSize: batchMetadata.length
|
|
2849
|
+
});
|
|
2850
|
+
return;
|
|
2851
|
+
}
|
|
2852
|
+
if (!((_a = this.order) == null ? void 0 : _a.getLocalOrderByLookup)) {
|
|
2853
|
+
this.logWarning(`${title}: Order 模块未注册,跳过本地订单 metadata 更新`, {
|
|
2854
|
+
lookup
|
|
2855
|
+
});
|
|
2856
|
+
return;
|
|
2857
|
+
}
|
|
2858
|
+
const localOrder = await this.order.getLocalOrderByLookup(lookup);
|
|
2859
|
+
if (!localOrder) {
|
|
2860
|
+
this.logWarning(`${title}: 本地订单不存在,跳过 metadata 更新`, {
|
|
2861
|
+
lookup
|
|
2862
|
+
});
|
|
2863
|
+
return;
|
|
2864
|
+
}
|
|
2865
|
+
const metadataByVoucherId = /* @__PURE__ */ new Map();
|
|
2866
|
+
for (const item of batchMetadata) {
|
|
2867
|
+
const voucherId = (item == null ? void 0 : item.voucher_id) ?? (item == null ? void 0 : item.voucherId);
|
|
2868
|
+
if (voucherId === void 0 || voucherId === null || voucherId === "")
|
|
2869
|
+
continue;
|
|
2870
|
+
metadataByVoucherId.set(String(voucherId), (item == null ? void 0 : item.metadata) || {});
|
|
2871
|
+
}
|
|
2872
|
+
if (metadataByVoucherId.size === 0) {
|
|
2873
|
+
this.logWarning(`${title}: voucher metadata 缺失,跳过本地订单 metadata 更新`, {
|
|
2874
|
+
lookup
|
|
2875
|
+
});
|
|
2876
|
+
return;
|
|
2877
|
+
}
|
|
2878
|
+
const vouchers = Array.isArray(localOrder.vouchers) ? localOrder.vouchers : [];
|
|
2879
|
+
let hasUpdatedVoucher = false;
|
|
2880
|
+
const nextVouchers = vouchers.map((voucher) => {
|
|
2881
|
+
const voucherId = voucher == null ? void 0 : voucher.voucher_id;
|
|
2882
|
+
if (voucherId === void 0 || voucherId === null)
|
|
2883
|
+
return voucher;
|
|
2884
|
+
const metadata = metadataByVoucherId.get(String(voucherId));
|
|
2885
|
+
if (!metadata)
|
|
2886
|
+
return voucher;
|
|
2887
|
+
hasUpdatedVoucher = true;
|
|
2888
|
+
return {
|
|
2889
|
+
...voucher,
|
|
2890
|
+
metadata
|
|
2891
|
+
};
|
|
2892
|
+
});
|
|
2893
|
+
if (!hasUpdatedVoucher) {
|
|
2894
|
+
this.logWarning(`${title}: 本地订单未命中任何 voucher,跳过 metadata 更新`, {
|
|
2895
|
+
lookup,
|
|
2896
|
+
voucher_ids: Array.from(metadataByVoucherId.keys())
|
|
2897
|
+
});
|
|
2898
|
+
return;
|
|
2899
|
+
}
|
|
2900
|
+
if (typeof this.order.overwriteExistingOrder === "function") {
|
|
2901
|
+
await this.order.overwriteExistingOrder({
|
|
2902
|
+
...localOrder,
|
|
2903
|
+
vouchers: nextVouchers
|
|
2904
|
+
});
|
|
2905
|
+
this.logInfo(`${title}: 本地订单 vouchers metadata 已更新`, {
|
|
2906
|
+
lookup,
|
|
2907
|
+
updatedCount: metadataByVoucherId.size
|
|
2908
|
+
});
|
|
2909
|
+
return;
|
|
2910
|
+
}
|
|
2911
|
+
if ((data == null ? void 0 : data.external_sale_number) && typeof this.order.markOrderSyncedByExternalSaleNumber === "function") {
|
|
2912
|
+
await this.order.markOrderSyncedByExternalSaleNumber({
|
|
2913
|
+
externalSaleNumber: data.external_sale_number,
|
|
2914
|
+
patch: { vouchers: nextVouchers }
|
|
2915
|
+
});
|
|
2916
|
+
this.logInfo(`${title}: 本地订单 vouchers metadata 已更新`, {
|
|
2917
|
+
lookup,
|
|
2918
|
+
updatedCount: metadataByVoucherId.size
|
|
2919
|
+
});
|
|
2920
|
+
return;
|
|
2921
|
+
}
|
|
2922
|
+
this.logWarning(`${title}: Order 模块不支持本地订单覆盖,跳过 metadata 更新`, {
|
|
2923
|
+
lookup
|
|
2924
|
+
});
|
|
2925
|
+
}
|
|
2717
2926
|
async handleOrderCheckoutSubmit(params) {
|
|
2718
2927
|
const { backendPath, data, title } = params;
|
|
2719
2928
|
this.logInfo(`${title}: 开始处理`, {
|
|
@@ -2911,11 +3120,8 @@ var Server = class {
|
|
|
2911
3120
|
);
|
|
2912
3121
|
}
|
|
2913
3122
|
shouldBuildSmallTicketData(order) {
|
|
2914
|
-
var _a;
|
|
2915
3123
|
if (!order || typeof order !== "object")
|
|
2916
3124
|
return false;
|
|
2917
|
-
if ((0, import_small_ticket.hasSmallTicketData)((_a = order.payment_info) == null ? void 0 : _a.small_ticket_data))
|
|
2918
|
-
return false;
|
|
2919
3125
|
return Number(order.small_ticket_data_flag ?? order.smallTicketDataFlag ?? 0) === 1;
|
|
2920
3126
|
}
|
|
2921
3127
|
shouldPrintSyncedOrder(params) {
|
|
@@ -15,7 +15,6 @@ export declare class OrderModule extends BaseModule implements Module {
|
|
|
15
15
|
private syncTimer?;
|
|
16
16
|
private isProcessingSyncBatch;
|
|
17
17
|
private isIdlePhase;
|
|
18
|
-
private resourceIdIndex;
|
|
19
18
|
private logger;
|
|
20
19
|
private storage;
|
|
21
20
|
private orderSQLiteSaveQueue; /** 按 storageKey 记录最近一次 SQLite 写入来源与时间,用于去重 */
|
|
@@ -89,7 +88,6 @@ export declare class OrderModule extends BaseModule implements Module {
|
|
|
89
88
|
* @returns 刷新后的订单列表
|
|
90
89
|
*/
|
|
91
90
|
silentRefresh(): Promise<OrderData[]>;
|
|
92
|
-
getOrdersByResourceId(resourceId: string | number): OrderData[];
|
|
93
91
|
private normalizeOrderForMemoryList;
|
|
94
92
|
/**
|
|
95
93
|
* 仅覆盖本地已存在的订单;不存在则直接跳过,不落库、不 emit。
|
|
@@ -126,7 +124,6 @@ export declare class OrderModule extends BaseModule implements Module {
|
|
|
126
124
|
private isBlankIdentityValue;
|
|
127
125
|
private getOrderIdentityEntries;
|
|
128
126
|
private getOrderIdentityKeys;
|
|
129
|
-
private extractResourceIds;
|
|
130
127
|
private getOrderDateKey;
|
|
131
128
|
/**
|
|
132
129
|
* 初始化 OrderDataSource 实例
|
|
@@ -175,6 +172,28 @@ export declare class OrderModule extends BaseModule implements Module {
|
|
|
175
172
|
* 将增量订单合并到 store
|
|
176
173
|
*/
|
|
177
174
|
private mergeOrdersToStore;
|
|
175
|
+
/**
|
|
176
|
+
* 在身份索引中查找与目标订单共享任一身份键的已存在订单下标。
|
|
177
|
+
* 返回所有命中键里最小的下标,等价于原 findOrderIndexByIdentity 的「取首个匹配」语义。
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* const idx = this.lookupOrderIndexByIdentityKeys(identityKeyToIndex, freshIdentityKeys)
|
|
181
|
+
*/
|
|
182
|
+
private lookupOrderIndexByIdentityKeys;
|
|
183
|
+
private isRemoteEchoMergeSource;
|
|
184
|
+
/**
|
|
185
|
+
* 将订单的全部身份键登记到索引并指向其在列表中的下标。
|
|
186
|
+
* 合并后订单新增的身份字段(如挂单同步后拿到 order_id)也会被登记。
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* this.registerOrderIdentityKeys(identityKeyToIndex, mergedOrder, matchIndex)
|
|
190
|
+
*/
|
|
191
|
+
private registerOrderIdentityKeys;
|
|
192
|
+
/**
|
|
193
|
+
* 合并热路径去重兜底:默认直接返回原列表(增量索引已保证无重复)。
|
|
194
|
+
* 仅当 ORDER_MERGE_SELF_CHECK 开启时,额外做一次全表压缩并在发现残留重复时告警。
|
|
195
|
+
*/
|
|
196
|
+
private runOrderStoreSelfCheck;
|
|
178
197
|
private normalizeOrderSyncMessage;
|
|
179
198
|
private normalizeOrderSyncPayload;
|
|
180
199
|
private uniqueOrderIds;
|
|
@@ -48,6 +48,7 @@ var ORDER_BUSINESS_WRITE_SOURCES = /* @__PURE__ */ new Set([
|
|
|
48
48
|
"overwriteExistingOrder",
|
|
49
49
|
"markOrderSyncedByExternalSaleNumber"
|
|
50
50
|
]);
|
|
51
|
+
var ORDER_MERGE_SELF_CHECK = false;
|
|
51
52
|
var OrderModule = class extends import_BaseModule.BaseModule {
|
|
52
53
|
constructor(name, version) {
|
|
53
54
|
super(name, version);
|
|
@@ -56,8 +57,6 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
56
57
|
this.pendingSyncMessages = [];
|
|
57
58
|
this.isProcessingSyncBatch = false;
|
|
58
59
|
this.isIdlePhase = true;
|
|
59
|
-
// Map<resource_id, OrderId[]> 资源到订单的倒排索引
|
|
60
|
-
this.resourceIdIndex = /* @__PURE__ */ new Map();
|
|
61
60
|
this.orderSQLiteSaveQueue = Promise.resolve();
|
|
62
61
|
/** 按 storageKey 记录最近一次 SQLite 写入来源与时间,用于去重 */
|
|
63
62
|
this.recentSqliteWriteByStorageKey = /* @__PURE__ */ new Map();
|
|
@@ -481,10 +480,6 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
481
480
|
return this.store.list;
|
|
482
481
|
}
|
|
483
482
|
}
|
|
484
|
-
getOrdersByResourceId(resourceId) {
|
|
485
|
-
const ids = this.resourceIdIndex.get(String(resourceId)) || [];
|
|
486
|
-
return ids.map((id) => this.store.map.get(id)).filter((order) => !!order);
|
|
487
|
-
}
|
|
488
483
|
normalizeOrderForMemoryList(order) {
|
|
489
484
|
const externalSaleNumber = order == null ? void 0 : order.external_sale_number;
|
|
490
485
|
if ((order == null ? void 0 : order.order_id) !== void 0 && (order == null ? void 0 : order.order_id) !== null && (order == null ? void 0 : order.order_id) !== "") {
|
|
@@ -617,22 +612,33 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
617
612
|
const patchedOrders = [...pendingMap.values()];
|
|
618
613
|
const mergeActions = {};
|
|
619
614
|
const updatedList = [...this.store.list];
|
|
615
|
+
const identityKeyToIndex = /* @__PURE__ */ new Map();
|
|
616
|
+
for (let index = 0; index < updatedList.length; index += 1) {
|
|
617
|
+
for (const key of this.getOrderIdentityMatchKeys(updatedList[index])) {
|
|
618
|
+
if (!identityKeyToIndex.has(key))
|
|
619
|
+
identityKeyToIndex.set(key, index);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
620
622
|
for (const [storageKey, pendingOrder] of memoryPendingMap.entries()) {
|
|
621
|
-
const matchIndex = this.
|
|
622
|
-
|
|
623
|
+
const matchIndex = this.lookupOrderIndexByIdentityKeys(
|
|
624
|
+
identityKeyToIndex,
|
|
625
|
+
this.getOrderIdentityMatchKeys(pendingOrder)
|
|
626
|
+
);
|
|
627
|
+
if (matchIndex >= 0) {
|
|
628
|
+
const mergedOrder = this.mergeOrderRecords(updatedList[matchIndex], pendingOrder, {
|
|
629
|
+
preferIncomingProducts: true
|
|
630
|
+
});
|
|
631
|
+
updatedList[matchIndex] = mergedOrder;
|
|
632
|
+
this.registerOrderIdentityKeys(identityKeyToIndex, mergedOrder, matchIndex);
|
|
633
|
+
mergeActions[storageKey] = "update";
|
|
623
634
|
continue;
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
memoryPendingMap.delete(storageKey);
|
|
629
|
-
mergeActions[storageKey] = "update";
|
|
630
|
-
}
|
|
631
|
-
for (const [storageKey, order] of memoryPendingMap.entries()) {
|
|
635
|
+
}
|
|
636
|
+
const insertIndex = updatedList.length;
|
|
637
|
+
updatedList.push(pendingOrder);
|
|
638
|
+
this.registerOrderIdentityKeys(identityKeyToIndex, pendingOrder, insertIndex);
|
|
632
639
|
mergeActions[storageKey] = "insert";
|
|
633
|
-
updatedList.push(order);
|
|
634
640
|
}
|
|
635
|
-
this.store.list = this.
|
|
641
|
+
this.store.list = this.runOrderStoreSelfCheck(updatedList, "upsertPendingSyncOrders.store");
|
|
636
642
|
this.syncOrdersMap();
|
|
637
643
|
await this.patchOrdersInSQLite(patchedOrders, "upsertPendingSyncOrders", mergeActions);
|
|
638
644
|
this.logInfo("upsertPendingSyncOrders-结束", {
|
|
@@ -674,21 +680,11 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
674
680
|
}
|
|
675
681
|
syncOrdersMap() {
|
|
676
682
|
this.store.map.clear();
|
|
677
|
-
this.resourceIdIndex.clear();
|
|
678
683
|
for (const order of this.store.list) {
|
|
679
684
|
const primaryIdentity = this.getOrderMapKey(order);
|
|
680
685
|
if (!primaryIdentity)
|
|
681
686
|
continue;
|
|
682
687
|
this.store.map.set(primaryIdentity, order);
|
|
683
|
-
const resourceIds = this.extractResourceIds(order);
|
|
684
|
-
for (const resourceId of resourceIds) {
|
|
685
|
-
const key = String(resourceId);
|
|
686
|
-
const existing = this.resourceIdIndex.get(key) || [];
|
|
687
|
-
if (existing.some((eid) => this.getIdKey(eid) === primaryIdentity))
|
|
688
|
-
continue;
|
|
689
|
-
existing.push(primaryIdentity);
|
|
690
|
-
this.resourceIdIndex.set(key, existing);
|
|
691
|
-
}
|
|
692
688
|
}
|
|
693
689
|
}
|
|
694
690
|
getOrderMapKey(order) {
|
|
@@ -735,24 +731,6 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
735
731
|
}
|
|
736
732
|
return keys;
|
|
737
733
|
}
|
|
738
|
-
extractResourceIds(order) {
|
|
739
|
-
const ids = /* @__PURE__ */ new Set();
|
|
740
|
-
const visitResources = (resources) => {
|
|
741
|
-
if (!Array.isArray(resources))
|
|
742
|
-
return;
|
|
743
|
-
resources.forEach((resource) => {
|
|
744
|
-
const rid = resource == null ? void 0 : resource.relation_id;
|
|
745
|
-
if (rid !== void 0 && rid !== null && rid !== "")
|
|
746
|
-
ids.add(String(rid));
|
|
747
|
-
if (Array.isArray(resource == null ? void 0 : resource.children))
|
|
748
|
-
visitResources(resource.children);
|
|
749
|
-
});
|
|
750
|
-
};
|
|
751
|
-
if (Array.isArray(order == null ? void 0 : order.bookings)) {
|
|
752
|
-
order.bookings.forEach((booking) => visitResources((booking == null ? void 0 : booking.resources) || []));
|
|
753
|
-
}
|
|
754
|
-
return [...ids];
|
|
755
|
-
}
|
|
756
734
|
getOrderDateKey(order) {
|
|
757
735
|
var _a, _b, _c, _d;
|
|
758
736
|
const candidates = [
|
|
@@ -971,9 +949,10 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
971
949
|
* 将增量订单合并到 store
|
|
972
950
|
*/
|
|
973
951
|
async mergeOrdersToStore(freshOrders, source) {
|
|
952
|
+
const storeOrderCountBefore = this.store.list.length;
|
|
974
953
|
this.logInfo("mergeOrdersToStore-开始", {
|
|
975
954
|
freshOrderCount: freshOrders.length,
|
|
976
|
-
storeOrderCountBefore
|
|
955
|
+
storeOrderCountBefore,
|
|
977
956
|
source
|
|
978
957
|
});
|
|
979
958
|
this.logDuplicateOrders(`${source}.beforeMerge`, this.store.list);
|
|
@@ -992,19 +971,45 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
992
971
|
const patchedOrders = [];
|
|
993
972
|
const mergeActions = {};
|
|
994
973
|
const updatedList = [...this.store.list];
|
|
974
|
+
const identityKeyToIndex = /* @__PURE__ */ new Map();
|
|
975
|
+
for (let index = 0; index < updatedList.length; index += 1) {
|
|
976
|
+
const identityKeys = this.getOrderIdentityMatchKeys(updatedList[index]);
|
|
977
|
+
for (const key of identityKeys) {
|
|
978
|
+
if (!identityKeyToIndex.has(key))
|
|
979
|
+
identityKeyToIndex.set(key, index);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
995
982
|
for (const fresh of uniqueFreshOrders) {
|
|
996
|
-
const
|
|
983
|
+
const freshIdentityKeys = this.getOrderIdentityMatchKeys(fresh);
|
|
984
|
+
const matchIndex = this.lookupOrderIndexByIdentityKeys(identityKeyToIndex, freshIdentityKeys);
|
|
997
985
|
const storageKey = this.getOrderStorageKey(fresh);
|
|
998
986
|
const mergeKey = storageKey || (fresh.order_id !== void 0 && fresh.order_id !== null ? this.getIdKey(fresh.order_id) : "");
|
|
999
987
|
if (matchIndex >= 0) {
|
|
1000
|
-
const
|
|
988
|
+
const existingOrder = updatedList[matchIndex];
|
|
989
|
+
if (this.isPendingSyncOrder(existingOrder) && this.isRemoteEchoMergeSource(source)) {
|
|
990
|
+
this.logInfo("mergeOrdersToStore-跳过 pending 订单远端覆盖", {
|
|
991
|
+
source,
|
|
992
|
+
order_id: existingOrder.order_id ?? fresh.order_id ?? null,
|
|
993
|
+
external_sale_number: existingOrder.external_sale_number ?? fresh.external_sale_number ?? null,
|
|
994
|
+
local_payment_status: existingOrder.payment_status ?? null,
|
|
995
|
+
remote_payment_status: fresh.payment_status ?? null,
|
|
996
|
+
local_need_sync: existingOrder.need_sync ?? null,
|
|
997
|
+
remote_need_sync: fresh.need_sync ?? null,
|
|
998
|
+
reason: "protect_pending_sync_order_from_remote_echo"
|
|
999
|
+
});
|
|
1000
|
+
continue;
|
|
1001
|
+
}
|
|
1002
|
+
const mergedOrder = this.mergeOrderRecords(existingOrder, fresh);
|
|
1001
1003
|
updatedList[matchIndex] = mergedOrder;
|
|
1004
|
+
this.registerOrderIdentityKeys(identityKeyToIndex, mergedOrder, matchIndex);
|
|
1002
1005
|
patchedOrders.push(mergedOrder);
|
|
1003
1006
|
if (mergeKey)
|
|
1004
1007
|
mergeActions[mergeKey] = "update";
|
|
1005
1008
|
continue;
|
|
1006
1009
|
}
|
|
1010
|
+
const insertIndex = updatedList.length;
|
|
1007
1011
|
updatedList.push(fresh);
|
|
1012
|
+
this.registerOrderIdentityKeys(identityKeyToIndex, fresh, insertIndex);
|
|
1008
1013
|
patchedOrders.push(fresh);
|
|
1009
1014
|
if (mergeKey)
|
|
1010
1015
|
mergeActions[mergeKey] = "insert";
|
|
@@ -1016,7 +1021,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
1016
1021
|
updateCount,
|
|
1017
1022
|
storeOrderCountAfter: this.store.list.length
|
|
1018
1023
|
});
|
|
1019
|
-
this.store.list = this.
|
|
1024
|
+
this.store.list = this.runOrderStoreSelfCheck(updatedList, `${source}.store`);
|
|
1020
1025
|
this.syncOrdersMap();
|
|
1021
1026
|
await this.patchOrdersInSQLite(patchedOrders, source, mergeActions);
|
|
1022
1027
|
this.logInfo("mergeOrdersToStore-结束", {
|
|
@@ -1027,6 +1032,53 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
1027
1032
|
});
|
|
1028
1033
|
await this.emitOrdersChanged(patchedOrders, source);
|
|
1029
1034
|
}
|
|
1035
|
+
/**
|
|
1036
|
+
* 在身份索引中查找与目标订单共享任一身份键的已存在订单下标。
|
|
1037
|
+
* 返回所有命中键里最小的下标,等价于原 findOrderIndexByIdentity 的「取首个匹配」语义。
|
|
1038
|
+
*
|
|
1039
|
+
* @example
|
|
1040
|
+
* const idx = this.lookupOrderIndexByIdentityKeys(identityKeyToIndex, freshIdentityKeys)
|
|
1041
|
+
*/
|
|
1042
|
+
lookupOrderIndexByIdentityKeys(identityKeyToIndex, identityKeys) {
|
|
1043
|
+
let matchIndex = -1;
|
|
1044
|
+
for (const key of identityKeys) {
|
|
1045
|
+
const index = identityKeyToIndex.get(key);
|
|
1046
|
+
if (index === void 0)
|
|
1047
|
+
continue;
|
|
1048
|
+
if (matchIndex < 0 || index < matchIndex)
|
|
1049
|
+
matchIndex = index;
|
|
1050
|
+
}
|
|
1051
|
+
return matchIndex;
|
|
1052
|
+
}
|
|
1053
|
+
isRemoteEchoMergeSource(source) {
|
|
1054
|
+
return source === "pubsub.bodyUpsert" || source === "pubsub.httpRefresh";
|
|
1055
|
+
}
|
|
1056
|
+
/**
|
|
1057
|
+
* 将订单的全部身份键登记到索引并指向其在列表中的下标。
|
|
1058
|
+
* 合并后订单新增的身份字段(如挂单同步后拿到 order_id)也会被登记。
|
|
1059
|
+
*
|
|
1060
|
+
* @example
|
|
1061
|
+
* this.registerOrderIdentityKeys(identityKeyToIndex, mergedOrder, matchIndex)
|
|
1062
|
+
*/
|
|
1063
|
+
registerOrderIdentityKeys(identityKeyToIndex, order, index) {
|
|
1064
|
+
const identityKeys = this.getOrderIdentityMatchKeys(order);
|
|
1065
|
+
for (const key of identityKeys) {
|
|
1066
|
+
identityKeyToIndex.set(key, index);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
/**
|
|
1070
|
+
* 合并热路径去重兜底:默认直接返回原列表(增量索引已保证无重复)。
|
|
1071
|
+
* 仅当 ORDER_MERGE_SELF_CHECK 开启时,额外做一次全表压缩并在发现残留重复时告警。
|
|
1072
|
+
*/
|
|
1073
|
+
runOrderStoreSelfCheck(orders, source) {
|
|
1074
|
+
if (!ORDER_MERGE_SELF_CHECK)
|
|
1075
|
+
return orders;
|
|
1076
|
+
const { list, removedCount } = this.compactOrderListByIdentity(orders, source);
|
|
1077
|
+
if (removedCount > 0) {
|
|
1078
|
+
this.logWarning("合并自检发现残留重复订单,已压缩", { source, removedCount });
|
|
1079
|
+
}
|
|
1080
|
+
return list;
|
|
1081
|
+
}
|
|
1030
1082
|
normalizeOrderSyncMessage(res) {
|
|
1031
1083
|
const data = (res == null ? void 0 : res.data) || res;
|
|
1032
1084
|
if (!data)
|
|
@@ -1640,7 +1692,6 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
1640
1692
|
async clear() {
|
|
1641
1693
|
this.store.list = [];
|
|
1642
1694
|
this.store.map = /* @__PURE__ */ new Map();
|
|
1643
|
-
this.resourceIdIndex.clear();
|
|
1644
1695
|
if (this.dbManager) {
|
|
1645
1696
|
await this.runInOrderSQLiteSaveQueue(async () => {
|
|
1646
1697
|
await this.dbManager.clear(INDEXDB_STORE_NAME);
|
|
@@ -30,7 +30,7 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
|
|
|
30
30
|
protected defaultVersion: string;
|
|
31
31
|
isSolution: boolean;
|
|
32
32
|
protected initializeOptions: ModuleOptions;
|
|
33
|
-
|
|
33
|
+
protected logger: any;
|
|
34
34
|
protected appPlugin: AppPlugin;
|
|
35
35
|
protected store: BaseSalesState;
|
|
36
36
|
protected otherParams: Record<string, any>;
|
|
@@ -46,6 +46,9 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
|
|
|
46
46
|
protected hasManualDiscountSelection: boolean;
|
|
47
47
|
private readonly reusedSharedSubModuleNames;
|
|
48
48
|
private paymentMethodsCache;
|
|
49
|
+
private queuedAutoPaymentSync;
|
|
50
|
+
private autoPaymentSyncFlushing;
|
|
51
|
+
private autoPaymentSyncTimer;
|
|
49
52
|
constructor(name?: string, version?: string);
|
|
50
53
|
/**
|
|
51
54
|
* 子类可覆盖此方法以追加/收敛要注册的子模块。
|
|
@@ -195,6 +198,7 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
|
|
|
195
198
|
payments?: OrderPaymentSource[];
|
|
196
199
|
paymentStatus?: BaseSalesPaymentStatus;
|
|
197
200
|
smallTicketDataFlag?: number;
|
|
201
|
+
confirmPendingVoucherPayments?: boolean;
|
|
198
202
|
enhancePayload?: SubmitPayloadEnhancer;
|
|
199
203
|
}): Promise<T>;
|
|
200
204
|
/**
|
|
@@ -207,12 +211,14 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
|
|
|
207
211
|
payments?: OrderPaymentSource[];
|
|
208
212
|
paymentStatus?: BaseSalesPaymentStatus;
|
|
209
213
|
smallTicketDataFlag?: number;
|
|
214
|
+
confirmPendingVoucherPayments?: boolean;
|
|
210
215
|
enhancePayload?: SubmitPayloadEnhancer;
|
|
211
216
|
}): Promise<T>;
|
|
212
217
|
submitTempOrderAsync<T = any>(params?: {
|
|
213
218
|
payments?: OrderPaymentSource[];
|
|
214
219
|
paymentStatus?: BaseSalesPaymentStatus;
|
|
215
220
|
smallTicketDataFlag?: number;
|
|
221
|
+
confirmPendingVoucherPayments?: boolean;
|
|
216
222
|
enhancePayload?: SubmitPayloadEnhancer;
|
|
217
223
|
}): Promise<T>;
|
|
218
224
|
printLocalOrderReceipt<T = any>(lookup?: BaseSalesPrintLocalOrderReceiptParams): Promise<T>;
|
|
@@ -222,6 +228,10 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
|
|
|
222
228
|
getOrderPayments(): OrderPaymentData[];
|
|
223
229
|
/** 覆盖当前订单支付项,内部由 OrderModule 清洗为 Sales 协议字段。 */
|
|
224
230
|
setOrderPayments(payments: OrderPaymentSource[]): OrderPaymentData[];
|
|
231
|
+
private getPaymentStatusForSync;
|
|
232
|
+
private queueLatestAutoPaymentSync;
|
|
233
|
+
private scheduleQueuedAutoPaymentSyncFlush;
|
|
234
|
+
private flushQueuedAutoPaymentSync;
|
|
225
235
|
/** 追加单个支付项到当前订单。 */
|
|
226
236
|
addOrderPayment(payment: OrderPaymentSource): OrderPaymentData[];
|
|
227
237
|
/** 更新当前订单中的指定支付项。 */
|
|
@@ -229,7 +239,11 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
|
|
|
229
239
|
/** 删除当前订单中的指定支付项。 */
|
|
230
240
|
deleteOrderPayment(paymentIdentity: string | number): OrderPaymentData[];
|
|
231
241
|
/** 覆盖 wallet pass 支付项,同时保留普通支付项。 */
|
|
232
|
-
updateVoucherOrderPayments(payments: OrderPaymentSource[]
|
|
242
|
+
updateVoucherOrderPayments(payments: OrderPaymentSource[], options?: {
|
|
243
|
+
status?: 'paid' | 'payment_pending';
|
|
244
|
+
}): OrderPaymentData[];
|
|
245
|
+
confirmPendingVoucherPayments(): OrderPaymentData[];
|
|
246
|
+
discardPendingVoucherPayments(): OrderPaymentData[];
|
|
233
247
|
private getWalletProductList;
|
|
234
248
|
initWalletData(params?: {
|
|
235
249
|
order_wait_pay_amount?: number;
|