@pisell/pisellos 2.3.164 → 2.3.166
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/evaluator.js +89 -104
- package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.d.ts +3 -1
- package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +28 -4
- package/dist/modules/Holder/utils.d.ts +2 -0
- package/dist/modules/Holder/utils.js +32 -2
- package/dist/modules/OpenData/contactInfo.d.ts +20 -0
- package/dist/modules/OpenData/contactInfo.js +70 -0
- package/dist/modules/OpenData/index.d.ts +1 -0
- package/dist/modules/OpenData/index.js +1 -0
- package/dist/modules/OpenData/types.d.ts +9 -0
- package/dist/modules/Order/holderRelations.d.ts +14 -0
- package/dist/modules/Order/holderRelations.js +154 -0
- package/dist/modules/Order/index.d.ts +1 -1
- package/dist/modules/Order/index.js +89 -44
- package/dist/modules/Order/types.d.ts +19 -1
- package/dist/modules/Order/types.js +2 -0
- package/dist/modules/Order/utils/discountProductLineIdentity.d.ts +1 -1
- package/dist/modules/Order/utils/discountProductLineIdentity.js +142 -7
- package/dist/modules/Order/utils/orderCollectionIdentity.js +24 -10
- package/dist/modules/Order/utils.d.ts +1 -1
- package/dist/modules/Order/utils.js +35 -9
- package/dist/modules/Rules/index.js +62 -61
- package/dist/server/index.js +18 -12
- package/dist/server/modules/order/index.d.ts +4 -0
- package/dist/server/modules/order/index.js +255 -190
- package/dist/server/modules/order/types.d.ts +3 -0
- package/dist/server/modules/products/index.js +3 -2
- package/dist/shared/receipt/small-ticket.js +17 -6
- package/dist/shared/sales/loadSalesOrderSnapshot.js +1 -1
- package/dist/solution/BaseSales/index.d.ts +4 -0
- package/dist/solution/BaseSales/index.js +1116 -935
- package/dist/solution/BaseSales/types.d.ts +2 -1
- package/dist/solution/BaseSales/utils/parseSalesResponse.d.ts +3 -1
- package/dist/solution/BaseSales/utils/parseSalesResponse.js +7 -3
- package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +14 -4
- package/dist/solution/BookingTicket/index.d.ts +15 -1
- package/dist/solution/BookingTicket/index.js +784 -501
- package/dist/solution/UnifiedBookingSales/index.js +1 -1
- package/lib/model/strategy/adapter/promotion/evaluator.js +9 -14
- package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.d.ts +3 -1
- package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +17 -1
- package/lib/modules/Holder/utils.d.ts +2 -0
- package/lib/modules/Holder/utils.js +27 -1
- package/lib/modules/OpenData/contactInfo.d.ts +20 -0
- package/lib/modules/OpenData/contactInfo.js +56 -0
- package/lib/modules/OpenData/index.d.ts +1 -0
- package/lib/modules/OpenData/index.js +12 -0
- package/lib/modules/OpenData/types.d.ts +9 -0
- package/lib/modules/Order/holderRelations.d.ts +14 -0
- package/lib/modules/Order/holderRelations.js +98 -0
- package/lib/modules/Order/index.d.ts +1 -1
- package/lib/modules/Order/index.js +44 -12
- package/lib/modules/Order/types.d.ts +19 -1
- package/lib/modules/Order/types.js +1 -0
- package/lib/modules/Order/utils/discountProductLineIdentity.d.ts +1 -1
- package/lib/modules/Order/utils/discountProductLineIdentity.js +51 -2
- package/lib/modules/Order/utils/orderCollectionIdentity.js +5 -0
- package/lib/modules/Order/utils.d.ts +1 -1
- package/lib/modules/Order/utils.js +27 -6
- package/lib/modules/Rules/index.js +2 -1
- package/lib/server/index.js +6 -0
- package/lib/server/modules/order/index.d.ts +4 -0
- package/lib/server/modules/order/index.js +46 -4
- package/lib/server/modules/order/types.d.ts +3 -0
- package/lib/server/modules/products/index.js +2 -1
- package/lib/shared/receipt/small-ticket.js +15 -7
- package/lib/shared/sales/loadSalesOrderSnapshot.js +1 -1
- package/lib/solution/BaseSales/index.d.ts +4 -0
- package/lib/solution/BaseSales/index.js +68 -2
- package/lib/solution/BaseSales/types.d.ts +2 -1
- package/lib/solution/BaseSales/utils/parseSalesResponse.d.ts +3 -1
- package/lib/solution/BaseSales/utils/parseSalesResponse.js +4 -0
- package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +16 -8
- package/lib/solution/BookingTicket/index.d.ts +15 -1
- package/lib/solution/BookingTicket/index.js +132 -3
- package/lib/solution/UnifiedBookingSales/index.js +1 -1
- package/package.json +1 -1
|
@@ -25,6 +25,8 @@ var _Order = require("../../modules/Order");
|
|
|
25
25
|
var _pickupTiming = require("../../modules/Order/pickupTiming");
|
|
26
26
|
var _pickupTiming2 = require("./pickupTiming");
|
|
27
27
|
var _fulfillmentItemRules = require("./fulfillmentItemRules");
|
|
28
|
+
var _holderRelations = require("../../modules/Order/holderRelations");
|
|
29
|
+
var _contactInfo = require("../../modules/OpenData/contactInfo");
|
|
28
30
|
var _decimal = _interopRequireDefault(require("decimal.js"));
|
|
29
31
|
var _lodashEs = require("lodash-es");
|
|
30
32
|
var _types2 = require("../BookingByStep/types");
|
|
@@ -349,6 +351,38 @@ function areBaseSalesNotesEquivalent(currentValue, loadedValue, options) {
|
|
|
349
351
|
const loadedNotes = normalizeBaseSalesNotesForComparison(loadedValue);
|
|
350
352
|
return currentNotes !== null && loadedNotes !== null && (0, _lodashEs.isEqual)(currentNotes, loadedNotes);
|
|
351
353
|
}
|
|
354
|
+
function mergeBaseSalesHolderRelations(currentRecord, incomingRecord, strategy) {
|
|
355
|
+
if (!Array.isArray(incomingRecord.holder_relations)) {
|
|
356
|
+
return (0, _lodashEs.cloneDeep)(currentRecord.holder_relations);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// 仅保留商品集合合并实际留下的本地行关系;匹配的行沿用 incoming 快照。
|
|
360
|
+
// 未提供 products 的稀疏包不能据此判断哪些行已被删除或被本地保留。
|
|
361
|
+
const retainedProductUids = new Set();
|
|
362
|
+
if (Array.isArray(incomingRecord.products)) {
|
|
363
|
+
for (const product of currentRecord.products || []) {
|
|
364
|
+
const retained = !incomingRecord.products.some(incomingProduct => (0, _orderCollectionIdentity.isSameOrderCollectionItem)('product', product, incomingProduct)) && (strategy === 'preserve-local' || !(0, _orderCollectionIdentity.getOrderCollectionPersistentId)('product', product));
|
|
365
|
+
const uid = (0, _orderCollectionIdentity.getOrderCollectionReferenceUid)('product', product);
|
|
366
|
+
if (retained && uid) retainedProductUids.add(uid);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
const belongsToRetainedProduct = relation => relation?.relation_model === 'order_detail' && retainedProductUids.has(String(relation.relation_uuid));
|
|
370
|
+
const selectedRelations = [...incomingRecord.holder_relations.filter(relation => !belongsToRetainedProduct(relation)), ...(Array.isArray(currentRecord.holder_relations) ? currentRecord.holder_relations.filter(belongsToRetainedProduct) : [])];
|
|
371
|
+
const result = [];
|
|
372
|
+
const indexByIdentity = new Map();
|
|
373
|
+
for (const relation of selectedRelations) {
|
|
374
|
+
const identityParts = [relation?.relation_model, relation?.relation_uuid, relation?.holder_type, relation?.holder_id];
|
|
375
|
+
const identity = identityParts.every(value => value !== undefined && value !== null) ? JSON.stringify(identityParts) : null;
|
|
376
|
+
const existingIndex = identity === null ? undefined : indexByIdentity.get(identity);
|
|
377
|
+
if (existingIndex !== undefined) {
|
|
378
|
+
result[existingIndex] = (0, _lodashEs.cloneDeep)(relation);
|
|
379
|
+
} else {
|
|
380
|
+
if (identity !== null) indexByIdentity.set(identity, result.length);
|
|
381
|
+
result.push((0, _lodashEs.cloneDeep)(relation));
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return result;
|
|
385
|
+
}
|
|
352
386
|
function mergeSalesDetailRecordWithTempOrder(currentTempOrder, loadedRecord, strategy) {
|
|
353
387
|
const loadedHasOrderMetaList = Object.prototype.hasOwnProperty.call(loadedRecord || {}, 'order_meta_list');
|
|
354
388
|
const loadedHasSalesNotes = Object.prototype.hasOwnProperty.call(loadedRecord || {}, 'notes');
|
|
@@ -367,8 +401,12 @@ function mergeSalesDetailRecordWithTempOrder(currentTempOrder, loadedRecord, str
|
|
|
367
401
|
const normalizedCurrentTempOrder = normalizeBaseSalesRecordCollections(currentTempOrder, {
|
|
368
402
|
ensureUid: false
|
|
369
403
|
});
|
|
404
|
+
const mergedHolderRelations = mergeBaseSalesHolderRelations(normalizedCurrentTempOrder, normalizedLoadedRecord, strategy);
|
|
370
405
|
const uidRemaps = [];
|
|
371
|
-
const mergedRecord = (0, _lodashEs.mergeWith)(normalizedCurrentTempOrder, normalizedLoadedRecord, (currentValue, loadedValue, key) => {
|
|
406
|
+
const mergedRecord = (0, _lodashEs.mergeWith)(normalizedCurrentTempOrder, normalizedLoadedRecord, (currentValue, loadedValue, key, object) => {
|
|
407
|
+
if (key === 'holder_relations' && object === normalizedCurrentTempOrder) {
|
|
408
|
+
return (0, _lodashEs.cloneDeep)(mergedHolderRelations);
|
|
409
|
+
}
|
|
372
410
|
// order_meta_list 是服务端/本地订单携带的完整业务快照。若 incoming
|
|
373
411
|
// 明确提供它,必须整体替换;若省略,则保留当前值,避免 snapshot
|
|
374
412
|
// 数组递归拼接后出现同 ID 的旧数据。
|
|
@@ -1862,6 +1900,29 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
|
|
|
1862
1900
|
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
1863
1901
|
return this.store.order.updateTempOrderContactsInfo(contactsInfo);
|
|
1864
1902
|
}
|
|
1903
|
+
async loadContactInfoOpenData() {
|
|
1904
|
+
return this.otherParams?.openData ?? null;
|
|
1905
|
+
}
|
|
1906
|
+
async getContactInfoConfig() {
|
|
1907
|
+
const scope = this.getPickupTimingScope();
|
|
1908
|
+
const data = await this.loadContactInfoOpenData();
|
|
1909
|
+
if (scope !== this.getPickupTimingScope()) throw new Error('Store settings changed. Please try again.');
|
|
1910
|
+
return (0, _contactInfo.resolveContactInfoConfig)(data);
|
|
1911
|
+
}
|
|
1912
|
+
async validateContactInfo() {
|
|
1913
|
+
const order = this.getTempOrder();
|
|
1914
|
+
const customerId = order?.customer_id;
|
|
1915
|
+
// Missing customer IDs and the reserved Walk-In IDs do not require contact info.
|
|
1916
|
+
if (!Number.isFinite(Number(customerId)) || Number(customerId) <= 1) return;
|
|
1917
|
+
const identity = order?._extend?.localId || order?.external_sale_number || order?.order_id || order;
|
|
1918
|
+
const config = await this.getContactInfoConfig();
|
|
1919
|
+
const current = this.getTempOrder();
|
|
1920
|
+
if (identity !== (current?._extend?.localId || current?.external_sale_number || current?.order_id || current) || customerId !== current?.customer_id) {
|
|
1921
|
+
throw new Error('The order changed. Please try again.');
|
|
1922
|
+
}
|
|
1923
|
+
const missing = (0, _contactInfo.getMissingContactInfoFields)(config, current?.contacts_info);
|
|
1924
|
+
if (missing.length) throw new _contactInfo.ContactInfoRequiredError(missing);
|
|
1925
|
+
}
|
|
1865
1926
|
|
|
1866
1927
|
/** @deprecated OrderModule 不再负责 tempOrder localStorage 持久化。 */
|
|
1867
1928
|
setEnableTempOrderPersist(enabled) {
|
|
@@ -2664,6 +2725,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
|
|
|
2664
2725
|
if (!this.store.order) {
|
|
2665
2726
|
throw new Error('BaseSales 解决方案需要 order 模块支持');
|
|
2666
2727
|
}
|
|
2728
|
+
await this.validateContactInfo();
|
|
2667
2729
|
if (this.isPickupTimingEnabled()) await this.preparePickupTimingForCheckout({
|
|
2668
2730
|
freeze: true
|
|
2669
2731
|
});
|
|
@@ -2824,6 +2886,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
|
|
|
2824
2886
|
for (const params of unfinished) await this.finalizeLocalEftposPayment(params);
|
|
2825
2887
|
throw new Error('订单已更新,请确认商品和金额后重新付款');
|
|
2826
2888
|
}
|
|
2889
|
+
await this.validateContactInfo();
|
|
2827
2890
|
const lookup = temp?.order_id || temp?.external_sale_number;
|
|
2828
2891
|
let localOrder = null;
|
|
2829
2892
|
if (lookup && this.core?.server?.hasRoute?.('post', '/shop/local/orderPaymentReadiness')) {
|
|
@@ -2875,6 +2938,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
|
|
|
2875
2938
|
if (!this.store.order) {
|
|
2876
2939
|
throw new Error('BaseSales 解决方案需要 order 模块支持');
|
|
2877
2940
|
}
|
|
2941
|
+
await this.validateContactInfo();
|
|
2878
2942
|
if (this.isPickupTimingEnabled()) await this.preparePickupTimingForCheckout({
|
|
2879
2943
|
freeze: true
|
|
2880
2944
|
});
|
|
@@ -3293,7 +3357,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
|
|
|
3293
3357
|
|
|
3294
3358
|
const walletProducts = tempOrder?.products.map(product => {
|
|
3295
3359
|
const metadata = product.metadata || {};
|
|
3296
|
-
const rawHolderId = product.holder_id ?? metadata.holder_id;
|
|
3360
|
+
const rawHolderId = (0, _holderRelations.hasProductBooking)(product) ? product.holder_id ?? metadata.holder_id : (0, _holderRelations.getProductHolderRelations)(tempOrder, product).filter(relation => relation.holder_type === 'form').map(relation => relation.holder_id);
|
|
3297
3361
|
const holderIdValue = Array.isArray(rawHolderId) ? rawHolderId[0] : rawHolderId;
|
|
3298
3362
|
const holder_id = Number.isInteger(Number(holderIdValue)) ? Number(holderIdValue) : undefined;
|
|
3299
3363
|
return {
|
|
@@ -3709,6 +3773,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
|
|
|
3709
3773
|
*/
|
|
3710
3774
|
async confirmWalletPayment() {
|
|
3711
3775
|
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
3776
|
+
await this.validateContactInfo();
|
|
3712
3777
|
const beforePayments = (0, _lodashEs.cloneDeep)(this.store.order.getOrderPayments());
|
|
3713
3778
|
const pendingWalletAmount = this.getPendingWalletPaymentAmount(beforePayments);
|
|
3714
3779
|
const amountSnapshot = this.store.order.getOrderAmountSnapshot?.();
|
|
@@ -3780,6 +3845,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
|
|
|
3780
3845
|
*/
|
|
3781
3846
|
async payCash(params) {
|
|
3782
3847
|
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
3848
|
+
await this.validateContactInfo();
|
|
3783
3849
|
const amount = Number(params.amount || 0);
|
|
3784
3850
|
if (!Number.isFinite(amount) || amount <= 0) {
|
|
3785
3851
|
throw new Error('现金支付金额必须大于 0');
|
|
@@ -4,7 +4,7 @@ import type { ScanOrderLogInput as BaseSalesLogInput, ScanOrderLoggerProviderCon
|
|
|
4
4
|
import type { QuantityCheckResult, QuantityLimitResult } from '../../model/strategy/adapter/itemRule';
|
|
5
5
|
import type { QuotationCustomerScopeInfo } from '../../modules/Quotation/types';
|
|
6
6
|
import type { PaymentMethod } from '../../modules/Payment/types';
|
|
7
|
-
import type { OrderPaymentSource } from '../../modules/Order/types';
|
|
7
|
+
import type { OrderHolderRelation, OrderPaymentSource } from '../../modules/Order/types';
|
|
8
8
|
import type { SubmitPayloadEnhancer } from '../../modules/Order/utils';
|
|
9
9
|
/**
|
|
10
10
|
* BaseSales 流程 hook 后缀。
|
|
@@ -277,6 +277,7 @@ export interface BaseSalesTempOrder {
|
|
|
277
277
|
contacts: any[];
|
|
278
278
|
contacts_info: Record<string, any> | null;
|
|
279
279
|
holder: Record<string, any> | null;
|
|
280
|
+
holder_relations?: OrderHolderRelation[];
|
|
280
281
|
metadata: Record<string, any>;
|
|
281
282
|
_extend?: {
|
|
282
283
|
productsByUid?: Record<string, Record<string, any>>;
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* - 不在此处发明等价关系;UI 侧(ticketBooking)若需要别的字段,由 view-model 适配层做转换
|
|
15
15
|
*/
|
|
16
16
|
import type { BaseSalesOrderProduct } from '../types';
|
|
17
|
-
import type { OrderMetaList } from '../../../modules/Order/types';
|
|
17
|
+
import type { OrderHolderRelation, OrderMetaList } from '../../../modules/Order/types';
|
|
18
18
|
export interface ISalesDetailHeader {
|
|
19
19
|
order_id: number | string | null;
|
|
20
20
|
order_number?: string;
|
|
@@ -121,6 +121,8 @@ export interface ISalesDetail {
|
|
|
121
121
|
header: ISalesDetailHeader;
|
|
122
122
|
/** 商品行快照(保留 tempOrder / 后端详情原字段) */
|
|
123
123
|
products: BaseSalesOrderProduct[];
|
|
124
|
+
/** 非预约商品 Holder 关系,保留关系及 metadata 的扩展字段。 */
|
|
125
|
+
holder_relations?: OrderHolderRelation[];
|
|
124
126
|
/** 全部 bookings,按服务端原始顺序输出(含 root + child) */
|
|
125
127
|
bookings: ISalesBooking[];
|
|
126
128
|
/** root booking(parent_id === 0),多个时取首个,无则为 null */
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.parseSalesResponse = parseSalesResponse;
|
|
7
7
|
var _orderCollectionIdentity = require("../../../modules/Order/utils/orderCollectionIdentity");
|
|
8
8
|
var _pickupTiming = require("../../../modules/Order/pickupTiming");
|
|
9
|
+
var _lodashEs = require("lodash-es");
|
|
9
10
|
/**
|
|
10
11
|
* 把 `/order/sales/{id}` 响应(OrderData)解析为 ISalesDetail。
|
|
11
12
|
*
|
|
@@ -130,6 +131,9 @@ function parseSalesResponse(input) {
|
|
|
130
131
|
return {
|
|
131
132
|
header: pickHeader(raw),
|
|
132
133
|
products,
|
|
134
|
+
...(Array.isArray(raw?.holder_relations) ? {
|
|
135
|
+
holder_relations: (0, _lodashEs.cloneDeep)(raw.holder_relations)
|
|
136
|
+
} : {}),
|
|
133
137
|
bookings,
|
|
134
138
|
rootBooking,
|
|
135
139
|
bookingsByProductUid,
|
|
@@ -5,7 +5,9 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.transformBaseProductToOrderProduct = transformBaseProductToOrderProduct;
|
|
7
7
|
var _weighedProduct = require("../../../modules/Order/utils/weighedProduct");
|
|
8
|
-
var _utils = require("../../../modules/
|
|
8
|
+
var _utils = require("../../../modules/Holder/utils");
|
|
9
|
+
var _serviceTimes = require("../../../modules/BookingContext/utils/serviceTimes");
|
|
10
|
+
var _utils2 = require("../../../modules/Order/utils");
|
|
9
11
|
var _manualProductDiscount = require("../../../modules/Order/utils/manualProductDiscount");
|
|
10
12
|
function toNumber(value, fallback = 0) {
|
|
11
13
|
const parsedValue = Number(value);
|
|
@@ -108,7 +110,7 @@ function normalizeBundleItems(bundleItems, preferPriceField = false, preferBundl
|
|
|
108
110
|
discount_list: preferPriceField ? [] : bundleItem.discount_list
|
|
109
111
|
};
|
|
110
112
|
// bundle_selling_price 落「正净额」:markdown = |price| − Σoptions;markup/原价维持原值。
|
|
111
|
-
normalizedItem.bundle_selling_price = (0,
|
|
113
|
+
normalizedItem.bundle_selling_price = (0, _utils2.getBundleSellingMagnitude)(magnitudeSource).toFixed(2);
|
|
112
114
|
if ((normalizedItem.price_type ?? normalizedItem.custom_price_type) === 'markdown' && (normalizedItem.price_type_ext === '' || normalizedItem.price_type_ext === undefined || normalizedItem.price_type_ext === null)) {
|
|
113
115
|
normalizedItem.custom_price = normalizedItem.custom_price ?? normalizedItem.price;
|
|
114
116
|
normalizedItem.price = normalizedItem.bundle_selling_price;
|
|
@@ -158,14 +160,14 @@ function transformBaseProductToOrderProduct(params) {
|
|
|
158
160
|
const explicitLineTotal = payload.line_total ?? payload.total ?? payload._extend?.total;
|
|
159
161
|
const hasExplicitLineTotal = Number.isFinite(Number(explicitLineTotal));
|
|
160
162
|
const lineCompositeTotal = toPriceString(explicitLineTotal, sourceProductPrice);
|
|
161
|
-
const productOptionItem = (0,
|
|
163
|
+
const productOptionItem = (0, _utils2.normalizeProductSkuOptions)(normalizeOptionItems(payload.product_sku?.option ?? payload.option ?? payload.options ?? payload.product_option_item, hasPriceOverride || weighedSourcePrice !== undefined));
|
|
162
164
|
const productBundle = normalizeBundleItems(payload.bundle ?? payload.product_bundle, hasPriceOverride, hasPriceOverride && weighedSourcePrice !== undefined);
|
|
163
165
|
// `payload.unique` 是业务侧商品/票务标识,可能在连续加同一商品时重复;
|
|
164
166
|
// 行级唯一值只接受显式协议字段,缺省交给 OrderModule 生成。
|
|
165
167
|
const uniqueIdentificationNumber = payload.unique_identification_number;
|
|
166
168
|
const resolvedQuantity = toSafePositiveInt(payload.quantity ?? payload.num, 1);
|
|
167
|
-
const originTotal = weighedSourcePrice !== undefined ? Number((0,
|
|
168
|
-
mainPrice: Number(weighedSourcePrice) + (0,
|
|
169
|
+
const originTotal = weighedSourcePrice !== undefined ? Number((0, _utils2.composeLinePrice)({
|
|
170
|
+
mainPrice: Number(weighedSourcePrice) + (0, _utils2.sumOptionUnitPrice)(productOptionItem).toNumber(),
|
|
169
171
|
bundle: hasPriceOverride ? (0, _weighedProduct.withOriginalBundleOptionPrices)(productBundle) : productBundle,
|
|
170
172
|
useOriginalBundle: true
|
|
171
173
|
})) : (0, _manualProductDiscount.resolveManualDiscountOriginTotal)({
|
|
@@ -197,14 +199,15 @@ function transformBaseProductToOrderProduct(params) {
|
|
|
197
199
|
})) : payload.discount_list || [];
|
|
198
200
|
|
|
199
201
|
// v2:source 不含 option;main_product_* 含 option(与 normalizeOrderProduct 一致)
|
|
200
|
-
const optionSum = (0,
|
|
202
|
+
const optionSum = (0, _utils2.sumOptionUnitPrice)(productOptionItem).toNumber();
|
|
201
203
|
const mainProductOriginalPrice = toPriceString(Number(sourceProductPrice) + optionSum);
|
|
202
|
-
const bundleOnlyComposite = (0,
|
|
204
|
+
const bundleOnlyComposite = (0, _utils2.composeLinePrice)({
|
|
203
205
|
mainPrice: 0,
|
|
204
206
|
bundle: productBundle
|
|
205
207
|
});
|
|
206
208
|
const derivedMainSelling = hasExplicitLineTotal ? Number(lineCompositeTotal) - Number(bundleOnlyComposite) : Number(mainProductOriginalPrice);
|
|
207
209
|
const mainProductSellingPrice = toPriceString(Number.isFinite(derivedMainSelling) && derivedMainSelling >= 0 ? derivedMainSelling : Number(mainProductOriginalPrice));
|
|
210
|
+
const holderRelations = [payload._extend, callbackOrigin?._extend, sourceProduct?._extend].find(source => Array.isArray(source?.holder_relations))?.holder_relations;
|
|
208
211
|
const metadata = {
|
|
209
212
|
unique: payload.unique,
|
|
210
213
|
session: payload.session,
|
|
@@ -219,7 +222,7 @@ function transformBaseProductToOrderProduct(params) {
|
|
|
219
222
|
main_product_selling_price: mainProductSellingPrice,
|
|
220
223
|
main_product_original_price: mainProductOriginalPrice,
|
|
221
224
|
price_schema_version: 2,
|
|
222
|
-
holder_config: sourceProduct
|
|
225
|
+
holder_config: (0, _utils.resolveProductHolderConfig)(sourceProduct) ?? undefined,
|
|
223
226
|
...(dataVariantIds !== undefined ? {
|
|
224
227
|
data_variant_ids: dataVariantIds
|
|
225
228
|
} : {}),
|
|
@@ -264,6 +267,11 @@ function transformBaseProductToOrderProduct(params) {
|
|
|
264
267
|
tax_fee: toPriceString(payload.tax_fee ?? matchedVariant?.tax_fee ?? origin?.tax_fee ?? sourceProduct?.tax_fee, '0.00'),
|
|
265
268
|
is_charge_tax: toNumber(payload.is_charge_tax ?? matchedVariant?.is_charge_tax ?? origin?.is_charge_tax ?? sourceProduct?.is_charge_tax, 0),
|
|
266
269
|
metadata,
|
|
270
|
+
...(holderRelations !== undefined && !(0, _serviceTimes.isBookingProduct)(origin) && !(0, _serviceTimes.isBookingProduct)(payload) ? {
|
|
271
|
+
_extend: {
|
|
272
|
+
holder_relations: holderRelations
|
|
273
|
+
}
|
|
274
|
+
} : {}),
|
|
267
275
|
note: payload.note != null ? String(payload.note) : '',
|
|
268
276
|
product_title: productTitle,
|
|
269
277
|
product_cover: firstNonBlank(matchedVariant?.cover, sourceProduct?.cover),
|
|
@@ -32,6 +32,7 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
|
|
|
32
32
|
private orderCustomerDiscountRefreshCustomerId;
|
|
33
33
|
private orderCustomerDiscountRefreshTarget;
|
|
34
34
|
private orderCustomerPromotionRefreshInFlight;
|
|
35
|
+
private orderCustomerContactRequest;
|
|
35
36
|
private readonly customerResolveRequests;
|
|
36
37
|
protected loadOpenDataInFlight: Promise<OpenDataConfig> | null;
|
|
37
38
|
protected loadOpenDataInFlightTarget: string | null;
|
|
@@ -62,6 +63,7 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
|
|
|
62
63
|
private getIdGeneratorPlugin;
|
|
63
64
|
protected loadOpenDataConfig(): Promise<OpenDataConfig | null>;
|
|
64
65
|
getOpenData(): Promise<OpenDataConfig | null>;
|
|
66
|
+
protected loadContactInfoOpenData(): Promise<OpenDataConfig | null>;
|
|
65
67
|
protected loadFulfillmentItemRuleOpenData(): Promise<OpenDataConfig | null>;
|
|
66
68
|
getInvoiceLayoutConfig(): Promise<InvoiceLayoutConfig>;
|
|
67
69
|
getInvoiceTemplateSettings(): Promise<InvoiceTemplateSettings>;
|
|
@@ -281,6 +283,16 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
|
|
|
281
283
|
setOrderCustomer(customer: ICustomer | null, options?: {
|
|
282
284
|
source?: OrderCustomerChangePayload['source'];
|
|
283
285
|
}): void;
|
|
286
|
+
private resetOrderCustomerContactInfo;
|
|
287
|
+
/** Explicit edits, including clearing the contact, must win over a pending default. */
|
|
288
|
+
updateTempOrderContactsInfo(contact: Record<string, any> | null): Record<string, any> | null;
|
|
289
|
+
private getCustomerContactScope;
|
|
290
|
+
private getCustomerContactOrderKey;
|
|
291
|
+
replaceTempOrder(...args: Parameters<BaseSalesImpl['replaceTempOrder']>): Promise<import("../../modules/Order/types").OrderTempOrder>;
|
|
292
|
+
private applyDefaultOrderCustomerContactInfo;
|
|
293
|
+
private prefillOrderCustomerContactInfo;
|
|
294
|
+
protected waitForOrderCustomerContactInfo(): Promise<void>;
|
|
295
|
+
validateContactInfo(): Promise<void>;
|
|
284
296
|
/**
|
|
285
297
|
* 读取 tempOrder 上的下单客户协议字段;customer_id 缺失时返回 null。
|
|
286
298
|
*/
|
|
@@ -292,7 +304,9 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
|
|
|
292
304
|
/**
|
|
293
305
|
* 清空下单客户(协议字段 + snapshot)。
|
|
294
306
|
*/
|
|
295
|
-
clearOrderCustomer(
|
|
307
|
+
clearOrderCustomer(options?: {
|
|
308
|
+
preserveContactInfo?: boolean;
|
|
309
|
+
}): void;
|
|
296
310
|
/**
|
|
297
311
|
* 重置 tempOrder,并同步清空下单客户运行态。
|
|
298
312
|
*
|
|
@@ -39,6 +39,9 @@ var _orderLineDisplay = require("../../modules/BookingContext/utils/orderLineDis
|
|
|
39
39
|
var _bookingStatus = require("./utils/bookingStatus");
|
|
40
40
|
var _resolveBestAddTimePlan = require("./utils/resolveBestAddTimePlan");
|
|
41
41
|
var _utils = require("../../modules/Order/utils");
|
|
42
|
+
var _contactInfo = require("../../modules/OpenData/contactInfo");
|
|
43
|
+
var _platform = require("../../utils/platform");
|
|
44
|
+
var _lodashEs = require("lodash-es");
|
|
42
45
|
var _types2 = require("../../server/modules/form/types");
|
|
43
46
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
44
47
|
const OPEN_DATA_SECTION_CODES = ['sale', 'reservation', 'fulfillment', 'menu', 'workflow', 'documents'];
|
|
@@ -163,6 +166,7 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
|
|
|
163
166
|
orderCustomerDiscountRefreshCustomerId = null;
|
|
164
167
|
orderCustomerDiscountRefreshTarget = null;
|
|
165
168
|
orderCustomerPromotionRefreshInFlight = null;
|
|
169
|
+
orderCustomerContactRequest = null;
|
|
166
170
|
customerResolveRequests = new Map();
|
|
167
171
|
loadOpenDataInFlight = null;
|
|
168
172
|
loadOpenDataInFlightTarget = null;
|
|
@@ -300,6 +304,9 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
|
|
|
300
304
|
async getOpenData() {
|
|
301
305
|
return this.loadOpenDataConfig();
|
|
302
306
|
}
|
|
307
|
+
async loadContactInfoOpenData() {
|
|
308
|
+
return this.store.openData ? this.getOpenData() : super.loadContactInfoOpenData();
|
|
309
|
+
}
|
|
303
310
|
async loadFulfillmentItemRuleOpenData() {
|
|
304
311
|
return this.store.openData ? this.getOpenData() : super.loadFulfillmentItemRuleOpenData();
|
|
305
312
|
}
|
|
@@ -1035,7 +1042,9 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
|
|
|
1035
1042
|
*/
|
|
1036
1043
|
setOrderCustomer(customer, options) {
|
|
1037
1044
|
if (!customer) {
|
|
1038
|
-
this.clearOrderCustomer(
|
|
1045
|
+
this.clearOrderCustomer({
|
|
1046
|
+
preserveContactInfo: options?.source === 'detailHydrate'
|
|
1047
|
+
});
|
|
1039
1048
|
return;
|
|
1040
1049
|
}
|
|
1041
1050
|
const previousCustomerId = this.store.order.getOrderCustomer()?.customer_id;
|
|
@@ -1046,7 +1055,16 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
|
|
|
1046
1055
|
this.discountConfigCacheKey = null;
|
|
1047
1056
|
this.hasManualDiscountSelection = false;
|
|
1048
1057
|
}
|
|
1058
|
+
if (!customerUnchanged && options?.source !== 'detailHydrate') {
|
|
1059
|
+
this.resetOrderCustomerContactInfo();
|
|
1060
|
+
this.store.order.updateTempOrderContactsInfo((0, _lodashEs.cloneDeep)((0, _contactInfo.extractCustomerContactInfo)(customer)));
|
|
1061
|
+
}
|
|
1049
1062
|
this.store.order.setOrderCustomer(patch, customer);
|
|
1063
|
+
if (options?.source === 'detailHydrate') {
|
|
1064
|
+
this.orderCustomerContactRequest = null;
|
|
1065
|
+
} else {
|
|
1066
|
+
this.prefillOrderCustomerContactInfo(customer, !customerUnchanged);
|
|
1067
|
+
}
|
|
1050
1068
|
const snapshotAfterOrderSet = this.store.order.getOrderCustomerSnapshot();
|
|
1051
1069
|
if (customerUnchanged) {
|
|
1052
1070
|
// hydrate 后 customer_id 已存在但 _extend.customerSnapshot 仍为空时,
|
|
@@ -1079,6 +1097,105 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
|
|
|
1079
1097
|
this.orderCustomerPromotionRefreshInFlight = null;
|
|
1080
1098
|
});
|
|
1081
1099
|
}
|
|
1100
|
+
resetOrderCustomerContactInfo() {
|
|
1101
|
+
this.orderCustomerContactRequest = null;
|
|
1102
|
+
const order = this.getTempOrder();
|
|
1103
|
+
if (order?._extend) delete order._extend.contactInfoEdited;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
/** Explicit edits, including clearing the contact, must win over a pending default. */
|
|
1107
|
+
updateTempOrderContactsInfo(contact) {
|
|
1108
|
+
const order = this.store.order.ensureTempOrder();
|
|
1109
|
+
order._extend || (order._extend = {});
|
|
1110
|
+
order._extend.contactInfoEdited = true;
|
|
1111
|
+
this.orderCustomerContactRequest = null;
|
|
1112
|
+
return super.updateTempOrderContactsInfo(contact);
|
|
1113
|
+
}
|
|
1114
|
+
getCustomerContactScope() {
|
|
1115
|
+
return JSON.stringify([this.getPickupTimingScope(), this.otherParams?.channel, this.otherParams?.shop_id, this.otherParams?.tenantId]);
|
|
1116
|
+
}
|
|
1117
|
+
getCustomerContactOrderKey() {
|
|
1118
|
+
const order = this.getTempOrder();
|
|
1119
|
+
// Cart mutations can replace the object without changing the order.
|
|
1120
|
+
const identity = order?.external_sale_number || order?.request_unique_idempotency_token || order?._extend?.localId || order?.order_id;
|
|
1121
|
+
return identity ? JSON.stringify([identity, order?.order_id]) : order;
|
|
1122
|
+
}
|
|
1123
|
+
async replaceTempOrder(...args) {
|
|
1124
|
+
this.orderCustomerContactRequest = null;
|
|
1125
|
+
return super.replaceTempOrder(...args);
|
|
1126
|
+
}
|
|
1127
|
+
async applyDefaultOrderCustomerContactInfo(contact) {
|
|
1128
|
+
this.store.order.updateTempOrderContactsInfo((0, _lodashEs.cloneDeep)(contact));
|
|
1129
|
+
// Metadata may finish after promotion/discount refresh; publish the updated cart explicitly.
|
|
1130
|
+
await this.effectsEmit('onTempOrderReplaced', {
|
|
1131
|
+
tempOrder: this.getTempOrder()
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
prefillOrderCustomerContactInfo(customer, customerChanged) {
|
|
1135
|
+
const order = this.getTempOrder();
|
|
1136
|
+
const customerId = Number(order?.customer_id);
|
|
1137
|
+
if (!order || !Number.isInteger(customerId) || customerId <= 1 || order.contacts_info != null || order._extend?.contactInfoEdited) return;
|
|
1138
|
+
// A restored new cart may already have its customer ID. Existing sales keep their snapshot,
|
|
1139
|
+
// even when it is empty; only an explicit customer switch replaces it.
|
|
1140
|
+
if (!customerChanged && order.order_id && !String(order.order_id).startsWith('local_order')) return;
|
|
1141
|
+
const contact = (0, _contactInfo.extractCustomerContactInfo)(customer);
|
|
1142
|
+
if (contact) {
|
|
1143
|
+
this.orderCustomerContactRequest = null;
|
|
1144
|
+
void this.applyDefaultOrderCustomerContactInfo(contact);
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
// A relation explicitly returned as null is authoritative. Only a missing relation needs fetching.
|
|
1148
|
+
if (customer.contacts_info !== undefined) return;
|
|
1149
|
+
const userPlatform = (0, _platform.isCustomerUserPlatform)(this.otherParams?.platform);
|
|
1150
|
+
const getAuthenticatedCustomerId = () => Number(this.core?.getPlugin?.('user')?.get?.()?.id);
|
|
1151
|
+
if (userPlatform && getAuthenticatedCustomerId() !== customerId) return;
|
|
1152
|
+
const scope = this.getCustomerContactScope();
|
|
1153
|
+
const orderKey = this.getCustomerContactOrderKey();
|
|
1154
|
+
const previous = this.orderCustomerContactRequest;
|
|
1155
|
+
if (previous?.orderKey === orderKey && previous.customerId === customerId && previous.scope === scope) return;
|
|
1156
|
+
const pending = {
|
|
1157
|
+
orderKey,
|
|
1158
|
+
customerId,
|
|
1159
|
+
scope,
|
|
1160
|
+
promise: Promise.resolve()
|
|
1161
|
+
};
|
|
1162
|
+
this.orderCustomerContactRequest = pending;
|
|
1163
|
+
const loadContact = async () => {
|
|
1164
|
+
if (userPlatform) {
|
|
1165
|
+
// Same endpoint as BookingByStep.getContactInfo; the PC/H5 host adds /h5 and login headers.
|
|
1166
|
+
const result = await this.request.get('/customer/metadata', {
|
|
1167
|
+
key: 'contacts_info'
|
|
1168
|
+
});
|
|
1169
|
+
if (result?.code !== 200 || result?.status === false) throw new Error('Customer contact lookup failed');
|
|
1170
|
+
return (0, _contactInfo.isContactRecord)(result.data) ? result.data : null;
|
|
1171
|
+
}
|
|
1172
|
+
// POS and physical Kiosk share the /shop request plugin and current-shop customer resolver.
|
|
1173
|
+
const resolved = await this.resolveCustomerById(customerId);
|
|
1174
|
+
return Number(resolved?.id) === customerId ? (0, _contactInfo.extractCustomerContactInfo)(resolved) : null;
|
|
1175
|
+
};
|
|
1176
|
+
pending.promise = loadContact().then(async value => {
|
|
1177
|
+
const current = this.getTempOrder();
|
|
1178
|
+
if (this.orderCustomerContactRequest !== pending || this.getCustomerContactOrderKey() !== orderKey || Number(current?.customer_id) !== customerId || scope !== this.getCustomerContactScope() || current?._extend?.contactInfoEdited || current?.contacts_info != null || userPlatform && getAuthenticatedCustomerId() !== customerId) return;
|
|
1179
|
+
if (value) await this.applyDefaultOrderCustomerContactInfo(value);
|
|
1180
|
+
}).catch(() => {
|
|
1181
|
+
// A failed default does not prevent manual entry; a subsequent sync can retry.
|
|
1182
|
+
if (this.orderCustomerContactRequest === pending) this.orderCustomerContactRequest = null;
|
|
1183
|
+
console.warn('[BookingTicket] Failed to load saved customer contact information');
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
waitForOrderCustomerContactInfo() {
|
|
1187
|
+
return this.orderCustomerContactRequest?.promise ?? Promise.resolve();
|
|
1188
|
+
}
|
|
1189
|
+
async validateContactInfo() {
|
|
1190
|
+
const orderKey = this.getCustomerContactOrderKey();
|
|
1191
|
+
const customerId = this.getTempOrder()?.customer_id;
|
|
1192
|
+
const scope = this.getCustomerContactScope();
|
|
1193
|
+
await this.waitForOrderCustomerContactInfo();
|
|
1194
|
+
if (this.getCustomerContactOrderKey() !== orderKey || this.getTempOrder()?.customer_id !== customerId || this.getCustomerContactScope() !== scope) {
|
|
1195
|
+
throw new Error('The order changed. Please try again.');
|
|
1196
|
+
}
|
|
1197
|
+
await super.validateContactInfo();
|
|
1198
|
+
}
|
|
1082
1199
|
|
|
1083
1200
|
/**
|
|
1084
1201
|
* 读取 tempOrder 上的下单客户协议字段;customer_id 缺失时返回 null。
|
|
@@ -1097,7 +1214,10 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
|
|
|
1097
1214
|
/**
|
|
1098
1215
|
* 清空下单客户(协议字段 + snapshot)。
|
|
1099
1216
|
*/
|
|
1100
|
-
clearOrderCustomer() {
|
|
1217
|
+
clearOrderCustomer(options) {
|
|
1218
|
+
this.orderCustomerContactRequest = null;
|
|
1219
|
+
if (!options?.preserveContactInfo) this.resetOrderCustomerContactInfo();
|
|
1220
|
+
if (!options?.preserveContactInfo) this.store.order.updateTempOrderContactsInfo(null);
|
|
1101
1221
|
this.store.order.clearOrderCustomer();
|
|
1102
1222
|
this.discountConfigCacheKey = null;
|
|
1103
1223
|
this.hasManualDiscountSelection = false;
|
|
@@ -1667,7 +1787,10 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
|
|
|
1667
1787
|
* 从已加车的普通商品 order line 反查 SkuDetailModal edit 所需的 cacheItem。
|
|
1668
1788
|
*/
|
|
1669
1789
|
buildNormalProductCacheItemFromOrderLine(input) {
|
|
1670
|
-
return (0, _buildNormalProductCacheItemFromOrderLine.buildNormalProductCacheItemFromOrderLine)(
|
|
1790
|
+
return (0, _buildNormalProductCacheItemFromOrderLine.buildNormalProductCacheItemFromOrderLine)({
|
|
1791
|
+
...input,
|
|
1792
|
+
holder_relations: input.holder_relations ?? this.store.order?.getTempOrder()?.holder_relations
|
|
1793
|
+
});
|
|
1671
1794
|
}
|
|
1672
1795
|
|
|
1673
1796
|
/** 文档别名:与 OrderModule.updateOrderProduct 一致。 */
|
|
@@ -1900,6 +2023,11 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
|
|
|
1900
2023
|
async setOtherParams(params, {
|
|
1901
2024
|
cover = false
|
|
1902
2025
|
} = {}) {
|
|
2026
|
+
const nextParams = cover ? params : {
|
|
2027
|
+
...this.otherParams,
|
|
2028
|
+
...params
|
|
2029
|
+
};
|
|
2030
|
+
if (['businessCode', 'business_code', 'channel', 'platform', 'shop_id', 'tenantId', 'cacheId'].some(key => nextParams?.[key] !== this.otherParams?.[key])) this.orderCustomerContactRequest = null;
|
|
1903
2031
|
await super.setOtherParams(params, {
|
|
1904
2032
|
cover
|
|
1905
2033
|
});
|
|
@@ -2015,6 +2143,7 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
|
|
|
2015
2143
|
* 再清理 BookingTicket 自有资源(scan 监听 + scan 内存缓存)。
|
|
2016
2144
|
*/
|
|
2017
2145
|
async destroy() {
|
|
2146
|
+
this.orderCustomerContactRequest = null;
|
|
2018
2147
|
await super.destroy();
|
|
2019
2148
|
try {
|
|
2020
2149
|
this.scan?.removeAllListeners();
|
|
@@ -1877,7 +1877,7 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1877
1877
|
}
|
|
1878
1878
|
this.store.customer?.setSelectedCustomer?.(authenticatedCustomer);
|
|
1879
1879
|
this.setOrderCustomer(authenticatedCustomer);
|
|
1880
|
-
await Promise.all([this.refreshDiscountConfigAfterOrderCustomerChange(authenticatedCustomerId), this.waitForOrderCustomerPromotionRefresh()]);
|
|
1880
|
+
await Promise.all([this.refreshDiscountConfigAfterOrderCustomerChange(authenticatedCustomerId), this.waitForOrderCustomerPromotionRefresh(), this.waitForOrderCustomerContactInfo()]);
|
|
1881
1881
|
return true;
|
|
1882
1882
|
}
|
|
1883
1883
|
isAuthenticatedCustomerUserPlatform() {
|