@pisell/pisellos 2.2.315 → 2.2.316
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 +2 -2
- 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 +2 -1
- package/dist/modules/Order/utils.js +36 -10
- package/dist/modules/Rules/index.js +62 -61
- package/dist/server/index.js +36 -28
- 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.d.ts +28 -7
- package/dist/server/modules/products/index.js +1297 -770
- package/dist/server/modules/products/replication.d.ts +8 -0
- package/dist/server/modules/products/replication.js +69 -0
- package/dist/server/modules/products/types.d.ts +12 -1
- package/dist/server/modules/products/types.js +4 -0
- package/dist/shared/receipt/small-ticket.js +24 -12
- package/dist/shared/receipt/templateVisibility.d.ts +3 -0
- package/dist/shared/receipt/templateVisibility.js +6 -2
- 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/BookingByStep/index.d.ts +1 -1
- package/dist/solution/BookingTicket/index.d.ts +15 -1
- package/dist/solution/BookingTicket/index.js +784 -501
- package/dist/solution/ScanOrder/index.js +65 -31
- package/dist/solution/ScanOrder/types.d.ts +3 -0
- 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 +2 -2
- 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 +2 -1
- package/lib/modules/Order/utils.js +28 -6
- package/lib/modules/Rules/index.js +2 -1
- package/lib/server/index.js +8 -2
- 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.d.ts +28 -7
- package/lib/server/modules/products/index.js +226 -63
- package/lib/server/modules/products/replication.d.ts +8 -0
- package/lib/server/modules/products/replication.js +30 -0
- package/lib/server/modules/products/types.d.ts +12 -1
- package/lib/server/modules/products/types.js +3 -0
- package/lib/shared/receipt/small-ticket.js +22 -13
- package/lib/shared/receipt/templateVisibility.d.ts +3 -0
- package/lib/shared/receipt/templateVisibility.js +6 -2
- 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/BookingByStep/index.d.ts +1 -1
- package/lib/solution/BookingTicket/index.d.ts +15 -1
- package/lib/solution/BookingTicket/index.js +132 -3
- package/lib/solution/ScanOrder/index.js +59 -20
- package/lib/solution/ScanOrder/types.d.ts +3 -0
- package/lib/solution/UnifiedBookingSales/index.js +1 -1
- package/package.json +1 -1
|
@@ -36,7 +36,7 @@ import { attachItemRuleLimitsToTopLevelProducts, buildProductKey, buildItemRuleB
|
|
|
36
36
|
import { createModule } from "../BookingByStep/types";
|
|
37
37
|
import { ProductList } from "../../modules/ProductList";
|
|
38
38
|
import { getDateIsInSchedule } from "../../modules/Schedule/getDateIsInSchedule";
|
|
39
|
-
import { composeLinePrice, buildSubmitPayload, createUuidV4, getProductSkuOptions, normalizeSubmitCollectPaxValue, sumOptionUnitPrice } from "../../modules/Order/utils";
|
|
39
|
+
import { composeLinePrice, buildSubmitPayload, createUuidV4, getProductSkuOptions, normalizeSubmitBooking, normalizeSubmitCollectPaxValue, resolveTableOccupancyDuration, sumOptionUnitPrice } from "../../modules/Order/utils";
|
|
40
40
|
import dayjs from 'dayjs';
|
|
41
41
|
import { ItemRuleEvaluator } from "../../model/strategy/adapter/itemRule";
|
|
42
42
|
export * from "./types";
|
|
@@ -1332,6 +1332,7 @@ export var ScanOrderImpl = /*#__PURE__*/function (_BaseSalesImpl) {
|
|
|
1332
1332
|
}
|
|
1333
1333
|
return prepareRetailAvailability;
|
|
1334
1334
|
}() // ScanOrder 提交 payload enhancer:
|
|
1335
|
+
// - 新单有桌台上下文但无 booking 时,补建基础预约(通用 Order 已不再自动生成)
|
|
1335
1336
|
// - 给所有 booking 注入 appointment_status: 'started'(扫码点餐语义)
|
|
1336
1337
|
// - 给所有 booking 的 metadata 注入 resource_select_type(来自预约规则商品的 resource.type)
|
|
1337
1338
|
// - 给第一条 booking 补 resources 与 product_uid(仅当存在 resource / rule product)
|
|
@@ -1358,7 +1359,8 @@ export var ScanOrderImpl = /*#__PURE__*/function (_BaseSalesImpl) {
|
|
|
1358
1359
|
return function (payload, _ref3) {
|
|
1359
1360
|
var _resourceState$relati, _tempOrder$metadata, _ref4, _pickOriginal, _resourceState$table_2, _resourceState$relati2, _pickFirstCustomCapac;
|
|
1360
1361
|
var bookingUuid = _ref3.bookingUuid,
|
|
1361
|
-
tempOrder = _ref3.tempOrder
|
|
1362
|
+
tempOrder = _ref3.tempOrder,
|
|
1363
|
+
now = _ref3.now;
|
|
1362
1364
|
var resourceId = String((_resourceState$relati = resourceState === null || resourceState === void 0 ? void 0 : resourceState.relationId) !== null && _resourceState$relati !== void 0 ? _resourceState$relati : '').trim();
|
|
1363
1365
|
var hasReservationBookingContext = Boolean(ruleProduct && resourceState && resourceId && resourceState.relationId && resourceState.tableFormId);
|
|
1364
1366
|
if (!hasReservationBookingContext) {
|
|
@@ -1393,7 +1395,31 @@ export var ScanOrderImpl = /*#__PURE__*/function (_BaseSalesImpl) {
|
|
|
1393
1395
|
} : undefined;
|
|
1394
1396
|
var ruleProductUid = ruleProduct ? createUuidV4() : undefined;
|
|
1395
1397
|
var bookingCapacityDimensionId = (_pickFirstCustomCapac = pickFirstCustomCapacityDimensionId(_this5.enabledReservationRuleProducts)) !== null && _pickFirstCustomCapac !== void 0 ? _pickFirstCustomCapac : 0;
|
|
1396
|
-
var
|
|
1398
|
+
var bookings = payload.bookings || [];
|
|
1399
|
+
if (!tempOrder.order_id && bookings.length === 0) {
|
|
1400
|
+
var bookingStart = dayjs(now);
|
|
1401
|
+
var bookingDuration = resolveTableOccupancyDuration(tempOrder);
|
|
1402
|
+
var bookingEnd = bookingStart.add(bookingDuration, 'minute');
|
|
1403
|
+
bookings = [normalizeSubmitBooking({
|
|
1404
|
+
relation_id: 0,
|
|
1405
|
+
form_id: 0,
|
|
1406
|
+
start_time: bookingStart.format('HH:mm'),
|
|
1407
|
+
start_date: bookingStart.format('YYYY-MM-DD'),
|
|
1408
|
+
end_time: bookingEnd.format('HH:mm'),
|
|
1409
|
+
end_date: bookingEnd.format('YYYY-MM-DD'),
|
|
1410
|
+
duration: bookingDuration,
|
|
1411
|
+
metadata: {
|
|
1412
|
+
unique_identification_number: bookingUuid
|
|
1413
|
+
},
|
|
1414
|
+
select_date: bookingStart.format('YYYY-MM-DD'),
|
|
1415
|
+
is_all: false,
|
|
1416
|
+
like_status: 'common',
|
|
1417
|
+
schedule_id: 0,
|
|
1418
|
+
relation_type: '',
|
|
1419
|
+
number: 1
|
|
1420
|
+
})];
|
|
1421
|
+
}
|
|
1422
|
+
var nextBookings = bookings.map(function (booking, idx) {
|
|
1397
1423
|
var metadata = _objectSpread({}, booking.metadata || {});
|
|
1398
1424
|
if (hasExplicitCollectPax) {
|
|
1399
1425
|
metadata.collect_pax = normalizeSubmitCollectPaxValue(rawCollectPax);
|
|
@@ -2142,7 +2168,7 @@ export var ScanOrderImpl = /*#__PURE__*/function (_BaseSalesImpl) {
|
|
|
2142
2168
|
key: "applyPrefillByItemRule",
|
|
2143
2169
|
value: function () {
|
|
2144
2170
|
var _applyPrefillByItemRule = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee30() {
|
|
2145
|
-
var strategyConfigs, businessData, prefillItems, productSourceMap, tempOrder, hasChanges, _iterator6, _step6, _prefillItem$product_, _origin2, _origin3, _origin4, prefillItem, productId, productVariantId, targetQuantity, sourceItem, productIndex, _sourceItem$price, sellingPrice, targetProduct, existedQuantity, delta, nextProduct, hasQuantityChanged, hasOriginChanged;
|
|
2171
|
+
var strategyConfigs, businessData, prefillItems, productSourceMap, tempOrder, hasChanges, _iterator6, _step6, _prefillItem$product_, _origin2, _origin3, _origin4, prefillItem, productId, productVariantId, targetQuantity, sourceItem, sourceTitle, productTitle, displayFields, productIndex, _sourceItem$price, sellingPrice, targetProduct, existedQuantity, delta, nextProduct, hasQuantityChanged, hasTitleChanged, hasOriginChanged;
|
|
2146
2172
|
return _regeneratorRuntime().wrap(function _callee30$(_context30) {
|
|
2147
2173
|
while (1) switch (_context30.prev = _context30.next) {
|
|
2148
2174
|
case 0:
|
|
@@ -2192,7 +2218,7 @@ export var ScanOrderImpl = /*#__PURE__*/function (_BaseSalesImpl) {
|
|
|
2192
2218
|
_iterator6.s();
|
|
2193
2219
|
case 22:
|
|
2194
2220
|
if ((_step6 = _iterator6.n()).done) {
|
|
2195
|
-
_context30.next =
|
|
2221
|
+
_context30.next = 51;
|
|
2196
2222
|
break;
|
|
2197
2223
|
}
|
|
2198
2224
|
prefillItem = _step6.value;
|
|
@@ -2203,25 +2229,32 @@ export var ScanOrderImpl = /*#__PURE__*/function (_BaseSalesImpl) {
|
|
|
2203
2229
|
_context30.next = 29;
|
|
2204
2230
|
break;
|
|
2205
2231
|
}
|
|
2206
|
-
return _context30.abrupt("continue",
|
|
2232
|
+
return _context30.abrupt("continue", 49);
|
|
2207
2233
|
case 29:
|
|
2208
2234
|
if (!Number.isNaN(productVariantId)) {
|
|
2209
2235
|
_context30.next = 31;
|
|
2210
2236
|
break;
|
|
2211
2237
|
}
|
|
2212
|
-
return _context30.abrupt("continue",
|
|
2238
|
+
return _context30.abrupt("continue", 49);
|
|
2213
2239
|
case 31:
|
|
2214
2240
|
sourceItem = productSourceMap.get(buildProductKey(productId, productVariantId)) || productSourceMap.get(buildProductKey(productId, 0)) || null;
|
|
2241
|
+
sourceTitle = (sourceItem === null || sourceItem === void 0 ? void 0 : sourceItem.product_title) || (sourceItem === null || sourceItem === void 0 ? void 0 : sourceItem.title) || (sourceItem === null || sourceItem === void 0 ? void 0 : sourceItem.name);
|
|
2242
|
+
productTitle = typeof sourceTitle === 'string' ? {
|
|
2243
|
+
auto: sourceTitle
|
|
2244
|
+
} : sourceTitle; // 标准标题在归一化后补入,供购物车展示,并由 Order 在折扣重算时保留。
|
|
2245
|
+
displayFields = productTitle ? {
|
|
2246
|
+
product_title: productTitle
|
|
2247
|
+
} : {};
|
|
2215
2248
|
productIndex = getProductIdentityIndex(tempOrder.products, {
|
|
2216
2249
|
product_id: productId,
|
|
2217
2250
|
product_variant_id: productVariantId
|
|
2218
2251
|
});
|
|
2219
2252
|
if (!(productIndex === -1)) {
|
|
2220
|
-
_context30.next =
|
|
2253
|
+
_context30.next = 41;
|
|
2221
2254
|
break;
|
|
2222
2255
|
}
|
|
2223
2256
|
sellingPrice = toPriceString((_sourceItem$price = sourceItem === null || sourceItem === void 0 ? void 0 : sourceItem.price) !== null && _sourceItem$price !== void 0 ? _sourceItem$price : sourceItem === null || sourceItem === void 0 ? void 0 : sourceItem.selling_price, '0.00');
|
|
2224
|
-
tempOrder.products.push(normalizeOrderProduct(_objectSpread(_objectSpread({}, sourceItem), {}, {
|
|
2257
|
+
tempOrder.products.push(_objectSpread(_objectSpread({}, normalizeOrderProduct(_objectSpread(_objectSpread({}, sourceItem), {}, {
|
|
2225
2258
|
product_id: productId,
|
|
2226
2259
|
product_variant_id: productVariantId,
|
|
2227
2260
|
num: targetQuantity,
|
|
@@ -2235,14 +2268,14 @@ export var ScanOrderImpl = /*#__PURE__*/function (_BaseSalesImpl) {
|
|
|
2235
2268
|
item_rule_prefill: true,
|
|
2236
2269
|
item_rule_id: prefillItem.ruleId
|
|
2237
2270
|
})
|
|
2238
|
-
})));
|
|
2271
|
+
}))), displayFields));
|
|
2239
2272
|
hasChanges = true;
|
|
2240
|
-
return _context30.abrupt("continue",
|
|
2241
|
-
case
|
|
2273
|
+
return _context30.abrupt("continue", 49);
|
|
2274
|
+
case 41:
|
|
2242
2275
|
targetProduct = tempOrder.products[productIndex];
|
|
2243
2276
|
existedQuantity = toNonNegativeInt(targetProduct.num);
|
|
2244
2277
|
delta = targetQuantity - existedQuantity;
|
|
2245
|
-
nextProduct = _objectSpread(_objectSpread(_objectSpread({}, targetProduct), delta > 0 ? {
|
|
2278
|
+
nextProduct = _objectSpread(_objectSpread(_objectSpread(_objectSpread({}, targetProduct), displayFields), delta > 0 ? {
|
|
2246
2279
|
num: getSafeProductNum(existedQuantity + delta)
|
|
2247
2280
|
} : {}), {}, {
|
|
2248
2281
|
metadata: _objectSpread(_objectSpread({}, targetProduct.metadata || {}), {}, {
|
|
@@ -2252,52 +2285,53 @@ export var ScanOrderImpl = /*#__PURE__*/function (_BaseSalesImpl) {
|
|
|
2252
2285
|
_origin: sourceItem ? _objectSpread(_objectSpread({}, targetProduct._origin || {}), sourceItem) : targetProduct._origin
|
|
2253
2286
|
});
|
|
2254
2287
|
hasQuantityChanged = delta > 0;
|
|
2288
|
+
hasTitleChanged = Boolean(productTitle && JSON.stringify(targetProduct.product_title) !== JSON.stringify(productTitle));
|
|
2255
2289
|
hasOriginChanged = Boolean(sourceItem && (!targetProduct._origin || ((_origin2 = targetProduct._origin) === null || _origin2 === void 0 ? void 0 : _origin2.name) !== (sourceItem === null || sourceItem === void 0 ? void 0 : sourceItem.name) || ((_origin3 = targetProduct._origin) === null || _origin3 === void 0 ? void 0 : _origin3.title) !== (sourceItem === null || sourceItem === void 0 ? void 0 : sourceItem.title) || ((_origin4 = targetProduct._origin) === null || _origin4 === void 0 ? void 0 : _origin4.price) !== (sourceItem === null || sourceItem === void 0 ? void 0 : sourceItem.price)));
|
|
2256
|
-
if (hasQuantityChanged || hasOriginChanged) {
|
|
2290
|
+
if (hasQuantityChanged || hasTitleChanged || hasOriginChanged) {
|
|
2257
2291
|
tempOrder.products[productIndex] = nextProduct;
|
|
2258
2292
|
hasChanges = true;
|
|
2259
2293
|
}
|
|
2260
|
-
case
|
|
2294
|
+
case 49:
|
|
2261
2295
|
_context30.next = 22;
|
|
2262
2296
|
break;
|
|
2263
|
-
case
|
|
2264
|
-
_context30.next =
|
|
2297
|
+
case 51:
|
|
2298
|
+
_context30.next = 56;
|
|
2265
2299
|
break;
|
|
2266
|
-
case
|
|
2267
|
-
_context30.prev =
|
|
2300
|
+
case 53:
|
|
2301
|
+
_context30.prev = 53;
|
|
2268
2302
|
_context30.t0 = _context30["catch"](20);
|
|
2269
2303
|
_iterator6.e(_context30.t0);
|
|
2270
|
-
case
|
|
2271
|
-
_context30.prev =
|
|
2304
|
+
case 56:
|
|
2305
|
+
_context30.prev = 56;
|
|
2272
2306
|
_iterator6.f();
|
|
2273
|
-
return _context30.finish(
|
|
2274
|
-
case
|
|
2307
|
+
return _context30.finish(56);
|
|
2308
|
+
case 59:
|
|
2275
2309
|
if (!hasChanges) {
|
|
2276
|
-
_context30.next =
|
|
2310
|
+
_context30.next = 64;
|
|
2277
2311
|
break;
|
|
2278
2312
|
}
|
|
2279
2313
|
// Prefill 属于 products CRUD 的一种形式:新增后让 Rules 重算,以支持自动应用可用优惠券。
|
|
2280
2314
|
this.store.order.applyDiscount();
|
|
2281
|
-
_context30.next =
|
|
2315
|
+
_context30.next = 63;
|
|
2282
2316
|
return this.store.order.recalculateSummary({
|
|
2283
2317
|
createIfMissing: true
|
|
2284
2318
|
});
|
|
2285
|
-
case
|
|
2319
|
+
case 63:
|
|
2286
2320
|
this.store.order.persistTempOrder();
|
|
2287
|
-
case
|
|
2288
|
-
_context30.next =
|
|
2321
|
+
case 64:
|
|
2322
|
+
_context30.next = 66;
|
|
2289
2323
|
return this.refreshItemRuleQuantityLimits();
|
|
2290
|
-
case
|
|
2324
|
+
case 66:
|
|
2291
2325
|
this.itemRulePrefillApplied = true;
|
|
2292
2326
|
this.logMethodSuccess('applyPrefillByItemRule', {
|
|
2293
2327
|
prefillCount: prefillItems.length,
|
|
2294
2328
|
hasChanges: hasChanges
|
|
2295
2329
|
});
|
|
2296
|
-
case
|
|
2330
|
+
case 68:
|
|
2297
2331
|
case "end":
|
|
2298
2332
|
return _context30.stop();
|
|
2299
2333
|
}
|
|
2300
|
-
}, _callee30, this, [[20,
|
|
2334
|
+
}, _callee30, this, [[20, 53, 56, 59]]);
|
|
2301
2335
|
}));
|
|
2302
2336
|
function applyPrefillByItemRule() {
|
|
2303
2337
|
return _applyPrefillByItemRule.apply(this, arguments);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { OrderModule, ProductList, SalesSummaryModule, ScanOrderLogInput, ScanOrderLoggerModule, ScanOrderLoggerProviderConfig, ScanOrderLoggerProviderType, ScheduleModule } from '../../modules';
|
|
2
2
|
import type { QuantityCheckResult, QuantityLimitResult } from '../../model/strategy/adapter/itemRule';
|
|
3
|
+
import type { OrderLocalizedText } from '../../modules/Order/types';
|
|
3
4
|
/**
|
|
4
5
|
* 扫码下单流程 hook
|
|
5
6
|
*/
|
|
@@ -43,6 +44,8 @@ export interface ScanOrderProductSku {
|
|
|
43
44
|
}
|
|
44
45
|
export interface ScanOrderOrderProduct extends ScanOrderOrderProductIdentity {
|
|
45
46
|
order_detail_id: number | null;
|
|
47
|
+
/** 与 Order 一致的商品展示名称。 */
|
|
48
|
+
product_title?: OrderLocalizedText;
|
|
46
49
|
num: number;
|
|
47
50
|
/** Product 或当前 Variant 的生产编码 */
|
|
48
51
|
production_code?: string;
|
|
@@ -2421,7 +2421,7 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
|
|
|
2421
2421
|
(_this$store$customer = this.store.customer) === null || _this$store$customer === void 0 || (_this$store$customer$ = _this$store$customer.setSelectedCustomer) === null || _this$store$customer$ === void 0 || _this$store$customer$.call(_this$store$customer, authenticatedCustomer);
|
|
2422
2422
|
this.setOrderCustomer(authenticatedCustomer);
|
|
2423
2423
|
_context10.next = 15;
|
|
2424
|
-
return Promise.all([this.refreshDiscountConfigAfterOrderCustomerChange(authenticatedCustomerId), this.waitForOrderCustomerPromotionRefresh()]);
|
|
2424
|
+
return Promise.all([this.refreshDiscountConfigAfterOrderCustomerChange(authenticatedCustomerId), this.waitForOrderCustomerPromotionRefresh(), this.waitForOrderCustomerContactInfo()]);
|
|
2425
2425
|
case 15:
|
|
2426
2426
|
return _context10.abrupt("return", true);
|
|
2427
2427
|
case 16:
|
|
@@ -227,10 +227,13 @@ class PromotionEvaluator {
|
|
|
227
227
|
const productResults = this.evaluateProducts(input);
|
|
228
228
|
|
|
229
229
|
// 2. 收集所有适用的促销(去重)
|
|
230
|
+
// strategyId is metadata and is not guaranteed to be unique. Use the actual
|
|
231
|
+
// configuration as the key so one strategy cannot overwrite another strategy's
|
|
232
|
+
// label/action when their IDs happen to be the same.
|
|
230
233
|
const promotionMap = new Map();
|
|
231
234
|
for (const productResult of productResults) {
|
|
232
235
|
for (const promo of productResult.applicablePromotions) {
|
|
233
|
-
const existing = promotionMap.get(promo.
|
|
236
|
+
const existing = promotionMap.get(promo.strategyConfig);
|
|
234
237
|
const product = productResult.product;
|
|
235
238
|
|
|
236
239
|
// 计算促销数量:如果是 bundle 子商品匹配,数量 = 主商品数量 × 子商品数量
|
|
@@ -253,7 +256,7 @@ class PromotionEvaluator {
|
|
|
253
256
|
// 新促销
|
|
254
257
|
const productMatchedBundleIndexMap = new Map();
|
|
255
258
|
productMatchedBundleIndexMap.set(product.id, promo.matchedBundleIndex);
|
|
256
|
-
promotionMap.set(promo.
|
|
259
|
+
promotionMap.set(promo.strategyConfig, {
|
|
257
260
|
...promo,
|
|
258
261
|
applicableProducts: [product],
|
|
259
262
|
totalQuantity: promoQuantity,
|
|
@@ -305,7 +308,7 @@ class PromotionEvaluator {
|
|
|
305
308
|
const cartResult = this.evaluateCart(input);
|
|
306
309
|
|
|
307
310
|
// 2. 按 action priority 排序促销组(从 strategyConfig 获取 priority)
|
|
308
|
-
const sortedGroups = this.sortPromotionGroupsByPriority(cartResult.promotionGroups
|
|
311
|
+
const sortedGroups = this.sortPromotionGroupsByPriority(cartResult.promotionGroups);
|
|
309
312
|
|
|
310
313
|
// 3. 追踪已处理的商品(避免重复处理)
|
|
311
314
|
// key: `${product_id}_${product_variant_id}`
|
|
@@ -544,19 +547,11 @@ class PromotionEvaluator {
|
|
|
544
547
|
* 按优先级排序促销组
|
|
545
548
|
* 优先级从 strategyConfig.actions[0].priority 获取,数值越小优先级越高
|
|
546
549
|
*/
|
|
547
|
-
sortPromotionGroupsByPriority(groups
|
|
548
|
-
// 创建策略ID到优先级的映射
|
|
549
|
-
const priorityMap = new Map();
|
|
550
|
-
for (const promo of promotions) {
|
|
551
|
-
const actions = promo.strategyConfig?.actions || [];
|
|
552
|
-
const priority = actions[0]?.priority ?? 999;
|
|
553
|
-
priorityMap.set(promo.strategyId, priority);
|
|
554
|
-
}
|
|
555
|
-
|
|
550
|
+
sortPromotionGroupsByPriority(groups) {
|
|
556
551
|
// 按优先级排序(数值越小优先级越高)
|
|
557
552
|
return [...groups].sort((a, b) => {
|
|
558
|
-
const priorityA =
|
|
559
|
-
const priorityB =
|
|
553
|
+
const priorityA = a.strategyConfig?.actions?.[0]?.priority ?? 999;
|
|
554
|
+
const priorityB = b.strategyConfig?.actions?.[0]?.priority ?? 999;
|
|
560
555
|
return priorityA - priorityB;
|
|
561
556
|
});
|
|
562
557
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import type { OrderProduct } from '../../Order/types';
|
|
1
|
+
import type { OrderHolderRelation, OrderProduct } from '../../Order/types';
|
|
2
2
|
export interface BuildNormalProductCacheItemFromOrderLineInput {
|
|
3
3
|
/** tempOrder.products 中的一行(普通商品,无关联 booking) */
|
|
4
4
|
product: OrderProduct;
|
|
5
5
|
/** 商品 catalog / 详情,用于 variant / option / bundle 模板字段 */
|
|
6
6
|
sourceProduct?: Record<string, any> | null;
|
|
7
|
+
/** 整单关系集合,按商品行 UID 回填。 */
|
|
8
|
+
holder_relations?: OrderHolderRelation[];
|
|
7
9
|
}
|
|
8
10
|
export declare function buildNormalProductCacheItemFromOrderLine(input: BuildNormalProductCacheItemFromOrderLineInput): Record<string, any>;
|
|
@@ -4,13 +4,16 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.buildNormalProductCacheItemFromOrderLine = buildNormalProductCacheItemFromOrderLine;
|
|
7
|
+
var _lodashEs = require("lodash-es");
|
|
8
|
+
var _holderRelations = require("../../Order/holderRelations");
|
|
7
9
|
var _utils = require("../../Order/utils");
|
|
8
10
|
var _entitlementPass = require("../../Discount/entitlementPass");
|
|
11
|
+
var _utils2 = require("../../Holder/utils");
|
|
9
12
|
var _orderLineDisplay = require("./orderLineDisplay");
|
|
10
13
|
/**
|
|
11
14
|
* 从已加车的普通商品 order line 反查 SkuDetailModal edit 所需的 cacheItem。
|
|
12
15
|
*
|
|
13
|
-
* 与 `buildCacheItemFromOrderLine`(预约行 + booking
|
|
16
|
+
* 与 `buildCacheItemFromOrderLine`(预约行 + booking)并列;Holder 从订单关系回填,不生成预约字段。
|
|
14
17
|
*/
|
|
15
18
|
/**
|
|
16
19
|
* 解析 v2 主商品 source 价(不含 option、不含 bundle),供 SkuDetailModal / transform 使用。
|
|
@@ -84,6 +87,15 @@ function buildNormalProductCacheItemFromOrderLine(input) {
|
|
|
84
87
|
const original = Number(product.original_price ?? selling);
|
|
85
88
|
const uid = product.metadata?.unique_identification_number;
|
|
86
89
|
const originExtend = product.metadata?.origin?._extend;
|
|
90
|
+
const holderRelations = (0, _lodashEs.cloneDeep)((0, _holderRelations.getProductHolderRelations)(input, product));
|
|
91
|
+
const formRelations = holderRelations.filter(relation => relation.holder_type === 'form');
|
|
92
|
+
const holderIds = formRelations.map(relation => relation.holder_id);
|
|
93
|
+
const holderNames = formRelations.map(relation => relation.metadata?.name).filter(name => typeof name === 'string' && name.length > 0);
|
|
94
|
+
const holder = formRelations.length > 0 ? {
|
|
95
|
+
...(formRelations[0].metadata || {}),
|
|
96
|
+
form_record: holderIds,
|
|
97
|
+
name: holderNames.join(',')
|
|
98
|
+
} : null;
|
|
87
99
|
const shouldPreserveEntitlementPriceOverride = hasEntitlementPassDiscount(product) && (originExtend?.priceOverride !== undefined || product.metadata?.price_override != null);
|
|
88
100
|
const hasPriceOverride = originExtend?.priceOverride !== undefined || shouldPreserveEntitlementPriceOverride;
|
|
89
101
|
const option = (0, _utils.getProductSkuOptions)(product);
|
|
@@ -115,6 +127,9 @@ function buildNormalProductCacheItemFromOrderLine(input) {
|
|
|
115
127
|
skuValue: JSON.parse(JSON.stringify(skuValue)),
|
|
116
128
|
_skuValue: JSON.parse(JSON.stringify(skuValue)),
|
|
117
129
|
isNormalProduct: true,
|
|
130
|
+
holder_relations: holderRelations,
|
|
131
|
+
holder,
|
|
132
|
+
holder_id: holderIds,
|
|
118
133
|
metadata: {
|
|
119
134
|
product_add_schedule_time: product.metadata?.product_add_schedule_time
|
|
120
135
|
},
|
|
@@ -134,6 +149,7 @@ function buildNormalProductCacheItemFromOrderLine(input) {
|
|
|
134
149
|
}
|
|
135
150
|
return {
|
|
136
151
|
...(sourceProduct || {}),
|
|
152
|
+
holder_config: (0, _utils2.resolveProductHolderConfig)(sourceProduct) ?? (0, _utils2.resolveProductHolderConfig)(product) ?? undefined,
|
|
137
153
|
id: resolvedId,
|
|
138
154
|
product_id: resolvedId,
|
|
139
155
|
_id: uid,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { ProductData } from '../Product/types';
|
|
2
2
|
import { IFormItemData, IHolderConfig } from './types';
|
|
3
|
+
/** 保留显式禁用配置;仅在新配置缺失时兼容商品旧 holder 定义。 */
|
|
4
|
+
export declare function resolveProductHolderConfig(product?: ProductData | Record<string, any> | null): Record<string, any> | null;
|
|
3
5
|
export declare function getProductHolderConfig(product?: ProductData | Record<string, any> | null): IHolderConfig | null;
|
|
4
6
|
export declare function getFormIdFromHolderConfig(config: IHolderConfig): number;
|
|
5
7
|
export declare function normalizeFormId(formId: number | string): number;
|
|
@@ -8,9 +8,35 @@ exports.getFormIdFromHolderConfig = getFormIdFromHolderConfig;
|
|
|
8
8
|
exports.getProductHolderConfig = getProductHolderConfig;
|
|
9
9
|
exports.normalizeFormId = normalizeFormId;
|
|
10
10
|
exports.resolveFormMetaFromHolderItem = resolveFormMetaFromHolderItem;
|
|
11
|
+
exports.resolveProductHolderConfig = resolveProductHolderConfig;
|
|
12
|
+
/** 保留显式禁用配置;仅在新配置缺失时兼容商品旧 holder 定义。 */
|
|
13
|
+
function resolveProductHolderConfig(product) {
|
|
14
|
+
if (!product || typeof product !== 'object') return null;
|
|
15
|
+
const source = product;
|
|
16
|
+
const configSources = [source.holder_config, source.metadata?.holder_config, source.origin?.holder_config, source.metadata?.origin?.holder_config, source._origin?.holder_config, source._extend?.holder_config, source._extend?.product?.holder_config, source._extend?.product?.origin?.holder_config, source._extend?.product?.metadata?.holder_config, source._extend?.product?.metadata?.origin?.holder_config, source._extend?.product?._origin?.holder_config, source._extend?.product?._extend?.holder_config];
|
|
17
|
+
const config = configSources.find(value => value !== null && typeof value === 'object');
|
|
18
|
+
if (config) return config;
|
|
19
|
+
|
|
20
|
+
// metadata.holder / _extend.holder 是选择结果,不能作为商品配置读取。
|
|
21
|
+
const legacySources = [source.holder, source.origin?.holder, source.metadata?.origin?.holder, source._origin?.holder, source._extend?.product?.holder, source._extend?.product?.origin?.holder, source._extend?.product?.metadata?.origin?.holder, source._extend?.product?._origin?.holder];
|
|
22
|
+
const legacy = legacySources.find(value => value && typeof value === 'object' && !Array.isArray(value) && typeof value.type === 'string');
|
|
23
|
+
if (!legacy) return null;
|
|
24
|
+
const formId = legacy.custom?.custom_id;
|
|
25
|
+
if (legacy.type === 'custom' && legacy.custom?.type === 'form' && (typeof formId === 'number' || typeof formId === 'string') && Number.isInteger(Number(formId)) && Number(formId) > 0) {
|
|
26
|
+
return {
|
|
27
|
+
status: 'enable',
|
|
28
|
+
required: 0,
|
|
29
|
+
resource_id: formId
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
status: 'disable',
|
|
34
|
+
required: 0
|
|
35
|
+
};
|
|
36
|
+
}
|
|
11
37
|
function getProductHolderConfig(product) {
|
|
12
38
|
if (!product) return null;
|
|
13
|
-
const config = product
|
|
39
|
+
const config = resolveProductHolderConfig(product);
|
|
14
40
|
if (config?.status && config?.status !== 'enable') return null;
|
|
15
41
|
if (!config?.resource_id) return null;
|
|
16
42
|
return config;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { OpenDataConfig } from './types';
|
|
2
|
+
export declare const CONTACT_INFO_FIELDS: readonly ["first_name", "last_name", "phone", "email"];
|
|
3
|
+
export type ContactInfoField = typeof CONTACT_INFO_FIELDS[number];
|
|
4
|
+
export interface ContactInfoConfig {
|
|
5
|
+
enabled: boolean;
|
|
6
|
+
fields: Record<ContactInfoField, {
|
|
7
|
+
visible: boolean;
|
|
8
|
+
required: boolean;
|
|
9
|
+
}>;
|
|
10
|
+
}
|
|
11
|
+
export declare function resolveContactInfoConfig(data?: OpenDataConfig | null): ContactInfoConfig;
|
|
12
|
+
export declare function getMissingContactInfoFields(config: ContactInfoConfig, value: unknown): ContactInfoField[];
|
|
13
|
+
export declare function isContactRecord(value: unknown): value is Record<string, any>;
|
|
14
|
+
/** Customer relations wrap the contact in value; sales orders store the object itself. */
|
|
15
|
+
export declare function extractCustomerContactInfo(customer: unknown): Record<string, any> | null;
|
|
16
|
+
export declare class ContactInfoRequiredError extends Error {
|
|
17
|
+
readonly fields: ContactInfoField[];
|
|
18
|
+
readonly code = "CONTACT_INFO_REQUIRED";
|
|
19
|
+
constructor(fields: ContactInfoField[]);
|
|
20
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.ContactInfoRequiredError = exports.CONTACT_INFO_FIELDS = void 0;
|
|
7
|
+
exports.extractCustomerContactInfo = extractCustomerContactInfo;
|
|
8
|
+
exports.getMissingContactInfoFields = getMissingContactInfoFields;
|
|
9
|
+
exports.isContactRecord = isContactRecord;
|
|
10
|
+
exports.resolveContactInfoConfig = resolveContactInfoConfig;
|
|
11
|
+
const CONTACT_INFO_FIELDS = exports.CONTACT_INFO_FIELDS = ['first_name', 'last_name', 'phone', 'email'];
|
|
12
|
+
function resolveContactInfoConfig(data) {
|
|
13
|
+
const enabled = data?.['sale.enable_contact_info'] === true;
|
|
14
|
+
const fields = CONTACT_INFO_FIELDS.reduce((result, field) => {
|
|
15
|
+
const visible = data?.[`sale.${field}.visible`] !== false;
|
|
16
|
+
result[field] = {
|
|
17
|
+
visible,
|
|
18
|
+
required: enabled && visible && data?.[`sale.${field}.required`] === true
|
|
19
|
+
};
|
|
20
|
+
return result;
|
|
21
|
+
}, {});
|
|
22
|
+
return {
|
|
23
|
+
enabled: enabled && CONTACT_INFO_FIELDS.some(field => fields[field].visible),
|
|
24
|
+
fields
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function getMissingContactInfoFields(config, value) {
|
|
28
|
+
if (!config.enabled) return [];
|
|
29
|
+
const contact = isContactRecord(value) ? value : {};
|
|
30
|
+
return CONTACT_INFO_FIELDS.filter(field => {
|
|
31
|
+
if (!config.fields[field].visible || !config.fields[field].required) return false;
|
|
32
|
+
const fieldValue = field === 'phone' ? isContactRecord(contact.phone) ? contact.phone.phone : undefined : contact[field];
|
|
33
|
+
return typeof fieldValue !== 'string' || !fieldValue.trim();
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function isContactRecord(value) {
|
|
37
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Customer relations wrap the contact in value; sales orders store the object itself. */
|
|
41
|
+
function extractCustomerContactInfo(customer) {
|
|
42
|
+
if (!isContactRecord(customer)) return null;
|
|
43
|
+
const contact = customer.contacts_info;
|
|
44
|
+
if (!isContactRecord(contact)) return null;
|
|
45
|
+
const value = Object.prototype.hasOwnProperty.call(contact, 'value') ? contact.value : contact;
|
|
46
|
+
return isContactRecord(value) ? value : null;
|
|
47
|
+
}
|
|
48
|
+
class ContactInfoRequiredError extends Error {
|
|
49
|
+
code = 'CONTACT_INFO_REQUIRED';
|
|
50
|
+
constructor(fields) {
|
|
51
|
+
super(`Contact information is required: ${fields.join(', ')}`);
|
|
52
|
+
this.fields = fields;
|
|
53
|
+
this.name = 'ContactInfoRequiredError';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
exports.ContactInfoRequiredError = ContactInfoRequiredError;
|
|
@@ -3,6 +3,7 @@ import { BaseModule } from '../BaseModule';
|
|
|
3
3
|
import type { ScheduleModule } from '../Schedule';
|
|
4
4
|
import type { OpenDataConfig, OpenDataFetchParams, OpenDataAvailabilityResult, InvoiceLayoutConfig, InvoiceTemplateSettings } from './types';
|
|
5
5
|
export * from './types';
|
|
6
|
+
export * from './contactInfo';
|
|
6
7
|
export { computeAvailability, getDefaultInvoiceLayoutConfig, getDefaultInvoiceTemplateSettings, normalizeInvoiceLayoutConfig, normalizeInvoiceLayoutFromTemplateSettings, normalizeInvoiceTemplateSettings, normalizeRawInvoiceTemplateSettings, resolveInvoiceReceiptLocales, } from './utils';
|
|
7
8
|
export declare class OpenDataModule extends BaseModule implements Module {
|
|
8
9
|
protected defaultName: string;
|
|
@@ -77,6 +77,18 @@ Object.keys(_types).forEach(function (key) {
|
|
|
77
77
|
}
|
|
78
78
|
});
|
|
79
79
|
});
|
|
80
|
+
var _contactInfo = require("./contactInfo");
|
|
81
|
+
Object.keys(_contactInfo).forEach(function (key) {
|
|
82
|
+
if (key === "default" || key === "__esModule") return;
|
|
83
|
+
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
|
84
|
+
if (key in exports && exports[key] === _contactInfo[key]) return;
|
|
85
|
+
Object.defineProperty(exports, key, {
|
|
86
|
+
enumerable: true,
|
|
87
|
+
get: function () {
|
|
88
|
+
return _contactInfo[key];
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
});
|
|
80
92
|
function isRecord(value) {
|
|
81
93
|
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
82
94
|
}
|
|
@@ -69,6 +69,15 @@ export interface OpenDataConfig {
|
|
|
69
69
|
'reservation.time_slice'?: number | string;
|
|
70
70
|
'sale.allow_item_notes'?: boolean;
|
|
71
71
|
'sale.allow_order_notes'?: boolean;
|
|
72
|
+
'sale.enable_contact_info'?: boolean;
|
|
73
|
+
'sale.first_name.visible'?: boolean;
|
|
74
|
+
'sale.first_name.required'?: boolean;
|
|
75
|
+
'sale.last_name.visible'?: boolean;
|
|
76
|
+
'sale.last_name.required'?: boolean;
|
|
77
|
+
'sale.phone.visible'?: boolean;
|
|
78
|
+
'sale.phone.required'?: boolean;
|
|
79
|
+
'sale.email.visible'?: boolean;
|
|
80
|
+
'sale.email.required'?: boolean;
|
|
72
81
|
/** False submits an order without starting or confirming payment. */
|
|
73
82
|
'sale.require_payment_on_submit'?: boolean | null;
|
|
74
83
|
'sale.enable_item_rules'?: boolean;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { OrderHolderRelation, OrderTempOrder } from './types';
|
|
2
|
+
/** 在原订单上补齐关系身份,让缓存、提交和重试共用同一个 UUID。 */
|
|
3
|
+
export declare function ensureHolderRelationUuids(order: {
|
|
4
|
+
holder_relations?: OrderHolderRelation[];
|
|
5
|
+
}): void;
|
|
6
|
+
export declare function hasProductBooking(product: Record<string, any>): boolean;
|
|
7
|
+
export declare function getProductHolderRelations(order: {
|
|
8
|
+
holder_relations?: OrderHolderRelation[];
|
|
9
|
+
}, product: Record<string, any>): OrderHolderRelation[];
|
|
10
|
+
export declare function replaceProductHolderRelations(order: OrderTempOrder, product: Record<string, any>, patch: OrderHolderRelation[] | undefined): void;
|
|
11
|
+
/** 只删除明确被用户删除的行,不从稀疏远端快照推断删除。 */
|
|
12
|
+
export declare function removeProductHolderRelations(order: OrderTempOrder, products: Array<Record<string, any>>): void;
|
|
13
|
+
/** 仅兼容尚无新字段的旧测试草稿;显式 [] 永远不恢复旧 product 数据。 */
|
|
14
|
+
export declare function restoreLegacyHolderRelations(order: Record<string, any>): OrderHolderRelation[] | undefined;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.ensureHolderRelationUuids = ensureHolderRelationUuids;
|
|
7
|
+
exports.getProductHolderRelations = getProductHolderRelations;
|
|
8
|
+
exports.hasProductBooking = hasProductBooking;
|
|
9
|
+
exports.removeProductHolderRelations = removeProductHolderRelations;
|
|
10
|
+
exports.replaceProductHolderRelations = replaceProductHolderRelations;
|
|
11
|
+
exports.restoreLegacyHolderRelations = restoreLegacyHolderRelations;
|
|
12
|
+
var _orderCollectionIdentity = require("./utils/orderCollectionIdentity");
|
|
13
|
+
function getHolderRelationUuid(relation) {
|
|
14
|
+
return typeof relation?.uuid === 'string' && relation.uuid.trim() ? relation.uuid : undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** 在原订单上补齐关系身份,让缓存、提交和重试共用同一个 UUID。 */
|
|
18
|
+
function ensureHolderRelationUuids(order) {
|
|
19
|
+
if (!Array.isArray(order.holder_relations)) return;
|
|
20
|
+
for (const relation of order.holder_relations) {
|
|
21
|
+
if (relation && typeof relation === 'object' && !getHolderRelationUuid(relation)) {
|
|
22
|
+
relation.uuid = (0, _orderCollectionIdentity.createUuidV4)();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function hasProductBooking(product) {
|
|
27
|
+
const bookingIds = [product.booking_id, product.metadata?.booking_id, product.schedule_event_id];
|
|
28
|
+
return Boolean(product.booking_uid || product.metadata?.booking_uid) || bookingIds.some(id => id !== undefined && id !== null && String(id) !== '' && String(id) !== '0');
|
|
29
|
+
}
|
|
30
|
+
function getProductHolderRelations(order, product) {
|
|
31
|
+
const uid = product.metadata?.unique_identification_number ?? product.unique_identification_number;
|
|
32
|
+
if (uid === undefined || uid === null || uid === '') return [];
|
|
33
|
+
return (order.holder_relations || []).filter(relation => relation.relation_model === 'order_detail' && String(relation.relation_uuid) === String(uid));
|
|
34
|
+
}
|
|
35
|
+
function replaceProductHolderRelations(order, product, patch) {
|
|
36
|
+
if (patch === undefined || hasProductBooking(product)) return;
|
|
37
|
+
const uid = product.metadata?.unique_identification_number ?? product.unique_identification_number;
|
|
38
|
+
if (uid === undefined || uid === null || uid === '') return;
|
|
39
|
+
const existing = getProductHolderRelations(order, product);
|
|
40
|
+
const next = new Map();
|
|
41
|
+
for (const relation of patch) {
|
|
42
|
+
const key = `${relation.holder_type}:${relation.holder_id}`;
|
|
43
|
+
const previous = existing.find(item => item.holder_type === relation.holder_type && String(item.holder_id) === String(relation.holder_id));
|
|
44
|
+
next.set(key, {
|
|
45
|
+
...previous,
|
|
46
|
+
...relation,
|
|
47
|
+
uuid: getHolderRelationUuid(previous) || getHolderRelationUuid(relation) || (0, _orderCollectionIdentity.createUuidV4)(),
|
|
48
|
+
relation_model: 'order_detail',
|
|
49
|
+
relation_uuid: String(uid),
|
|
50
|
+
...(previous?.metadata || relation.metadata ? {
|
|
51
|
+
metadata: {
|
|
52
|
+
...previous?.metadata,
|
|
53
|
+
...relation.metadata
|
|
54
|
+
}
|
|
55
|
+
} : {})
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
order.holder_relations = [...(order.holder_relations || []).filter(relation => relation.relation_model !== 'order_detail' || String(relation.relation_uuid) !== String(uid)), ...next.values()];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** 只删除明确被用户删除的行,不从稀疏远端快照推断删除。 */
|
|
62
|
+
function removeProductHolderRelations(order, products) {
|
|
63
|
+
if (!Array.isArray(order.holder_relations)) return;
|
|
64
|
+
const removedUids = new Set(products.map(product => String(product.metadata?.unique_identification_number ?? product.unique_identification_number ?? '')));
|
|
65
|
+
order.holder_relations = order.holder_relations.filter(relation => relation.relation_model !== 'order_detail' || !removedUids.has(String(relation.relation_uuid)));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** 仅兼容尚无新字段的旧测试草稿;显式 [] 永远不恢复旧 product 数据。 */
|
|
69
|
+
function restoreLegacyHolderRelations(order) {
|
|
70
|
+
if (Object.prototype.hasOwnProperty.call(order, 'holder_relations')) {
|
|
71
|
+
return Array.isArray(order.holder_relations) ? order.holder_relations : undefined;
|
|
72
|
+
}
|
|
73
|
+
const relations = [];
|
|
74
|
+
const bookingProductUids = new Set((order.bookings || []).map(booking => String(booking.product_uid ?? booking.metadata?.product_uid ?? '')));
|
|
75
|
+
for (const product of order.products || []) {
|
|
76
|
+
if (hasProductBooking(product)) continue;
|
|
77
|
+
const uid = product.metadata?.unique_identification_number ?? product.unique_identification_number;
|
|
78
|
+
if (uid === undefined || uid === null || uid === '' || bookingProductUids.has(String(uid))) continue;
|
|
79
|
+
const holder = product.metadata?.holder || {};
|
|
80
|
+
const rawIds = product.metadata?.holder_id ?? holder.form_record;
|
|
81
|
+
const ids = (Array.isArray(rawIds) ? rawIds : [rawIds]).filter(id => id !== undefined && id !== null && id !== '' && Number.isInteger(Number(id)) && Number(id) >= 0);
|
|
82
|
+
for (const id of ids) {
|
|
83
|
+
const metadata = {
|
|
84
|
+
...holder,
|
|
85
|
+
form_record: [Number(id)]
|
|
86
|
+
};
|
|
87
|
+
if (ids.length !== 1) delete metadata.name;
|
|
88
|
+
relations.push({
|
|
89
|
+
relation_model: 'order_detail',
|
|
90
|
+
relation_uuid: String(uid),
|
|
91
|
+
holder_type: 'form',
|
|
92
|
+
holder_id: Number(id),
|
|
93
|
+
metadata
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return relations.length ? relations : undefined;
|
|
98
|
+
}
|