@pisell/pisellos 2.3.94 → 2.3.96
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/itemRule/adapter.d.ts +4 -1
- package/dist/model/strategy/adapter/itemRule/adapter.js +135 -35
- package/dist/model/strategy/adapter/itemRule/evaluator.d.ts +8 -1
- package/dist/model/strategy/adapter/itemRule/evaluator.js +25 -1
- package/dist/model/strategy/adapter/itemRule/type.d.ts +44 -0
- package/dist/model/strategy/adapter/itemRule/type.js +19 -1
- package/dist/model/strategy/adapter/promotion/index.js +9 -0
- package/dist/modules/Order/index.d.ts +20 -3
- package/dist/modules/Order/index.js +931 -508
- package/dist/modules/Order/salesNotes.d.ts +31 -0
- package/dist/modules/Order/salesNotes.js +492 -0
- package/dist/modules/Order/types.d.ts +91 -5
- package/dist/modules/Order/types.js +5 -0
- package/dist/modules/Order/utils.d.ts +12 -0
- package/dist/modules/Order/utils.js +75 -26
- package/dist/modules/ProductList/index.d.ts +2 -1
- package/dist/modules/ProductList/index.js +100 -20
- package/dist/modules/ProductList/types.d.ts +10 -0
- package/dist/modules/SalesSummary/index.js +6 -4
- package/dist/server/index.d.ts +6 -0
- package/dist/server/index.js +57 -11
- 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.js +23 -2
- package/dist/solution/BaseSales/index.d.ts +9 -1
- package/dist/solution/BaseSales/index.js +894 -706
- package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +3 -2
- package/dist/solution/BookingByStep/index.d.ts +2 -2
- package/dist/solution/BookingTicket/utils/orderCustomer.js +3 -2
- package/dist/solution/RegisterAndLogin/config.d.ts +9 -3
- package/dist/solution/RegisterAndLogin/config.js +95 -40
- package/dist/solution/RegisterAndLogin/index.js +4 -1
- package/dist/solution/ScanOrder/index.d.ts +1 -0
- package/dist/solution/ScanOrder/index.js +31 -27
- package/dist/solution/ScanOrder/utils.d.ts +1 -0
- package/dist/solution/ScanOrder/utils.js +2 -0
- package/dist/solution/UnifiedBookingSales/index.d.ts +15 -2
- package/dist/solution/UnifiedBookingSales/index.js +751 -483
- package/dist/solution/UnifiedBookingSales/types.d.ts +14 -1
- package/dist/solution/VenueBooking/index.d.ts +1 -0
- package/dist/solution/VenueBooking/index.js +8 -4
- package/lib/model/strategy/adapter/dataVariant/evaluator.js +12 -2
- package/lib/model/strategy/adapter/itemRule/adapter.d.ts +4 -1
- package/lib/model/strategy/adapter/itemRule/adapter.js +73 -4
- package/lib/model/strategy/adapter/itemRule/evaluator.d.ts +8 -1
- package/lib/model/strategy/adapter/itemRule/evaluator.js +23 -1
- package/lib/model/strategy/adapter/itemRule/type.d.ts +44 -0
- package/lib/model/strategy/adapter/itemRule/type.js +19 -1
- package/lib/modules/Order/index.d.ts +20 -3
- package/lib/modules/Order/index.js +358 -42
- package/lib/modules/Order/salesNotes.d.ts +31 -0
- package/lib/modules/Order/salesNotes.js +382 -0
- package/lib/modules/Order/types.d.ts +91 -5
- package/lib/modules/Order/types.js +4 -0
- package/lib/modules/Order/utils.d.ts +12 -0
- package/lib/modules/Order/utils.js +68 -16
- package/lib/modules/ProductList/index.d.ts +2 -1
- package/lib/modules/ProductList/index.js +47 -2
- package/lib/modules/ProductList/types.d.ts +10 -0
- package/lib/modules/SalesSummary/index.js +3 -1
- package/lib/server/index.d.ts +6 -0
- package/lib/server/index.js +47 -9
- 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.js +22 -1
- package/lib/solution/BaseSales/index.d.ts +9 -1
- package/lib/solution/BaseSales/index.js +105 -1
- package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +3 -2
- package/lib/solution/BookingByStep/index.d.ts +2 -2
- package/lib/solution/BookingTicket/utils/orderCustomer.js +2 -1
- package/lib/solution/RegisterAndLogin/config.d.ts +9 -3
- package/lib/solution/RegisterAndLogin/config.js +49 -8
- package/lib/solution/RegisterAndLogin/index.js +4 -1
- package/lib/solution/ScanOrder/index.d.ts +1 -0
- package/lib/solution/ScanOrder/index.js +5 -1
- package/lib/solution/ScanOrder/utils.d.ts +1 -0
- package/lib/solution/ScanOrder/utils.js +1 -0
- package/lib/solution/UnifiedBookingSales/index.d.ts +15 -2
- package/lib/solution/UnifiedBookingSales/index.js +125 -1
- package/lib/solution/UnifiedBookingSales/types.d.ts +14 -1
- package/lib/solution/VenueBooking/index.d.ts +1 -0
- package/lib/solution/VenueBooking/index.js +5 -1
- package/package.json +1 -1
|
@@ -101,6 +101,7 @@ Object.defineProperty(exports, "resolveSafeProductNum", {
|
|
|
101
101
|
}
|
|
102
102
|
});
|
|
103
103
|
exports.resolveSubmitCollectPax = resolveSubmitCollectPax;
|
|
104
|
+
exports.resolveSubmitOrderType = resolveSubmitOrderType;
|
|
104
105
|
Object.defineProperty(exports, "shouldBuildManualProductDiscount", {
|
|
105
106
|
enumerable: true,
|
|
106
107
|
get: function () {
|
|
@@ -125,6 +126,7 @@ var _decimal = _interopRequireDefault(require("decimal.js"));
|
|
|
125
126
|
var _utils = require("../Holder/utils");
|
|
126
127
|
var _utils2 = require("../../solution/ScanOrder/utils");
|
|
127
128
|
var _orderCollectionIdentity = require("./utils/orderCollectionIdentity");
|
|
129
|
+
var _salesNotes = require("./salesNotes");
|
|
128
130
|
var _manualProductDiscount = require("./utils/manualProductDiscount");
|
|
129
131
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
130
132
|
/**
|
|
@@ -1127,6 +1129,7 @@ function createDefaultTempOrder(params) {
|
|
|
1127
1129
|
request_unique_idempotency_token: '',
|
|
1128
1130
|
vouchers: [],
|
|
1129
1131
|
products: [],
|
|
1132
|
+
notes: [],
|
|
1130
1133
|
bookings: [],
|
|
1131
1134
|
payments: [],
|
|
1132
1135
|
surcharges: [],
|
|
@@ -1136,10 +1139,58 @@ function createDefaultTempOrder(params) {
|
|
|
1136
1139
|
holder: null,
|
|
1137
1140
|
metadata: {},
|
|
1138
1141
|
_extend: {
|
|
1139
|
-
productsByUid: {}
|
|
1142
|
+
productsByUid: {},
|
|
1143
|
+
salesNotesState: {
|
|
1144
|
+
loaded: true,
|
|
1145
|
+
dirty: false
|
|
1146
|
+
}
|
|
1140
1147
|
}
|
|
1141
1148
|
};
|
|
1142
1149
|
}
|
|
1150
|
+
function hasBoundBookingResource(bookings) {
|
|
1151
|
+
if (!Array.isArray(bookings)) return false;
|
|
1152
|
+
return bookings.some(booking => {
|
|
1153
|
+
if (!booking || typeof booking !== 'object') return false;
|
|
1154
|
+
if (Number(booking.parent_id) === 0) return false;
|
|
1155
|
+
const resources = booking.resources;
|
|
1156
|
+
if (!Array.isArray(resources)) return false;
|
|
1157
|
+
return resources.some(resource => {
|
|
1158
|
+
if (!resource || typeof resource !== 'object') return false;
|
|
1159
|
+
const relationId = String(resource.relation_id ?? '').trim();
|
|
1160
|
+
return relationId !== '' && relationId !== '0';
|
|
1161
|
+
});
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
function isPersistedSubmitOrderId(orderId) {
|
|
1165
|
+
if (orderId === undefined || orderId === null || orderId === '') return false;
|
|
1166
|
+
const normalizedOrderId = Number(orderId);
|
|
1167
|
+
return Number.isFinite(normalizedOrderId) && normalizedOrderId > 0;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
/**
|
|
1171
|
+
* 解析 checkout 的订单类型。
|
|
1172
|
+
* dine_in 普通落库单在新增桌台资源后,即使原类型仍是 virtual,也必须按 table-order 提交。
|
|
1173
|
+
*/
|
|
1174
|
+
function resolveSubmitOrderType(params) {
|
|
1175
|
+
const {
|
|
1176
|
+
requestedType,
|
|
1177
|
+
tempOrderType,
|
|
1178
|
+
orderId,
|
|
1179
|
+
businessCode,
|
|
1180
|
+
tempOrderBusinessCode,
|
|
1181
|
+
bookings
|
|
1182
|
+
} = params;
|
|
1183
|
+
const currentType = requestedType ?? tempOrderType;
|
|
1184
|
+
const resolvedBusinessCode = businessCode ?? tempOrderBusinessCode;
|
|
1185
|
+
if (isPersistedSubmitOrderId(orderId) && resolvedBusinessCode === 'dine_in' && currentType === 'virtual' && hasBoundBookingResource(bookings)) {
|
|
1186
|
+
return 'table-order';
|
|
1187
|
+
}
|
|
1188
|
+
if (currentType) return currentType;
|
|
1189
|
+
if (Array.isArray(bookings) && bookings.length > 0) {
|
|
1190
|
+
return businessCode === 'dine_in' ? 'table-order' : 'appointment_booking';
|
|
1191
|
+
}
|
|
1192
|
+
return 'virtual';
|
|
1193
|
+
}
|
|
1143
1194
|
function buildSubmitPayload(params) {
|
|
1144
1195
|
const {
|
|
1145
1196
|
tempOrder,
|
|
@@ -1173,22 +1224,18 @@ function buildSubmitPayload(params) {
|
|
|
1173
1224
|
total_refund_amount: _totalRefundAmount,
|
|
1174
1225
|
create_date: _createDate,
|
|
1175
1226
|
holder_id: _holderId,
|
|
1227
|
+
notes: _notes,
|
|
1176
1228
|
...tempOrderRest
|
|
1177
1229
|
} = tempOrder;
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
}
|
|
1188
|
-
} else {
|
|
1189
|
-
submitType = 'virtual';
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1230
|
+
const resolvedBusinessCode = businessCode ?? tempOrder.business_code;
|
|
1231
|
+
const submitType = resolveSubmitOrderType({
|
|
1232
|
+
requestedType: type,
|
|
1233
|
+
tempOrderType: tempOrder.type,
|
|
1234
|
+
orderId: tempOrder.order_id,
|
|
1235
|
+
businessCode,
|
|
1236
|
+
tempOrderBusinessCode: tempOrder.business_code,
|
|
1237
|
+
bookings: tempOrder.bookings
|
|
1238
|
+
});
|
|
1192
1239
|
const payload = {
|
|
1193
1240
|
...tempOrderRest,
|
|
1194
1241
|
customer_id: tempOrder.customer_id ?? 1,
|
|
@@ -1202,7 +1249,7 @@ function buildSubmitPayload(params) {
|
|
|
1202
1249
|
platform: normalizeSubmitPlatform(platform),
|
|
1203
1250
|
request_unique_idempotency_token: params?.request_unique_idempotency_token || tempOrder.request_unique_idempotency_token || `${tempOrder.external_sale_number}_${now.getTime()}`,
|
|
1204
1251
|
type: submitType,
|
|
1205
|
-
business_code:
|
|
1252
|
+
business_code: resolvedBusinessCode,
|
|
1206
1253
|
sales_channel: tempOrder.sales_channel || 'my_pisel',
|
|
1207
1254
|
order_sales_channel: channel ?? tempOrder.order_sales_channel ?? 'online_store',
|
|
1208
1255
|
status: tempOrder.status || 'normal',
|
|
@@ -1235,6 +1282,11 @@ function buildSubmitPayload(params) {
|
|
|
1235
1282
|
...rest
|
|
1236
1283
|
};
|
|
1237
1284
|
})(),
|
|
1285
|
+
...(tempOrder._extend?.salesNotesState?.dirty ? {
|
|
1286
|
+
notes: (0, _salesNotes.encodeSalesNotes)(tempOrder.notes || [], {
|
|
1287
|
+
allowUnresolvedOrderTarget: true
|
|
1288
|
+
})
|
|
1289
|
+
} : {}),
|
|
1238
1290
|
products: (tempOrder.products || []).map(product => normalizeSubmitProduct(product))
|
|
1239
1291
|
};
|
|
1240
1292
|
if (!enhance) return payload;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Module, PisellCore } from '../../types';
|
|
2
2
|
import { BaseModule } from '../BaseModule';
|
|
3
|
-
import { ProductListLoadProductsParams } from './types';
|
|
3
|
+
import { ProductListLoadProductsParams, ProductListStoreQueryParams } from './types';
|
|
4
4
|
import { ProductData } from '../Product/types';
|
|
5
5
|
export * from './types';
|
|
6
6
|
export declare class ProductList extends BaseModule implements Module {
|
|
@@ -52,6 +52,7 @@ export declare class ProductList extends BaseModule implements Module {
|
|
|
52
52
|
getRawProductPriceSources(ids: number[]): Map<number, ProductData>;
|
|
53
53
|
getProducts(): Promise<ProductData[]>;
|
|
54
54
|
getProduct(id: number): Promise<ProductData | undefined>;
|
|
55
|
+
queryProducts(params?: ProductListStoreQueryParams): Promise<ProductData[]>;
|
|
55
56
|
getProductByIds(ids: number[]): Promise<ProductData[] | undefined>;
|
|
56
57
|
addProduct(products: ProductData[]): Promise<void>;
|
|
57
58
|
selectProducts(products: ProductData[]): Promise<void>;
|
|
@@ -25,6 +25,15 @@ Object.keys(_types).forEach(function (key) {
|
|
|
25
25
|
});
|
|
26
26
|
});
|
|
27
27
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
28
|
+
function normalizeQuerySet(values) {
|
|
29
|
+
if (!Array.isArray(values) || values.length === 0) return null;
|
|
30
|
+
const normalized = values.map(value => String(value ?? '').trim()).filter(Boolean);
|
|
31
|
+
return normalized.length ? new Set(normalized) : null;
|
|
32
|
+
}
|
|
33
|
+
function matchesAny(values, querySet) {
|
|
34
|
+
if (!querySet) return true;
|
|
35
|
+
return values.some(value => querySet.has(String(value ?? '').trim()));
|
|
36
|
+
}
|
|
28
37
|
class ProductList extends _BaseModule.BaseModule {
|
|
29
38
|
defaultName = 'productList';
|
|
30
39
|
defaultVersion = '1.0.0';
|
|
@@ -281,10 +290,46 @@ class ProductList extends _BaseModule.BaseModule {
|
|
|
281
290
|
const product = this.store.list.find(product => product.id === id);
|
|
282
291
|
return product ? (0, _lodashEs.cloneDeep)(product) : undefined;
|
|
283
292
|
}
|
|
293
|
+
async queryProducts(params = {}) {
|
|
294
|
+
const includeIds = normalizeQuerySet(params.includeIds);
|
|
295
|
+
const excludeIds = normalizeQuerySet(params.excludeIds);
|
|
296
|
+
const categoryIds = normalizeQuerySet(params.categoryIds);
|
|
297
|
+
const collectionIds = normalizeQuerySet(params.collectionIds);
|
|
298
|
+
const extensionTypes = normalizeQuerySet(params.extensionTypes);
|
|
299
|
+
const statuses = normalizeQuerySet(params.statuses);
|
|
300
|
+
const channelApplications = normalizeQuerySet(params.channelApplications);
|
|
301
|
+
const codeOrBarcode = typeof params.codeOrBarcode === 'string' ? params.codeOrBarcode.trim() : '';
|
|
302
|
+
const products = (this.store.list || []).filter(product => {
|
|
303
|
+
const productId = String(product.id ?? '').trim();
|
|
304
|
+
if (includeIds && !includeIds.has(productId)) return false;
|
|
305
|
+
if (excludeIds && excludeIds.has(productId)) return false;
|
|
306
|
+
if (categoryIds) {
|
|
307
|
+
const productCategoryIds = (product.categories || []).flatMap(category => [category?.id, category?.pivot?.product_category_id]);
|
|
308
|
+
if (!matchesAny(productCategoryIds, categoryIds)) return false;
|
|
309
|
+
}
|
|
310
|
+
if (collectionIds) {
|
|
311
|
+
const productCollectionIds = (product.collection || []).map(collection => collection?.id);
|
|
312
|
+
if (!matchesAny(productCollectionIds, collectionIds)) return false;
|
|
313
|
+
}
|
|
314
|
+
if (extensionTypes && !extensionTypes.has(String(product.extension_type ?? '').trim())) return false;
|
|
315
|
+
if (statuses && !statuses.has(String(product.status ?? '').trim())) return false;
|
|
316
|
+
if (channelApplications) {
|
|
317
|
+
if (!matchesAny(product.channel_application || [], channelApplications)) return false;
|
|
318
|
+
}
|
|
319
|
+
if (codeOrBarcode) {
|
|
320
|
+
const code = typeof product.code === 'string' ? product.code.trim() : '';
|
|
321
|
+
const barcode = typeof product.barcode === 'string' ? product.barcode.trim() : '';
|
|
322
|
+
if (code !== codeOrBarcode && barcode !== codeOrBarcode) return false;
|
|
323
|
+
}
|
|
324
|
+
return true;
|
|
325
|
+
});
|
|
326
|
+
return (0, _lodashEs.cloneDeep)(products);
|
|
327
|
+
}
|
|
284
328
|
async getProductByIds(ids) {
|
|
285
329
|
await this.core.effects.emit(`${this.name}:onGetProductByIds`, this.store.list);
|
|
286
|
-
|
|
287
|
-
|
|
330
|
+
return this.queryProducts({
|
|
331
|
+
includeIds: ids
|
|
332
|
+
});
|
|
288
333
|
}
|
|
289
334
|
async addProduct(products) {
|
|
290
335
|
// list 需要根据 id 去重
|
|
@@ -15,6 +15,16 @@ export interface ProductListLoadProductsParams {
|
|
|
15
15
|
/** 智能定价条件上下文,仅影响策略命中,不参与商品集合筛选 */
|
|
16
16
|
strategy_context?: Record<string, any>;
|
|
17
17
|
}
|
|
18
|
+
export interface ProductListStoreQueryParams {
|
|
19
|
+
includeIds?: Array<number | string>;
|
|
20
|
+
excludeIds?: Array<number | string>;
|
|
21
|
+
categoryIds?: Array<number | string>;
|
|
22
|
+
collectionIds?: Array<number | string>;
|
|
23
|
+
extensionTypes?: string[];
|
|
24
|
+
statuses?: ProductData['status'][];
|
|
25
|
+
channelApplications?: string[];
|
|
26
|
+
codeOrBarcode?: string;
|
|
27
|
+
}
|
|
18
28
|
export interface ProductListData {
|
|
19
29
|
list: ProductData[];
|
|
20
30
|
selectProducts: ProductData[];
|
|
@@ -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.d.ts
CHANGED
|
@@ -520,6 +520,12 @@ declare class Server {
|
|
|
520
520
|
private stringifyCheckoutResourceIdentifier;
|
|
521
521
|
private normalizeCheckoutSubmitData;
|
|
522
522
|
private ensureCheckoutOrderNumbers;
|
|
523
|
+
/**
|
|
524
|
+
* Notes 允许在本地草稿阶段暂存空订单目标;checkout 的统一出口负责用最终
|
|
525
|
+
* shop_order_number 完成绑定并校验,保证后端请求、pending、设备任务和打印
|
|
526
|
+
* 都使用同一份完整快照。
|
|
527
|
+
*/
|
|
528
|
+
private finalizeCheckoutSalesNotes;
|
|
523
529
|
private dispatchCheckoutSyncTask;
|
|
524
530
|
private dispatchPrintOtherReceiptTask;
|
|
525
531
|
private buildPrintOtherReceiptIdempotencyKey;
|
package/lib/server/index.js
CHANGED
|
@@ -27,6 +27,7 @@ var _filterBookings = require("./modules/order/utils/filterBookings");
|
|
|
27
27
|
var _smallTicket = require("./utils/small-ticket");
|
|
28
28
|
var _BaseSales = require("../solution/BaseSales");
|
|
29
29
|
var _paymentUtils = require("../modules/Order/payment-utils");
|
|
30
|
+
var _salesNotes = require("../modules/Order/salesNotes");
|
|
30
31
|
var _modules = require("./modules");
|
|
31
32
|
Object.keys(_modules).forEach(function (key) {
|
|
32
33
|
if (key === "default" || key === "__esModule") return;
|
|
@@ -3463,26 +3464,28 @@ class Server {
|
|
|
3463
3464
|
if (!Array.isArray(bookings)) return '';
|
|
3464
3465
|
const values = [];
|
|
3465
3466
|
const seen = new Set();
|
|
3466
|
-
const visitResource = resource => {
|
|
3467
|
-
const identifier = this.getCheckoutResourceIdentifier(resource);
|
|
3467
|
+
const visitResource = (resource, fallbackName) => {
|
|
3468
|
+
const identifier = this.getCheckoutResourceIdentifier(resource, fallbackName);
|
|
3468
3469
|
if (identifier && !seen.has(identifier)) {
|
|
3469
3470
|
seen.add(identifier);
|
|
3470
3471
|
values.push(identifier);
|
|
3471
3472
|
}
|
|
3472
3473
|
if (Array.isArray(resource?.children)) {
|
|
3473
|
-
resource.children.forEach(visitResource);
|
|
3474
|
+
resource.children.forEach(child => visitResource(child));
|
|
3474
3475
|
}
|
|
3475
3476
|
};
|
|
3476
3477
|
bookings.forEach(booking => {
|
|
3477
3478
|
if (Array.isArray(booking?.resources)) {
|
|
3478
|
-
booking.resources.forEach(
|
|
3479
|
+
booking.resources.forEach((resource, index) => {
|
|
3480
|
+
visitResource(resource, index === 0 ? booking?.relation_form_name : undefined);
|
|
3481
|
+
});
|
|
3479
3482
|
}
|
|
3480
3483
|
});
|
|
3481
3484
|
return values.join(',');
|
|
3482
3485
|
}
|
|
3483
|
-
getCheckoutResourceIdentifier(resource) {
|
|
3486
|
+
getCheckoutResourceIdentifier(resource, fallbackName) {
|
|
3484
3487
|
if (!resource || typeof resource !== 'object') return '';
|
|
3485
|
-
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];
|
|
3486
3489
|
for (const candidate of candidates) {
|
|
3487
3490
|
const value = this.stringifyCheckoutResourceIdentifier(candidate);
|
|
3488
3491
|
if (value) return value;
|
|
@@ -3585,14 +3588,16 @@ class Server {
|
|
|
3585
3588
|
};
|
|
3586
3589
|
const needsShopOrderNumber = next.shop_order_number === undefined || next.shop_order_number === null || next.shop_order_number === '';
|
|
3587
3590
|
const needsShopFullOrderNumber = next.shop_full_order_number === undefined || next.shop_full_order_number === null || next.shop_full_order_number === '';
|
|
3588
|
-
if (!needsShopOrderNumber && !needsShopFullOrderNumber)
|
|
3591
|
+
if (!needsShopOrderNumber && !needsShopFullOrderNumber) {
|
|
3592
|
+
return this.finalizeCheckoutSalesNotes(next, title);
|
|
3593
|
+
}
|
|
3589
3594
|
const idGenerator = this.getIdGeneratorPlugin();
|
|
3590
3595
|
if (!idGenerator) {
|
|
3591
3596
|
this.logWarning(`${title}: idGenerator 插件不可用,无法生成订单号`, {
|
|
3592
3597
|
needsShopOrderNumber,
|
|
3593
3598
|
needsShopFullOrderNumber
|
|
3594
3599
|
});
|
|
3595
|
-
return next;
|
|
3600
|
+
return this.finalizeCheckoutSalesNotes(next, title);
|
|
3596
3601
|
}
|
|
3597
3602
|
try {
|
|
3598
3603
|
if (needsShopOrderNumber && idGenerator.generateShopOrderNumber) {
|
|
@@ -3614,7 +3619,38 @@ class Server {
|
|
|
3614
3619
|
error: error instanceof Error ? error.message : String(error)
|
|
3615
3620
|
});
|
|
3616
3621
|
}
|
|
3617
|
-
return next;
|
|
3622
|
+
return this.finalizeCheckoutSalesNotes(next, title);
|
|
3623
|
+
}
|
|
3624
|
+
|
|
3625
|
+
/**
|
|
3626
|
+
* Notes 允许在本地草稿阶段暂存空订单目标;checkout 的统一出口负责用最终
|
|
3627
|
+
* shop_order_number 完成绑定并校验,保证后端请求、pending、设备任务和打印
|
|
3628
|
+
* 都使用同一份完整快照。
|
|
3629
|
+
*/
|
|
3630
|
+
finalizeCheckoutSalesNotes(data, title) {
|
|
3631
|
+
if (!data || typeof data !== 'object') return data;
|
|
3632
|
+
if (!Object.prototype.hasOwnProperty.call(data, 'notes')) return data;
|
|
3633
|
+
if (data.notes === undefined || data.notes === null) return data;
|
|
3634
|
+
if (!Array.isArray(data.notes)) {
|
|
3635
|
+
throw new Error('[Order Notes] checkout notes 必须为数组');
|
|
3636
|
+
}
|
|
3637
|
+
if (data.notes.length === 0) return data;
|
|
3638
|
+
const productTargetUuids = (Array.isArray(data.products) ? data.products : []).map(product => product?.metadata?.unique_identification_number ?? product?.unique_identification_number).filter(targetUuid => targetUuid !== undefined && targetUuid !== null && String(targetUuid).trim().length > 0).map(targetUuid => String(targetUuid));
|
|
3639
|
+
const shopOrderNumber = String(data.shop_order_number ?? '').trim();
|
|
3640
|
+
const notes = (0, _salesNotes.finalizeSalesNotesForCheckout)({
|
|
3641
|
+
notes: data.notes,
|
|
3642
|
+
shopOrderNumber,
|
|
3643
|
+
productTargetUuids
|
|
3644
|
+
});
|
|
3645
|
+
this.logInfo(`${title}: checkout Notes 已绑定订单号`, {
|
|
3646
|
+
shop_order_number: shopOrderNumber,
|
|
3647
|
+
note_count: notes.length
|
|
3648
|
+
});
|
|
3649
|
+
return {
|
|
3650
|
+
...data,
|
|
3651
|
+
shop_order_number: shopOrderNumber,
|
|
3652
|
+
notes
|
|
3653
|
+
};
|
|
3618
3654
|
}
|
|
3619
3655
|
async dispatchCheckoutSyncTask(params) {
|
|
3620
3656
|
this.registerDeviceTaskActions();
|
|
@@ -5706,6 +5742,8 @@ class Server {
|
|
|
5706
5742
|
schedule_datetime: context.schedule_datetime,
|
|
5707
5743
|
customer_id: context.customer_id,
|
|
5708
5744
|
strategy_context: context.strategy_context,
|
|
5745
|
+
// 固定本次商品查询使用的语言,确保格式化与缓存使用同一份 locale 快照。
|
|
5746
|
+
locale: this.core.context?.locale,
|
|
5709
5747
|
menuList: this.menu?.getMenuList?.() || [],
|
|
5710
5748
|
scheduleList: scheduleModule?.getScheduleList?.() || [],
|
|
5711
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
|
}
|