@pisell/pisellos 2.2.309 → 2.2.311
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 +0 -9
- package/dist/model/strategy/adapter/walletPass/evaluator.d.ts +2 -0
- package/dist/model/strategy/adapter/walletPass/evaluator.js +4 -2
- package/dist/modules/BookingContext/index.js +4 -4
- package/dist/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +58 -19
- package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +24 -12
- package/dist/modules/Customer/index.js +6 -3
- package/dist/modules/Customer/types.d.ts +2 -0
- package/dist/modules/Discount/availability.d.ts +7 -0
- package/dist/modules/Discount/availability.js +49 -0
- package/dist/modules/Discount/entitlementPass.d.ts +34 -0
- package/dist/modules/Discount/entitlementPass.js +147 -0
- package/dist/modules/Discount/index.d.ts +2 -2
- package/dist/modules/Discount/index.js +37 -151
- package/dist/modules/Discount/types.d.ts +36 -5
- package/dist/modules/OpenData/index.d.ts +1 -1
- package/dist/modules/OpenData/index.js +21 -12
- package/dist/modules/Order/index.d.ts +32 -4
- package/dist/modules/Order/index.js +733 -373
- package/dist/modules/Order/types.d.ts +17 -0
- package/dist/modules/Order/utils/orderCollectionIdentity.js +16 -12
- package/dist/modules/Order/utils.d.ts +19 -5
- package/dist/modules/Order/utils.js +306 -86
- package/dist/modules/Payment/index.d.ts +0 -5
- package/dist/modules/Payment/index.js +35 -85
- package/dist/modules/Payment/types.d.ts +21 -1
- package/dist/modules/Payment/types.js +2 -0
- package/dist/modules/Payment/walletpass.d.ts +1 -1
- package/dist/modules/Payment/walletpass.js +80 -47
- package/dist/modules/ProductList/index.js +18 -5
- package/dist/modules/Rules/index.d.ts +2 -2
- package/dist/modules/Rules/index.js +480 -186
- package/dist/modules/Rules/types.d.ts +4 -0
- package/dist/server/index.js +8 -5
- package/dist/server/modules/floor-plan/index.js +34 -3
- package/dist/server/modules/order/index.js +6 -0
- package/dist/server/modules/order/types.d.ts +12 -0
- package/dist/server/modules/payment/index.d.ts +25 -6
- package/dist/server/modules/payment/index.js +660 -133
- package/dist/server/utils/small-ticket.d.ts +8 -5
- package/dist/server/utils/small-ticket.js +1269 -199
- package/dist/solution/BaseSales/index.d.ts +11 -2
- package/dist/solution/BaseSales/index.js +1463 -1173
- package/dist/solution/BaseSales/types.d.ts +7 -3
- package/dist/solution/BaseSales/utils/cartPromotion.js +141 -126
- package/dist/solution/BookingTicket/index.d.ts +11 -2
- package/dist/solution/BookingTicket/index.js +663 -542
- package/dist/solution/BookingTicket/types.d.ts +5 -1
- package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +2 -0
- package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.js +338 -147
- package/dist/solution/BookingTicket/utils/scan/cloudSearch.js +8 -4
- package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +6 -0
- package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.js +19 -6
- package/dist/solution/ShopDiscount/index.d.ts +2 -0
- package/dist/solution/ShopDiscount/index.js +99 -42
- package/dist/solution/ShopDiscount/utils.d.ts +4 -2
- package/dist/solution/ShopDiscount/utils.js +43 -23
- package/dist/solution/UnifiedBookingSales/index.d.ts +2 -2
- package/dist/solution/UnifiedBookingSales/index.js +5 -5
- package/dist/utils/os-private-payload.d.ts +5 -0
- package/dist/utils/os-private-payload.js +17 -0
- package/lib/model/strategy/adapter/walletPass/evaluator.d.ts +2 -0
- package/lib/model/strategy/adapter/walletPass/evaluator.js +2 -0
- package/lib/modules/BookingContext/index.js +3 -3
- package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +35 -6
- package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +13 -6
- package/lib/modules/Customer/index.js +5 -1
- package/lib/modules/Customer/types.d.ts +2 -0
- package/lib/modules/Discount/availability.d.ts +7 -0
- package/lib/modules/Discount/availability.js +43 -0
- package/lib/modules/Discount/entitlementPass.d.ts +34 -0
- package/lib/modules/Discount/entitlementPass.js +141 -0
- package/lib/modules/Discount/index.d.ts +2 -2
- package/lib/modules/Discount/index.js +23 -129
- package/lib/modules/Discount/types.d.ts +36 -5
- package/lib/modules/OpenData/index.d.ts +1 -1
- package/lib/modules/OpenData/index.js +11 -2
- package/lib/modules/Order/index.d.ts +32 -4
- package/lib/modules/Order/index.js +341 -71
- package/lib/modules/Order/types.d.ts +17 -0
- package/lib/modules/Order/utils/orderCollectionIdentity.js +14 -11
- package/lib/modules/Order/utils.d.ts +19 -5
- package/lib/modules/Order/utils.js +215 -13
- package/lib/modules/Payment/index.d.ts +0 -5
- package/lib/modules/Payment/index.js +3 -28
- package/lib/modules/Payment/types.d.ts +21 -1
- package/lib/modules/Payment/types.js +1 -0
- package/lib/modules/Payment/walletpass.d.ts +1 -1
- package/lib/modules/Payment/walletpass.js +29 -9
- package/lib/modules/ProductList/index.js +14 -1
- package/lib/modules/Rules/index.d.ts +2 -2
- package/lib/modules/Rules/index.js +357 -110
- package/lib/modules/Rules/types.d.ts +4 -0
- package/lib/server/index.js +8 -5
- package/lib/server/modules/floor-plan/index.js +29 -1
- package/lib/server/modules/order/index.js +6 -0
- package/lib/server/modules/order/types.d.ts +12 -0
- package/lib/server/modules/payment/index.d.ts +25 -6
- package/lib/server/modules/payment/index.js +350 -78
- package/lib/server/utils/small-ticket.d.ts +8 -5
- package/lib/server/utils/small-ticket.js +1009 -121
- package/lib/solution/BaseSales/index.d.ts +11 -2
- package/lib/solution/BaseSales/index.js +164 -30
- package/lib/solution/BaseSales/types.d.ts +7 -3
- package/lib/solution/BaseSales/utils/cartPromotion.js +12 -4
- package/lib/solution/BookingTicket/index.d.ts +11 -2
- package/lib/solution/BookingTicket/index.js +103 -41
- package/lib/solution/BookingTicket/types.d.ts +5 -1
- package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +2 -0
- package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.js +174 -42
- package/lib/solution/BookingTicket/utils/scan/cloudSearch.js +8 -4
- package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +6 -0
- package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.js +11 -3
- package/lib/solution/ShopDiscount/index.d.ts +2 -0
- package/lib/solution/ShopDiscount/index.js +55 -9
- package/lib/solution/ShopDiscount/utils.d.ts +4 -2
- package/lib/solution/ShopDiscount/utils.js +42 -25
- package/lib/solution/UnifiedBookingSales/index.d.ts +2 -2
- package/lib/solution/UnifiedBookingSales/index.js +5 -5
- package/lib/utils/os-private-payload.d.ts +5 -0
- package/lib/utils/os-private-payload.js +22 -0
- package/package.json +1 -1
|
@@ -160,7 +160,9 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
|
|
|
160
160
|
addTimeProductsCatalog = [];
|
|
161
161
|
orderCustomerDiscountRefreshInFlight = null;
|
|
162
162
|
orderCustomerDiscountRefreshCustomerId = null;
|
|
163
|
+
orderCustomerDiscountRefreshTarget = null;
|
|
163
164
|
orderCustomerPromotionRefreshInFlight = null;
|
|
165
|
+
customerResolveRequests = new Map();
|
|
164
166
|
loadOpenDataInFlight = null;
|
|
165
167
|
loadOpenDataInFlightTarget = null;
|
|
166
168
|
openDataTarget = null;
|
|
@@ -193,10 +195,37 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
|
|
|
193
195
|
throw new Error('bookingTicket解决方案需要 request 插件支持');
|
|
194
196
|
}
|
|
195
197
|
this.platform = this.otherParams?.platform;
|
|
198
|
+
await this.prepareInvoiceTemplateAtSolutionReady(this.getInjectedOpenDataConfig());
|
|
196
199
|
await this.configureIdGeneratorFromOpenData();
|
|
197
200
|
}
|
|
201
|
+
getInjectedOpenDataConfig() {
|
|
202
|
+
const openData = this.otherParams?.openData;
|
|
203
|
+
return openData && typeof openData === 'object' && !Array.isArray(openData) ? openData : undefined;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* 在解决方案对外 ready 前准备 Invoice Document;失败只降级模板,不阻断销售。
|
|
208
|
+
* 已由页面取得的 OpenData 直接交给模块接管,没有注入数据时再走 target-scoped 加载。
|
|
209
|
+
*/
|
|
210
|
+
async prepareInvoiceTemplateAtSolutionReady(injectedOpenData) {
|
|
211
|
+
const businessCode = this.getBookingTicketBusinessCode();
|
|
212
|
+
if (!businessCode || !this.store.openData) return;
|
|
213
|
+
const channel = this.getSubmitOrderSalesChannel();
|
|
214
|
+
const target = `${businessCode}+${channel}`;
|
|
215
|
+
try {
|
|
216
|
+
if (injectedOpenData && typeof this.store.openData.ensureInvoiceTemplateSettings === 'function') {
|
|
217
|
+
this.openDataTarget = target;
|
|
218
|
+
await this.store.openData.ensureInvoiceTemplateSettings(target, injectedOpenData);
|
|
219
|
+
this.otherParams.openData = injectedOpenData;
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
await this.loadOpenDataConfig();
|
|
223
|
+
} catch (error) {
|
|
224
|
+
console.warn('[BookingTicket] Invoice 模板预加载失败,使用默认配置', error);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
198
227
|
getBookingTicketBusinessCode() {
|
|
199
|
-
const candidates = [this.otherParams?.businessCode, this.otherParams?.business_code, this.getOrderHeader()?.business_code, this.store.order?.getTempOrder()?.business_code, this.getSalesOrder()?.raw?.business_code];
|
|
228
|
+
const candidates = [this.otherParams?.businessCode, this.otherParams?.business_code, this.getOrderHeader()?.business_code, typeof this.store.order?.getTempOrder === 'function' ? this.store.order.getTempOrder()?.business_code : undefined, this.getSalesOrder()?.raw?.business_code];
|
|
200
229
|
for (const candidate of candidates) {
|
|
201
230
|
if (candidate === undefined || candidate === null) continue;
|
|
202
231
|
const normalized = String(candidate).trim();
|
|
@@ -415,7 +444,15 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
|
|
|
415
444
|
|
|
416
445
|
// 缺 Holder 等场景 isAvailable=false,但仍需把 Pass 归属客户带入订单,供 Holder 弹窗使用。
|
|
417
446
|
if (!hasOrderCustomer && extractedCustomerId && !isUserPlatform) {
|
|
418
|
-
|
|
447
|
+
try {
|
|
448
|
+
await this.hydrateOrderCustomerFromScanDiscounts(scannedList);
|
|
449
|
+
} catch (error) {
|
|
450
|
+
// 客户映射失败不能阻断原有 Pass 的可用性、展示或优惠处理。
|
|
451
|
+
console.warn('[BookingTicket] scan discount customer resolve failed', {
|
|
452
|
+
customerId: extractedCustomerId,
|
|
453
|
+
error
|
|
454
|
+
});
|
|
455
|
+
}
|
|
419
456
|
}
|
|
420
457
|
if (raw.isAvailable) {
|
|
421
458
|
await this.store.order.recalculateSummary({
|
|
@@ -466,43 +503,39 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
|
|
|
466
503
|
return null;
|
|
467
504
|
}
|
|
468
505
|
async resolveCustomerById(customerId) {
|
|
469
|
-
const
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
if (normalizedLocalCustomer && this.hasUsableCustomerDetails(normalizedLocalCustomer)) {
|
|
473
|
-
return normalizedLocalCustomer;
|
|
474
|
-
}
|
|
475
|
-
const result = await this.store.customer.queryCustomerList({
|
|
476
|
-
ids: [customerId],
|
|
477
|
-
skip: 1,
|
|
478
|
-
num: 1
|
|
479
|
-
});
|
|
480
|
-
const remoteCustomer = this.findCustomerById(result?.list || [], customerId) || result?.list?.[0] || null;
|
|
481
|
-
if (remoteCustomer) {
|
|
482
|
-
return this.normalizeCustomer(remoteCustomer);
|
|
483
|
-
}
|
|
484
|
-
try {
|
|
485
|
-
const detailCustomer = await this.store.customer.queryCustomerDetail(customerId);
|
|
486
|
-
return detailCustomer ? this.normalizeCustomer(detailCustomer) : null;
|
|
487
|
-
} catch (error) {
|
|
488
|
-
console.warn('[BookingTicket] resolveCustomerById detail request failed', {
|
|
489
|
-
customerId,
|
|
490
|
-
error
|
|
491
|
-
});
|
|
506
|
+
const rawCustomerId = String(customerId ?? '').trim();
|
|
507
|
+
const numericCustomerId = Number(rawCustomerId);
|
|
508
|
+
if (!rawCustomerId || !Number.isInteger(numericCustomerId) || numericCustomerId <= 1) {
|
|
492
509
|
return null;
|
|
493
510
|
}
|
|
511
|
+
const normalizedCustomerId = String(numericCustomerId);
|
|
512
|
+
const existingRequest = this.customerResolveRequests.get(normalizedCustomerId);
|
|
513
|
+
if (existingRequest) return existingRequest;
|
|
514
|
+
const request = this.request.post(`/customer/resolve/${encodeURIComponent(normalizedCustomerId)}`, {
|
|
515
|
+
with_franchisor_data: 1,
|
|
516
|
+
with: ['latestWalletDetail.wallet', 'contactsInfo', 'formRecord']
|
|
517
|
+
}).then(result => {
|
|
518
|
+
const customer = result?.data;
|
|
519
|
+
const resolvedCustomerId = Number(customer && typeof customer === 'object' && !Array.isArray(customer) ? this.getRawCustomerId(customer) : NaN);
|
|
520
|
+
if (result?.status !== true || result?.code !== 200 || !customer || typeof customer !== 'object' || Array.isArray(customer) || !Number.isInteger(resolvedCustomerId) || resolvedCustomerId <= 1) {
|
|
521
|
+
const error = new Error(result?.message || 'Customer resolve failed');
|
|
522
|
+
error.code = result?.code;
|
|
523
|
+
throw error;
|
|
524
|
+
}
|
|
525
|
+
return this.normalizeCustomer(customer);
|
|
526
|
+
}).finally(() => {
|
|
527
|
+
if (this.customerResolveRequests.get(normalizedCustomerId) === request) {
|
|
528
|
+
this.customerResolveRequests.delete(normalizedCustomerId);
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
this.customerResolveRequests.set(normalizedCustomerId, request);
|
|
532
|
+
return request;
|
|
494
533
|
}
|
|
495
534
|
|
|
496
535
|
/** 兼容既有优惠码扫码逻辑。 */
|
|
497
536
|
resolveCustomerByDiscountCustomerId(customerId) {
|
|
498
537
|
return this.resolveCustomerById(customerId);
|
|
499
538
|
}
|
|
500
|
-
findCustomerById(customers = [], customerId) {
|
|
501
|
-
return customers.find(customer => {
|
|
502
|
-
const rawId = this.getRawCustomerId(customer);
|
|
503
|
-
return rawId !== null && String(rawId) === String(customerId);
|
|
504
|
-
}) || null;
|
|
505
|
-
}
|
|
506
539
|
normalizeCustomer(customer) {
|
|
507
540
|
const raw = customer;
|
|
508
541
|
const rawId = this.getRawCustomerId(customer);
|
|
@@ -520,9 +553,6 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
|
|
|
520
553
|
const raw = customer;
|
|
521
554
|
return customer.id ?? raw.customer_id ?? raw.customerId ?? null;
|
|
522
555
|
}
|
|
523
|
-
hasUsableCustomerDetails(customer) {
|
|
524
|
-
return !!(customer.name || customer.phone || customer.email || customer.display_name || customer.customer_name);
|
|
525
|
-
}
|
|
526
556
|
|
|
527
557
|
/**
|
|
528
558
|
* 更新当前预约的 appointment_status。
|
|
@@ -1094,7 +1124,13 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
|
|
|
1094
1124
|
}
|
|
1095
1125
|
refreshDiscountConfigAfterOrderCustomerChange(customerId) {
|
|
1096
1126
|
if (!customerId || customerId === 1) return Promise.resolve();
|
|
1097
|
-
|
|
1127
|
+
const tempOrder = this.store.order.getTempOrder?.();
|
|
1128
|
+
const orderIdentity = this.getOrderIdentity?.();
|
|
1129
|
+
const orderId = tempOrder?.order_id || orderIdentity?.orderId;
|
|
1130
|
+
const action = !orderId || String(orderId).startsWith('local_order') ? 'create' : 'edit';
|
|
1131
|
+
const orderTarget = orderId ? `order:${String(orderId)}` : `external:${String(tempOrder?.external_sale_number || orderIdentity?.externalSaleNumber || orderIdentity?.localId || 'new')}`;
|
|
1132
|
+
const refreshTarget = `${customerId}:${action}:${orderTarget}`;
|
|
1133
|
+
if (this.orderCustomerDiscountRefreshInFlight && this.orderCustomerDiscountRefreshTarget === refreshTarget) {
|
|
1098
1134
|
return this.orderCustomerDiscountRefreshInFlight;
|
|
1099
1135
|
}
|
|
1100
1136
|
const previousRefresh = this.orderCustomerDiscountRefreshInFlight;
|
|
@@ -1106,22 +1142,33 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
|
|
|
1106
1142
|
if (!currentCustomer || Number(currentCustomer.customer_id) !== customerId) {
|
|
1107
1143
|
return;
|
|
1108
1144
|
}
|
|
1109
|
-
const
|
|
1110
|
-
const
|
|
1111
|
-
const
|
|
1145
|
+
const currentTempOrder = this.store.order.getTempOrder?.();
|
|
1146
|
+
const currentOrderIdentity = this.getOrderIdentity?.();
|
|
1147
|
+
const currentOrderId = currentTempOrder?.order_id || currentOrderIdentity?.orderId;
|
|
1148
|
+
const currentAction = !currentOrderId || String(currentOrderId).startsWith('local_order') ? 'create' : 'edit';
|
|
1149
|
+
const currentOrderTarget = currentOrderId ? `order:${String(currentOrderId)}` : `external:${String(currentTempOrder?.external_sale_number || currentOrderIdentity?.externalSaleNumber || currentOrderIdentity?.localId || 'new')}`;
|
|
1150
|
+
if (`${customerId}:${currentAction}:${currentOrderTarget}` !== refreshTarget) {
|
|
1151
|
+
return;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// 上一个订单的迟到请求可能在客户切换后重新写入 BaseSales cache key;
|
|
1155
|
+
// 客户变更触发的目标订单刷新必须绕过该缓存。
|
|
1112
1156
|
await this.loadDiscountConfig({
|
|
1113
1157
|
customerId,
|
|
1114
|
-
action
|
|
1158
|
+
action,
|
|
1159
|
+
force: true
|
|
1115
1160
|
});
|
|
1116
1161
|
})();
|
|
1117
1162
|
this.orderCustomerDiscountRefreshInFlight = refreshTask;
|
|
1118
1163
|
this.orderCustomerDiscountRefreshCustomerId = customerId;
|
|
1164
|
+
this.orderCustomerDiscountRefreshTarget = refreshTarget;
|
|
1119
1165
|
refreshTask.catch(error => {
|
|
1120
1166
|
console.error('Failed to load discount config after customer change:', error);
|
|
1121
1167
|
}).finally(() => {
|
|
1122
1168
|
if (this.orderCustomerDiscountRefreshInFlight !== refreshTask) return;
|
|
1123
1169
|
this.orderCustomerDiscountRefreshInFlight = null;
|
|
1124
1170
|
this.orderCustomerDiscountRefreshCustomerId = null;
|
|
1171
|
+
this.orderCustomerDiscountRefreshTarget = null;
|
|
1125
1172
|
});
|
|
1126
1173
|
return refreshTask;
|
|
1127
1174
|
}
|
|
@@ -1642,6 +1689,7 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
|
|
|
1642
1689
|
const result = await (0, _applyGlobalScan.applyGlobalScan)({
|
|
1643
1690
|
setOrderCustomer: customer => this.setOrderCustomer(customer),
|
|
1644
1691
|
getOrderCustomerSnapshot: () => this.getOrderCustomerSnapshot(),
|
|
1692
|
+
resolveCustomerById: customerId => this.resolveCustomerById(customerId),
|
|
1645
1693
|
getBusinessCode: () => this.otherParams?.businessCode,
|
|
1646
1694
|
buildAddProductCtx: extra => this.buildAddProductCtx(extra),
|
|
1647
1695
|
decideAddProduct: (item, options) => this.decideAddProduct(item, options),
|
|
@@ -1662,6 +1710,18 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
|
|
|
1662
1710
|
};
|
|
1663
1711
|
}
|
|
1664
1712
|
}
|
|
1713
|
+
|
|
1714
|
+
/** 查询码类型;复用原有识别和缓存,不写订单、不打开选择 UI。 */
|
|
1715
|
+
async getGlobalScanCodeType(code) {
|
|
1716
|
+
const trimmed = typeof code === 'string' ? code.trim() : '';
|
|
1717
|
+
if (!trimmed) return null;
|
|
1718
|
+
const scan = (0, _handleScan.handleGlobalScan)(this.request, value => this.store.products.findProductsByCodeOrBarcode(value), this.getSubmitOrderSalesChannel());
|
|
1719
|
+
const raw = await scan({
|
|
1720
|
+
type: 'nativeScanner',
|
|
1721
|
+
value: trimmed
|
|
1722
|
+
});
|
|
1723
|
+
return (0, _formatGlobalScan.formatGlobalScanResult)(raw, trimmed)?.searchType ?? null;
|
|
1724
|
+
}
|
|
1665
1725
|
resolveBestAddTimePlan(addTimeProducts, targetMinutes) {
|
|
1666
1726
|
return (0, _resolveBestAddTimePlan.resolveBestAddTimePlan)(addTimeProducts, targetMinutes);
|
|
1667
1727
|
}
|
|
@@ -1784,10 +1844,12 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
|
|
|
1784
1844
|
await super.setOtherParams(params, {
|
|
1785
1845
|
cover
|
|
1786
1846
|
});
|
|
1847
|
+
const invoiceContextChanged = params?.businessCode !== undefined || params?.business_code !== undefined || params?.channel !== undefined || params?.platform !== undefined || Object.prototype.hasOwnProperty.call(params, 'openData');
|
|
1848
|
+
if (invoiceContextChanged) {
|
|
1849
|
+
await this.prepareInvoiceTemplateAtSolutionReady(Object.prototype.hasOwnProperty.call(params, 'openData') ? this.getInjectedOpenDataConfig() : undefined);
|
|
1850
|
+
}
|
|
1787
1851
|
if (params?.businessCode !== undefined || params?.business_code !== undefined) {
|
|
1788
1852
|
await this.configureIdGeneratorFromOpenData();
|
|
1789
|
-
} else if (params?.channel !== undefined) {
|
|
1790
|
-
await this.loadOpenDataConfig();
|
|
1791
1853
|
}
|
|
1792
1854
|
}
|
|
1793
1855
|
getDeviceTaskPlugin() {
|
|
@@ -230,12 +230,16 @@ export interface GlobalScanHostBridge {
|
|
|
230
230
|
/**
|
|
231
231
|
* walk-in 扫 walletPass:直接核销优惠码(对齐 SaleDetail Voucher.handleApplyCode / cart.scanCode)。
|
|
232
232
|
*/
|
|
233
|
-
applyPromotionCode?: (code: string, customerId?: number
|
|
233
|
+
applyPromotionCode?: (code: string, customerId?: number, options?: {
|
|
234
|
+
suppressFailureNotification?: boolean;
|
|
235
|
+
}) => Promise<{
|
|
234
236
|
isAvailable?: boolean;
|
|
235
237
|
isAccepted?: boolean;
|
|
236
238
|
type?: string;
|
|
237
239
|
unavailableReason?: string;
|
|
238
240
|
}>;
|
|
241
|
+
/** 当前订单已有其他客户时,由宿主复用客户组件完成确认及切换。 */
|
|
242
|
+
requestCustomerSwitch?: (customer: ICustomer) => Promise<boolean | null>;
|
|
239
243
|
onNotify?: (type: 'success' | 'fail' | 'info', messageKey?: string) => void;
|
|
240
244
|
refresh?: () => void;
|
|
241
245
|
}
|
|
@@ -5,6 +5,7 @@ import { GlobalScanFormattedResult } from './formatGlobalScan';
|
|
|
5
5
|
export interface GlobalScanApplyHost {
|
|
6
6
|
setOrderCustomer(customer: ICustomer | null): void;
|
|
7
7
|
getOrderCustomerSnapshot(): ICustomer | null;
|
|
8
|
+
resolveCustomerById?(customerId: string | number): Promise<ICustomer | null>;
|
|
8
9
|
getBusinessCode(): string | undefined;
|
|
9
10
|
buildAddProductCtx(extra?: Record<string, any>): Record<string, any>;
|
|
10
11
|
decideAddProduct(item: any, options?: Record<string, any>): AddProductDecision;
|
|
@@ -26,6 +27,7 @@ export interface GlobalScanApplyHost {
|
|
|
26
27
|
addProductToOrder(product: any, booking?: any): Promise<any>;
|
|
27
28
|
refreshWalletAssets?(params?: {
|
|
28
29
|
preserveSelection?: boolean;
|
|
30
|
+
retainSearchResultCodes?: string[];
|
|
29
31
|
}): Promise<any>;
|
|
30
32
|
scanWalletAsset?(code: string): Promise<{
|
|
31
33
|
type: 'walletCode' | 'normalCode';
|
|
@@ -5,10 +5,21 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.applyGlobalScan = applyGlobalScan;
|
|
7
7
|
var _addProductDecision = require("../addProductDecision");
|
|
8
|
-
var _cartView = require("../cartView");
|
|
9
8
|
var _scanProductValues = require("./scanProductValues");
|
|
10
9
|
/** applyGlobalScan 所需的 BookingTicket 运行时能力 */
|
|
11
10
|
|
|
11
|
+
function getRealCustomerId(customer) {
|
|
12
|
+
const customerId = Number(customer?.id ?? customer?.customer_id);
|
|
13
|
+
return Number.isInteger(customerId) && customerId > 1 ? customerId : undefined;
|
|
14
|
+
}
|
|
15
|
+
function getWalletAssetCustomerId(asset) {
|
|
16
|
+
const customerId = Number(asset?.customer_id ?? asset?.holder?.customer_id);
|
|
17
|
+
return Number.isInteger(customerId) && customerId > 1 ? customerId : undefined;
|
|
18
|
+
}
|
|
19
|
+
function getErrorMessage(error) {
|
|
20
|
+
return error instanceof Error ? error.message : String(error || '');
|
|
21
|
+
}
|
|
22
|
+
|
|
12
23
|
/**
|
|
13
24
|
* 处理单条扫码商品的加车决策(含弹窗链)。
|
|
14
25
|
*/
|
|
@@ -93,17 +104,81 @@ async function applyGlobalScan(host, formatted, bridge) {
|
|
|
93
104
|
const {
|
|
94
105
|
searchType,
|
|
95
106
|
data,
|
|
96
|
-
scanCode
|
|
107
|
+
scanCode,
|
|
108
|
+
customerId: formattedCustomerId,
|
|
109
|
+
isCrossShopWallet,
|
|
110
|
+
isWalletPassUnavailableAtStore
|
|
97
111
|
} = formatted;
|
|
98
112
|
if (searchType === 'wallet') {
|
|
99
|
-
|
|
113
|
+
const currentCustomer = host.getOrderCustomerSnapshot();
|
|
114
|
+
const currentCustomerId = getRealCustomerId(currentCustomer);
|
|
115
|
+
let customer = data;
|
|
116
|
+
if (isCrossShopWallet) {
|
|
117
|
+
const sourceCustomerId = getRealCustomerId({
|
|
118
|
+
id: formattedCustomerId
|
|
119
|
+
});
|
|
120
|
+
if (sourceCustomerId === undefined || !host.resolveCustomerById) {
|
|
121
|
+
bridge?.onNotify?.('fail', 'pisell2.text.scan-global-failed');
|
|
122
|
+
return {
|
|
123
|
+
status: 'failed',
|
|
124
|
+
reason: 'invalid_data'
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
customer = await host.resolveCustomerById(sourceCustomerId);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
bridge?.onNotify?.('fail', getErrorMessage(error) || 'pisell2.text.scan-global-failed');
|
|
131
|
+
return {
|
|
132
|
+
status: 'failed',
|
|
133
|
+
reason: 'invalid_data'
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const customerId = getRealCustomerId(customer) ?? (!isCrossShopWallet ? getRealCustomerId({
|
|
138
|
+
id: formattedCustomerId
|
|
139
|
+
}) : undefined);
|
|
140
|
+
if (!customer || customerId === undefined) {
|
|
100
141
|
bridge?.onNotify?.('fail', 'pisell2.text.scan-global-failed');
|
|
101
142
|
return {
|
|
102
143
|
status: 'failed',
|
|
103
144
|
reason: 'invalid_data'
|
|
104
145
|
};
|
|
105
146
|
}
|
|
106
|
-
|
|
147
|
+
const orderCustomer = customer.id === undefined || customer.id === null ? {
|
|
148
|
+
...customer,
|
|
149
|
+
id: customerId
|
|
150
|
+
} : customer;
|
|
151
|
+
if (currentCustomerId === customerId) {
|
|
152
|
+
return {
|
|
153
|
+
status: 'success',
|
|
154
|
+
kind: 'customer'
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
if (currentCustomerId !== undefined) {
|
|
158
|
+
// 未接入新桥接的宿主保持原行为:已有客户时不直接覆盖。
|
|
159
|
+
if (!bridge?.requestCustomerSwitch) {
|
|
160
|
+
return {
|
|
161
|
+
status: 'success',
|
|
162
|
+
kind: 'customer'
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
const switched = await bridge.requestCustomerSwitch(orderCustomer);
|
|
167
|
+
return switched ? {
|
|
168
|
+
status: 'success',
|
|
169
|
+
kind: 'customer'
|
|
170
|
+
} : {
|
|
171
|
+
status: 'cancelled'
|
|
172
|
+
};
|
|
173
|
+
} catch (error) {
|
|
174
|
+
bridge.onNotify?.('fail', getErrorMessage(error) || 'pisell2.text.scan-global-failed');
|
|
175
|
+
return {
|
|
176
|
+
status: 'failed',
|
|
177
|
+
reason: 'invalid_data'
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
host.setOrderCustomer(orderCustomer);
|
|
107
182
|
bridge?.onNotify?.('success', 'pisell2.text.add-client-success');
|
|
108
183
|
return {
|
|
109
184
|
status: 'success',
|
|
@@ -111,8 +186,70 @@ async function applyGlobalScan(host, formatted, bridge) {
|
|
|
111
186
|
};
|
|
112
187
|
}
|
|
113
188
|
if (searchType === 'walletPass') {
|
|
189
|
+
if (isWalletPassUnavailableAtStore) {
|
|
190
|
+
bridge?.onNotify?.('fail', 'pisell2.text.wallet-pass-not-available-at-store');
|
|
191
|
+
return {
|
|
192
|
+
status: 'failed',
|
|
193
|
+
reason: 'invalid_data'
|
|
194
|
+
};
|
|
195
|
+
}
|
|
114
196
|
const snapshot = host.getOrderCustomerSnapshot();
|
|
115
|
-
|
|
197
|
+
let activeCustomer = snapshot;
|
|
198
|
+
const bindWalletPassCustomer = async asset => {
|
|
199
|
+
if (getRealCustomerId(activeCustomer) !== undefined) {
|
|
200
|
+
return {
|
|
201
|
+
resolveFailed: false,
|
|
202
|
+
customerBound: false
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
const assetCustomerId = getWalletAssetCustomerId(asset) ?? getRealCustomerId({
|
|
206
|
+
id: formattedCustomerId
|
|
207
|
+
});
|
|
208
|
+
let customer = null;
|
|
209
|
+
if (assetCustomerId !== undefined) {
|
|
210
|
+
if (!host.resolveCustomerById) {
|
|
211
|
+
bridge?.onNotify?.('fail', 'pisell2.text.scan-global-failed');
|
|
212
|
+
return {
|
|
213
|
+
resolveFailed: true,
|
|
214
|
+
customerBound: false
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
try {
|
|
218
|
+
customer = await host.resolveCustomerById(assetCustomerId);
|
|
219
|
+
} catch (error) {
|
|
220
|
+
console.warn('[GlobalScan] Wallet asset customer lookup failed', error);
|
|
221
|
+
bridge?.onNotify?.('fail', getErrorMessage(error) || 'pisell2.text.scan-global-failed');
|
|
222
|
+
return {
|
|
223
|
+
resolveFailed: true,
|
|
224
|
+
customerBound: false
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const customerId = getRealCustomerId(customer);
|
|
229
|
+
if (!customer || customerId === undefined) {
|
|
230
|
+
if (assetCustomerId !== undefined) {
|
|
231
|
+
bridge?.onNotify?.('fail', 'pisell2.text.scan-global-failed');
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
resolveFailed: assetCustomerId !== undefined,
|
|
235
|
+
customerBound: false
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
const orderCustomer = customer.id === undefined || customer.id === null ? {
|
|
239
|
+
...customer,
|
|
240
|
+
id: customerId
|
|
241
|
+
} : customer;
|
|
242
|
+
host.setOrderCustomer(orderCustomer);
|
|
243
|
+
activeCustomer = orderCustomer;
|
|
244
|
+
await host.refreshWalletAssets?.({
|
|
245
|
+
preserveSelection: false,
|
|
246
|
+
retainSearchResultCodes: [scanCode]
|
|
247
|
+
});
|
|
248
|
+
return {
|
|
249
|
+
resolveFailed: false,
|
|
250
|
+
customerBound: true
|
|
251
|
+
};
|
|
252
|
+
};
|
|
116
253
|
const trySelectWalletAsset = async () => {
|
|
117
254
|
if (!host.scanWalletAsset) return null;
|
|
118
255
|
const walletResult = await host.scanWalletAsset(scanCode).catch(error => {
|
|
@@ -123,61 +260,56 @@ async function applyGlobalScan(host, formatted, bridge) {
|
|
|
123
260
|
if (walletResult.type !== 'normalCode' || !walletResult.asset) {
|
|
124
261
|
return null;
|
|
125
262
|
}
|
|
263
|
+
const bindResult = await bindWalletPassCustomer(walletResult.asset);
|
|
126
264
|
if (walletResult.selected) {
|
|
127
|
-
|
|
265
|
+
if (!bindResult.resolveFailed) {
|
|
266
|
+
bridge?.onNotify?.('success');
|
|
267
|
+
}
|
|
128
268
|
return {
|
|
129
269
|
status: 'success',
|
|
130
270
|
kind: 'promotion'
|
|
131
271
|
};
|
|
132
272
|
}
|
|
133
273
|
// 已识别为 Wallet 支付资产但当前不可用:保留禁用展示,不回退优惠码。
|
|
134
|
-
|
|
274
|
+
if (!bindResult.resolveFailed) {
|
|
275
|
+
bridge?.onNotify?.('fail', 'pisell2.text.goodpass.code-invalid-cart');
|
|
276
|
+
}
|
|
135
277
|
return {
|
|
136
278
|
status: 'failed',
|
|
137
279
|
reason: 'invalid_data'
|
|
138
280
|
};
|
|
139
281
|
};
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
reason: 'no_bridge'
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
const result = await bridge.applyPromotionCode(scanCode);
|
|
151
|
-
if (result?.isAccepted ?? result?.isAvailable) {
|
|
152
|
-
return {
|
|
153
|
-
status: 'success',
|
|
154
|
-
kind: 'promotion'
|
|
155
|
-
};
|
|
156
|
-
}
|
|
282
|
+
|
|
283
|
+
// batch-search 可能返回来源门店客户;先映射到当前门店,确保 Wallet
|
|
284
|
+
// 支付资产查询使用本店客户上下文。
|
|
285
|
+
const bindResult = await bindWalletPassCustomer();
|
|
286
|
+
const walletSelection = await trySelectWalletAsset();
|
|
287
|
+
if (walletSelection) return walletSelection;
|
|
288
|
+
if (!bridge?.applyPromotionCode) {
|
|
157
289
|
return {
|
|
158
290
|
status: 'failed',
|
|
159
|
-
reason: '
|
|
291
|
+
reason: 'no_bridge'
|
|
160
292
|
};
|
|
161
293
|
}
|
|
162
|
-
|
|
163
|
-
await
|
|
164
|
-
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
294
|
+
const promotionCustomerId = getRealCustomerId(activeCustomer);
|
|
295
|
+
const promotionResult = bindResult.customerBound ? await bridge.applyPromotionCode(scanCode, promotionCustomerId, {
|
|
296
|
+
suppressFailureNotification: true
|
|
297
|
+
}) : await bridge.applyPromotionCode(scanCode, promotionCustomerId);
|
|
298
|
+
if (promotionResult?.isAccepted ?? promotionResult?.isAvailable) {
|
|
299
|
+
return {
|
|
300
|
+
status: 'success',
|
|
301
|
+
kind: 'promotion'
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
if (bindResult.customerBound) {
|
|
305
|
+
return {
|
|
306
|
+
status: 'success',
|
|
307
|
+
kind: 'customer'
|
|
308
|
+
};
|
|
176
309
|
}
|
|
177
|
-
bridge?.onNotify?.('success', 'pisell2.text.add-client-success');
|
|
178
310
|
return {
|
|
179
|
-
status: '
|
|
180
|
-
|
|
311
|
+
status: 'failed',
|
|
312
|
+
reason: 'invalid_data'
|
|
181
313
|
};
|
|
182
314
|
}
|
|
183
315
|
if (searchType === 'product' || searchType === 'local_product') {
|
|
@@ -14,7 +14,8 @@ const searchWalletPass = async (request, code) => {
|
|
|
14
14
|
// 翻译识别码名称 0:不翻译 1:翻译
|
|
15
15
|
relation_product: 1,
|
|
16
16
|
// 组装关联商品: 0不处理 1处理
|
|
17
|
-
|
|
17
|
+
with_franchisor_data: 1,
|
|
18
|
+
with: ['customer', 'franchiseeWalletPass:shop_id,source_shop_id,product_id,use'],
|
|
18
19
|
tags: ['point_card',
|
|
19
20
|
// 积分卡
|
|
20
21
|
'gift_card',
|
|
@@ -25,6 +26,8 @@ const searchWalletPass = async (request, code) => {
|
|
|
25
26
|
// 折扣卡
|
|
26
27
|
'good_pass',
|
|
27
28
|
// 商品券
|
|
29
|
+
'entitlement_pass',
|
|
30
|
+
// 月卡 / 年卡 / 多次使用卡
|
|
28
31
|
'session_ticket',
|
|
29
32
|
// 场次票
|
|
30
33
|
'appointment_ticket' // 预约类门票
|
|
@@ -51,17 +54,18 @@ exports.searchWalletPass = searchWalletPass;
|
|
|
51
54
|
const searchWallet = async (request, code) => {
|
|
52
55
|
const params = {
|
|
53
56
|
code,
|
|
54
|
-
with_customer: 1
|
|
57
|
+
with_customer: 1,
|
|
58
|
+
with_franchisor_data: 1
|
|
55
59
|
};
|
|
56
60
|
try {
|
|
57
61
|
const res = await request?.post('/wallet/detail/search', params);
|
|
58
|
-
if (res?.code
|
|
62
|
+
if (res?.status === true && res?.code === 200 && !!res?.data) {
|
|
59
63
|
return {
|
|
60
64
|
searchType: 'wallet',
|
|
61
65
|
response: res
|
|
62
66
|
};
|
|
63
67
|
}
|
|
64
|
-
throw new Error('没有找到对应的识别码');
|
|
68
|
+
throw new Error(res?.message || '没有找到对应的识别码');
|
|
65
69
|
} catch (error) {
|
|
66
70
|
throw error;
|
|
67
71
|
}
|
|
@@ -6,6 +6,12 @@ export interface GlobalScanFormattedResult {
|
|
|
6
6
|
searchType: GlobalScanSearchType;
|
|
7
7
|
data: any;
|
|
8
8
|
scanCode: string;
|
|
9
|
+
/** Wallet / Wallet Pass 来源客户 ID;跨店时必须先解析为当前店客户。 */
|
|
10
|
+
customerId?: string | number;
|
|
11
|
+
/** 仅 Wallet 返回;true 时禁止直接使用响应内的来源客户。 */
|
|
12
|
+
isCrossShopWallet?: boolean;
|
|
13
|
+
/** 总店 Wallet Pass 未共享给当前子店。 */
|
|
14
|
+
isWalletPassUnavailableAtStore?: boolean;
|
|
9
15
|
}
|
|
10
16
|
interface RawScanSearchResult {
|
|
11
17
|
searchType?: GlobalScanSearchType;
|
|
@@ -36,14 +36,22 @@ function formatGlobalScanResult(raw, scanCode) {
|
|
|
36
36
|
return {
|
|
37
37
|
searchType,
|
|
38
38
|
data: resultData?.customer,
|
|
39
|
-
scanCode
|
|
39
|
+
scanCode,
|
|
40
|
+
customerId: resultData?.customer_id,
|
|
41
|
+
isCrossShopWallet: resultData?.is_cross_shop_wallet === true || Number(resultData?.is_cross_shop_wallet) === 1
|
|
40
42
|
};
|
|
41
43
|
}
|
|
42
44
|
if (searchType === 'walletPass') {
|
|
45
|
+
const firstResult = resultData?.[0];
|
|
46
|
+
const isWalletPassUnavailableAtStore = firstResult?.is_franchisor_pass === 1 && firstResult?.franchisee_wallet_pass?.use !== 'enabled';
|
|
43
47
|
return {
|
|
44
48
|
searchType,
|
|
45
|
-
data:
|
|
46
|
-
scanCode
|
|
49
|
+
data: firstResult?.customer,
|
|
50
|
+
scanCode,
|
|
51
|
+
customerId: firstResult?.customer_id ?? firstResult?.holder?.customer_id ?? firstResult?.customer?.id ?? firstResult?.customer?.customer_id,
|
|
52
|
+
...(isWalletPassUnavailableAtStore ? {
|
|
53
|
+
isWalletPassUnavailableAtStore: true
|
|
54
|
+
} : {})
|
|
47
55
|
};
|
|
48
56
|
}
|
|
49
57
|
if (searchType === 'product' || searchType === 'local_product') {
|
|
@@ -43,6 +43,7 @@ export declare class ShopDiscountImpl extends BaseModule implements Module {
|
|
|
43
43
|
batchSearchByProductIds(productIds: number[]): Promise<Discount[]>;
|
|
44
44
|
scanCode(code: string, customerId?: number): Promise<{
|
|
45
45
|
isAvailable: boolean;
|
|
46
|
+
isAccepted?: boolean;
|
|
46
47
|
productList: Record<string, any>[];
|
|
47
48
|
discountList: Discount[];
|
|
48
49
|
type: "server" | "clientCalc";
|
|
@@ -62,6 +63,7 @@ export declare class ShopDiscountImpl extends BaseModule implements Module {
|
|
|
62
63
|
action?: 'create' | 'edit';
|
|
63
64
|
with_good_pass?: 0 | 1;
|
|
64
65
|
with_discount_card?: 0 | 1;
|
|
66
|
+
with_entitlement_pass?: 0 | 1;
|
|
65
67
|
}): Promise<void>;
|
|
66
68
|
}
|
|
67
69
|
export default ShopDiscountImpl;
|