@pisell/pisellos 2.3.77 → 2.3.79
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/index.js +0 -9
- package/dist/modules/ProductList/index.js +91 -65
- package/dist/modules/ResourcePlanner/planner.js +4 -1
- package/dist/modules/ResourcePlanner/types.d.ts +1 -0
- package/dist/solution/BookingByStep/index.d.ts +1 -1
- package/dist/solution/BookingTicket/index.d.ts +14 -2
- package/dist/solution/UnifiedBookingSales/index.d.ts +5 -1
- package/dist/solution/UnifiedBookingSales/index.js +649 -455
- package/dist/solution/UnifiedBookingSales/types.d.ts +14 -0
- package/lib/modules/ProductList/index.js +7 -1
- package/lib/modules/ResourcePlanner/planner.js +2 -0
- package/lib/modules/ResourcePlanner/types.d.ts +1 -0
- package/lib/solution/BookingByStep/index.d.ts +1 -1
- package/lib/solution/BookingTicket/index.d.ts +14 -2
- package/lib/solution/UnifiedBookingSales/index.d.ts +5 -1
- package/lib/solution/UnifiedBookingSales/index.js +176 -11
- package/lib/solution/UnifiedBookingSales/types.d.ts +14 -0
- package/package.json +1 -1
|
@@ -6,6 +6,16 @@ import type { BookingTicketState } from '../BookingTicket/types';
|
|
|
6
6
|
export type { AvailabilityAbsoluteRange, AvailabilityAssignment, AvailabilityCell, AvailabilityDateRange, AvailabilityDisplayRange, AvailabilityErrorCode, AvailabilityId, AvailabilityMode, AvailabilityOccupancy, AvailabilityProduct, AvailabilityProductKind, AvailabilityProjection, AvailabilityProjectionInput, AvailabilityProjectionMeta, AvailabilityQuery, AvailabilityQueryResult, AvailabilityRequirementGroup, AvailabilityRequirementGroupResult, AvailabilityRequirementSelection, AvailabilityResource, AvailabilityResourceOption, AvailabilityResourceType, AvailabilityResourceWindow, AvailabilityScheduleMode, AvailabilityScheduleRef, AvailabilityScheduleRule, AvailabilitySelectionValidationParams, AvailabilitySelectionValidationResult, AvailabilitySessionRange, AvailabilityStatus, DateAvailabilityGroup, DateAvailabilityItem, DateAvailabilityQuery, DateAvailabilityQueryResult, GetProductTimeRangesRequest, ProductAvailabilityGroup, ProductAvailabilityItem, ProductAvailabilityQuery, ProductAvailabilityQueryResult, ProductTimeRange, ProductTimeRangeGroup, QueryAvailabilityBaseRequest, ResourceAvailabilityGroup, ResourceAvailabilityQuery, ResourceAvailabilityQueryResult, ResourceAvailabilityRangeGroup, TimeAvailabilityGroup, TimeAvailabilityItem, TimeAvailabilityQuery, TimeAvailabilityQueryResult, } from '../../modules/ResourcePlanner';
|
|
7
7
|
export interface UnifiedBookingSalesState extends BookingTicketState {
|
|
8
8
|
}
|
|
9
|
+
export interface UnifiedBookingSalesPolicy {
|
|
10
|
+
id?: AvailabilityId;
|
|
11
|
+
title?: unknown;
|
|
12
|
+
content?: string;
|
|
13
|
+
[key: string]: unknown;
|
|
14
|
+
}
|
|
15
|
+
export interface UnifiedBookingSalesPolicyResponse {
|
|
16
|
+
data?: UnifiedBookingSalesPolicy;
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
}
|
|
9
19
|
export declare enum UnifiedBookingSalesHooks {
|
|
10
20
|
onAvailabilityProjectionPrepared = "unifiedBookingSales:onAvailabilityProjectionPrepared",
|
|
11
21
|
onAvailabilitySelectionCommitted = "unifiedBookingSales:onAvailabilitySelectionCommitted",
|
|
@@ -141,6 +151,10 @@ export interface ContextualCommitAvailabilitySelectionParams {
|
|
|
141
151
|
requirementSelections: AvailabilityRequirementSelection[];
|
|
142
152
|
partySize: number;
|
|
143
153
|
bookingCount?: number;
|
|
154
|
+
/** 编辑预约时,允许本次显式报价覆盖已落库订单行的历史价格。 */
|
|
155
|
+
allowBookingReprice?: boolean;
|
|
156
|
+
/** 编辑预约重报价后,允许订单模块重新应用已选编辑态折扣。 */
|
|
157
|
+
allowBookingDiscountReapply?: boolean;
|
|
144
158
|
}
|
|
145
159
|
export type UnifiedBookingSalesCommitAvailabilitySelectionParams = ContextualCommitAvailabilitySelectionParams;
|
|
146
160
|
export interface UnifiedBookingSalesCommitAvailabilitySelectionResult {
|
|
@@ -149,9 +149,15 @@ class ProductList extends _BaseModule.BaseModule {
|
|
|
149
149
|
extension_type,
|
|
150
150
|
strategy_context
|
|
151
151
|
};
|
|
152
|
+
const originalCallback = options?.callback;
|
|
153
|
+
const subscriptionCallback = originalCallback ? async result => {
|
|
154
|
+
const products = Array.isArray(result?.data?.list) ? result.data.list : [];
|
|
155
|
+
await this.addProduct(products);
|
|
156
|
+
originalCallback(result);
|
|
157
|
+
} : undefined;
|
|
152
158
|
const productsData = await this.request.post(`/product/query`, queryPayload, {
|
|
153
159
|
osServer: true,
|
|
154
|
-
callback:
|
|
160
|
+
callback: subscriptionCallback,
|
|
155
161
|
subscriberId: options?.subscriberId,
|
|
156
162
|
customToast: () => {}
|
|
157
163
|
});
|
|
@@ -1319,6 +1319,7 @@ function evaluateCandidate(projection, candidate, request) {
|
|
|
1319
1319
|
resourceId: resource.id,
|
|
1320
1320
|
resourceName: resource.name,
|
|
1321
1321
|
resourceFormId: resource.formId,
|
|
1322
|
+
policyId: resource.raw?.policy_id,
|
|
1322
1323
|
resourceType: resource.resourceType,
|
|
1323
1324
|
status: isPast ? 'past' : 'unavailable',
|
|
1324
1325
|
maxPartySize: 0,
|
|
@@ -1341,6 +1342,7 @@ function evaluateCandidate(projection, candidate, request) {
|
|
|
1341
1342
|
resourceId: resource.id,
|
|
1342
1343
|
resourceName: resource.name,
|
|
1343
1344
|
resourceFormId: resource.formId,
|
|
1345
|
+
policyId: resource.raw?.policy_id,
|
|
1344
1346
|
resourceType: resource.resourceType,
|
|
1345
1347
|
status,
|
|
1346
1348
|
remainingCapacity,
|
|
@@ -258,6 +258,7 @@ export interface AvailabilityResourceOption {
|
|
|
258
258
|
resourceId: AvailabilityId;
|
|
259
259
|
resourceName?: string;
|
|
260
260
|
resourceFormId?: AvailabilityId;
|
|
261
|
+
policyId?: AvailabilityId;
|
|
261
262
|
resourceType: AvailabilityResourceType;
|
|
262
263
|
status: AvailabilityStatus;
|
|
263
264
|
remainingCapacity?: number;
|
|
@@ -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 | 1 | 2 | 5 |
|
|
329
|
+
weekNum: 0 | 1 | 2 | 5 | 3 | 4 | 6;
|
|
330
330
|
}[]>;
|
|
331
331
|
submitTimeSlot(timeSlots: TimeSliceItem): void;
|
|
332
332
|
private getScheduleDataByIds;
|
|
@@ -334,7 +334,7 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
|
|
|
334
334
|
* 获取当前的客户搜索条件
|
|
335
335
|
* @returns 当前搜索条件
|
|
336
336
|
*/
|
|
337
|
-
getCurrentCustomerSearchParams(): Omit<import("../../modules").ShopGetCustomerListParams, "
|
|
337
|
+
getCurrentCustomerSearchParams(): Omit<import("../../modules").ShopGetCustomerListParams, "num" | "skip">;
|
|
338
338
|
/**
|
|
339
339
|
* 获取客户列表状态(包含滚动加载相关状态)
|
|
340
340
|
* @returns 客户状态
|
|
@@ -453,7 +453,19 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
|
|
|
453
453
|
* 加车两阶段主决策(规格弹窗 / 资源编辑 / 直接加车)。
|
|
454
454
|
* 与 ticketBooking `handleSelectProduct` + `handleBooking4Service` 等价。
|
|
455
455
|
*/
|
|
456
|
-
decideAddProduct(item: any, options?: Partial<AddProductDecideContext>):
|
|
456
|
+
decideAddProduct(item: any, options?: Partial<AddProductDecideContext>): {
|
|
457
|
+
action: "reloadCatalog";
|
|
458
|
+
} | {
|
|
459
|
+
action: "add";
|
|
460
|
+
cacheItem: any;
|
|
461
|
+
} | {
|
|
462
|
+
action: "requiresDetail";
|
|
463
|
+
payload: AddProductRequiresDetailPayload;
|
|
464
|
+
} | {
|
|
465
|
+
action: "requiresBookingEdit";
|
|
466
|
+
cacheItem: any;
|
|
467
|
+
payload: import("./utils/addProductDecision").AddProductRequiresBookingEditPayload;
|
|
468
|
+
};
|
|
457
469
|
/** 规格弹窗 callback 后的第二段决策。 */
|
|
458
470
|
decideAfterDetail(cacheItem: any, options?: Partial<AddProductDecideContext>): import("./utils/addProductDecision").AddProductDecision;
|
|
459
471
|
/**
|
|
@@ -4,7 +4,7 @@ import type { OpenDataConfig } from '../../modules/OpenData';
|
|
|
4
4
|
import type { LoadScheduleAvailableDateParams, ScheduleItem } from '../../modules/Schedule/types';
|
|
5
5
|
import type { ProductListLoadProductsParams } from '../../modules/ProductList';
|
|
6
6
|
import type { AvailabilityDateRange, AvailabilityOccupancy, AvailabilityProduct, AvailabilityResource, GetProductTimeRangesRequest } from '../../modules/ResourcePlanner';
|
|
7
|
-
import type { AvailabilityContextRef, ContextualAvailabilityQuery, ContextualAvailabilityQueryResult, ContextualCommitAvailabilitySelectionParams, ContextualProductTimeRangeGroup, UnifiedBookingSalesAddRetailProductParams, UnifiedBookingSalesApplyPlannerDiscountsParams, UnifiedBookingSalesCommitAvailabilitySelectionResult, UnifiedBookingSalesDiscountResult, UnifiedBookingSalesLoadProductsByScheduleDateParams, UnifiedBookingSalesLoadOpenDataParams, UnifiedBookingSalesPrepareAvailabilityProjectionParams, UnifiedBookingSalesStageRetailProductsParams, UnifiedBookingSalesState } from './types';
|
|
7
|
+
import type { AvailabilityContextRef, ContextualAvailabilityQuery, ContextualAvailabilityQueryResult, ContextualCommitAvailabilitySelectionParams, ContextualProductTimeRangeGroup, UnifiedBookingSalesAddRetailProductParams, UnifiedBookingSalesApplyPlannerDiscountsParams, UnifiedBookingSalesCommitAvailabilitySelectionResult, UnifiedBookingSalesDiscountResult, UnifiedBookingSalesLoadProductsByScheduleDateParams, UnifiedBookingSalesLoadOpenDataParams, UnifiedBookingSalesPrepareAvailabilityProjectionParams, UnifiedBookingSalesPolicyResponse, UnifiedBookingSalesStageRetailProductsParams, UnifiedBookingSalesState } from './types';
|
|
8
8
|
import { BookingTicket } from '../BookingTicket';
|
|
9
9
|
export * from './types';
|
|
10
10
|
export { AvailabilityError } from '../../modules/ResourcePlanner';
|
|
@@ -147,6 +147,10 @@ export declare class UnifiedBookingSalesImpl extends BookingTicket {
|
|
|
147
147
|
*/
|
|
148
148
|
loadOpenData(params: UnifiedBookingSalesLoadOpenDataParams): Promise<OpenDataConfig>;
|
|
149
149
|
getOpenData(): Promise<OpenDataConfig | null>;
|
|
150
|
+
/**
|
|
151
|
+
* Load a resource policy without depending on the legacy appointmentBooking solution.
|
|
152
|
+
*/
|
|
153
|
+
getProtocol(policyId: string | number): Promise<UnifiedBookingSalesPolicyResponse>;
|
|
150
154
|
/**
|
|
151
155
|
* Loads the product catalog used by both UI and Planner. Schedule data is requested with each
|
|
152
156
|
* product because session products carry their fixed intervals there.
|
|
@@ -980,6 +980,125 @@ function buildBookingFromAvailabilitySelection(params) {
|
|
|
980
980
|
}
|
|
981
981
|
};
|
|
982
982
|
}
|
|
983
|
+
const POS_OVERRIDABLE_AVAILABILITY_CONFLICT_CODES = new Set(['past', 'booking_cutoff', 'resource_full', 'resource_unavailable', 'resource_capacity_exceeded',
|
|
984
|
+
// A required group whose selected resource is outside its window is surfaced
|
|
985
|
+
// as missing_resource at group level. Structural validation below still
|
|
986
|
+
// rejects a genuinely missing selection.
|
|
987
|
+
'missing_resource']);
|
|
988
|
+
function matchesForcedSelectionGroup(selection, group) {
|
|
989
|
+
const hasGroupId = typeof selection.requirementGroupId === 'string' && selection.requirementGroupId.trim().length > 0;
|
|
990
|
+
const hasFormId = selection.formId !== undefined && selection.formId !== null;
|
|
991
|
+
if (!hasGroupId && !hasFormId) return false;
|
|
992
|
+
if (hasGroupId && selection.requirementGroupId !== group.id) return false;
|
|
993
|
+
if (hasFormId && !sameAvailabilityId(selection.formId, group.formId)) return false;
|
|
994
|
+
return true;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
/**
|
|
998
|
+
* POS may intentionally overbook a configured time/resource. Rebuild the
|
|
999
|
+
* assignments from the submitted, structurally valid resource identities so
|
|
1000
|
+
* a full/unavailable option is not silently dropped by normal availability
|
|
1001
|
+
* evaluation. This deliberately ignores capacity, occupancy and resource
|
|
1002
|
+
* windows, while retaining product/group/resource identity and selection
|
|
1003
|
+
* cardinality checks.
|
|
1004
|
+
*/
|
|
1005
|
+
function buildPosForcedAvailabilityAssignments(params) {
|
|
1006
|
+
const {
|
|
1007
|
+
product,
|
|
1008
|
+
resources,
|
|
1009
|
+
candidate,
|
|
1010
|
+
requirementSelections,
|
|
1011
|
+
partySize
|
|
1012
|
+
} = params;
|
|
1013
|
+
const groups = product.requirementGroups || [];
|
|
1014
|
+
const conflicts = [];
|
|
1015
|
+
const assignments = [];
|
|
1016
|
+
const selectionsByGroup = new Map();
|
|
1017
|
+
const addConflict = (code, message, details = {}) => conflicts.push({
|
|
1018
|
+
code,
|
|
1019
|
+
message,
|
|
1020
|
+
productId: candidate.productId,
|
|
1021
|
+
candidateKey: candidate.key,
|
|
1022
|
+
...details
|
|
1023
|
+
});
|
|
1024
|
+
requirementSelections.forEach(selection => {
|
|
1025
|
+
const matchedGroups = groups.filter(group => matchesForcedSelectionGroup(selection, group));
|
|
1026
|
+
if (matchedGroups.length !== 1) {
|
|
1027
|
+
addConflict('invalid_requirement_group', '选择的资源组不属于当前商品', {
|
|
1028
|
+
requirementGroupId: selection.requirementGroupId,
|
|
1029
|
+
formId: selection.formId
|
|
1030
|
+
});
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
const group = matchedGroups[0];
|
|
1034
|
+
if (selectionsByGroup.has(group.id)) {
|
|
1035
|
+
addConflict('invalid_selection_count', `资源组 ${group.id} 被重复提交`, {
|
|
1036
|
+
requirementGroupId: group.id,
|
|
1037
|
+
formId: group.formId
|
|
1038
|
+
});
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
selectionsByGroup.set(group.id, selection);
|
|
1042
|
+
if (!Array.isArray(selection.resourceIds)) {
|
|
1043
|
+
addConflict('invalid_selection_count', `资源组 ${group.id} 的资源选择必须是数组`, {
|
|
1044
|
+
requirementGroupId: group.id,
|
|
1045
|
+
formId: group.formId
|
|
1046
|
+
});
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
const selectedResourceIds = Array.from(new Map(selection.resourceIds.map(resourceId => [String(resourceId), resourceId])).values());
|
|
1050
|
+
const required = group.required !== false;
|
|
1051
|
+
const min = Math.max(required ? 1 : 0, Number(group.min ?? (required ? 1 : 0)) || 0);
|
|
1052
|
+
const max = Math.max(min, Number(group.max ?? Math.max(min, 1)) || Math.max(min, 1));
|
|
1053
|
+
if (selectedResourceIds.length < min) {
|
|
1054
|
+
addConflict('missing_resource', `资源组 ${group.id} 缺少已选资源`, {
|
|
1055
|
+
requirementGroupId: group.id,
|
|
1056
|
+
formId: group.formId
|
|
1057
|
+
});
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
if (selectedResourceIds.length > max) {
|
|
1061
|
+
addConflict('invalid_selection_count', `资源组 ${group.id} 已选资源数量超出上限`, {
|
|
1062
|
+
requirementGroupId: group.id,
|
|
1063
|
+
formId: group.formId
|
|
1064
|
+
});
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
selectedResourceIds.forEach(resourceId => {
|
|
1068
|
+
const resource = resources.find(item => sameAvailabilityId(item.id, resourceId));
|
|
1069
|
+
const belongsToGroup = Boolean(resource && !(group.formId !== undefined && resource.formId !== undefined && !sameAvailabilityId(group.formId, resource.formId)) && !(group.resourceIds?.length && !group.resourceIds.some(id => sameAvailabilityId(id, resourceId))));
|
|
1070
|
+
if (!resource || !belongsToGroup) {
|
|
1071
|
+
addConflict('resource_unavailable', `资源 ${String(resourceId)} 不属于当前商品资源组`, {
|
|
1072
|
+
requirementGroupId: group.id,
|
|
1073
|
+
formId: group.formId,
|
|
1074
|
+
resourceId
|
|
1075
|
+
});
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1078
|
+
assignments.push({
|
|
1079
|
+
productId: product.id,
|
|
1080
|
+
resourceId,
|
|
1081
|
+
formId: group.formId ?? resource.formId,
|
|
1082
|
+
startAt: candidate.bookingStartAt || candidate.startAt,
|
|
1083
|
+
endAt: candidate.bookingEndAt || candidate.endAt,
|
|
1084
|
+
capacityRequired: Math.max(1, partySize),
|
|
1085
|
+
scheduleId: candidate.primaryScheduleRef?.scheduleId,
|
|
1086
|
+
timeSlotId: candidate.primaryScheduleRef?.timeSlotId
|
|
1087
|
+
});
|
|
1088
|
+
});
|
|
1089
|
+
});
|
|
1090
|
+
groups.filter(group => group.required !== false).forEach(group => {
|
|
1091
|
+
if (selectionsByGroup.has(group.id)) return;
|
|
1092
|
+
addConflict('missing_resource', `资源组 ${group.id} 缺少已选资源`, {
|
|
1093
|
+
requirementGroupId: group.id,
|
|
1094
|
+
formId: group.formId
|
|
1095
|
+
});
|
|
1096
|
+
});
|
|
1097
|
+
return {
|
|
1098
|
+
assignments,
|
|
1099
|
+
conflicts
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
983
1102
|
class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
984
1103
|
defaultName = 'unifiedBookingSales';
|
|
985
1104
|
defaultVersion = '1.0.0';
|
|
@@ -1521,6 +1640,21 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1521
1640
|
});
|
|
1522
1641
|
}
|
|
1523
1642
|
|
|
1643
|
+
/**
|
|
1644
|
+
* Load a resource policy without depending on the legacy appointmentBooking solution.
|
|
1645
|
+
*/
|
|
1646
|
+
async getProtocol(policyId) {
|
|
1647
|
+
const normalizedPolicyId = String(policyId ?? '').trim();
|
|
1648
|
+
if (!normalizedPolicyId) {
|
|
1649
|
+
throw new Error('[UnifiedBookingSales] getProtocol 需要 policyId');
|
|
1650
|
+
}
|
|
1651
|
+
let policyUrl = 'shop-policy';
|
|
1652
|
+
if (this.otherParams.platform === 'kiosk' || this.otherParams.platform === 'pos' || this.otherParams.platform === 'shop') {
|
|
1653
|
+
policyUrl = 'policy';
|
|
1654
|
+
}
|
|
1655
|
+
return this.request.get(`/${policyUrl}/${encodeURIComponent(normalizedPolicyId)}`);
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1524
1658
|
/**
|
|
1525
1659
|
* Loads the product catalog used by both UI and Planner. Schedule data is requested with each
|
|
1526
1660
|
* product because session products carry their fixed intervals there.
|
|
@@ -1710,6 +1844,7 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1710
1844
|
}
|
|
1711
1845
|
buildUnifiedOrderProduct(sourceProduct, ownershipKey, product) {
|
|
1712
1846
|
const normalizedProductRaw = product?.raw || {};
|
|
1847
|
+
const quotationShelfId = sourceProduct._extend?.quotation_shelf_id ?? sourceProduct.quotation_shelf_id ?? sourceProduct.metadata?.quotation_shelf_id;
|
|
1713
1848
|
const orderProduct = this.transformBaseProductToOrderProduct({
|
|
1714
1849
|
payload: {
|
|
1715
1850
|
...sourceProduct,
|
|
@@ -1726,11 +1861,14 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1726
1861
|
sourceProduct
|
|
1727
1862
|
});
|
|
1728
1863
|
if (orderProduct && typeof orderProduct === 'object') {
|
|
1729
|
-
// BaseSales may rebuild metadata from its price source. Stamp ownership
|
|
1730
|
-
//
|
|
1864
|
+
// BaseSales may rebuild metadata from its price source. Stamp ownership and the selected
|
|
1865
|
+
// quotation shelf after that transform so neither booking adds nor edits lose quote identity.
|
|
1731
1866
|
orderProduct.metadata = {
|
|
1732
1867
|
...(orderProduct.metadata || {}),
|
|
1733
|
-
[ownershipKey]: true
|
|
1868
|
+
[ownershipKey]: true,
|
|
1869
|
+
...(quotationShelfId !== undefined ? {
|
|
1870
|
+
quotation_shelf_id: quotationShelfId
|
|
1871
|
+
} : {})
|
|
1734
1872
|
};
|
|
1735
1873
|
}
|
|
1736
1874
|
return orderProduct;
|
|
@@ -2890,12 +3028,39 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
2890
3028
|
refreshedSnapshotId: refreshedProjection.meta.snapshotId
|
|
2891
3029
|
});
|
|
2892
3030
|
}
|
|
2893
|
-
const
|
|
3031
|
+
const product = refreshedProjection.products.find(item => sameAvailabilityId(item.id, refreshedCandidateBase.productId));
|
|
3032
|
+
if (!product) {
|
|
3033
|
+
throw new _ResourcePlanner.AvailabilityError('REVALIDATION_FAILED', '缓存重校验后商品不在 projection 中', {
|
|
3034
|
+
productId: refreshedCandidateBase.productId
|
|
3035
|
+
});
|
|
3036
|
+
}
|
|
3037
|
+
let validation = (0, _ResourcePlanner.validateAvailabilitySelection)(refreshedProjection, {
|
|
2894
3038
|
candidate: refreshedCandidateBase,
|
|
2895
3039
|
requirementSelections: params.requirementSelections,
|
|
2896
3040
|
partySize,
|
|
2897
3041
|
bookingCount
|
|
2898
3042
|
});
|
|
3043
|
+
const allowPosAvailabilityOverride = String(this.otherParams?.platform ?? '').trim().toLowerCase() === 'pos';
|
|
3044
|
+
if (allowPosAvailabilityOverride && !validation.ok && validation.conflicts.every(conflict => POS_OVERRIDABLE_AVAILABILITY_CONFLICT_CODES.has(conflict.code))) {
|
|
3045
|
+
const forced = buildPosForcedAvailabilityAssignments({
|
|
3046
|
+
product,
|
|
3047
|
+
resources: refreshedProjection.resources,
|
|
3048
|
+
candidate: refreshedCandidateBase,
|
|
3049
|
+
requirementSelections: params.requirementSelections,
|
|
3050
|
+
partySize
|
|
3051
|
+
});
|
|
3052
|
+
validation = forced.conflicts.length > 0 ? {
|
|
3053
|
+
...validation,
|
|
3054
|
+
assignments: [],
|
|
3055
|
+
conflicts: forced.conflicts
|
|
3056
|
+
} : {
|
|
3057
|
+
...validation,
|
|
3058
|
+
ok: true,
|
|
3059
|
+
status: validation.availability.status,
|
|
3060
|
+
assignments: forced.assignments,
|
|
3061
|
+
conflicts: []
|
|
3062
|
+
};
|
|
3063
|
+
}
|
|
2899
3064
|
if (!validation.ok) {
|
|
2900
3065
|
const capacityConflict = validation.conflicts.find(conflict => conflict.code === 'resource_capacity_exceeded');
|
|
2901
3066
|
throw new _ResourcePlanner.AvailabilityError('REVALIDATION_FAILED', capacityConflict?.message || '缓存重校验后资源已不可提交', {
|
|
@@ -2933,12 +3098,6 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
2933
3098
|
expectedOrderRevision: revalidationEntry.orderRevision
|
|
2934
3099
|
};
|
|
2935
3100
|
const refreshedCandidate = this.decorateAvailabilityCandidate(normalizedContextId, revalidatedContext.activeVersion, refreshedCandidateBase);
|
|
2936
|
-
const product = refreshedProjection.products.find(item => sameAvailabilityId(item.id, refreshedCandidate.productId));
|
|
2937
|
-
if (!product) {
|
|
2938
|
-
throw new _ResourcePlanner.AvailabilityError('REVALIDATION_FAILED', '缓存重校验后商品不在 projection 中', {
|
|
2939
|
-
productId: refreshedCandidate.productId
|
|
2940
|
-
});
|
|
2941
|
-
}
|
|
2942
3101
|
const existingBooking = editingLine ? this.findLinkedBooking(editingLine) : undefined;
|
|
2943
3102
|
const existingBookingUid = existingBooking ? getBookingUid(existingBooking) : '';
|
|
2944
3103
|
if (params.mode === 'edit' && !existingBookingUid) {
|
|
@@ -2993,7 +3152,13 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
2993
3152
|
product_variant_id: transformed.product_variant_id ?? editingLine.product_variant_id ?? 0,
|
|
2994
3153
|
unique_identification_number: params.productLineUid,
|
|
2995
3154
|
updates: transformed,
|
|
2996
|
-
booking
|
|
3155
|
+
booking,
|
|
3156
|
+
...(params.allowBookingReprice !== undefined ? {
|
|
3157
|
+
allow_booking_reprice: params.allowBookingReprice
|
|
3158
|
+
} : {}),
|
|
3159
|
+
...(params.allowBookingDiscountReapply !== undefined ? {
|
|
3160
|
+
allow_booking_discount_reapply: params.allowBookingDiscountReapply
|
|
3161
|
+
} : {})
|
|
2997
3162
|
});
|
|
2998
3163
|
productLineUid = params.productLineUid;
|
|
2999
3164
|
bookingUid = existingBookingUid;
|
|
@@ -6,6 +6,16 @@ import type { BookingTicketState } from '../BookingTicket/types';
|
|
|
6
6
|
export type { AvailabilityAbsoluteRange, AvailabilityAssignment, AvailabilityCell, AvailabilityDateRange, AvailabilityDisplayRange, AvailabilityErrorCode, AvailabilityId, AvailabilityMode, AvailabilityOccupancy, AvailabilityProduct, AvailabilityProductKind, AvailabilityProjection, AvailabilityProjectionInput, AvailabilityProjectionMeta, AvailabilityQuery, AvailabilityQueryResult, AvailabilityRequirementGroup, AvailabilityRequirementGroupResult, AvailabilityRequirementSelection, AvailabilityResource, AvailabilityResourceOption, AvailabilityResourceType, AvailabilityResourceWindow, AvailabilityScheduleMode, AvailabilityScheduleRef, AvailabilityScheduleRule, AvailabilitySelectionValidationParams, AvailabilitySelectionValidationResult, AvailabilitySessionRange, AvailabilityStatus, DateAvailabilityGroup, DateAvailabilityItem, DateAvailabilityQuery, DateAvailabilityQueryResult, GetProductTimeRangesRequest, ProductAvailabilityGroup, ProductAvailabilityItem, ProductAvailabilityQuery, ProductAvailabilityQueryResult, ProductTimeRange, ProductTimeRangeGroup, QueryAvailabilityBaseRequest, ResourceAvailabilityGroup, ResourceAvailabilityQuery, ResourceAvailabilityQueryResult, ResourceAvailabilityRangeGroup, TimeAvailabilityGroup, TimeAvailabilityItem, TimeAvailabilityQuery, TimeAvailabilityQueryResult, } from '../../modules/ResourcePlanner';
|
|
7
7
|
export interface UnifiedBookingSalesState extends BookingTicketState {
|
|
8
8
|
}
|
|
9
|
+
export interface UnifiedBookingSalesPolicy {
|
|
10
|
+
id?: AvailabilityId;
|
|
11
|
+
title?: unknown;
|
|
12
|
+
content?: string;
|
|
13
|
+
[key: string]: unknown;
|
|
14
|
+
}
|
|
15
|
+
export interface UnifiedBookingSalesPolicyResponse {
|
|
16
|
+
data?: UnifiedBookingSalesPolicy;
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
}
|
|
9
19
|
export declare enum UnifiedBookingSalesHooks {
|
|
10
20
|
onAvailabilityProjectionPrepared = "unifiedBookingSales:onAvailabilityProjectionPrepared",
|
|
11
21
|
onAvailabilitySelectionCommitted = "unifiedBookingSales:onAvailabilitySelectionCommitted",
|
|
@@ -141,6 +151,10 @@ export interface ContextualCommitAvailabilitySelectionParams {
|
|
|
141
151
|
requirementSelections: AvailabilityRequirementSelection[];
|
|
142
152
|
partySize: number;
|
|
143
153
|
bookingCount?: number;
|
|
154
|
+
/** 编辑预约时,允许本次显式报价覆盖已落库订单行的历史价格。 */
|
|
155
|
+
allowBookingReprice?: boolean;
|
|
156
|
+
/** 编辑预约重报价后,允许订单模块重新应用已选编辑态折扣。 */
|
|
157
|
+
allowBookingDiscountReapply?: boolean;
|
|
144
158
|
}
|
|
145
159
|
export type UnifiedBookingSalesCommitAvailabilitySelectionParams = ContextualCommitAvailabilitySelectionParams;
|
|
146
160
|
export interface UnifiedBookingSalesCommitAvailabilitySelectionResult {
|