@pisell/pisellos 0.10.59 → 0.10.60
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/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.d.ts +3 -1
- package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +25 -2
- 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 +92 -47
- package/dist/modules/Order/types.d.ts +19 -1
- package/dist/modules/Order/types.js +2 -0
- package/dist/modules/Order/utils/discountProductLineIdentity.d.ts +1 -1
- package/dist/modules/Order/utils/discountProductLineIdentity.js +142 -7
- package/dist/modules/Order/utils/orderCollectionIdentity.js +24 -10
- package/dist/modules/Order/utils.d.ts +1 -1
- package/dist/modules/Order/utils.js +35 -9
- package/dist/server/index.js +18 -12
- package/dist/server/modules/order/index.d.ts +4 -0
- package/dist/server/modules/order/index.js +255 -190
- package/dist/server/modules/order/types.d.ts +3 -0
- package/dist/shared/receipt/small-ticket.js +17 -0
- package/dist/shared/sales/loadSalesOrderSnapshot.js +1 -1
- package/dist/solution/BaseSales/index.js +97 -27
- 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 +12 -3
- package/dist/solution/BookingByStep/index.d.ts +1 -1
- package/dist/solution/BookingTicket/index.d.ts +1 -13
- package/dist/solution/BookingTicket/index.js +7 -4
- package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.js +6 -2
- package/lib/model/strategy/adapter/promotion/index.js +46 -1
- package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.d.ts +3 -1
- package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +16 -1
- 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 +1 -1
- package/lib/modules/Order/utils.js +27 -6
- package/lib/server/index.js +6 -0
- package/lib/server/modules/order/index.d.ts +4 -0
- package/lib/server/modules/order/index.js +46 -4
- package/lib/server/modules/order/types.d.ts +3 -0
- package/lib/shared/receipt/small-ticket.js +15 -0
- package/lib/shared/sales/loadSalesOrderSnapshot.js +1 -1
- package/lib/solution/BaseSales/index.js +39 -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 +7 -0
- package/lib/solution/BookingByStep/index.d.ts +1 -1
- package/lib/solution/BookingTicket/index.d.ts +1 -13
- package/lib/solution/BookingTicket/index.js +4 -1
- package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.js +7 -1
- package/package.json +1 -1
|
@@ -128,6 +128,7 @@ Object.defineProperty(exports, "syncManualProductDiscountProductNum", {
|
|
|
128
128
|
return _manualProductDiscount.syncManualProductDiscountProductNum;
|
|
129
129
|
}
|
|
130
130
|
});
|
|
131
|
+
var _lodashEs = require("lodash-es");
|
|
131
132
|
var _dayjs = _interopRequireDefault(require("dayjs"));
|
|
132
133
|
var _customParseFormat = _interopRequireDefault(require("dayjs/plugin/customParseFormat"));
|
|
133
134
|
var _decimal = _interopRequireDefault(require("decimal.js"));
|
|
@@ -138,6 +139,7 @@ var _utils2 = require("../../solution/ScanOrder/utils");
|
|
|
138
139
|
var _orderCollectionIdentity = require("./utils/orderCollectionIdentity");
|
|
139
140
|
var _salesNotes = require("./salesNotes");
|
|
140
141
|
var _pickupTiming = require("./pickupTiming");
|
|
142
|
+
var _holderRelations = require("./holderRelations");
|
|
141
143
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
142
144
|
_dayjs.default.extend(_customParseFormat.default);
|
|
143
145
|
const POINT_CARD_SNAPSHOT_TAG = 'point_card';
|
|
@@ -824,7 +826,7 @@ function resolveEffectivePerUnitDiscount(product) {
|
|
|
824
826
|
if (!Number.isFinite(originalPrice) || !Number.isFinite(finalPrice)) return fromList;
|
|
825
827
|
return fromList + (originalPrice - finalPrice);
|
|
826
828
|
}
|
|
827
|
-
function createDefaultOrderRulesHooks(getBookings) {
|
|
829
|
+
function createDefaultOrderRulesHooks(getBookings, getHolderRelations) {
|
|
828
830
|
const getProductStartDate = product => {
|
|
829
831
|
const productUid = String(product.metadata?.unique_identification_number ?? product.unique_identification_number ?? '');
|
|
830
832
|
const bookingUid = String(product.booking_uid ?? product.metadata?.booking_uid ?? '');
|
|
@@ -942,7 +944,9 @@ function createDefaultOrderRulesHooks(getBookings) {
|
|
|
942
944
|
// 勿写死 false:否则 Rules 不会走 total≠origin_total 的手动价推断,
|
|
943
945
|
// 无券时会把 bundle 价还原成 original_price(手动改价套餐会从 20 回到 30)。
|
|
944
946
|
isManualDiscount: resolveRulesManualDiscountFlag(metadataAny),
|
|
945
|
-
holder_id: product.metadata?.holder_id,
|
|
947
|
+
holder_id: (0, _holderRelations.hasProductBooking)(product) || !getHolderRelations ? product.metadata?.holder_id : (0, _holderRelations.getProductHolderRelations)({
|
|
948
|
+
holder_relations: getHolderRelations()
|
|
949
|
+
}, product).filter(relation => relation.holder_type === 'form').map(relation => relation.holder_id),
|
|
946
950
|
startDate: getProductStartDate(product),
|
|
947
951
|
main_product_selling_price: metadataAny.main_product_selling_price !== undefined ? new _decimal.default(Number(metadataAny.main_product_selling_price) || 0).minus(optionSellingSum).toDecimalPlaces(2).toString() : undefined,
|
|
948
952
|
inPromotion: metadataAny._promotion?.inPromotion === true,
|
|
@@ -1233,6 +1237,7 @@ function normalizeSubmitProduct(product, formatOptions = {}) {
|
|
|
1233
1237
|
} = product;
|
|
1234
1238
|
const {
|
|
1235
1239
|
num,
|
|
1240
|
+
_extend: _productExtend,
|
|
1236
1241
|
surcharge_fee: _productSurchargeFee,
|
|
1237
1242
|
discount_amount: _deprecatedDiscountAmount,
|
|
1238
1243
|
discount_type: _deprecatedDiscountType,
|
|
@@ -1419,6 +1424,7 @@ function createDefaultTempOrder(params) {
|
|
|
1419
1424
|
request_unique_idempotency_token: '',
|
|
1420
1425
|
vouchers: [],
|
|
1421
1426
|
products: [],
|
|
1427
|
+
holder_relations: [],
|
|
1422
1428
|
notes: [],
|
|
1423
1429
|
bookings: [],
|
|
1424
1430
|
payments: [],
|
|
@@ -1494,6 +1500,7 @@ function buildSubmitPayload(params) {
|
|
|
1494
1500
|
summary,
|
|
1495
1501
|
enhance
|
|
1496
1502
|
} = params;
|
|
1503
|
+
(0, _holderRelations.ensureHolderRelationUuids)(tempOrder);
|
|
1497
1504
|
const scheduleDate = tempOrder.schedule_date || tempOrder.created_at || formatDateTime(now);
|
|
1498
1505
|
const bookingUuid = (0, _orderCollectionIdentity.createUuidV4)();
|
|
1499
1506
|
const virtualSettlementMode = tempOrder.metadata?.currency_settlement_config?.settlement_type === 'virtual_currency';
|
|
@@ -1533,6 +1540,9 @@ function buildSubmitPayload(params) {
|
|
|
1533
1540
|
});
|
|
1534
1541
|
const payload = {
|
|
1535
1542
|
...tempOrderRest,
|
|
1543
|
+
...(tempOrder.holder_relations !== undefined ? {
|
|
1544
|
+
holder_relations: (0, _lodashEs.cloneDeep)(tempOrder.holder_relations)
|
|
1545
|
+
} : {}),
|
|
1536
1546
|
customer_id: tempOrder.customer_id ?? 1,
|
|
1537
1547
|
customer_name: tempOrder.customer_name || 'Walk-In',
|
|
1538
1548
|
country_calling_code: String(tempOrder.country_calling_code ?? ''),
|
|
@@ -1725,15 +1735,24 @@ function hasAssignedHolderId(value) {
|
|
|
1725
1735
|
* clearTempOrderHolderAssignments(tempOrder);
|
|
1726
1736
|
*/
|
|
1727
1737
|
function clearTempOrderHolderAssignments(tempOrder) {
|
|
1728
|
-
|
|
1738
|
+
const previousRelationCount = tempOrder.holder_relations?.length || 0;
|
|
1739
|
+
(0, _holderRelations.removeProductHolderRelations)(tempOrder, (tempOrder.products || []).filter(product => !(0, _holderRelations.hasProductBooking)(product)));
|
|
1740
|
+
let changed = previousRelationCount !== (tempOrder.holder_relations?.length || 0);
|
|
1729
1741
|
const resetHolderField = (target, key = 'holder_id') => {
|
|
1730
1742
|
if (!hasAssignedHolderId(target[key])) return;
|
|
1731
1743
|
target[key] = null;
|
|
1732
1744
|
changed = true;
|
|
1733
1745
|
};
|
|
1746
|
+
const clearHolder = target => {
|
|
1747
|
+
resetHolderField(target);
|
|
1748
|
+
if (target.holder !== undefined && target.holder !== null) {
|
|
1749
|
+
target.holder = null;
|
|
1750
|
+
changed = true;
|
|
1751
|
+
}
|
|
1752
|
+
};
|
|
1734
1753
|
for (const product of tempOrder.products || []) {
|
|
1735
1754
|
if (product.metadata && typeof product.metadata === 'object') {
|
|
1736
|
-
|
|
1755
|
+
clearHolder(product.metadata);
|
|
1737
1756
|
}
|
|
1738
1757
|
}
|
|
1739
1758
|
for (const booking of tempOrder.bookings || []) {
|
|
@@ -1751,11 +1770,13 @@ function clearTempOrderHolderAssignments(tempOrder) {
|
|
|
1751
1770
|
for (const entry of Object.values(productsByUid)) {
|
|
1752
1771
|
const origin = entry?.origin;
|
|
1753
1772
|
if (!origin || typeof origin !== 'object') continue;
|
|
1773
|
+
clearHolder(origin);
|
|
1754
1774
|
if (origin.metadata && typeof origin.metadata === 'object') {
|
|
1755
|
-
|
|
1775
|
+
clearHolder(origin.metadata);
|
|
1756
1776
|
}
|
|
1757
1777
|
if (origin._extend && typeof origin._extend === 'object') {
|
|
1758
|
-
|
|
1778
|
+
clearHolder(origin._extend);
|
|
1779
|
+
delete origin._extend.holder_relations;
|
|
1759
1780
|
}
|
|
1760
1781
|
}
|
|
1761
1782
|
}
|
package/lib/server/index.js
CHANGED
|
@@ -824,6 +824,9 @@ class Server {
|
|
|
824
824
|
need_sync: 0,
|
|
825
825
|
is_draft_order: 0
|
|
826
826
|
};
|
|
827
|
+
if (!Array.isArray(fresh.holder_relations) && Array.isArray(checkoutData?.holder_relations)) {
|
|
828
|
+
syncedOrder.holder_relations = (0, _lodashEs.cloneDeep)(checkoutData.holder_relations);
|
|
829
|
+
}
|
|
827
830
|
await this.order.upsertOrdersFromRemote([syncedOrder], 'checkoutSync');
|
|
828
831
|
this.logInfo(`${title}: sync_sales 完整订单已同步到本地`, {
|
|
829
832
|
order_id: fresh.order_id,
|
|
@@ -5423,6 +5426,9 @@ class Server {
|
|
|
5423
5426
|
if (fresh.order_number !== undefined && fresh.order_number !== null) {
|
|
5424
5427
|
patch.order_number = fresh.order_number;
|
|
5425
5428
|
}
|
|
5429
|
+
if (Array.isArray(fresh.holder_relations)) {
|
|
5430
|
+
patch.holder_relations = (0, _lodashEs.cloneDeep)(fresh.holder_relations);
|
|
5431
|
+
}
|
|
5426
5432
|
return patch;
|
|
5427
5433
|
}
|
|
5428
5434
|
buildSyncedOrderForPrint(params) {
|
|
@@ -311,6 +311,10 @@ export declare class OrderModule extends BaseModule implements Module {
|
|
|
311
311
|
private getIdentifiedPendingPayments;
|
|
312
312
|
private shouldProtectLocalOrderFromRemoteSnapshot;
|
|
313
313
|
private getUnmatchedIdentifiedPendingPayments;
|
|
314
|
+
/** 关系数组是完整快照,不能拼接或按商品子项生成 UID;扩展字段整体保留。 */
|
|
315
|
+
private applyHolderRelationsSnapshot;
|
|
316
|
+
/** 全量拉取缺少关系字段时,仍保留同一订单已有的关系快照。 */
|
|
317
|
+
private preserveOmittedHolderRelations;
|
|
314
318
|
private mergeOrderRecords;
|
|
315
319
|
private summarizeDuplicateOrders;
|
|
316
320
|
private logDuplicateOrders;
|
|
@@ -288,6 +288,7 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
288
288
|
const existingRecord = existing;
|
|
289
289
|
const incomingRecord = incoming;
|
|
290
290
|
const uidRemaps = [];
|
|
291
|
+
this.applyHolderRelationsSnapshot(reconciled, existing, incoming);
|
|
291
292
|
for (const field of ORDER_AUTHORITATIVE_COLLECTION_FIELDS) {
|
|
292
293
|
if (!Array.isArray(incomingRecord[field])) continue;
|
|
293
294
|
reconciled[field] = this.mergeAuthoritativeOrderCollection(field, existingRecord[field], incomingRecord[field], uidRemaps);
|
|
@@ -364,7 +365,7 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
364
365
|
continue;
|
|
365
366
|
}
|
|
366
367
|
|
|
367
|
-
//
|
|
368
|
+
// 远端商品成员或 Holder 关系确实变化时必须落库,避免被回声窗口跳过。
|
|
368
369
|
if (forceWriteStorageKeys.has(storageKey)) {
|
|
369
370
|
toWrite.push(order);
|
|
370
371
|
continue;
|
|
@@ -1234,6 +1235,7 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
1234
1235
|
if (matchIndex >= 0) {
|
|
1235
1236
|
const existingOrder = updatedList[matchIndex];
|
|
1236
1237
|
const reconciledDraftOrder = this.preserveExistingBusinessCodeForDraft(existingOrder, draftOrder);
|
|
1238
|
+
this.applyHolderRelationsSnapshot(reconciledDraftOrder, existingOrder, draftOrder);
|
|
1237
1239
|
const mergedOrder = this.mergeOrderRecords(existingOrder, reconciledDraftOrder);
|
|
1238
1240
|
const nextOrder = {
|
|
1239
1241
|
...mergedOrder,
|
|
@@ -1512,7 +1514,10 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
1512
1514
|
const hasSingleId = routeInfo.hasSingleId;
|
|
1513
1515
|
const normalizedBody = this.normalizeOrderSyncPayload(msg.body || msg.data);
|
|
1514
1516
|
if (routeInfo.route === 'bodyUpsert' && normalizedBody) {
|
|
1515
|
-
|
|
1517
|
+
const orderKey = this.getIdKey(normalizedBody.order_id);
|
|
1518
|
+
const previousBody = upsertOrders.get(orderKey);
|
|
1519
|
+
if (previousBody) this.applyHolderRelationsSnapshot(normalizedBody, previousBody, normalizedBody);
|
|
1520
|
+
upsertOrders.set(orderKey, normalizedBody);
|
|
1516
1521
|
return;
|
|
1517
1522
|
}
|
|
1518
1523
|
if (hasBatchIds) refreshIds.push(...routeInfo.batchIds);else if (routeInfo.route === 'httpRefresh.singleId' && singleId !== null) refreshIds.push(singleId);else if (routeInfo.route === 'httpRefresh.relationIds') refreshIds.push(...routeInfo.batchIds);
|
|
@@ -1705,6 +1710,9 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
1705
1710
|
const mergedStorageKey = this.getOrderStorageKey(mergedOrder);
|
|
1706
1711
|
if (mergedStorageKey) forceWriteStorageKeys.add(mergedStorageKey);
|
|
1707
1712
|
}
|
|
1713
|
+
if (nextStorageKey && !(0, _lodashEs.isEqual)(existingOrder.holder_relations, mergedOrder.holder_relations)) {
|
|
1714
|
+
forceWriteStorageKeys.add(nextStorageKey);
|
|
1715
|
+
}
|
|
1708
1716
|
// 合并后身份字段可能新增(如挂单拿到 order_id),同步登记到索引,
|
|
1709
1717
|
// 供同批后续 fresh 命中同一槽位,避免重复插入
|
|
1710
1718
|
this.registerOrderIdentityKeys(identityKeyToIndex, mergedOrder, adjustedMatchIndex);
|
|
@@ -2005,6 +2013,38 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
2005
2013
|
const incomingIdentityKeys = new Set(incomingPayments.flatMap(payment => (0, _paymentUtils.getOrderPaymentIdentityKeys)(payment)));
|
|
2006
2014
|
return existingPayments.filter(payment => (0, _paymentUtils.isIdentifiedPendingOrderPayment)(payment) && !(0, _paymentUtils.getOrderPaymentIdentityKeys)(payment).some(key => incomingIdentityKeys.has(key)));
|
|
2007
2015
|
}
|
|
2016
|
+
|
|
2017
|
+
/** 关系数组是完整快照,不能拼接或按商品子项生成 UID;扩展字段整体保留。 */
|
|
2018
|
+
applyHolderRelationsSnapshot(target, existing, incoming) {
|
|
2019
|
+
if (Array.isArray(incoming.holder_relations)) {
|
|
2020
|
+
target.holder_relations = (0, _lodashEs.cloneDeep)(incoming.holder_relations);
|
|
2021
|
+
} else if (Array.isArray(existing.holder_relations)) {
|
|
2022
|
+
target.holder_relations = (0, _lodashEs.cloneDeep)(existing.holder_relations);
|
|
2023
|
+
} else {
|
|
2024
|
+
delete target.holder_relations;
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
/** 全量拉取缺少关系字段时,仍保留同一订单已有的关系快照。 */
|
|
2029
|
+
preserveOmittedHolderRelations(incomingOrders, existingOrders) {
|
|
2030
|
+
const existingByIdentity = new Map();
|
|
2031
|
+
for (const order of existingOrders) {
|
|
2032
|
+
if (!Array.isArray(order.holder_relations)) continue;
|
|
2033
|
+
for (const key of this.getOrderIdentityMatchKeys(order)) {
|
|
2034
|
+
existingByIdentity.set(key, order);
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
return incomingOrders.map(order => {
|
|
2038
|
+
if (Array.isArray(order.holder_relations)) return order;
|
|
2039
|
+
const existingOrder = this.getOrderIdentityMatchKeys(order).map(key => existingByIdentity.get(key)).find(Boolean);
|
|
2040
|
+
if (!existingOrder) return order;
|
|
2041
|
+
const nextOrder = {
|
|
2042
|
+
...order
|
|
2043
|
+
};
|
|
2044
|
+
this.applyHolderRelationsSnapshot(nextOrder, existingOrder, order);
|
|
2045
|
+
return nextOrder;
|
|
2046
|
+
});
|
|
2047
|
+
}
|
|
2008
2048
|
mergeOrderRecords(existing, incoming) {
|
|
2009
2049
|
const preferred = this.pickPreferredOrder(existing, incoming);
|
|
2010
2050
|
const secondary = preferred === existing ? incoming : existing;
|
|
@@ -2041,6 +2081,7 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
2041
2081
|
if (Object.prototype.hasOwnProperty.call(incomingRecord, 'order_meta_list')) {
|
|
2042
2082
|
mergedRecord.order_meta_list = (0, _lodashEs.cloneDeep)(incomingRecord.order_meta_list);
|
|
2043
2083
|
}
|
|
2084
|
+
this.applyHolderRelationsSnapshot(merged, existing, incoming);
|
|
2044
2085
|
|
|
2045
2086
|
// 子项数组遵循完整快照语义:incoming 自有且为数组(包括 [])时权威替换;
|
|
2046
2087
|
// 字段缺席或不是数组时视为稀疏 patch,继续保留 existing。
|
|
@@ -2482,7 +2523,7 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
2482
2523
|
async replaceOrdersSnapshotInSQLite(orderList, source, protectedOrdersBeforeRemoteFetch = []) {
|
|
2483
2524
|
if (!this.dbManager) {
|
|
2484
2525
|
return {
|
|
2485
|
-
orders: this.compactOrderListByIdentity(orderList.map(order => this.normalizeRemoteSyncedOrder(order)), `${source}.snapshotNoDb`).list,
|
|
2526
|
+
orders: this.compactOrderListByIdentity(this.preserveOmittedHolderRelations(orderList, this.store.list).map(order => this.normalizeRemoteSyncedOrder(order)), `${source}.snapshotNoDb`).list,
|
|
2486
2527
|
persisted: false
|
|
2487
2528
|
};
|
|
2488
2529
|
}
|
|
@@ -2492,6 +2533,7 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
2492
2533
|
try {
|
|
2493
2534
|
await this.runInOrderSQLiteSaveQueue(async () => {
|
|
2494
2535
|
const existingOrders = typeof this.dbManager.getAll === 'function' ? await this.dbManager.getAll(INDEXDB_STORE_NAME) : [];
|
|
2536
|
+
const remoteSnapshotWithHolderRelations = this.preserveOmittedHolderRelations(remoteSnapshot, [...protectedOrdersBeforeRemoteFetch, ...(existingOrders || [])]);
|
|
2495
2537
|
const protectedLocalOrders = protectedOrdersBeforeRemoteFetch.map(order => this.normalizeOrderCollectionsForIngress(order, `${source}.protectedBeforeRemoteFetch`)).filter(order => this.shouldProtectLocalOrderFromRemoteSnapshot(order));
|
|
2496
2538
|
for (const rawCurrentOrder of existingOrders || []) {
|
|
2497
2539
|
const currentOrder = this.normalizeOrderCollectionsForIngress(rawCurrentOrder, `${source}.currentSQLite`);
|
|
@@ -2507,7 +2549,7 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
2507
2549
|
}
|
|
2508
2550
|
if (currentNeedsProtection) protectedLocalOrders.push(currentOrder);
|
|
2509
2551
|
}
|
|
2510
|
-
const orderListSnapshot = this.mergeRemoteSnapshotWithPendingOrders(
|
|
2552
|
+
const orderListSnapshot = this.mergeRemoteSnapshotWithPendingOrders(remoteSnapshotWithHolderRelations, protectedLocalOrders);
|
|
2511
2553
|
mergedSnapshot = this.compactOrderListByIdentity(orderListSnapshot, `${source}.sqliteSnapshot`).list;
|
|
2512
2554
|
this.logInfo('replaceOrdersSnapshotInSQLite-开始', {
|
|
2513
2555
|
source,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ScheduleEventResource } from '../resource/types';
|
|
2
|
+
import type { OrderHolderRelation } from '../../../modules/Order/types';
|
|
2
3
|
/**
|
|
3
4
|
* Order 模块主键类型(对齐标准 Sales 协议的 order_id)
|
|
4
5
|
*/
|
|
@@ -448,6 +449,8 @@ export interface OrderData {
|
|
|
448
449
|
contacts_info?: unknown[];
|
|
449
450
|
/** 订单级主体参数 */
|
|
450
451
|
holder?: Record<string, unknown> | null;
|
|
452
|
+
/** 非预约商品 Holder 关系完整快照;缺席表示本次回包未提供 */
|
|
453
|
+
holder_relations?: OrderHolderRelation[];
|
|
451
454
|
/** 订单前端扩展快照 */
|
|
452
455
|
order_meta_list?: {
|
|
453
456
|
point_cards_snapshot?: Array<{
|
|
@@ -7,6 +7,7 @@ exports.buildSmallTicketData = buildSmallTicketData;
|
|
|
7
7
|
exports.hasSmallTicketData = hasSmallTicketData;
|
|
8
8
|
var _dayjs = _interopRequireDefault(require("dayjs"));
|
|
9
9
|
var _decimal = _interopRequireDefault(require("decimal.js"));
|
|
10
|
+
var _holderRelations = require("../../modules/Order/holderRelations");
|
|
10
11
|
var _pickupTiming = require("../../modules/Order/pickupTiming");
|
|
11
12
|
var _templateVisibility = require("./templateVisibility");
|
|
12
13
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
@@ -97,6 +98,7 @@ const LABELS = {
|
|
|
97
98
|
bookingDateTime: 'Date & time',
|
|
98
99
|
bookingResource: 'Resource',
|
|
99
100
|
bookingHolder: 'Holder',
|
|
101
|
+
productHolder: 'Holder',
|
|
100
102
|
orderDiscount: 'Order discount',
|
|
101
103
|
manualDiscount: 'Manual discount',
|
|
102
104
|
manualMarkup: 'Manual markup',
|
|
@@ -142,6 +144,7 @@ const LABELS = {
|
|
|
142
144
|
bookingDateTime: '日期和时间',
|
|
143
145
|
bookingResource: '资源',
|
|
144
146
|
bookingHolder: '预约人',
|
|
147
|
+
productHolder: '持有人',
|
|
145
148
|
orderDiscount: '整单折扣',
|
|
146
149
|
manualDiscount: '手动折扣',
|
|
147
150
|
manualMarkup: '手动加价',
|
|
@@ -187,6 +190,7 @@ const LABELS = {
|
|
|
187
190
|
bookingDateTime: '日期和時間',
|
|
188
191
|
bookingResource: '資源',
|
|
189
192
|
bookingHolder: '預約人',
|
|
193
|
+
productHolder: '持有人',
|
|
190
194
|
orderDiscount: '整單折扣',
|
|
191
195
|
manualDiscount: '手動折扣',
|
|
192
196
|
manualMarkup: '手動加價',
|
|
@@ -230,6 +234,7 @@ const LABELS = {
|
|
|
230
234
|
bookingDateTime: '日時',
|
|
231
235
|
bookingResource: 'リソース',
|
|
232
236
|
bookingHolder: '予約者',
|
|
237
|
+
productHolder: '利用者',
|
|
233
238
|
orderDiscount: '注文割引',
|
|
234
239
|
manualDiscount: '手動割引',
|
|
235
240
|
manualMarkup: '手動値上げ',
|
|
@@ -274,6 +279,7 @@ const LABELS = {
|
|
|
274
279
|
bookingDateTime: 'Data e hora',
|
|
275
280
|
bookingResource: 'Recurso',
|
|
276
281
|
bookingHolder: 'Titular',
|
|
282
|
+
productHolder: 'Titular',
|
|
277
283
|
orderDiscount: 'Desconto do pedido',
|
|
278
284
|
manualDiscount: 'Desconto manual',
|
|
279
285
|
manualMarkup: 'Acréscimo manual',
|
|
@@ -445,6 +451,15 @@ function buildProducts(order, locale, productMap) {
|
|
|
445
451
|
quantity,
|
|
446
452
|
originalPrice
|
|
447
453
|
});
|
|
454
|
+
const holderName = (0, _holderRelations.hasProductBooking)(product) ? '' : (0, _holderRelations.getProductHolderRelations)(order, product).map(relation => getLocalizedValue(relation.metadata?.name, locale).trim()).filter(Boolean).join(', ');
|
|
455
|
+
if (holderName) {
|
|
456
|
+
// 复用 Web Invoice / 原生均支持的文本扩展行,不混入金额或商品备注。
|
|
457
|
+
extensionList.push({
|
|
458
|
+
...receiptKey(rawProduct, 'product-holder', 'text'),
|
|
459
|
+
item: `${label(locale, 'productHolder')}: ${holderName}`,
|
|
460
|
+
value: ''
|
|
461
|
+
});
|
|
462
|
+
}
|
|
448
463
|
const productNote = resolveProductNote(product, locale);
|
|
449
464
|
const allergy = isPresentId(productUid) ? allergyByProductUid.get(String(productUid).trim()) || '' : '';
|
|
450
465
|
const basePrice = resolveProductBasePrice(product);
|
|
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.SALES_ORDER_RELATIONS = void 0;
|
|
7
7
|
exports.loadSalesOrderSnapshot = loadSalesOrderSnapshot;
|
|
8
|
-
const SALES_ORDER_RELATIONS = exports.SALES_ORDER_RELATIONS = ['products', 'bookings', 'payments', 'customer', 'summary', 'contacts_info', 'order_meta_list', 'refunds', 'notes'];
|
|
8
|
+
const SALES_ORDER_RELATIONS = exports.SALES_ORDER_RELATIONS = ['products', 'bookings', 'payments', 'customer', 'summary', 'contacts_info', 'order_meta_list', 'refunds', 'notes', 'holder_relations'];
|
|
9
9
|
|
|
10
10
|
/** Shared transport contract; remote callers use their host's /h5 prefix. */
|
|
11
11
|
function loadSalesOrderSnapshot(request, params) {
|
|
@@ -24,6 +24,7 @@ Object.keys(_types).forEach(function (key) {
|
|
|
24
24
|
var _Order = require("../../modules/Order");
|
|
25
25
|
var _pickupTiming = require("../../modules/Order/pickupTiming");
|
|
26
26
|
var _pickupTiming2 = require("./pickupTiming");
|
|
27
|
+
var _holderRelations = require("../../modules/Order/holderRelations");
|
|
27
28
|
var _decimal = _interopRequireDefault(require("decimal.js"));
|
|
28
29
|
var _lodashEs = require("lodash-es");
|
|
29
30
|
var _types2 = require("../BookingByStep/types");
|
|
@@ -348,6 +349,38 @@ function areBaseSalesNotesEquivalent(currentValue, loadedValue, options) {
|
|
|
348
349
|
const loadedNotes = normalizeBaseSalesNotesForComparison(loadedValue);
|
|
349
350
|
return currentNotes !== null && loadedNotes !== null && (0, _lodashEs.isEqual)(currentNotes, loadedNotes);
|
|
350
351
|
}
|
|
352
|
+
function mergeBaseSalesHolderRelations(currentRecord, incomingRecord, strategy) {
|
|
353
|
+
if (!Array.isArray(incomingRecord.holder_relations)) {
|
|
354
|
+
return (0, _lodashEs.cloneDeep)(currentRecord.holder_relations);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// 仅保留商品集合合并实际留下的本地行关系;匹配的行沿用 incoming 快照。
|
|
358
|
+
// 未提供 products 的稀疏包不能据此判断哪些行已被删除或被本地保留。
|
|
359
|
+
const retainedProductUids = new Set();
|
|
360
|
+
if (Array.isArray(incomingRecord.products)) {
|
|
361
|
+
for (const product of currentRecord.products || []) {
|
|
362
|
+
const retained = !incomingRecord.products.some(incomingProduct => (0, _orderCollectionIdentity.isSameOrderCollectionItem)('product', product, incomingProduct)) && (strategy === 'preserve-local' || !(0, _orderCollectionIdentity.getOrderCollectionPersistentId)('product', product));
|
|
363
|
+
const uid = (0, _orderCollectionIdentity.getOrderCollectionReferenceUid)('product', product);
|
|
364
|
+
if (retained && uid) retainedProductUids.add(uid);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
const belongsToRetainedProduct = relation => relation?.relation_model === 'order_detail' && retainedProductUids.has(String(relation.relation_uuid));
|
|
368
|
+
const selectedRelations = [...incomingRecord.holder_relations.filter(relation => !belongsToRetainedProduct(relation)), ...(Array.isArray(currentRecord.holder_relations) ? currentRecord.holder_relations.filter(belongsToRetainedProduct) : [])];
|
|
369
|
+
const result = [];
|
|
370
|
+
const indexByIdentity = new Map();
|
|
371
|
+
for (const relation of selectedRelations) {
|
|
372
|
+
const identityParts = [relation?.relation_model, relation?.relation_uuid, relation?.holder_type, relation?.holder_id];
|
|
373
|
+
const identity = identityParts.every(value => value !== undefined && value !== null) ? JSON.stringify(identityParts) : null;
|
|
374
|
+
const existingIndex = identity === null ? undefined : indexByIdentity.get(identity);
|
|
375
|
+
if (existingIndex !== undefined) {
|
|
376
|
+
result[existingIndex] = (0, _lodashEs.cloneDeep)(relation);
|
|
377
|
+
} else {
|
|
378
|
+
if (identity !== null) indexByIdentity.set(identity, result.length);
|
|
379
|
+
result.push((0, _lodashEs.cloneDeep)(relation));
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return result;
|
|
383
|
+
}
|
|
351
384
|
function mergeSalesDetailRecordWithTempOrder(currentTempOrder, loadedRecord, strategy) {
|
|
352
385
|
const loadedHasOrderMetaList = Object.prototype.hasOwnProperty.call(loadedRecord || {}, 'order_meta_list');
|
|
353
386
|
const loadedHasSalesNotes = Object.prototype.hasOwnProperty.call(loadedRecord || {}, 'notes');
|
|
@@ -366,8 +399,12 @@ function mergeSalesDetailRecordWithTempOrder(currentTempOrder, loadedRecord, str
|
|
|
366
399
|
const normalizedCurrentTempOrder = normalizeBaseSalesRecordCollections(currentTempOrder, {
|
|
367
400
|
ensureUid: false
|
|
368
401
|
});
|
|
402
|
+
const mergedHolderRelations = mergeBaseSalesHolderRelations(normalizedCurrentTempOrder, normalizedLoadedRecord, strategy);
|
|
369
403
|
const uidRemaps = [];
|
|
370
|
-
const mergedRecord = (0, _lodashEs.mergeWith)(normalizedCurrentTempOrder, normalizedLoadedRecord, (currentValue, loadedValue, key) => {
|
|
404
|
+
const mergedRecord = (0, _lodashEs.mergeWith)(normalizedCurrentTempOrder, normalizedLoadedRecord, (currentValue, loadedValue, key, object) => {
|
|
405
|
+
if (key === 'holder_relations' && object === normalizedCurrentTempOrder) {
|
|
406
|
+
return (0, _lodashEs.cloneDeep)(mergedHolderRelations);
|
|
407
|
+
}
|
|
371
408
|
// order_meta_list 是服务端/本地订单携带的完整业务快照。若 incoming
|
|
372
409
|
// 明确提供它,必须整体替换;若省略,则保留当前值,避免 snapshot
|
|
373
410
|
// 数组递归拼接后出现同 ID 的旧数据。
|
|
@@ -3184,7 +3221,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
|
|
|
3184
3221
|
|
|
3185
3222
|
const walletProducts = tempOrder?.products.map(product => {
|
|
3186
3223
|
const metadata = product.metadata || {};
|
|
3187
|
-
const rawHolderId = product.holder_id ?? metadata.holder_id;
|
|
3224
|
+
const rawHolderId = (0, _holderRelations.hasProductBooking)(product) ? product.holder_id ?? metadata.holder_id : (0, _holderRelations.getProductHolderRelations)(tempOrder, product).filter(relation => relation.holder_type === 'form').map(relation => relation.holder_id);
|
|
3188
3225
|
const holderIdValue = Array.isArray(rawHolderId) ? rawHolderId[0] : rawHolderId;
|
|
3189
3226
|
const holder_id = Number.isInteger(Number(holderIdValue)) ? Number(holderIdValue) : undefined;
|
|
3190
3227
|
return {
|
|
@@ -4,7 +4,7 @@ import type { ScanOrderLogInput as BaseSalesLogInput, ScanOrderLoggerProviderCon
|
|
|
4
4
|
import type { QuantityCheckResult, QuantityLimitResult } from '../../model/strategy/adapter/itemRule';
|
|
5
5
|
import type { QuotationCustomerScopeInfo } from '../../modules/Quotation/types';
|
|
6
6
|
import type { PaymentMethod } from '../../modules/Payment/types';
|
|
7
|
-
import type { OrderPaymentSource } from '../../modules/Order/types';
|
|
7
|
+
import type { OrderHolderRelation, OrderPaymentSource } from '../../modules/Order/types';
|
|
8
8
|
import type { SubmitPayloadEnhancer } from '../../modules/Order/utils';
|
|
9
9
|
/**
|
|
10
10
|
* BaseSales 流程 hook 后缀。
|
|
@@ -277,6 +277,7 @@ export interface BaseSalesTempOrder {
|
|
|
277
277
|
contacts: any[];
|
|
278
278
|
contacts_info: Record<string, any> | null;
|
|
279
279
|
holder: Record<string, any> | null;
|
|
280
|
+
holder_relations?: OrderHolderRelation[];
|
|
280
281
|
metadata: Record<string, any>;
|
|
281
282
|
_extend?: {
|
|
282
283
|
productsByUid?: Record<string, Record<string, any>>;
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* - 不在此处发明等价关系;UI 侧(ticketBooking)若需要别的字段,由 view-model 适配层做转换
|
|
15
15
|
*/
|
|
16
16
|
import type { BaseSalesOrderProduct } from '../types';
|
|
17
|
-
import type { OrderMetaList } from '../../../modules/Order/types';
|
|
17
|
+
import type { OrderHolderRelation, OrderMetaList } from '../../../modules/Order/types';
|
|
18
18
|
export interface ISalesDetailHeader {
|
|
19
19
|
order_id: number | string | null;
|
|
20
20
|
order_number?: string;
|
|
@@ -121,6 +121,8 @@ export interface ISalesDetail {
|
|
|
121
121
|
header: ISalesDetailHeader;
|
|
122
122
|
/** 商品行快照(保留 tempOrder / 后端详情原字段) */
|
|
123
123
|
products: BaseSalesOrderProduct[];
|
|
124
|
+
/** 非预约商品 Holder 关系,保留关系及 metadata 的扩展字段。 */
|
|
125
|
+
holder_relations?: OrderHolderRelation[];
|
|
124
126
|
/** 全部 bookings,按服务端原始顺序输出(含 root + child) */
|
|
125
127
|
bookings: ISalesBooking[];
|
|
126
128
|
/** root booking(parent_id === 0),多个时取首个,无则为 null */
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.parseSalesResponse = parseSalesResponse;
|
|
7
7
|
var _orderCollectionIdentity = require("../../../modules/Order/utils/orderCollectionIdentity");
|
|
8
8
|
var _pickupTiming = require("../../../modules/Order/pickupTiming");
|
|
9
|
+
var _lodashEs = require("lodash-es");
|
|
9
10
|
/**
|
|
10
11
|
* 把 `/order/sales/{id}` 响应(OrderData)解析为 ISalesDetail。
|
|
11
12
|
*
|
|
@@ -130,6 +131,9 @@ function parseSalesResponse(input) {
|
|
|
130
131
|
return {
|
|
131
132
|
header: pickHeader(raw),
|
|
132
133
|
products,
|
|
134
|
+
...(Array.isArray(raw?.holder_relations) ? {
|
|
135
|
+
holder_relations: (0, _lodashEs.cloneDeep)(raw.holder_relations)
|
|
136
|
+
} : {}),
|
|
133
137
|
bookings,
|
|
134
138
|
rootBooking,
|
|
135
139
|
bookingsByProductUid,
|
|
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.transformBaseProductToOrderProduct = transformBaseProductToOrderProduct;
|
|
7
7
|
var _weighedProduct = require("../../../modules/Order/utils/weighedProduct");
|
|
8
|
+
var _serviceTimes = require("../../../modules/BookingContext/utils/serviceTimes");
|
|
8
9
|
var _utils = require("../../../modules/Order/utils");
|
|
9
10
|
var _manualProductDiscount = require("../../../modules/Order/utils/manualProductDiscount");
|
|
10
11
|
function toNumber(value, fallback = 0) {
|
|
@@ -205,6 +206,7 @@ function transformBaseProductToOrderProduct(params) {
|
|
|
205
206
|
});
|
|
206
207
|
const derivedMainSelling = hasExplicitLineTotal ? Number(lineCompositeTotal) - Number(bundleOnlyComposite) : Number(mainProductOriginalPrice);
|
|
207
208
|
const mainProductSellingPrice = toPriceString(Number.isFinite(derivedMainSelling) && derivedMainSelling >= 0 ? derivedMainSelling : Number(mainProductOriginalPrice));
|
|
209
|
+
const holderRelations = [payload._extend, callbackOrigin?._extend, sourceProduct?._extend].find(source => Array.isArray(source?.holder_relations))?.holder_relations;
|
|
208
210
|
const metadata = {
|
|
209
211
|
unique: payload.unique,
|
|
210
212
|
session: payload.session,
|
|
@@ -264,6 +266,11 @@ function transformBaseProductToOrderProduct(params) {
|
|
|
264
266
|
tax_fee: toPriceString(payload.tax_fee ?? matchedVariant?.tax_fee ?? origin?.tax_fee ?? sourceProduct?.tax_fee, '0.00'),
|
|
265
267
|
is_charge_tax: toNumber(payload.is_charge_tax ?? matchedVariant?.is_charge_tax ?? origin?.is_charge_tax ?? sourceProduct?.is_charge_tax, 0),
|
|
266
268
|
metadata,
|
|
269
|
+
...(holderRelations !== undefined && !(0, _serviceTimes.isBookingProduct)(origin) && !(0, _serviceTimes.isBookingProduct)(payload) ? {
|
|
270
|
+
_extend: {
|
|
271
|
+
holder_relations: holderRelations
|
|
272
|
+
}
|
|
273
|
+
} : {}),
|
|
267
274
|
note: payload.note != null ? String(payload.note) : '',
|
|
268
275
|
product_title: productTitle,
|
|
269
276
|
product_cover: firstNonBlank(matchedVariant?.cover, sourceProduct?.cover),
|
|
@@ -326,7 +326,7 @@ export declare class BookingByStepImpl extends BaseModule implements Module {
|
|
|
326
326
|
date: string;
|
|
327
327
|
status: string;
|
|
328
328
|
week: string;
|
|
329
|
-
weekNum: 0 |
|
|
329
|
+
weekNum: 0 | 1 | 2 | 6 | 4 | 5 | 3;
|
|
330
330
|
}[]>;
|
|
331
331
|
submitTimeSlot(timeSlots: TimeSliceItem): void;
|
|
332
332
|
private getScheduleDataByIds;
|
|
@@ -465,19 +465,7 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
|
|
|
465
465
|
* 加车两阶段主决策(规格弹窗 / 资源编辑 / 直接加车)。
|
|
466
466
|
* 与 ticketBooking `handleSelectProduct` + `handleBooking4Service` 等价。
|
|
467
467
|
*/
|
|
468
|
-
decideAddProduct(item: any, options?: Partial<AddProductDecideContext>):
|
|
469
|
-
action: "reloadCatalog";
|
|
470
|
-
} | {
|
|
471
|
-
action: "add";
|
|
472
|
-
cacheItem: any;
|
|
473
|
-
} | {
|
|
474
|
-
action: "requiresDetail";
|
|
475
|
-
payload: AddProductRequiresDetailPayload;
|
|
476
|
-
} | {
|
|
477
|
-
action: "requiresBookingEdit";
|
|
478
|
-
cacheItem: any;
|
|
479
|
-
payload: import("./utils/addProductDecision").AddProductRequiresBookingEditPayload;
|
|
480
|
-
};
|
|
468
|
+
decideAddProduct(item: any, options?: Partial<AddProductDecideContext>): import("./utils/addProductDecision").AddProductDecision;
|
|
481
469
|
/** 规格弹窗 callback 后的第二段决策。 */
|
|
482
470
|
decideAfterDetail(cacheItem: any, options?: Partial<AddProductDecideContext>): import("./utils/addProductDecision").AddProductDecision;
|
|
483
471
|
/**
|
|
@@ -1641,7 +1641,10 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
|
|
|
1641
1641
|
* 从已加车的普通商品 order line 反查 SkuDetailModal edit 所需的 cacheItem。
|
|
1642
1642
|
*/
|
|
1643
1643
|
buildNormalProductCacheItemFromOrderLine(input) {
|
|
1644
|
-
return (0, _buildNormalProductCacheItemFromOrderLine.buildNormalProductCacheItemFromOrderLine)(
|
|
1644
|
+
return (0, _buildNormalProductCacheItemFromOrderLine.buildNormalProductCacheItemFromOrderLine)({
|
|
1645
|
+
...input,
|
|
1646
|
+
holder_relations: input.holder_relations ?? this.store.order?.getTempOrder()?.holder_relations
|
|
1647
|
+
});
|
|
1645
1648
|
}
|
|
1646
1649
|
|
|
1647
1650
|
/** 文档别名:与 OrderModule.updateOrderProduct 一致。 */
|
|
@@ -32,9 +32,15 @@ async function processScannedProductItem(host, item, code, bridge) {
|
|
|
32
32
|
return runRequiresDetailFlow(host, item, ctx, bridge);
|
|
33
33
|
}
|
|
34
34
|
const values = (0, _scanProductValues.genScanProductValues)(item, code);
|
|
35
|
-
|
|
35
|
+
// 条码已唯一命中某个规格时,商品目录的 price 可能是“最低规格价”。
|
|
36
|
+
// 仅在扫码预选规格的这条路径改用命中规格价,避免影响商品卡片和手工选规格。
|
|
37
|
+
const itemWithSelection = values.price === undefined ? {
|
|
36
38
|
...item,
|
|
37
39
|
cartDetailValue: values
|
|
40
|
+
} : {
|
|
41
|
+
...item,
|
|
42
|
+
price: values.price,
|
|
43
|
+
cartDetailValue: values
|
|
38
44
|
};
|
|
39
45
|
const decision = host.decideAddProduct(itemWithSelection, ctx);
|
|
40
46
|
return runAddProductDecision(host, item, decision, ctx, bridge);
|