@pisell/pisellos 2.3.95 → 2.3.97
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/dataVariant/evaluator.js +13 -2
- package/dist/model/strategy/adapter/promotion/index.js +0 -9
- package/dist/modules/Order/index.d.ts +2 -0
- package/dist/modules/Order/index.js +51 -28
- package/dist/modules/SalesSummary/index.js +6 -4
- package/dist/server/index.js +14 -8
- package/dist/server/modules/products/index.d.ts +2 -2
- package/dist/server/modules/products/index.js +10 -9
- package/dist/server/utils/product.d.ts +1 -1
- package/dist/server/utils/product.js +186 -22
- package/dist/server/utils/small-ticket.d.ts +9 -1
- package/dist/server/utils/small-ticket.js +136 -41
- package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +3 -2
- package/dist/solution/BookingByStep/index.d.ts +2 -2
- package/lib/model/strategy/adapter/dataVariant/evaluator.js +12 -2
- package/lib/model/strategy/adapter/promotion/index.js +46 -1
- package/lib/modules/Order/index.d.ts +2 -0
- package/lib/modules/Order/index.js +33 -14
- package/lib/modules/SalesSummary/index.js +3 -1
- package/lib/server/index.js +10 -6
- package/lib/server/modules/products/index.d.ts +2 -2
- package/lib/server/modules/products/index.js +5 -4
- package/lib/server/utils/product.d.ts +1 -1
- package/lib/server/utils/product.js +163 -3
- package/lib/server/utils/small-ticket.d.ts +9 -1
- package/lib/server/utils/small-ticket.js +126 -33
- package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +3 -2
- package/lib/solution/BookingByStep/index.d.ts +2 -2
- package/package.json +1 -1
|
@@ -234,15 +234,25 @@ class DataVariantEvaluator {
|
|
|
234
234
|
if (!appliedInfo?.appliedFields?.length) {
|
|
235
235
|
return originalItem;
|
|
236
236
|
}
|
|
237
|
+
|
|
238
|
+
// 套餐加价/减价使用配置价;仅商品原价模式跟随子商品的 Data Variant 价格。
|
|
239
|
+
const isOriginalPriceBundleItem = originalItem.price_type === 'markup' && originalItem.price_type_ext === 'product_price';
|
|
240
|
+
const appliedFields = appliedInfo.appliedFields.filter(field => field !== 'price' || isOriginalPriceBundleItem);
|
|
241
|
+
if (!appliedFields.length) {
|
|
242
|
+
return originalItem;
|
|
243
|
+
}
|
|
237
244
|
const result = {
|
|
238
245
|
...originalItem
|
|
239
246
|
};
|
|
240
|
-
for (const field of
|
|
247
|
+
for (const field of appliedFields) {
|
|
241
248
|
if (resolvedItem[field] !== undefined) {
|
|
242
249
|
result[field] = resolvedItem[field];
|
|
243
250
|
}
|
|
244
251
|
}
|
|
245
|
-
result._dataVariant =
|
|
252
|
+
result._dataVariant = {
|
|
253
|
+
...appliedInfo,
|
|
254
|
+
appliedFields
|
|
255
|
+
};
|
|
246
256
|
return result;
|
|
247
257
|
}
|
|
248
258
|
evaluateProductRules(rule, product, businessData, menuScopeMap) {
|
|
@@ -1 +1,46 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
Object.defineProperty(exports, "BUY_X_GET_Y_FREE_STRATEGY", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function () {
|
|
9
|
+
return _examples.BUY_X_GET_Y_FREE_STRATEGY;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
Object.defineProperty(exports, "ITEM_REWARD_STRATEGY", {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
get: function () {
|
|
15
|
+
return _examples.ITEM_REWARD_STRATEGY;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
Object.defineProperty(exports, "PromotionAdapter", {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
get: function () {
|
|
21
|
+
return _adapter.PromotionAdapter;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
Object.defineProperty(exports, "PromotionEvaluator", {
|
|
25
|
+
enumerable: true,
|
|
26
|
+
get: function () {
|
|
27
|
+
return _evaluator.PromotionEvaluator;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
Object.defineProperty(exports, "X_ITEMS_FOR_Y_PRICE_STRATEGY", {
|
|
31
|
+
enumerable: true,
|
|
32
|
+
get: function () {
|
|
33
|
+
return _examples.X_ITEMS_FOR_Y_PRICE_STRATEGY;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
Object.defineProperty(exports, "default", {
|
|
37
|
+
enumerable: true,
|
|
38
|
+
get: function () {
|
|
39
|
+
return _adapter.default;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
var _evaluator = require("./evaluator");
|
|
43
|
+
var _adapter = _interopRequireWildcard(require("./adapter"));
|
|
44
|
+
var _examples = require("./examples");
|
|
45
|
+
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
46
|
+
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
|
@@ -179,6 +179,8 @@ export declare class OrderModule extends BaseModule implements Module, OrderModu
|
|
|
179
179
|
private calculateOrderPaidPaymentTotal;
|
|
180
180
|
private getDepositAmount;
|
|
181
181
|
private normalizeDepositAmount;
|
|
182
|
+
private normalizeDepositFlag;
|
|
183
|
+
private getHistoricalDepositSnapshot;
|
|
182
184
|
private getLastOrderDepositSnapshot;
|
|
183
185
|
private getLastOrderSubmitSnapshot;
|
|
184
186
|
private applyDepositAmountToSummary;
|
|
@@ -1008,19 +1008,26 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
1008
1008
|
normalizeDepositAmount(amount) {
|
|
1009
1009
|
return _decimal.default.max(new _decimal.default(Number(amount) || 0), 0).toDecimalPlaces(2).toFixed(2);
|
|
1010
1010
|
}
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
if (
|
|
1014
|
-
|
|
1011
|
+
normalizeDepositFlag(value) {
|
|
1012
|
+
if (value === 0 || value === '0') return 0;
|
|
1013
|
+
if (value === 1 || value === '1') return 1;
|
|
1014
|
+
return null;
|
|
1015
|
+
}
|
|
1016
|
+
getHistoricalDepositSnapshot(source) {
|
|
1017
|
+
if (!source || typeof source !== 'object') return null;
|
|
1018
|
+
const record = source;
|
|
1019
|
+
const rawDepositAmount = record.deposit_amount ?? record.summary?.deposit_amount;
|
|
1015
1020
|
const depositAmount = this.normalizeDepositAmount(rawDepositAmount ?? 0);
|
|
1016
|
-
|
|
1017
|
-
const isDeposit = Number(lastOrderInfo.is_deposit) === 1 || hasDepositAmount;
|
|
1018
|
-
if (!isDeposit || !hasDepositAmount) return null;
|
|
1021
|
+
if (!new _decimal.default(depositAmount).gt(0)) return null;
|
|
1019
1022
|
return {
|
|
1020
|
-
|
|
1023
|
+
// 兼容历史数据:旧快照缺少 is_deposit 时,沿用正定金金额的推导规则。
|
|
1024
|
+
isDeposit: this.normalizeDepositFlag(record.is_deposit) ?? 1,
|
|
1021
1025
|
depositAmount
|
|
1022
1026
|
};
|
|
1023
1027
|
}
|
|
1028
|
+
getLastOrderDepositSnapshot() {
|
|
1029
|
+
return this.getHistoricalDepositSnapshot(this.store.lastOrderInfo);
|
|
1030
|
+
}
|
|
1024
1031
|
getLastOrderSubmitSnapshot() {
|
|
1025
1032
|
const lastOrderInfo = this.store.lastOrderInfo;
|
|
1026
1033
|
if (!lastOrderInfo || typeof lastOrderInfo !== 'object') return {};
|
|
@@ -1068,7 +1075,7 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
1068
1075
|
}
|
|
1069
1076
|
};
|
|
1070
1077
|
tempOrder.deposit_amount = snapshot.depositAmount;
|
|
1071
|
-
tempOrder.is_deposit =
|
|
1078
|
+
tempOrder.is_deposit = snapshot.isDeposit;
|
|
1072
1079
|
return nextSummary;
|
|
1073
1080
|
}
|
|
1074
1081
|
isDepositOrder(tempOrder, depositAmount) {
|
|
@@ -1605,10 +1612,17 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
1605
1612
|
...(0, _lodashEs.cloneDeep)(snapshot.summary || {})
|
|
1606
1613
|
};
|
|
1607
1614
|
const shouldUseManualDepositOverride = Boolean(manualDepositOverride && manualDepositOverride.productStructuralFingerprint === this.buildOrderProductsStructuralFingerprint(tempOrder.products));
|
|
1615
|
+
const historicalDepositSnapshot = this.getHistoricalDepositSnapshot(snapshot);
|
|
1608
1616
|
if (shouldUseManualDepositOverride) {
|
|
1609
1617
|
summary = this.applyDepositAmountToSummary(tempOrder, summary, manualDepositOverride.amount);
|
|
1610
|
-
} else
|
|
1611
|
-
|
|
1618
|
+
} else {
|
|
1619
|
+
if (manualDepositOverride) {
|
|
1620
|
+
this.clearManualDepositOverride(tempOrder);
|
|
1621
|
+
}
|
|
1622
|
+
if (historicalDepositSnapshot) {
|
|
1623
|
+
summary = this.applyDepositAmountToSummary(tempOrder, summary, historicalDepositSnapshot.depositAmount);
|
|
1624
|
+
tempOrder.is_deposit = historicalDepositSnapshot.isDeposit;
|
|
1625
|
+
}
|
|
1612
1626
|
}
|
|
1613
1627
|
const isPayableAmountUnchanged = this.isSnapshotPayableAmountUnchanged(tempOrder, snapshot, summary);
|
|
1614
1628
|
if (!isPayableAmountUnchanged) return null;
|
|
@@ -1625,8 +1639,8 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
1625
1639
|
tempOrder.surcharges = (0, _lodashEs.cloneDeep)(snapshot.surcharges);
|
|
1626
1640
|
}
|
|
1627
1641
|
tempOrder.surcharge_fee = summary.surcharge_fee || tempOrder.surcharge_fee;
|
|
1628
|
-
tempOrder.deposit_amount = summary.deposit_amount ||
|
|
1629
|
-
tempOrder.is_deposit =
|
|
1642
|
+
tempOrder.deposit_amount = this.normalizeDepositAmount(summary.deposit_amount || 0);
|
|
1643
|
+
tempOrder.is_deposit = shouldUseManualDepositOverride ? new _decimal.default(tempOrder.deposit_amount).gt(0) ? 1 : 0 : historicalDepositSnapshot?.isDeposit ?? (new _decimal.default(tempOrder.deposit_amount).gt(0) ? 1 : 0);
|
|
1630
1644
|
const previousSummary = this.store.summary;
|
|
1631
1645
|
this.store.summary = summary;
|
|
1632
1646
|
this.updateTempOrderPaymentStatus(tempOrder, {
|
|
@@ -2083,7 +2097,7 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
2083
2097
|
const isExistingDepositDisabled = tempOrder.is_deposit === 0 && new _decimal.default(tempOrder.deposit_amount || 0).gt(0);
|
|
2084
2098
|
tempOrder.deposit_amount = depositAmount;
|
|
2085
2099
|
tempOrder.is_deposit = hasDepositAmount && !isExistingDepositDisabled ? 1 : 0;
|
|
2086
|
-
return this.applyLastOrderDepositSnapshot(tempOrder, nextSummary);
|
|
2100
|
+
return shouldUseManualDepositOverride ? nextSummary : this.applyLastOrderDepositSnapshot(tempOrder, nextSummary);
|
|
2087
2101
|
}
|
|
2088
2102
|
async recalculateSummary(options) {
|
|
2089
2103
|
const tempOrder = options?.createIfMissing ? this.ensureTempOrder() : this.store.tempOrder;
|
|
@@ -4469,6 +4483,7 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
4469
4483
|
externalSaleNumber: nextTempOrder.external_sale_number
|
|
4470
4484
|
};
|
|
4471
4485
|
const rawSummary = raw.summary && typeof raw.summary === 'object' ? raw.summary : {};
|
|
4486
|
+
const historicalDepositSnapshot = this.getHistoricalDepositSnapshot(sourceLastOrderInfo);
|
|
4472
4487
|
const summarySurcharges = Array.isArray(rawSummary.surcharges) ? rawSummary.surcharges.map(s => ({
|
|
4473
4488
|
...(s || {})
|
|
4474
4489
|
})) : (0, _lodashEs.cloneDeep)(nextTempOrder.surcharges || []);
|
|
@@ -4487,6 +4502,10 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
4487
4502
|
surcharges: (0, _lodashEs.cloneDeep)(nextTempOrder.surcharges || []),
|
|
4488
4503
|
shop_discount: nextTempOrder.shop_discount || '0.00',
|
|
4489
4504
|
productFingerprint: this.buildOrderProductsFingerprint(nextTempOrder.products || []),
|
|
4505
|
+
...(historicalDepositSnapshot ? {
|
|
4506
|
+
is_deposit: historicalDepositSnapshot.isDeposit,
|
|
4507
|
+
deposit_amount: historicalDepositSnapshot.depositAmount
|
|
4508
|
+
} : {}),
|
|
4490
4509
|
// merged record 的 summary 仍是远端旧值,保留快照仅用于完整重算的价格清理基线。
|
|
4491
4510
|
...(isMergedSalesDetailHydrate ? {
|
|
4492
4511
|
requiresFullSummaryRecalculation: true
|
|
@@ -133,7 +133,9 @@ class SalesSummaryModule extends _BaseModule.BaseModule {
|
|
|
133
133
|
await this.getSurchargeList(summaryChannel);
|
|
134
134
|
}
|
|
135
135
|
const taxConfig = this.getTaxConfig();
|
|
136
|
-
const
|
|
136
|
+
const fatherModuleName = this.otherParams?.fatherModule || this.name.replace(/_salesSummary$/, '');
|
|
137
|
+
const scheduleModuleName = `${fatherModuleName}_schedule`;
|
|
138
|
+
const summarySchedule = this.core.getModule(scheduleModuleName);
|
|
137
139
|
const needScheduleIds = (this.store.surchargeList?.map(item => item.available_schedule_ids)?.flat() || []).filter(id => id !== undefined && id !== null && id !== '');
|
|
138
140
|
const scheduleList = needScheduleIds.length > 0 ? summarySchedule?.getScheduleListByIds(needScheduleIds) : [];
|
|
139
141
|
const scheduleById = {};
|
package/lib/server/index.js
CHANGED
|
@@ -3464,26 +3464,28 @@ class Server {
|
|
|
3464
3464
|
if (!Array.isArray(bookings)) return '';
|
|
3465
3465
|
const values = [];
|
|
3466
3466
|
const seen = new Set();
|
|
3467
|
-
const visitResource = resource => {
|
|
3468
|
-
const identifier = this.getCheckoutResourceIdentifier(resource);
|
|
3467
|
+
const visitResource = (resource, fallbackName) => {
|
|
3468
|
+
const identifier = this.getCheckoutResourceIdentifier(resource, fallbackName);
|
|
3469
3469
|
if (identifier && !seen.has(identifier)) {
|
|
3470
3470
|
seen.add(identifier);
|
|
3471
3471
|
values.push(identifier);
|
|
3472
3472
|
}
|
|
3473
3473
|
if (Array.isArray(resource?.children)) {
|
|
3474
|
-
resource.children.forEach(visitResource);
|
|
3474
|
+
resource.children.forEach(child => visitResource(child));
|
|
3475
3475
|
}
|
|
3476
3476
|
};
|
|
3477
3477
|
bookings.forEach(booking => {
|
|
3478
3478
|
if (Array.isArray(booking?.resources)) {
|
|
3479
|
-
booking.resources.forEach(
|
|
3479
|
+
booking.resources.forEach((resource, index) => {
|
|
3480
|
+
visitResource(resource, index === 0 ? booking?.relation_form_name : undefined);
|
|
3481
|
+
});
|
|
3480
3482
|
}
|
|
3481
3483
|
});
|
|
3482
3484
|
return values.join(',');
|
|
3483
3485
|
}
|
|
3484
|
-
getCheckoutResourceIdentifier(resource) {
|
|
3486
|
+
getCheckoutResourceIdentifier(resource, fallbackName) {
|
|
3485
3487
|
if (!resource || typeof resource !== 'object') return '';
|
|
3486
|
-
const candidates = [resource.main_field, resource.name, resource.title, resource.metadata?.resource_name, resource.metadata?.name, resource.relation_id, resource.id];
|
|
3488
|
+
const candidates = [resource.main_field, resource.name, resource.title, resource.metadata?.resource_name, resource.metadata?.name, fallbackName, resource.relation_id, resource.id];
|
|
3487
3489
|
for (const candidate of candidates) {
|
|
3488
3490
|
const value = this.stringifyCheckoutResourceIdentifier(candidate);
|
|
3489
3491
|
if (value) return value;
|
|
@@ -5740,6 +5742,8 @@ class Server {
|
|
|
5740
5742
|
schedule_datetime: context.schedule_datetime,
|
|
5741
5743
|
customer_id: context.customer_id,
|
|
5742
5744
|
strategy_context: context.strategy_context,
|
|
5745
|
+
// 固定本次商品查询使用的语言,确保格式化与缓存使用同一份 locale 快照。
|
|
5746
|
+
locale: this.core.context?.locale,
|
|
5743
5747
|
menuList: this.menu?.getMenuList?.() || [],
|
|
5744
5748
|
scheduleList: scheduleModule?.getScheduleList?.() || [],
|
|
5745
5749
|
dataVariantEvaluator: this.core.context?.dataVariantEvaluator
|
|
@@ -89,8 +89,8 @@ export declare class ProductsModule extends BaseModule implements Module {
|
|
|
89
89
|
private buildBundleGroupPriceData;
|
|
90
90
|
/**
|
|
91
91
|
* 获取应用了价格的商品列表(带缓存)
|
|
92
|
-
*
|
|
93
|
-
*
|
|
92
|
+
* 使用时间、客户、策略、商品范围和语言作为缓存 key
|
|
93
|
+
* 缓存的是已经应用了价格与多语言的完整商品列表,避免重复转换
|
|
94
94
|
* @param schedule_date 日期
|
|
95
95
|
* @param extraContext 额外的上下文数据(可选,由 Server 层传入)
|
|
96
96
|
* @param options 可选参数
|
|
@@ -30,7 +30,7 @@ class ProductsModule extends _BaseModule.BaseModule {
|
|
|
30
30
|
logger; // LoggerManager 实例
|
|
31
31
|
otherParams = {};
|
|
32
32
|
|
|
33
|
-
//
|
|
33
|
+
// 商品格式化缓存:Map<查询上下文 key, 已应用价格与多语言的商品列表>
|
|
34
34
|
productsPriceCache = new Map();
|
|
35
35
|
|
|
36
36
|
// 缓存配置
|
|
@@ -394,8 +394,8 @@ class ProductsModule extends _BaseModule.BaseModule {
|
|
|
394
394
|
|
|
395
395
|
/**
|
|
396
396
|
* 获取应用了价格的商品列表(带缓存)
|
|
397
|
-
*
|
|
398
|
-
*
|
|
397
|
+
* 使用时间、客户、策略、商品范围和语言作为缓存 key
|
|
398
|
+
* 缓存的是已经应用了价格与多语言的完整商品列表,避免重复转换
|
|
399
399
|
* @param schedule_date 日期
|
|
400
400
|
* @param extraContext 额外的上下文数据(可选,由 Server 层传入)
|
|
401
401
|
* @param options 可选参数
|
|
@@ -600,9 +600,10 @@ class ProductsModule extends _BaseModule.BaseModule {
|
|
|
600
600
|
}
|
|
601
601
|
getProductsPriceCacheKey(schedule_date, extraContext, productIds) {
|
|
602
602
|
const datetime = extraContext?.schedule_datetime || schedule_date;
|
|
603
|
+
const locale = extraContext?.locale ?? this.core?.context?.locale ?? '';
|
|
603
604
|
const productScope = productIds ? `:products:${productIds.join(',')}` : '';
|
|
604
605
|
const strategyScope = this.stringifyStable(extraContext?.strategy_context || {});
|
|
605
|
-
return `${datetime}:customer:${extraContext?.customer_id ?? ''}
|
|
606
|
+
return `${datetime}:locale:${locale}:customer:${extraContext?.customer_id ?? ''}` + `:strategy:${strategyScope}${productScope}`;
|
|
606
607
|
}
|
|
607
608
|
|
|
608
609
|
/**
|
|
@@ -11,7 +11,7 @@ export declare function perfMark(label: string, durationMs: number, meta?: Recor
|
|
|
11
11
|
export declare function applyPriceDataToProducts(products: ProductData[], priceData: LoadProductsPriceData[]): ProductData[];
|
|
12
12
|
export declare const getIsSessionProduct: (product: ProductData) => boolean;
|
|
13
13
|
/**
|
|
14
|
-
* 根据 locale
|
|
14
|
+
* 根据 locale 格式化商品、分类、规格及套餐文案,缺少对应语言时回退 original。
|
|
15
15
|
*/
|
|
16
16
|
export declare function applyI18nToProducts(products: ProductData[], locale?: string): ProductData[];
|
|
17
17
|
/**
|
|
@@ -194,9 +194,139 @@ const I18N_FIELD_MAP = {
|
|
|
194
194
|
description_i18n: 'description',
|
|
195
195
|
cover_i18n: 'cover'
|
|
196
196
|
};
|
|
197
|
+
/**
|
|
198
|
+
* 按当前语言读取 i18n 文案;当前语言为空时回退到商家原始文案。
|
|
199
|
+
*/
|
|
200
|
+
function resolveI18nValue(i18nDict, locale) {
|
|
201
|
+
if (!i18nDict) return undefined;
|
|
202
|
+
return i18nDict[locale] || i18nDict.original || undefined;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* 递归格式化商品分类名称,同时保留未变化节点的引用。
|
|
207
|
+
*/
|
|
208
|
+
function applyI18nToCategory(category, locale) {
|
|
209
|
+
if (!category || typeof category !== 'object') return category;
|
|
210
|
+
const localizedName = resolveI18nValue(category.name_i18n, locale);
|
|
211
|
+
const localizedParent = applyI18nToCategory(category.parent_category, locale);
|
|
212
|
+
const nameChanged = localizedName !== undefined && localizedName !== category.name;
|
|
213
|
+
const parentChanged = localizedParent !== category.parent_category;
|
|
214
|
+
if (!nameChanged && !parentChanged) return category;
|
|
215
|
+
return {
|
|
216
|
+
...category,
|
|
217
|
+
...(nameChanged ? {
|
|
218
|
+
name: localizedName
|
|
219
|
+
} : {}),
|
|
220
|
+
...(parentChanged ? {
|
|
221
|
+
parent_category: localizedParent
|
|
222
|
+
} : {})
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function applyI18nToCategories(categories, locale) {
|
|
226
|
+
if (!Array.isArray(categories)) return categories;
|
|
227
|
+
let changed = false;
|
|
228
|
+
const localizedCategories = categories.map(category => {
|
|
229
|
+
const localizedCategory = applyI18nToCategory(category, locale);
|
|
230
|
+
if (localizedCategory !== category) changed = true;
|
|
231
|
+
return localizedCategory;
|
|
232
|
+
});
|
|
233
|
+
return changed ? localizedCategories : categories;
|
|
234
|
+
}
|
|
235
|
+
function applyI18nToNamedItems(items, locale) {
|
|
236
|
+
if (!Array.isArray(items)) return items;
|
|
237
|
+
let changed = false;
|
|
238
|
+
const localizedItems = items.map(item => {
|
|
239
|
+
if (!item || typeof item !== 'object') return item;
|
|
240
|
+
const localizedName = resolveI18nValue(item.name_i18n, locale);
|
|
241
|
+
if (localizedName === undefined || localizedName === item.name) return item;
|
|
242
|
+
changed = true;
|
|
243
|
+
return {
|
|
244
|
+
...item,
|
|
245
|
+
name: localizedName
|
|
246
|
+
};
|
|
247
|
+
});
|
|
248
|
+
return changed ? localizedItems : items;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* 格式化选项组、规格组及其子项名称,同时保留未变化节点的引用。
|
|
253
|
+
*/
|
|
254
|
+
function applyI18nToNamedGroups(groups, itemKey, locale) {
|
|
255
|
+
if (!Array.isArray(groups)) return groups;
|
|
256
|
+
let changed = false;
|
|
257
|
+
const localizedGroups = groups.map(group => {
|
|
258
|
+
if (!group || typeof group !== 'object') return group;
|
|
259
|
+
const localizedName = resolveI18nValue(group.name_i18n, locale);
|
|
260
|
+
const localizedItems = applyI18nToNamedItems(group[itemKey], locale);
|
|
261
|
+
const nameChanged = localizedName !== undefined && localizedName !== group.name;
|
|
262
|
+
const itemsChanged = localizedItems !== group[itemKey];
|
|
263
|
+
if (!nameChanged && !itemsChanged) return group;
|
|
264
|
+
changed = true;
|
|
265
|
+
return {
|
|
266
|
+
...group,
|
|
267
|
+
...(nameChanged ? {
|
|
268
|
+
name: localizedName
|
|
269
|
+
} : {}),
|
|
270
|
+
...(itemsChanged ? {
|
|
271
|
+
[itemKey]: localizedItems
|
|
272
|
+
} : {})
|
|
273
|
+
};
|
|
274
|
+
});
|
|
275
|
+
return changed ? localizedGroups : groups;
|
|
276
|
+
}
|
|
277
|
+
function applyI18nToBundleItems(items, locale) {
|
|
278
|
+
if (!Array.isArray(items)) return items;
|
|
279
|
+
let changed = false;
|
|
280
|
+
const localizedItems = items.map(item => {
|
|
281
|
+
if (!item || typeof item !== 'object') return item;
|
|
282
|
+
const localizedTitle = resolveI18nValue(item.trans_title, locale);
|
|
283
|
+
const localizedOptionGroups = applyI18nToNamedGroups(item.option_group, 'option_item', locale);
|
|
284
|
+
const titleChanged = localizedTitle !== undefined && localizedTitle !== item.title;
|
|
285
|
+
const optionGroupsChanged = localizedOptionGroups !== item.option_group;
|
|
286
|
+
if (!titleChanged && !optionGroupsChanged) return item;
|
|
287
|
+
changed = true;
|
|
288
|
+
return {
|
|
289
|
+
...item,
|
|
290
|
+
...(titleChanged ? {
|
|
291
|
+
title: localizedTitle
|
|
292
|
+
} : {}),
|
|
293
|
+
...(optionGroupsChanged ? {
|
|
294
|
+
option_group: localizedOptionGroups
|
|
295
|
+
} : {})
|
|
296
|
+
};
|
|
297
|
+
});
|
|
298
|
+
return changed ? localizedItems : items;
|
|
299
|
+
}
|
|
197
300
|
|
|
198
301
|
/**
|
|
199
|
-
*
|
|
302
|
+
* 格式化套餐组名称、套餐子商品标题及子商品选项,同时保留未变化节点的引用。
|
|
303
|
+
*/
|
|
304
|
+
function applyI18nToBundleGroups(groups, locale) {
|
|
305
|
+
if (!Array.isArray(groups)) return groups;
|
|
306
|
+
let changed = false;
|
|
307
|
+
const localizedGroups = groups.map(group => {
|
|
308
|
+
if (!group || typeof group !== 'object') return group;
|
|
309
|
+
const localizedName = resolveI18nValue(group.name_i18n, locale);
|
|
310
|
+
const localizedItems = applyI18nToBundleItems(group.bundle_item, locale);
|
|
311
|
+
const nameChanged = localizedName !== undefined && localizedName !== group.name;
|
|
312
|
+
const itemsChanged = localizedItems !== group.bundle_item;
|
|
313
|
+
if (!nameChanged && !itemsChanged) return group;
|
|
314
|
+
changed = true;
|
|
315
|
+
return {
|
|
316
|
+
...group,
|
|
317
|
+
...(nameChanged ? {
|
|
318
|
+
name: localizedName
|
|
319
|
+
} : {}),
|
|
320
|
+
...(itemsChanged ? {
|
|
321
|
+
bundle_item: localizedItems
|
|
322
|
+
} : {})
|
|
323
|
+
};
|
|
324
|
+
});
|
|
325
|
+
return changed ? localizedGroups : groups;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* 根据 locale 格式化商品、分类、规格及套餐文案,缺少对应语言时回退 original。
|
|
200
330
|
*/
|
|
201
331
|
function applyI18nToProducts(products, locale) {
|
|
202
332
|
if (!locale) return products;
|
|
@@ -205,14 +335,44 @@ function applyI18nToProducts(products, locale) {
|
|
|
205
335
|
for (const [i18nKey, originalKey] of Object.entries(I18N_FIELD_MAP)) {
|
|
206
336
|
const i18nDict = product[i18nKey];
|
|
207
337
|
if (!i18nDict) continue;
|
|
208
|
-
const localizedValue = i18nDict
|
|
209
|
-
if (localizedValue) {
|
|
338
|
+
const localizedValue = resolveI18nValue(i18nDict, locale);
|
|
339
|
+
if (localizedValue !== undefined && localizedValue !== product[originalKey]) {
|
|
210
340
|
if (!patched) patched = {
|
|
211
341
|
...product
|
|
212
342
|
};
|
|
213
343
|
patched[originalKey] = localizedValue;
|
|
214
344
|
}
|
|
215
345
|
}
|
|
346
|
+
for (const categoryKey of ['category', 'categories']) {
|
|
347
|
+
const categories = product[categoryKey];
|
|
348
|
+
const localizedCategories = applyI18nToCategories(categories, locale);
|
|
349
|
+
if (localizedCategories !== categories) {
|
|
350
|
+
if (!patched) patched = {
|
|
351
|
+
...product
|
|
352
|
+
};
|
|
353
|
+
patched[categoryKey] = localizedCategories;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
for (const [groupKey, itemKey] of [['option_group', 'option_item'], ['variant_group', 'variant_item']]) {
|
|
357
|
+
const groups = product[groupKey];
|
|
358
|
+
const localizedGroups = applyI18nToNamedGroups(groups, itemKey, locale);
|
|
359
|
+
if (localizedGroups !== groups) {
|
|
360
|
+
if (!patched) patched = {
|
|
361
|
+
...product
|
|
362
|
+
};
|
|
363
|
+
patched[groupKey] = localizedGroups;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
for (const bundleGroupKey of ['bundle_group', 'bundle_groups']) {
|
|
367
|
+
const groups = product[bundleGroupKey];
|
|
368
|
+
const localizedGroups = applyI18nToBundleGroups(groups, locale);
|
|
369
|
+
if (localizedGroups !== groups) {
|
|
370
|
+
if (!patched) patched = {
|
|
371
|
+
...product
|
|
372
|
+
};
|
|
373
|
+
patched[bundleGroupKey] = localizedGroups;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
216
376
|
return patched ?? product;
|
|
217
377
|
});
|
|
218
378
|
}
|
|
@@ -35,6 +35,8 @@ export interface SmallTicketProduct {
|
|
|
35
35
|
original_price: string;
|
|
36
36
|
total_original_price: string;
|
|
37
37
|
extension_list: any[];
|
|
38
|
+
product_uid?: string | number;
|
|
39
|
+
booking_uid?: string | number;
|
|
38
40
|
options?: Array<{
|
|
39
41
|
name: string;
|
|
40
42
|
num?: number;
|
|
@@ -44,6 +46,11 @@ export interface SmallTicketProduct {
|
|
|
44
46
|
variant?: string;
|
|
45
47
|
combinations?: any[];
|
|
46
48
|
}
|
|
49
|
+
export interface SmallTicketBookingSnapshot extends Record<string, any> {
|
|
50
|
+
product_uid?: string | number;
|
|
51
|
+
booking_uid?: string | number;
|
|
52
|
+
}
|
|
53
|
+
export type SmallTicketAppointment = SmallTicketItem[] | SmallTicketBookingSnapshot[];
|
|
47
54
|
export interface SmallTicketLocaleData {
|
|
48
55
|
base_data: Record<string, any>;
|
|
49
56
|
products: SmallTicketProduct[];
|
|
@@ -52,7 +59,7 @@ export interface SmallTicketLocaleData {
|
|
|
52
59
|
countFee: Record<string, any>;
|
|
53
60
|
};
|
|
54
61
|
customer: SmallTicketItem[];
|
|
55
|
-
appointment:
|
|
62
|
+
appointment: SmallTicketAppointment;
|
|
56
63
|
delivery?: SmallTicketItem[];
|
|
57
64
|
resources: Array<{
|
|
58
65
|
id: string | number;
|
|
@@ -61,6 +68,7 @@ export interface SmallTicketLocaleData {
|
|
|
61
68
|
form_title: string;
|
|
62
69
|
}>;
|
|
63
70
|
payment_data: SmallTicketItem[][];
|
|
71
|
+
wallets: SmallTicketItem[][];
|
|
64
72
|
tickets: any[];
|
|
65
73
|
source_shop: any[];
|
|
66
74
|
refund_data: any[];
|