@pisell/pisellos 2.2.205 → 2.2.206
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/OpenData/index.d.ts +1 -0
- package/dist/modules/OpenData/index.js +21 -16
- package/dist/modules/Order/index.d.ts +9 -0
- package/dist/modules/Order/index.js +230 -145
- package/dist/modules/Order/types.d.ts +10 -0
- package/dist/modules/Order/utils.js +7 -5
- package/dist/server/index.d.ts +136 -0
- package/dist/server/index.js +2054 -781
- package/dist/server/modules/order/index.d.ts +7 -0
- package/dist/server/modules/order/index.js +375 -337
- package/dist/server/modules/order/types.d.ts +15 -0
- package/dist/server/modules/order/types.js +8 -0
- package/dist/server/utils/small-ticket.d.ts +69 -0
- package/dist/server/utils/small-ticket.js +528 -0
- package/dist/solution/BaseSales/index.d.ts +4 -2
- package/dist/solution/BaseSales/index.js +248 -188
- package/dist/solution/BookingByStep/index.d.ts +1 -1
- package/dist/solution/BookingTicket/index.d.ts +8 -1
- package/dist/solution/BookingTicket/index.js +443 -263
- package/dist/solution/BookingTicket/types.d.ts +2 -1
- package/dist/solution/BookingTicket/types.js +3 -1
- package/lib/modules/OpenData/index.d.ts +1 -0
- package/lib/modules/OpenData/index.js +6 -4
- package/lib/modules/Order/index.d.ts +9 -0
- package/lib/modules/Order/index.js +59 -3
- package/lib/modules/Order/types.d.ts +10 -0
- package/lib/modules/Order/utils.js +4 -3
- package/lib/server/index.d.ts +136 -0
- package/lib/server/index.js +822 -50
- package/lib/server/modules/order/index.d.ts +7 -0
- package/lib/server/modules/order/index.js +21 -7
- package/lib/server/modules/order/types.d.ts +15 -0
- package/lib/server/utils/small-ticket.d.ts +69 -0
- package/lib/server/utils/small-ticket.js +555 -0
- package/lib/solution/BaseSales/index.d.ts +4 -2
- package/lib/solution/BaseSales/index.js +47 -1
- package/lib/solution/BookingByStep/index.d.ts +1 -1
- package/lib/solution/BookingTicket/index.d.ts +8 -1
- package/lib/solution/BookingTicket/index.js +108 -1
- package/lib/solution/BookingTicket/types.d.ts +2 -1
- package/lib/solution/BookingTicket/types.js +5 -0
- package/package.json +1 -1
package/lib/server/index.js
CHANGED
|
@@ -49,6 +49,7 @@ var import_order = require("./modules/order");
|
|
|
49
49
|
var import_types3 = require("./modules/order/types");
|
|
50
50
|
var import_filterOrders = require("./modules/order/utils/filterOrders");
|
|
51
51
|
var import_filterBookings = require("./modules/order/utils/filterBookings");
|
|
52
|
+
var import_small_ticket = require("./utils/small-ticket");
|
|
52
53
|
__reExport(server_exports, require("./modules"), module.exports);
|
|
53
54
|
var Server = class {
|
|
54
55
|
constructor(core) {
|
|
@@ -70,6 +71,8 @@ var Server = class {
|
|
|
70
71
|
this.bookingRemoteCache = /* @__PURE__ */ new Map();
|
|
71
72
|
this.BOOKING_REMOTE_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
72
73
|
this.BOOKING_REMOTE_CACHE_MAX_ENTRIES = 80;
|
|
74
|
+
this.salesSearchSubscribers = /* @__PURE__ */ new Map();
|
|
75
|
+
this.deviceTaskActionsRegistered = false;
|
|
73
76
|
this.floorPlanQuerySubscribers = /* @__PURE__ */ new Map();
|
|
74
77
|
// 模块注册表 - 定义所有可用的模块配置
|
|
75
78
|
this.moduleRegistry = {
|
|
@@ -154,7 +157,6 @@ var Server = class {
|
|
|
154
157
|
}
|
|
155
158
|
};
|
|
156
159
|
this.handlePaymentMethods = async ({ data, config }) => {
|
|
157
|
-
debugger;
|
|
158
160
|
const payment = await this.getPaymentRouteModule();
|
|
159
161
|
const payMethods = await payment.getPayMethodListAsync({
|
|
160
162
|
shouldFilter: (data == null ? void 0 : data.filterPaymentMethods) === true || (config == null ? void 0 : config.filterPaymentMethods) === true
|
|
@@ -292,6 +294,41 @@ var Server = class {
|
|
|
292
294
|
}
|
|
293
295
|
return { code: 200, message: "ok", status: true };
|
|
294
296
|
};
|
|
297
|
+
/**
|
|
298
|
+
* 处理 sales 单模块搜索:请求透传后端,仅在 OS 层维护已返回订单号集合。
|
|
299
|
+
*
|
|
300
|
+
* @example
|
|
301
|
+
* await this.handleSalesSearchModule({ url: '/shop/es/search/sales', method: 'post', data: { keyword: 'A' } })
|
|
302
|
+
*/
|
|
303
|
+
this.handleSalesSearchModule = async ({ data, config }) => {
|
|
304
|
+
return this.handleSalesSearchRequest("single", "/shop/es/search/sales", data, config);
|
|
305
|
+
};
|
|
306
|
+
/**
|
|
307
|
+
* 处理 sales 聚合搜索:请求透传后端,仅订阅 sales 分组的已返回订单号。
|
|
308
|
+
*
|
|
309
|
+
* @example
|
|
310
|
+
* await this.handleSalesSearchAggregate({ url: '/shop/es/search', method: 'post', data: { modules: [] } })
|
|
311
|
+
*/
|
|
312
|
+
this.handleSalesSearchAggregate = async ({ data, config }) => {
|
|
313
|
+
return this.handleSalesSearchRequest("aggregate", "/shop/es/search", data, config);
|
|
314
|
+
};
|
|
315
|
+
/**
|
|
316
|
+
* 取消 sales 搜索订阅。
|
|
317
|
+
*
|
|
318
|
+
* @example
|
|
319
|
+
* await this.handleUnsubscribeSalesSearch({ data: { subscriberId: 'global-search-order' } } as any)
|
|
320
|
+
*/
|
|
321
|
+
this.handleUnsubscribeSalesSearch = async ({ data }) => {
|
|
322
|
+
const { subscriberId } = data || {};
|
|
323
|
+
if (subscriberId) {
|
|
324
|
+
this.salesSearchSubscribers.delete(subscriberId);
|
|
325
|
+
this.logInfo("handleUnsubscribeSalesSearch: 已移除订阅者", {
|
|
326
|
+
subscriberId,
|
|
327
|
+
remaining: this.salesSearchSubscribers.size
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
return { code: 200, message: "ok", status: true };
|
|
331
|
+
};
|
|
295
332
|
/**
|
|
296
333
|
* 日历(compact_fields)场景下要直接在 SSE 层排除的订单类型:
|
|
297
334
|
* 这些类型不会出现在日历上,拉取它们只会浪费带宽并增加前端过滤成本。
|
|
@@ -488,6 +525,54 @@ var Server = class {
|
|
|
488
525
|
title: "handleOrderCheckout"
|
|
489
526
|
});
|
|
490
527
|
};
|
|
528
|
+
this.handleLocalPrintOrder = async ({ data }) => {
|
|
529
|
+
try {
|
|
530
|
+
const inputOrder = (data == null ? void 0 : data.order) && typeof data.order === "object" ? data.order : data;
|
|
531
|
+
if (!inputOrder || typeof inputOrder !== "object") {
|
|
532
|
+
return {
|
|
533
|
+
code: 400,
|
|
534
|
+
status: false,
|
|
535
|
+
message: "订单数据不能为空",
|
|
536
|
+
data: { printed: false, order: null }
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
const checkoutData = await this.ensureCheckoutOrderNumbers(
|
|
540
|
+
{
|
|
541
|
+
...inputOrder,
|
|
542
|
+
small_ticket_data_flag: 1
|
|
543
|
+
},
|
|
544
|
+
"handleLocalPrintOrder"
|
|
545
|
+
);
|
|
546
|
+
const printableOrder = {
|
|
547
|
+
...checkoutData,
|
|
548
|
+
need_sync: checkoutData.need_sync ?? 1
|
|
549
|
+
};
|
|
550
|
+
const printResult = await this.dispatchLocalReceiptPrint({
|
|
551
|
+
checkoutData,
|
|
552
|
+
order: printableOrder,
|
|
553
|
+
response: null,
|
|
554
|
+
requireFullyPaid: false
|
|
555
|
+
});
|
|
556
|
+
return {
|
|
557
|
+
code: 200,
|
|
558
|
+
status: true,
|
|
559
|
+
message: "",
|
|
560
|
+
data: {
|
|
561
|
+
printed: printResult.printed,
|
|
562
|
+
order: printResult.order
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
} catch (error) {
|
|
566
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
567
|
+
this.logError("handleLocalPrintOrder: 本地打印小票失败", { error: message });
|
|
568
|
+
return {
|
|
569
|
+
code: 500,
|
|
570
|
+
status: false,
|
|
571
|
+
message,
|
|
572
|
+
data: { printed: false, order: null }
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
};
|
|
491
576
|
this.handleOrderSalesDetail = async ({ url, data, path }) => {
|
|
492
577
|
var _a;
|
|
493
578
|
const requestPath = this.parseRequestPath(url, path);
|
|
@@ -934,6 +1019,7 @@ var Server = class {
|
|
|
934
1019
|
moduleConfigs
|
|
935
1020
|
});
|
|
936
1021
|
this.registerServerRoutes();
|
|
1022
|
+
this.registerDeviceTaskActions();
|
|
937
1023
|
const registeredModules = await this.registerModules(moduleConfigs);
|
|
938
1024
|
if (autoPreload) {
|
|
939
1025
|
await this.preloadModulesData(registeredModules, options);
|
|
@@ -949,10 +1035,11 @@ var Server = class {
|
|
|
949
1035
|
this.core.effects.on(import_types.FloorPlanHooks.onFloorPlansChanged, () => {
|
|
950
1036
|
void this.recomputeAndNotifyFloorPlanQuery();
|
|
951
1037
|
});
|
|
952
|
-
this.core.effects.on(import_types3.OrderHooks.onOrdersChanged, () => {
|
|
1038
|
+
this.core.effects.on(import_types3.OrderHooks.onOrdersChanged, (payload) => {
|
|
953
1039
|
this.recomputeAndNotifyOrderQuery();
|
|
954
1040
|
this.recomputeAndNotifyBookingQuery();
|
|
955
1041
|
this.syncBookingRemoteCacheFromOrderChanges();
|
|
1042
|
+
this.notifySalesSearchSubscribers(payload);
|
|
956
1043
|
});
|
|
957
1044
|
const duration = Date.now() - startTime;
|
|
958
1045
|
this.logInfo("Server 初始化完成", {
|
|
@@ -961,6 +1048,114 @@ var Server = class {
|
|
|
961
1048
|
});
|
|
962
1049
|
return registeredModules;
|
|
963
1050
|
}
|
|
1051
|
+
registerDeviceTaskActions() {
|
|
1052
|
+
if (this.deviceTaskActionsRegistered)
|
|
1053
|
+
return;
|
|
1054
|
+
const deviceTask = this.getDeviceTaskPlugin();
|
|
1055
|
+
if (!(deviceTask == null ? void 0 : deviceTask.registerAction)) {
|
|
1056
|
+
this.logWarning("registerDeviceTaskActions: deviceTask 插件不可用");
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
const registeredActions = new Set(
|
|
1060
|
+
typeof deviceTask.getActions === "function" ? (deviceTask.getActions() || []).map((action) => action == null ? void 0 : action.action).filter(Boolean) : []
|
|
1061
|
+
);
|
|
1062
|
+
const register = (definition) => {
|
|
1063
|
+
var _a;
|
|
1064
|
+
if (registeredActions.has(definition.action))
|
|
1065
|
+
return;
|
|
1066
|
+
(_a = deviceTask.registerAction) == null ? void 0 : _a.call(deviceTask, definition);
|
|
1067
|
+
registeredActions.add(definition.action);
|
|
1068
|
+
};
|
|
1069
|
+
register({
|
|
1070
|
+
action: "sync_sales",
|
|
1071
|
+
name: "同步订单",
|
|
1072
|
+
validate: (payload) => (payload == null ? void 0 : payload.backendPath) && (payload == null ? void 0 : payload.data) ? null : "缺少 backendPath 或 data",
|
|
1073
|
+
handler: (ctx) => this.handleSyncSalesTask(ctx)
|
|
1074
|
+
});
|
|
1075
|
+
this.deviceTaskActionsRegistered = true;
|
|
1076
|
+
this.logInfo("DeviceTask actions 已注册", {
|
|
1077
|
+
actions: ["sync_sales"]
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
async handleSyncSalesTask(ctx) {
|
|
1081
|
+
var _a, _b, _c;
|
|
1082
|
+
const payload = ((_a = ctx.task) == null ? void 0 : _a.payload) || {};
|
|
1083
|
+
const backendPath = payload.backendPath;
|
|
1084
|
+
const data = payload.data;
|
|
1085
|
+
const title = payload.title || "sync_sales";
|
|
1086
|
+
if (!backendPath || !data) {
|
|
1087
|
+
return this.taskFail("INVALID_PAYLOAD", "缺少 backendPath 或 data");
|
|
1088
|
+
}
|
|
1089
|
+
if (!((_b = this.app) == null ? void 0 : _b.request)) {
|
|
1090
|
+
this.logError(`${title}: sync_sales app.request 不可用`);
|
|
1091
|
+
return this.taskTimeout("app.request 不可用");
|
|
1092
|
+
}
|
|
1093
|
+
try {
|
|
1094
|
+
const response = await this.app.request.post("/shop/order/sales/checkout", data, {
|
|
1095
|
+
isShopApi: true,
|
|
1096
|
+
customToast: () => {
|
|
1097
|
+
}
|
|
1098
|
+
});
|
|
1099
|
+
if (this.isCheckoutResponseRejected(response)) {
|
|
1100
|
+
const message = (response == null ? void 0 : response.message) || "后端明确拒绝 checkout";
|
|
1101
|
+
this.logError(`${title}: sync_sales 后端明确失败`, {
|
|
1102
|
+
backendPath,
|
|
1103
|
+
response
|
|
1104
|
+
});
|
|
1105
|
+
return this.taskFail("BACKEND_REJECTED", message, response);
|
|
1106
|
+
}
|
|
1107
|
+
const fresh = this.extractOrderDataFromCheckoutResponse(response);
|
|
1108
|
+
if (!fresh) {
|
|
1109
|
+
this.logWarning(`${title}: sync_sales 返回未包含可同步订单`, {
|
|
1110
|
+
backendPath,
|
|
1111
|
+
response
|
|
1112
|
+
});
|
|
1113
|
+
return this.taskTimeout("checkout 返回未包含可同步订单");
|
|
1114
|
+
}
|
|
1115
|
+
const syncedOrder = { ...fresh, need_sync: 0 };
|
|
1116
|
+
if (this.order) {
|
|
1117
|
+
await this.order.upsertOrdersFromRemote([syncedOrder]);
|
|
1118
|
+
this.logInfo(`${title}: sync_sales 订单已同步到本地`, {
|
|
1119
|
+
order_id: fresh.order_id,
|
|
1120
|
+
external_sale_number: fresh.external_sale_number,
|
|
1121
|
+
order_number: fresh.order_number
|
|
1122
|
+
});
|
|
1123
|
+
} else {
|
|
1124
|
+
this.logWarning(`${title}: sync_sales Order 模块未注册`, {
|
|
1125
|
+
backendPath,
|
|
1126
|
+
order_id: fresh.order_id
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
if (this.shouldPrintSyncedOrder({ checkoutData: data, syncedOrder })) {
|
|
1130
|
+
await this.dispatchPrintOtherReceiptTask({
|
|
1131
|
+
checkoutData: data,
|
|
1132
|
+
syncedOrder,
|
|
1133
|
+
response,
|
|
1134
|
+
deviceId: (_c = ctx.task) == null ? void 0 : _c.device_id
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
return this.taskOk({
|
|
1138
|
+
order: syncedOrder,
|
|
1139
|
+
response: this.normalizeCheckoutResponse(response)
|
|
1140
|
+
});
|
|
1141
|
+
} catch (error) {
|
|
1142
|
+
const backendError = this.extractBackendErrorResponse(error);
|
|
1143
|
+
if (backendError) {
|
|
1144
|
+
const message = (backendError == null ? void 0 : backendError.message) || "后端明确拒绝 checkout";
|
|
1145
|
+
this.logError(`${title}: sync_sales 后端明确失败`, {
|
|
1146
|
+
backendPath,
|
|
1147
|
+
backendError
|
|
1148
|
+
});
|
|
1149
|
+
return this.taskFail("BACKEND_REJECTED", message, backendError);
|
|
1150
|
+
}
|
|
1151
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1152
|
+
this.logError(`${title}: sync_sales 请求未明确成功`, {
|
|
1153
|
+
backendPath,
|
|
1154
|
+
error: errorMessage
|
|
1155
|
+
});
|
|
1156
|
+
return this.taskTimeout(errorMessage);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
964
1159
|
/**
|
|
965
1160
|
* 将普通层 QuotationModule 的报价单计算能力桥接到 Server Products 模块。
|
|
966
1161
|
*/
|
|
@@ -1264,6 +1459,26 @@ var Server = class {
|
|
|
1264
1459
|
path: "/shop/order/v2/list/unsubscribe",
|
|
1265
1460
|
handler: this.handleUnsubscribeOrderQuery.bind(this)
|
|
1266
1461
|
},
|
|
1462
|
+
{
|
|
1463
|
+
method: "post",
|
|
1464
|
+
path: "/shop/es/search/sales",
|
|
1465
|
+
handler: this.handleSalesSearchModule.bind(this)
|
|
1466
|
+
},
|
|
1467
|
+
{
|
|
1468
|
+
method: "post",
|
|
1469
|
+
path: "/shop/es/search/sales/unsubscribe",
|
|
1470
|
+
handler: this.handleUnsubscribeSalesSearch.bind(this)
|
|
1471
|
+
},
|
|
1472
|
+
{
|
|
1473
|
+
method: "post",
|
|
1474
|
+
path: "/shop/es/search",
|
|
1475
|
+
handler: this.handleSalesSearchAggregate.bind(this)
|
|
1476
|
+
},
|
|
1477
|
+
{
|
|
1478
|
+
method: "post",
|
|
1479
|
+
path: "/shop/es/search/unsubscribe",
|
|
1480
|
+
handler: this.handleUnsubscribeSalesSearch.bind(this)
|
|
1481
|
+
},
|
|
1267
1482
|
{
|
|
1268
1483
|
method: "get",
|
|
1269
1484
|
path: "/shop/schedule/booking",
|
|
@@ -1296,8 +1511,13 @@ var Server = class {
|
|
|
1296
1511
|
},
|
|
1297
1512
|
{
|
|
1298
1513
|
method: "post",
|
|
1299
|
-
path: "/
|
|
1514
|
+
path: "/order/checkout",
|
|
1300
1515
|
handler: this.handleOrderCheckout.bind(this)
|
|
1516
|
+
},
|
|
1517
|
+
{
|
|
1518
|
+
method: "post",
|
|
1519
|
+
path: "/local/print-order",
|
|
1520
|
+
handler: this.handleLocalPrintOrder.bind(this)
|
|
1301
1521
|
}
|
|
1302
1522
|
]);
|
|
1303
1523
|
this.registerPrefixRoutes([
|
|
@@ -1344,6 +1564,162 @@ var Server = class {
|
|
|
1344
1564
|
});
|
|
1345
1565
|
}
|
|
1346
1566
|
}
|
|
1567
|
+
/**
|
|
1568
|
+
* 执行 sales 搜索并登记可见订单号。
|
|
1569
|
+
* 搜索参数完全透传后端,OS 不做过滤、筛选、排序。
|
|
1570
|
+
*
|
|
1571
|
+
* @example
|
|
1572
|
+
* const result = await this.handleSalesSearchRequest('single', '/shop/es/search/sales', { keyword: '1001' }, {})
|
|
1573
|
+
*/
|
|
1574
|
+
async handleSalesSearchRequest(kind, backendPath, data, config) {
|
|
1575
|
+
const queryPayload = data && typeof data === "object" ? { ...data } : {};
|
|
1576
|
+
const result = await this.fetchSalesSearchFromAPI(backendPath, queryPayload);
|
|
1577
|
+
const { callback, subscriberId, resetVisibleOrderIds } = config || {};
|
|
1578
|
+
if (subscriberId && typeof callback === "function") {
|
|
1579
|
+
const contextSignature = this.buildSalesSearchSignature(kind, queryPayload);
|
|
1580
|
+
const existing = this.salesSearchSubscribers.get(subscriberId);
|
|
1581
|
+
const shouldReset = resetVisibleOrderIds === true || !existing || existing.kind !== kind || existing.contextSignature !== contextSignature;
|
|
1582
|
+
const visibleOrderIds = shouldReset ? /* @__PURE__ */ new Set() : new Set(existing.visibleOrderIds);
|
|
1583
|
+
for (const orderId of this.extractSalesSearchOrderIds(kind, result)) {
|
|
1584
|
+
visibleOrderIds.add(orderId);
|
|
1585
|
+
}
|
|
1586
|
+
this.salesSearchSubscribers.set(subscriberId, {
|
|
1587
|
+
callback,
|
|
1588
|
+
kind,
|
|
1589
|
+
contextSignature,
|
|
1590
|
+
visibleOrderIds,
|
|
1591
|
+
lastResponseData: this.extractSalesSearchResponseData(result)
|
|
1592
|
+
});
|
|
1593
|
+
this.logInfo("handleSalesSearchRequest: 已注册/更新订阅者", {
|
|
1594
|
+
subscriberId,
|
|
1595
|
+
kind,
|
|
1596
|
+
shouldReset,
|
|
1597
|
+
visibleCount: visibleOrderIds.size
|
|
1598
|
+
});
|
|
1599
|
+
}
|
|
1600
|
+
return result;
|
|
1601
|
+
}
|
|
1602
|
+
/**
|
|
1603
|
+
* 透传调用后端 sales 搜索接口。
|
|
1604
|
+
*
|
|
1605
|
+
* @example
|
|
1606
|
+
* const res = await this.fetchSalesSearchFromAPI('/shop/es/search/sales', { keyword: '1001' })
|
|
1607
|
+
*/
|
|
1608
|
+
async fetchSalesSearchFromAPI(backendPath, data) {
|
|
1609
|
+
var _a;
|
|
1610
|
+
if (!((_a = this.app) == null ? void 0 : _a.request)) {
|
|
1611
|
+
this.logError("fetchSalesSearchFromAPI: app.request 不可用", { backendPath });
|
|
1612
|
+
return {
|
|
1613
|
+
code: 500,
|
|
1614
|
+
message: "app.request 不可用",
|
|
1615
|
+
data: backendPath === "/shop/es/search" ? { results: [] } : { list: [], count: 0 },
|
|
1616
|
+
status: false
|
|
1617
|
+
};
|
|
1618
|
+
}
|
|
1619
|
+
try {
|
|
1620
|
+
const response = await this.app.request.post(backendPath, data, {
|
|
1621
|
+
isShopApi: true
|
|
1622
|
+
});
|
|
1623
|
+
const payload = (response == null ? void 0 : response.data) ?? response;
|
|
1624
|
+
return {
|
|
1625
|
+
code: (response == null ? void 0 : response.code) ?? 200,
|
|
1626
|
+
status: (response == null ? void 0 : response.status) ?? true,
|
|
1627
|
+
message: (response == null ? void 0 : response.message) ?? "",
|
|
1628
|
+
data: payload
|
|
1629
|
+
};
|
|
1630
|
+
} catch (error) {
|
|
1631
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1632
|
+
this.logError("fetchSalesSearchFromAPI: 请求失败", {
|
|
1633
|
+
backendPath,
|
|
1634
|
+
error: errorMessage
|
|
1635
|
+
});
|
|
1636
|
+
return {
|
|
1637
|
+
code: 500,
|
|
1638
|
+
message: errorMessage,
|
|
1639
|
+
data: backendPath === "/shop/es/search" ? { results: [] } : { list: [], count: 0 },
|
|
1640
|
+
status: false
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
/**
|
|
1645
|
+
* 构建搜索订阅签名;页码变化不重置,可继续累计 visibleOrderIds。
|
|
1646
|
+
*
|
|
1647
|
+
* @example
|
|
1648
|
+
* const signature = this.buildSalesSearchSignature('single', { keyword: 'A', page: 2 })
|
|
1649
|
+
*/
|
|
1650
|
+
buildSalesSearchSignature(kind, payload) {
|
|
1651
|
+
return this.stableSerialize({
|
|
1652
|
+
kind,
|
|
1653
|
+
payload: this.stripSalesSearchPageParams(payload)
|
|
1654
|
+
});
|
|
1655
|
+
}
|
|
1656
|
+
/**
|
|
1657
|
+
* 移除页码参数,保留 page size、keyword、filters、sort 等查询语义参数。
|
|
1658
|
+
*
|
|
1659
|
+
* @example
|
|
1660
|
+
* this.stripSalesSearchPageParams({ page: 2, per_page: 20, keyword: 'A' })
|
|
1661
|
+
*/
|
|
1662
|
+
stripSalesSearchPageParams(value) {
|
|
1663
|
+
if (Array.isArray(value)) {
|
|
1664
|
+
return value.map((item) => this.stripSalesSearchPageParams(item));
|
|
1665
|
+
}
|
|
1666
|
+
if (!value || typeof value !== "object")
|
|
1667
|
+
return value;
|
|
1668
|
+
const next = {};
|
|
1669
|
+
for (const [key, item] of Object.entries(value)) {
|
|
1670
|
+
if (key === "page" || key === "skip")
|
|
1671
|
+
continue;
|
|
1672
|
+
next[key] = this.stripSalesSearchPageParams(item);
|
|
1673
|
+
}
|
|
1674
|
+
return next;
|
|
1675
|
+
}
|
|
1676
|
+
/**
|
|
1677
|
+
* 提取响应 data,兼容 OS 包装结构与后端直接响应结构。
|
|
1678
|
+
*
|
|
1679
|
+
* @example
|
|
1680
|
+
* const data = this.extractSalesSearchResponseData(result)
|
|
1681
|
+
*/
|
|
1682
|
+
extractSalesSearchResponseData(result) {
|
|
1683
|
+
return (result == null ? void 0 : result.data) ?? result;
|
|
1684
|
+
}
|
|
1685
|
+
/**
|
|
1686
|
+
* 从搜索响应中提取 sales 命中的订单号。
|
|
1687
|
+
*
|
|
1688
|
+
* @example
|
|
1689
|
+
* const ids = this.extractSalesSearchOrderIds('single', result)
|
|
1690
|
+
*/
|
|
1691
|
+
extractSalesSearchOrderIds(kind, result) {
|
|
1692
|
+
var _a;
|
|
1693
|
+
const data = this.extractSalesSearchResponseData(result);
|
|
1694
|
+
const list = kind === "aggregate" ? (_a = this.findSalesSearchAggregateGroup(data)) == null ? void 0 : _a.list : data == null ? void 0 : data.list;
|
|
1695
|
+
if (!Array.isArray(list))
|
|
1696
|
+
return [];
|
|
1697
|
+
return list.map((item) => this.getSalesSearchOrderId(item)).filter((id) => !!id);
|
|
1698
|
+
}
|
|
1699
|
+
/**
|
|
1700
|
+
* 查找聚合搜索响应中的 sales 分组。
|
|
1701
|
+
*
|
|
1702
|
+
* @example
|
|
1703
|
+
* const group = this.findSalesSearchAggregateGroup({ results: [{ module: 'sales' }] })
|
|
1704
|
+
*/
|
|
1705
|
+
findSalesSearchAggregateGroup(data) {
|
|
1706
|
+
const results = Array.isArray(data == null ? void 0 : data.results) ? data.results : [];
|
|
1707
|
+
return results.find((item) => (item == null ? void 0 : item.module) === "sales") || null;
|
|
1708
|
+
}
|
|
1709
|
+
/**
|
|
1710
|
+
* 从订单或搜索命中项中取订单号。
|
|
1711
|
+
*
|
|
1712
|
+
* @example
|
|
1713
|
+
* const id = this.getSalesSearchOrderId({ order_id: 1001 })
|
|
1714
|
+
*/
|
|
1715
|
+
getSalesSearchOrderId(order) {
|
|
1716
|
+
if (!order || typeof order !== "object")
|
|
1717
|
+
return null;
|
|
1718
|
+
const raw = order.order_id ?? order.id;
|
|
1719
|
+
if (raw === void 0 || raw === null || raw === "")
|
|
1720
|
+
return null;
|
|
1721
|
+
return String(raw);
|
|
1722
|
+
}
|
|
1347
1723
|
/**
|
|
1348
1724
|
* 从查询值中提取 YYYY-MM-DD 日期串。
|
|
1349
1725
|
*
|
|
@@ -1784,8 +2160,203 @@ var Server = class {
|
|
|
1784
2160
|
};
|
|
1785
2161
|
}
|
|
1786
2162
|
}
|
|
1787
|
-
|
|
2163
|
+
getDeviceTaskPlugin() {
|
|
2164
|
+
var _a, _b;
|
|
2165
|
+
return ((_b = (_a = this.app) == null ? void 0 : _a.getPlugin) == null ? void 0 : _b.call(_a, "deviceTask")) || null;
|
|
2166
|
+
}
|
|
2167
|
+
getIdGeneratorPlugin() {
|
|
2168
|
+
var _a, _b;
|
|
2169
|
+
return ((_b = (_a = this.app) == null ? void 0 : _a.getPlugin) == null ? void 0 : _b.call(_a, "idGenerator")) || null;
|
|
2170
|
+
}
|
|
2171
|
+
getAppData(key) {
|
|
2172
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v;
|
|
2173
|
+
const getData = (_b = (_a = this.app) == null ? void 0 : _a.getData) == null ? void 0 : _b.call(_a);
|
|
2174
|
+
if (typeof getData === "function")
|
|
2175
|
+
return getData(key);
|
|
2176
|
+
const directValue = (_d = (_c = this.app) == null ? void 0 : _c.getData) == null ? void 0 : _d.call(_c, key);
|
|
2177
|
+
if (directValue !== void 0)
|
|
2178
|
+
return directValue;
|
|
2179
|
+
const coreData = ((_i = (_g = (_f = (_e = this.app) == null ? void 0 : _e.models) == null ? void 0 : _f.getStore) == null ? void 0 : (_h = _g.call(_f)).getDataByModel) == null ? void 0 : _i.call(_h, "core", "core")) || ((_o = (_m = (_l = (_k = (_j = this.app) == null ? void 0 : _j.data) == null ? void 0 : _k.store) == null ? void 0 : _l.getStore) == null ? void 0 : (_n = _m.call(_l)).getDataByModel) == null ? void 0 : _o.call(_n, "core", "core")) || ((_v = (_u = (_s = (_r = (_q = (_p = this.app) == null ? void 0 : _p.data) == null ? void 0 : _q.store) == null ? void 0 : _r.getStore) == null ? void 0 : (_t = _s.call(_r)).getDataByModel) == null ? void 0 : _u.call(_t, "core")) == null ? void 0 : _v.core);
|
|
2180
|
+
return coreData == null ? void 0 : coreData[key];
|
|
2181
|
+
}
|
|
2182
|
+
getSmallTicketShopInfo() {
|
|
2183
|
+
const shopInfo = this.getAppData("shop_info") || {};
|
|
2184
|
+
return {
|
|
2185
|
+
...shopInfo,
|
|
2186
|
+
currency_symbol: shopInfo.currency_symbol || this.getAppData("shop_symbol") || this.getAppData("currency_symbol"),
|
|
2187
|
+
currency_code: shopInfo.currency_code || this.getAppData("shop_currency_code") || this.getAppData("currency_code"),
|
|
2188
|
+
tax_title: shopInfo.tax_title || this.getAppData("tax_title"),
|
|
2189
|
+
tax_country_code: shopInfo.tax_country_code || this.getAppData("tax_country_code"),
|
|
2190
|
+
is_price_include_tax: shopInfo.is_price_include_tax ?? this.getAppData("is_price_include_tax")
|
|
2191
|
+
};
|
|
2192
|
+
}
|
|
2193
|
+
taskOk(result) {
|
|
2194
|
+
return { status: "success", result };
|
|
2195
|
+
}
|
|
2196
|
+
taskFail(code, message, result) {
|
|
2197
|
+
return {
|
|
2198
|
+
status: "failed",
|
|
2199
|
+
error: { code, message },
|
|
2200
|
+
...result !== void 0 ? { result } : {}
|
|
2201
|
+
};
|
|
2202
|
+
}
|
|
2203
|
+
taskTimeout(message) {
|
|
2204
|
+
return {
|
|
2205
|
+
status: "timeout",
|
|
2206
|
+
error: { code: "TIMEOUT", message }
|
|
2207
|
+
};
|
|
2208
|
+
}
|
|
2209
|
+
resolveCheckoutTaskDeviceId(_data) {
|
|
2210
|
+
return this.getAppData("device_id") || 0;
|
|
2211
|
+
}
|
|
2212
|
+
buildCheckoutTaskIdempotencyKey(data) {
|
|
2213
|
+
const identity = (data == null ? void 0 : data.request_unique_idempotency_token) || (data == null ? void 0 : data.external_sale_number) || (data == null ? void 0 : data.order_id) || (data == null ? void 0 : data.shop_order_number) || (data == null ? void 0 : data.shop_full_order_number);
|
|
2214
|
+
if (identity === void 0 || identity === null || identity === "")
|
|
2215
|
+
return void 0;
|
|
2216
|
+
return `sync_sales:${identity}`;
|
|
2217
|
+
}
|
|
2218
|
+
async ensureCheckoutOrderNumbers(data, title) {
|
|
2219
|
+
if (!data || typeof data !== "object")
|
|
2220
|
+
return data;
|
|
2221
|
+
const next = { ...data };
|
|
2222
|
+
const needsShopOrderNumber = next.shop_order_number === void 0 || next.shop_order_number === null || next.shop_order_number === "";
|
|
2223
|
+
const needsShopFullOrderNumber = next.shop_full_order_number === void 0 || next.shop_full_order_number === null || next.shop_full_order_number === "";
|
|
2224
|
+
if (!needsShopOrderNumber && !needsShopFullOrderNumber)
|
|
2225
|
+
return next;
|
|
2226
|
+
const idGenerator = this.getIdGeneratorPlugin();
|
|
2227
|
+
if (!idGenerator) {
|
|
2228
|
+
this.logWarning(`${title}: idGenerator 插件不可用,无法生成订单号`, {
|
|
2229
|
+
needsShopOrderNumber,
|
|
2230
|
+
needsShopFullOrderNumber
|
|
2231
|
+
});
|
|
2232
|
+
return next;
|
|
2233
|
+
}
|
|
2234
|
+
try {
|
|
2235
|
+
if (needsShopOrderNumber && idGenerator.generateShopOrderNumber) {
|
|
2236
|
+
next.shop_order_number = await idGenerator.generateShopOrderNumber({
|
|
2237
|
+
biz: next.business_code || "ticket"
|
|
2238
|
+
});
|
|
2239
|
+
}
|
|
2240
|
+
if (needsShopFullOrderNumber && idGenerator.generateReceiptId) {
|
|
2241
|
+
next.shop_full_order_number = await idGenerator.generateReceiptId({
|
|
2242
|
+
biz: next.business_code || "ticket"
|
|
2243
|
+
});
|
|
2244
|
+
}
|
|
2245
|
+
this.logInfo(`${title}: checkout 订单号已准备`, {
|
|
2246
|
+
shop_order_number: next.shop_order_number,
|
|
2247
|
+
shop_full_order_number: next.shop_full_order_number
|
|
2248
|
+
});
|
|
2249
|
+
} catch (error) {
|
|
2250
|
+
this.logError(`${title}: 生成 checkout 订单号失败`, {
|
|
2251
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2252
|
+
});
|
|
2253
|
+
}
|
|
2254
|
+
return next;
|
|
2255
|
+
}
|
|
2256
|
+
async dispatchCheckoutSyncTask(params) {
|
|
2257
|
+
var _a, _b, _c;
|
|
2258
|
+
this.registerDeviceTaskActions();
|
|
2259
|
+
const deviceTask = this.getDeviceTaskPlugin();
|
|
2260
|
+
const debugDeviceId = this.resolveCheckoutTaskDeviceId(params.data);
|
|
2261
|
+
const debugIdempotencyKey = this.buildCheckoutTaskIdempotencyKey(params.data);
|
|
2262
|
+
if (!(deviceTask == null ? void 0 : deviceTask.dispatch)) {
|
|
2263
|
+
this.logError(`${params.title}: deviceTask.dispatch 不可用`);
|
|
2264
|
+
return;
|
|
2265
|
+
}
|
|
2266
|
+
try {
|
|
2267
|
+
const result = await deviceTask.dispatch({
|
|
2268
|
+
action: "sync_sales",
|
|
2269
|
+
device_id: debugDeviceId,
|
|
2270
|
+
payload: {
|
|
2271
|
+
backendPath: params.backendPath,
|
|
2272
|
+
data: params.data,
|
|
2273
|
+
title: params.title
|
|
2274
|
+
},
|
|
2275
|
+
retry_limit: 5,
|
|
2276
|
+
idempotency_key: debugIdempotencyKey,
|
|
2277
|
+
name: "同步订单",
|
|
2278
|
+
source: "server",
|
|
2279
|
+
source_type: "order",
|
|
2280
|
+
source_id: ((_a = params.data) == null ? void 0 : _a.order_id) || ((_b = params.data) == null ? void 0 : _b.external_sale_number) || ((_c = params.data) == null ? void 0 : _c.shop_order_number)
|
|
2281
|
+
});
|
|
2282
|
+
this.logInfo(`${params.title}: sync_sales 任务已派发`, {
|
|
2283
|
+
uuid: result == null ? void 0 : result.uuid,
|
|
2284
|
+
status: result == null ? void 0 : result.status,
|
|
2285
|
+
reused: result == null ? void 0 : result.reused
|
|
2286
|
+
});
|
|
2287
|
+
} catch (error) {
|
|
2288
|
+
this.logError(`${params.title}: 派发 sync_sales 任务失败`, {
|
|
2289
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2290
|
+
});
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
async dispatchPrintOtherReceiptTask(params) {
|
|
2294
|
+
var _a, _b, _c, _d, _e;
|
|
2295
|
+
const deviceTask = this.getDeviceTaskPlugin();
|
|
2296
|
+
if (!(deviceTask == null ? void 0 : deviceTask.dispatch)) {
|
|
2297
|
+
this.logWarning("dispatchPrintOtherReceiptTask: deviceTask.dispatch 不可用");
|
|
2298
|
+
return;
|
|
2299
|
+
}
|
|
2300
|
+
try {
|
|
2301
|
+
const printIdentity = this.getPrintOrderIdentity(params.syncedOrder);
|
|
2302
|
+
await deviceTask.dispatch({
|
|
2303
|
+
action: "print_all",
|
|
2304
|
+
device_id: params.deviceId ?? this.resolveCheckoutTaskDeviceId(params.checkoutData),
|
|
2305
|
+
payload: {
|
|
2306
|
+
type: params.receiptOnly ? "0" : "99",
|
|
2307
|
+
data: params.receiptOnly ? {
|
|
2308
|
+
order_id: printIdentity,
|
|
2309
|
+
small_ticket_data: (_b = (_a = params.syncedOrder) == null ? void 0 : _a.payment_info) == null ? void 0 : _b.small_ticket_data
|
|
2310
|
+
} : {
|
|
2311
|
+
order_id: printIdentity,
|
|
2312
|
+
machine_code_print_info: (_d = (_c = params.syncedOrder) == null ? void 0 : _c.payment_info) == null ? void 0 : _d.machine_code_print_info,
|
|
2313
|
+
machine_code_print_info_v2: (_e = params.syncedOrder) == null ? void 0 : _e.machine_code_print_info_v2,
|
|
2314
|
+
skip_receipt: true
|
|
2315
|
+
}
|
|
2316
|
+
},
|
|
2317
|
+
idempotency_key: this.buildPrintOtherReceiptIdempotencyKey(params.syncedOrder, {
|
|
2318
|
+
receiptOnly: params.receiptOnly
|
|
2319
|
+
}),
|
|
2320
|
+
name: "打印转发",
|
|
2321
|
+
source: "server",
|
|
2322
|
+
source_type: "order",
|
|
2323
|
+
source_id: printIdentity
|
|
2324
|
+
});
|
|
2325
|
+
} catch (error) {
|
|
2326
|
+
this.logError("dispatchPrintOtherReceiptTask: 派发失败", {
|
|
2327
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2328
|
+
});
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
buildPrintOtherReceiptIdempotencyKey(order, options) {
|
|
2332
|
+
const identity = (order == null ? void 0 : order.external_sale_number) || (order == null ? void 0 : order.shop_order_number) || (order == null ? void 0 : order.shop_full_order_number) || (order == null ? void 0 : order.order_id);
|
|
2333
|
+
if (identity === void 0 || identity === null || identity === "")
|
|
2334
|
+
return void 0;
|
|
2335
|
+
return `${(options == null ? void 0 : options.receiptOnly) ? "print_receipt" : "print_voucher"}:${identity}`;
|
|
2336
|
+
}
|
|
2337
|
+
getPrintOrderIdentity(order) {
|
|
2338
|
+
return (order == null ? void 0 : order.order_id) || (order == null ? void 0 : order.external_sale_number) || (order == null ? void 0 : order.shop_order_number) || (order == null ? void 0 : order.shop_full_order_number);
|
|
2339
|
+
}
|
|
2340
|
+
async dispatchLocalReceiptPrint(params) {
|
|
1788
2341
|
var _a;
|
|
2342
|
+
if (params.requireFullyPaid && !this.isSalesOrderFullyPaid(params.order)) {
|
|
2343
|
+
return { printed: false, order: params.order };
|
|
2344
|
+
}
|
|
2345
|
+
const printableOrder = await this.withLocalSmallTicketData(params.order, {
|
|
2346
|
+
requireFullyPaid: params.requireFullyPaid
|
|
2347
|
+
});
|
|
2348
|
+
if (!(0, import_small_ticket.hasSmallTicketData)((_a = printableOrder.payment_info) == null ? void 0 : _a.small_ticket_data)) {
|
|
2349
|
+
return { printed: false, order: printableOrder };
|
|
2350
|
+
}
|
|
2351
|
+
await this.dispatchPrintOtherReceiptTask({
|
|
2352
|
+
checkoutData: params.checkoutData,
|
|
2353
|
+
syncedOrder: printableOrder,
|
|
2354
|
+
response: params.response,
|
|
2355
|
+
receiptOnly: true
|
|
2356
|
+
});
|
|
2357
|
+
return { printed: true, order: printableOrder };
|
|
2358
|
+
}
|
|
2359
|
+
async handleOrderCheckoutSubmit(params) {
|
|
1789
2360
|
const { backendPath, data, title } = params;
|
|
1790
2361
|
this.logInfo(`${title}: 开始处理`, {
|
|
1791
2362
|
backendPath,
|
|
@@ -1793,54 +2364,34 @@ var Server = class {
|
|
|
1793
2364
|
external_sale_number: data == null ? void 0 : data.external_sale_number,
|
|
1794
2365
|
order_number: data == null ? void 0 : data.order_number
|
|
1795
2366
|
});
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
const fresh = this.extractOrderDataFromCheckoutResponse(response);
|
|
1812
|
-
if (fresh && this.order) {
|
|
1813
|
-
const syncedOrder = { ...fresh, need_sync: 0 };
|
|
1814
|
-
await this.order.upsertOrdersFromRemote([syncedOrder]);
|
|
1815
|
-
this.logInfo(`${title}: 订单已同步到本地`, {
|
|
1816
|
-
order_id: fresh.order_id,
|
|
1817
|
-
external_sale_number: fresh.external_sale_number,
|
|
1818
|
-
order_number: fresh.order_number
|
|
1819
|
-
});
|
|
1820
|
-
} else {
|
|
1821
|
-
this.logWarning(`${title}: checkout 返回未包含可同步订单`, {
|
|
1822
|
-
hasOrderModule: !!this.order,
|
|
1823
|
-
backendPath
|
|
2367
|
+
const checkoutData = await this.ensureCheckoutOrderNumbers(data, title);
|
|
2368
|
+
const pendingResult = await this.handlePendingSyncCheckoutOrder({
|
|
2369
|
+
backendPath,
|
|
2370
|
+
data: checkoutData,
|
|
2371
|
+
title,
|
|
2372
|
+
reason: "checkout 已转入设备任务同步"
|
|
2373
|
+
});
|
|
2374
|
+
if ((pendingResult == null ? void 0 : pendingResult.status) === true) {
|
|
2375
|
+
const pendingOrder = pendingResult.data;
|
|
2376
|
+
if (pendingOrder && this.shouldBuildSmallTicketData(checkoutData) && this.isSalesOrderFullyPaid(pendingOrder)) {
|
|
2377
|
+
await this.dispatchLocalReceiptPrint({
|
|
2378
|
+
checkoutData,
|
|
2379
|
+
order: pendingOrder,
|
|
2380
|
+
response: pendingResult,
|
|
2381
|
+
requireFullyPaid: true
|
|
1824
2382
|
});
|
|
1825
2383
|
}
|
|
1826
|
-
|
|
1827
|
-
} catch (error) {
|
|
1828
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1829
|
-
this.logError(`${title}: 请求失败`, { backendPath, error: errorMessage });
|
|
1830
|
-
const backendError = this.extractBackendErrorResponse(error);
|
|
1831
|
-
if (backendError)
|
|
1832
|
-
return backendError;
|
|
1833
|
-
return this.handlePendingSyncCheckoutOrder({
|
|
2384
|
+
await this.dispatchCheckoutSyncTask({
|
|
1834
2385
|
backendPath,
|
|
1835
|
-
data,
|
|
1836
|
-
title
|
|
1837
|
-
reason: errorMessage
|
|
2386
|
+
data: checkoutData,
|
|
2387
|
+
title
|
|
1838
2388
|
});
|
|
1839
2389
|
}
|
|
2390
|
+
return pendingResult;
|
|
1840
2391
|
}
|
|
1841
2392
|
async handlePendingSyncCheckoutOrder(params) {
|
|
1842
2393
|
const { backendPath, data, title, reason } = params;
|
|
1843
|
-
const pendingOrder = this.buildPendingSyncCheckoutOrder(data);
|
|
2394
|
+
const pendingOrder = await this.buildPendingSyncCheckoutOrder(data);
|
|
1844
2395
|
if (!pendingOrder) {
|
|
1845
2396
|
this.logError(`${title}: checkout 失败且订单缺少本地存储标识`, {
|
|
1846
2397
|
backendPath,
|
|
@@ -1867,7 +2418,7 @@ var Server = class {
|
|
|
1867
2418
|
}
|
|
1868
2419
|
try {
|
|
1869
2420
|
await this.order.upsertPendingSyncOrders([pendingOrder]);
|
|
1870
|
-
this.logWarning(`${title}:
|
|
2421
|
+
this.logWarning(`${title}: 订单已写入本地待同步`, {
|
|
1871
2422
|
backendPath,
|
|
1872
2423
|
reason,
|
|
1873
2424
|
order_id: pendingOrder.order_id,
|
|
@@ -1890,16 +2441,135 @@ var Server = class {
|
|
|
1890
2441
|
return { code: 500, status: false, message: errorMessage, data: null };
|
|
1891
2442
|
}
|
|
1892
2443
|
}
|
|
1893
|
-
buildPendingSyncCheckoutOrder(data) {
|
|
2444
|
+
async buildPendingSyncCheckoutOrder(data) {
|
|
1894
2445
|
if (!data || typeof data !== "object")
|
|
1895
2446
|
return null;
|
|
1896
2447
|
const hasStorageKey = data.external_sale_number !== void 0 && data.external_sale_number !== null && data.external_sale_number !== "" ? true : data.order_id !== void 0 && data.order_id !== null && data.order_id !== "";
|
|
1897
2448
|
if (!hasStorageKey)
|
|
1898
2449
|
return null;
|
|
1899
|
-
return
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
2450
|
+
return this.withLocalSmallTicketData(
|
|
2451
|
+
{
|
|
2452
|
+
...data,
|
|
2453
|
+
need_sync: 1
|
|
2454
|
+
},
|
|
2455
|
+
{ requireFullyPaid: true }
|
|
2456
|
+
);
|
|
2457
|
+
}
|
|
2458
|
+
shouldBuildSmallTicketData(order) {
|
|
2459
|
+
var _a;
|
|
2460
|
+
if (!order || typeof order !== "object")
|
|
2461
|
+
return false;
|
|
2462
|
+
if ((0, import_small_ticket.hasSmallTicketData)((_a = order.payment_info) == null ? void 0 : _a.small_ticket_data))
|
|
2463
|
+
return false;
|
|
2464
|
+
return Number(order.small_ticket_data_flag ?? order.smallTicketDataFlag ?? 0) === 1;
|
|
2465
|
+
}
|
|
2466
|
+
shouldPrintSyncedOrder(params) {
|
|
2467
|
+
if (this.hasPaymentStatus(params.syncedOrder)) {
|
|
2468
|
+
return this.isSalesOrderFullyPaid(params.syncedOrder);
|
|
2469
|
+
}
|
|
2470
|
+
return this.isSalesOrderFullyPaid(params.checkoutData);
|
|
2471
|
+
}
|
|
2472
|
+
hasPaymentStatus(order) {
|
|
2473
|
+
return (order == null ? void 0 : order.payment_status) !== void 0 && (order == null ? void 0 : order.payment_status) !== null && (order == null ? void 0 : order.payment_status) !== "";
|
|
2474
|
+
}
|
|
2475
|
+
isSalesOrderFullyPaid(order) {
|
|
2476
|
+
var _a, _b, _c;
|
|
2477
|
+
if (!order || typeof order !== "object")
|
|
2478
|
+
return false;
|
|
2479
|
+
const paymentStatus = String(order.payment_status || "").toLowerCase();
|
|
2480
|
+
if (paymentStatus === "paid" || paymentStatus === "finished")
|
|
2481
|
+
return true;
|
|
2482
|
+
if (paymentStatus === "partially_paid" || paymentStatus === "payment_processing" || paymentStatus === "pending" || paymentStatus === "unpaid") {
|
|
2483
|
+
return false;
|
|
2484
|
+
}
|
|
2485
|
+
const amountGap = ((_a = order.summary) == null ? void 0 : _a.amount_gap) ?? order.amount_gap;
|
|
2486
|
+
if (amountGap !== void 0 && amountGap !== null && amountGap !== "") {
|
|
2487
|
+
return this.toAmountNumber(amountGap) <= 0;
|
|
2488
|
+
}
|
|
2489
|
+
const paidAmount = this.sumPaidOrderPayments(order.payments);
|
|
2490
|
+
const targetAmount = this.toAmountNumber(
|
|
2491
|
+
((_b = order.summary) == null ? void 0 : _b.expect_amount) ?? ((_c = order.summary) == null ? void 0 : _c.total_amount) ?? order.expect_amount ?? order.total_amount
|
|
2492
|
+
);
|
|
2493
|
+
if (targetAmount <= 0)
|
|
2494
|
+
return paidAmount > 0;
|
|
2495
|
+
return paidAmount >= targetAmount;
|
|
2496
|
+
}
|
|
2497
|
+
sumPaidOrderPayments(payments) {
|
|
2498
|
+
if (!Array.isArray(payments))
|
|
2499
|
+
return 0;
|
|
2500
|
+
return payments.reduce((sum, payment) => {
|
|
2501
|
+
const status = String((payment == null ? void 0 : payment.status) || "").toLowerCase();
|
|
2502
|
+
if (status === "voided" || status === "failed" || status === "cancelled")
|
|
2503
|
+
return sum;
|
|
2504
|
+
return sum + this.toAmountNumber((payment == null ? void 0 : payment.amount) ?? (payment == null ? void 0 : payment.origin_amount));
|
|
2505
|
+
}, 0);
|
|
2506
|
+
}
|
|
2507
|
+
toAmountNumber(value) {
|
|
2508
|
+
if (typeof value === "number")
|
|
2509
|
+
return Number.isFinite(value) ? value : 0;
|
|
2510
|
+
if (typeof value !== "string")
|
|
2511
|
+
return 0;
|
|
2512
|
+
const parsed = Number(value.replace(/[^\d.-]/g, ""));
|
|
2513
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
2514
|
+
}
|
|
2515
|
+
async buildSmallTicketProductMap(order) {
|
|
2516
|
+
var _a;
|
|
2517
|
+
const products = Array.isArray(order == null ? void 0 : order.products) ? order.products : [];
|
|
2518
|
+
const productIds = Array.from(
|
|
2519
|
+
new Set(
|
|
2520
|
+
products.map((product) => (product == null ? void 0 : product.product_id) ?? (product == null ? void 0 : product.id)).filter((id) => id !== void 0 && id !== null && id !== "")
|
|
2521
|
+
)
|
|
2522
|
+
);
|
|
2523
|
+
if (!productIds.length || typeof ((_a = this.products) == null ? void 0 : _a.getProductById) !== "function")
|
|
2524
|
+
return {};
|
|
2525
|
+
const entries = await Promise.all(
|
|
2526
|
+
productIds.map(async (id) => {
|
|
2527
|
+
var _a2, _b;
|
|
2528
|
+
try {
|
|
2529
|
+
const product = await ((_b = (_a2 = this.products) == null ? void 0 : _a2.getProductById) == null ? void 0 : _b.call(_a2, Number(id)));
|
|
2530
|
+
return product ? [String(id), product] : null;
|
|
2531
|
+
} catch (error) {
|
|
2532
|
+
this.logWarning("buildSmallTicketProductMap: 查询本地商品失败", {
|
|
2533
|
+
product_id: id,
|
|
2534
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2535
|
+
});
|
|
2536
|
+
return null;
|
|
2537
|
+
}
|
|
2538
|
+
})
|
|
2539
|
+
);
|
|
2540
|
+
return entries.reduce((map, entry) => {
|
|
2541
|
+
if (!entry)
|
|
2542
|
+
return map;
|
|
2543
|
+
const [id, product] = entry;
|
|
2544
|
+
map[id] = product;
|
|
2545
|
+
return map;
|
|
2546
|
+
}, {});
|
|
2547
|
+
}
|
|
2548
|
+
async withLocalSmallTicketData(order, options) {
|
|
2549
|
+
if (!this.shouldBuildSmallTicketData(order))
|
|
2550
|
+
return order;
|
|
2551
|
+
if ((options == null ? void 0 : options.requireFullyPaid) && !this.isSalesOrderFullyPaid(order))
|
|
2552
|
+
return order;
|
|
2553
|
+
try {
|
|
2554
|
+
const productMap = await this.buildSmallTicketProductMap(order);
|
|
2555
|
+
const smallTicketData = (0, import_small_ticket.buildSmallTicketData)({
|
|
2556
|
+
order,
|
|
2557
|
+
shopInfo: this.getSmallTicketShopInfo(),
|
|
2558
|
+
productMap
|
|
2559
|
+
});
|
|
2560
|
+
return {
|
|
2561
|
+
...order,
|
|
2562
|
+
payment_info: {
|
|
2563
|
+
...order.payment_info || {},
|
|
2564
|
+
small_ticket_data: smallTicketData
|
|
2565
|
+
}
|
|
2566
|
+
};
|
|
2567
|
+
} catch (error) {
|
|
2568
|
+
this.logError("withLocalSmallTicketData: 生成本地小票数据失败", {
|
|
2569
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2570
|
+
});
|
|
2571
|
+
return order;
|
|
2572
|
+
}
|
|
1903
2573
|
}
|
|
1904
2574
|
/**
|
|
1905
2575
|
* 解析 order_ids 入参,兼容 url query / data 字段、字符串(csv) / 数组两种形态。
|
|
@@ -2104,6 +2774,13 @@ var Server = class {
|
|
|
2104
2774
|
data: response
|
|
2105
2775
|
};
|
|
2106
2776
|
}
|
|
2777
|
+
isCheckoutResponseRejected(response) {
|
|
2778
|
+
const normalized = this.normalizeCheckoutResponse(response);
|
|
2779
|
+
const isErrorCode = (normalized == null ? void 0 : normalized.code) !== void 0 && normalized.code !== 200 && normalized.code !== "200";
|
|
2780
|
+
const isErrorStatus = (normalized == null ? void 0 : normalized.status) === false || typeof (normalized == null ? void 0 : normalized.status) === "number" && normalized.status >= 400;
|
|
2781
|
+
const isErrorSuccess = (normalized == null ? void 0 : normalized.success) === false;
|
|
2782
|
+
return isErrorCode || isErrorStatus || isErrorSuccess;
|
|
2783
|
+
}
|
|
2107
2784
|
extractBackendErrorResponse(error) {
|
|
2108
2785
|
var _a, _b;
|
|
2109
2786
|
const candidates = [
|
|
@@ -2515,6 +3192,101 @@ var Server = class {
|
|
|
2515
3192
|
totalNotifyDurationMs: notifyEndAt - notifyStartAt
|
|
2516
3193
|
});
|
|
2517
3194
|
}
|
|
3195
|
+
/**
|
|
3196
|
+
* 订单变更后通知 sales 搜索订阅者。
|
|
3197
|
+
* 仅回传当前查询已返回订单号集合中的变更订单。
|
|
3198
|
+
*
|
|
3199
|
+
* @example
|
|
3200
|
+
* this.notifySalesSearchSubscribers({ list: [], changedOrders: [order], source: 'pubsub.bodyUpsert' })
|
|
3201
|
+
*/
|
|
3202
|
+
notifySalesSearchSubscribers(payload) {
|
|
3203
|
+
if (this.salesSearchSubscribers.size === 0)
|
|
3204
|
+
return;
|
|
3205
|
+
const changedOrders = this.extractChangedOrdersFromPayload(payload);
|
|
3206
|
+
if (changedOrders.length === 0) {
|
|
3207
|
+
return;
|
|
3208
|
+
}
|
|
3209
|
+
for (const [subscriberId, subscriber] of this.salesSearchSubscribers.entries()) {
|
|
3210
|
+
try {
|
|
3211
|
+
const changedVisibleOrders = changedOrders.filter((order) => {
|
|
3212
|
+
const orderId = this.getSalesSearchOrderId(order);
|
|
3213
|
+
return !!orderId && subscriber.visibleOrderIds.has(orderId);
|
|
3214
|
+
});
|
|
3215
|
+
if (changedVisibleOrders.length === 0) {
|
|
3216
|
+
continue;
|
|
3217
|
+
}
|
|
3218
|
+
subscriber.callback(
|
|
3219
|
+
this.buildSalesSearchDeltaResponse(subscriber, changedVisibleOrders)
|
|
3220
|
+
);
|
|
3221
|
+
this.logInfo("notifySalesSearchSubscribers: 已推送 delta", {
|
|
3222
|
+
subscriberId,
|
|
3223
|
+
kind: subscriber.kind,
|
|
3224
|
+
count: changedVisibleOrders.length
|
|
3225
|
+
});
|
|
3226
|
+
} catch (error) {
|
|
3227
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3228
|
+
this.logError("notifySalesSearchSubscribers: 推送失败", {
|
|
3229
|
+
subscriberId,
|
|
3230
|
+
error: errorMessage
|
|
3231
|
+
});
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
/**
|
|
3236
|
+
* 从订单变更事件中提取明确 delta。
|
|
3237
|
+
*
|
|
3238
|
+
* @example
|
|
3239
|
+
* const list = this.extractChangedOrdersFromPayload(payload)
|
|
3240
|
+
*/
|
|
3241
|
+
extractChangedOrdersFromPayload(payload) {
|
|
3242
|
+
if (!payload || Array.isArray(payload))
|
|
3243
|
+
return [];
|
|
3244
|
+
return Array.isArray(payload.changedOrders) ? payload.changedOrders : [];
|
|
3245
|
+
}
|
|
3246
|
+
/**
|
|
3247
|
+
* 构建与当前搜索接口一致的 delta 响应。
|
|
3248
|
+
*
|
|
3249
|
+
* @example
|
|
3250
|
+
* const response = this.buildSalesSearchDeltaResponse(subscriber, [order])
|
|
3251
|
+
*/
|
|
3252
|
+
buildSalesSearchDeltaResponse(subscriber, changedOrders) {
|
|
3253
|
+
if (subscriber.kind === "aggregate") {
|
|
3254
|
+
return this.buildAggregateSalesSearchDeltaResponse(subscriber, changedOrders);
|
|
3255
|
+
}
|
|
3256
|
+
const data = subscriber.lastResponseData || {};
|
|
3257
|
+
return {
|
|
3258
|
+
code: 200,
|
|
3259
|
+
status: true,
|
|
3260
|
+
message: "",
|
|
3261
|
+
data: {
|
|
3262
|
+
...data,
|
|
3263
|
+
list: changedOrders
|
|
3264
|
+
}
|
|
3265
|
+
};
|
|
3266
|
+
}
|
|
3267
|
+
/**
|
|
3268
|
+
* 构建聚合搜索接口的 sales 分组 delta 响应。
|
|
3269
|
+
*
|
|
3270
|
+
* @example
|
|
3271
|
+
* const response = this.buildAggregateSalesSearchDeltaResponse(subscriber, [order])
|
|
3272
|
+
*/
|
|
3273
|
+
buildAggregateSalesSearchDeltaResponse(subscriber, changedOrders) {
|
|
3274
|
+
const data = subscriber.lastResponseData || {};
|
|
3275
|
+
const results = Array.isArray(data == null ? void 0 : data.results) ? data.results : [];
|
|
3276
|
+
const hasSalesGroup = results.some((item) => (item == null ? void 0 : item.module) === "sales");
|
|
3277
|
+
const nextResults = hasSalesGroup ? results.map(
|
|
3278
|
+
(item) => (item == null ? void 0 : item.module) === "sales" ? { ...item, list: changedOrders } : item
|
|
3279
|
+
) : [{ module: "sales", list: changedOrders, count: changedOrders.length }];
|
|
3280
|
+
return {
|
|
3281
|
+
code: 200,
|
|
3282
|
+
status: true,
|
|
3283
|
+
message: "",
|
|
3284
|
+
data: {
|
|
3285
|
+
...data,
|
|
3286
|
+
results: nextResults
|
|
3287
|
+
}
|
|
3288
|
+
};
|
|
3289
|
+
}
|
|
2518
3290
|
/**
|
|
2519
3291
|
* 预约数据变更后,遍历订阅者重新计算并通过 callback 推送
|
|
2520
3292
|
*/
|