@pisell/pisellos 2.2.314 → 2.2.315
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/index.d.ts +2 -0
- package/dist/index.js +3 -1
- package/dist/model/strategy/adapter/itemRule/adapter.d.ts +3 -1
- package/dist/model/strategy/adapter/itemRule/adapter.js +74 -7
- package/dist/model/strategy/adapter/itemRule/evaluator.d.ts +3 -1
- package/dist/model/strategy/adapter/itemRule/evaluator.js +13 -0
- package/dist/model/strategy/adapter/itemRule/type.d.ts +40 -1
- package/dist/model/strategy/adapter/itemRule/type.js +10 -0
- package/dist/modules/BookingContext/utils/productExtend.d.ts +7 -0
- package/dist/modules/BookingContext/utils/productExtend.js +62 -18
- package/dist/modules/BookingContext/utils/resources.js +1 -1
- package/dist/modules/BusinessAvailability/index.d.ts +30 -0
- package/dist/modules/BusinessAvailability/index.js +204 -0
- package/dist/modules/Cart/index.js +4 -3
- package/dist/modules/Cart/types.d.ts +2 -0
- package/dist/modules/Cart/utils/cartProduct.d.ts +3 -1
- package/dist/modules/Cart/utils/cartProduct.js +59 -32
- package/dist/modules/FulfillmentTiming/index.d.ts +11 -0
- package/dist/modules/FulfillmentTiming/index.js +407 -0
- package/dist/modules/FulfillmentTiming/types.d.ts +67 -0
- package/dist/modules/FulfillmentTiming/types.js +1 -0
- package/dist/modules/OpenData/types.d.ts +14 -0
- package/dist/modules/Order/index.d.ts +14 -2
- package/dist/modules/Order/index.js +856 -486
- package/dist/modules/Order/localEftposState.d.ts +13 -0
- package/dist/modules/Order/localEftposState.js +39 -0
- package/dist/modules/Order/pickupTiming.d.ts +46 -0
- package/dist/modules/Order/pickupTiming.js +53 -0
- package/dist/modules/Order/types.d.ts +30 -2
- package/dist/modules/Order/utils.js +25 -7
- package/dist/modules/Payment/index.js +9 -10
- package/dist/modules/Product/types.d.ts +11 -0
- package/dist/modules/ProductList/index.js +1 -1
- package/dist/modules/ResourcePlanner/planner.d.ts +2 -0
- package/dist/modules/ResourcePlanner/planner.js +332 -23
- package/dist/modules/ResourcePlanner/types.d.ts +29 -0
- package/dist/modules/Schedule/index.d.ts +8 -0
- package/dist/modules/Schedule/index.js +141 -39
- package/dist/modules/index.d.ts +1 -0
- package/dist/modules/index.js +1 -0
- package/dist/plugins/request.d.ts +7 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1536 -1309
- package/dist/server/modules/form/index.d.ts +19 -0
- package/dist/server/modules/form/index.js +205 -0
- package/dist/server/modules/form/types.d.ts +13 -0
- package/dist/server/modules/form/types.js +3 -0
- package/dist/server/modules/index.d.ts +3 -0
- package/dist/server/modules/index.js +4 -0
- package/dist/server/modules/order/index.d.ts +15 -0
- package/dist/server/modules/order/index.js +920 -688
- package/dist/server/modules/order/types.d.ts +1 -0
- package/dist/server/test.json +1 -1
- package/dist/server/utils/small-ticket.d.ts +1 -85
- package/dist/server/utils/small-ticket.js +2 -2542
- package/dist/shared/payment/loadPaymentMethods.d.ts +9 -0
- package/dist/shared/payment/loadPaymentMethods.js +66 -0
- package/dist/shared/receipt/customerInvoiceLanguages.d.ts +8 -0
- package/dist/shared/receipt/customerInvoiceLanguages.js +60 -0
- package/dist/shared/receipt/loadCustomerInvoiceLayout.d.ts +27 -0
- package/dist/shared/receipt/loadCustomerInvoiceLayout.js +184 -0
- package/dist/shared/receipt/small-ticket.d.ts +96 -0
- package/dist/shared/receipt/small-ticket.js +2751 -0
- package/dist/shared/receipt/templateVisibility.d.ts +23 -0
- package/dist/shared/receipt/templateVisibility.js +64 -0
- package/dist/shared/sales/loadSalesOrderSnapshot.d.ts +9 -0
- package/dist/shared/sales/loadSalesOrderSnapshot.js +21 -0
- package/dist/solution/BaseSales/fulfillmentItemRules.d.ts +27 -0
- package/dist/solution/BaseSales/fulfillmentItemRules.js +750 -0
- package/dist/solution/BaseSales/index.d.ts +39 -16
- package/dist/solution/BaseSales/index.js +1754 -1154
- package/dist/solution/BaseSales/pickupTiming.d.ts +47 -0
- package/dist/solution/BaseSales/pickupTiming.js +616 -0
- package/dist/solution/BaseSales/types.d.ts +11 -0
- package/dist/solution/BaseSales/utils/parseSalesResponse.js +3 -0
- package/dist/solution/BookingByStep/index.d.ts +3 -1
- package/dist/solution/BookingByStep/index.js +8 -3
- package/dist/solution/BookingTicket/index.d.ts +8 -2
- package/dist/solution/BookingTicket/index.js +513 -436
- package/dist/solution/BookingTicket/utils/addProductDecision.d.ts +0 -1
- package/dist/solution/BookingTicket/utils/addProductDecision.js +1 -1
- package/dist/solution/BookingTicket/utils/cartView.js +3 -1
- package/dist/solution/OrderInvoicePreview/index.d.ts +66 -0
- package/dist/solution/OrderInvoicePreview/index.js +340 -0
- package/dist/solution/RegisterAndLogin/config.js +54 -11
- package/dist/solution/RegisterAndLogin/index.d.ts +2 -0
- package/dist/solution/RegisterAndLogin/index.js +1 -1
- package/dist/solution/RegisterAndLogin/utils.d.ts +12 -4
- package/dist/solution/RegisterAndLogin/utils.js +36 -16
- package/dist/solution/UnifiedBookingSales/businessAvailability.d.ts +27 -0
- package/dist/solution/UnifiedBookingSales/businessAvailability.js +292 -0
- package/dist/solution/UnifiedBookingSales/index.d.ts +36 -4
- package/dist/solution/UnifiedBookingSales/index.js +1714 -905
- package/dist/solution/UnifiedBookingSales/types.d.ts +16 -2
- package/dist/solution/VenueBooking/index.d.ts +15 -2
- package/dist/solution/VenueBooking/index.js +1062 -785
- package/dist/solution/VenueBooking/types.d.ts +2 -1
- package/dist/solution/VenueBooking/types.js +1 -0
- package/dist/solution/VenueBooking/utils/dateSummary.d.ts +1 -0
- package/dist/solution/VenueBooking/utils/dateSummary.js +7 -4
- package/dist/solution/VenueBooking/utils/smartPricing.d.ts +71 -0
- package/dist/solution/VenueBooking/utils/smartPricing.js +273 -0
- package/dist/solution/VenueBooking/utils/timeSlot.d.ts +1 -0
- package/dist/solution/VenueBooking/utils/timeSlot.js +6 -3
- package/dist/solution/index.d.ts +1 -0
- package/dist/solution/index.js +2 -1
- package/lib/index.d.ts +2 -0
- package/lib/index.js +21 -1
- package/lib/model/strategy/adapter/itemRule/adapter.d.ts +3 -1
- package/lib/model/strategy/adapter/itemRule/adapter.js +54 -4
- package/lib/model/strategy/adapter/itemRule/evaluator.d.ts +3 -1
- package/lib/model/strategy/adapter/itemRule/evaluator.js +9 -0
- package/lib/model/strategy/adapter/itemRule/type.d.ts +40 -1
- package/lib/model/strategy/adapter/itemRule/type.js +10 -0
- package/lib/model/strategy/adapter/promotion/index.js +46 -1
- package/lib/modules/BookingContext/utils/productExtend.d.ts +7 -0
- package/lib/modules/BookingContext/utils/productExtend.js +45 -2
- package/lib/modules/BookingContext/utils/resources.js +1 -1
- package/lib/modules/BusinessAvailability/index.d.ts +30 -0
- package/lib/modules/BusinessAvailability/index.js +146 -0
- package/lib/modules/Cart/index.js +4 -2
- package/lib/modules/Cart/types.d.ts +2 -0
- package/lib/modules/Cart/utils/cartProduct.d.ts +3 -1
- package/lib/modules/Cart/utils/cartProduct.js +39 -20
- package/lib/modules/FulfillmentTiming/index.d.ts +11 -0
- package/lib/modules/FulfillmentTiming/index.js +359 -0
- package/lib/modules/FulfillmentTiming/types.d.ts +67 -0
- package/lib/modules/FulfillmentTiming/types.js +5 -0
- package/lib/modules/OpenData/types.d.ts +14 -0
- package/lib/modules/Order/index.d.ts +14 -2
- package/lib/modules/Order/index.js +240 -38
- package/lib/modules/Order/localEftposState.d.ts +13 -0
- package/lib/modules/Order/localEftposState.js +44 -0
- package/lib/modules/Order/pickupTiming.d.ts +46 -0
- package/lib/modules/Order/pickupTiming.js +61 -0
- package/lib/modules/Order/types.d.ts +30 -2
- package/lib/modules/Order/utils.js +21 -5
- package/lib/modules/Payment/index.js +3 -4
- package/lib/modules/Product/types.d.ts +11 -0
- package/lib/modules/ProductList/index.js +1 -1
- package/lib/modules/ResourcePlanner/planner.d.ts +2 -0
- package/lib/modules/ResourcePlanner/planner.js +252 -8
- package/lib/modules/ResourcePlanner/types.d.ts +29 -0
- package/lib/modules/Schedule/index.d.ts +8 -0
- package/lib/modules/Schedule/index.js +68 -12
- package/lib/modules/index.d.ts +1 -0
- package/lib/modules/index.js +11 -0
- package/lib/plugins/request.d.ts +7 -0
- package/lib/server/index.d.ts +1 -0
- package/lib/server/index.js +158 -37
- package/lib/server/modules/form/index.d.ts +19 -0
- package/lib/server/modules/form/index.js +73 -0
- package/lib/server/modules/form/types.d.ts +13 -0
- package/lib/server/modules/form/types.js +9 -0
- package/lib/server/modules/index.d.ts +3 -0
- package/lib/server/modules/index.js +15 -1
- package/lib/server/modules/order/index.d.ts +15 -0
- package/lib/server/modules/order/index.js +114 -3
- package/lib/server/modules/order/types.d.ts +1 -0
- package/lib/server/test.json +1 -1
- package/lib/server/utils/small-ticket.d.ts +1 -85
- package/lib/server/utils/small-ticket.js +9 -2305
- package/lib/shared/payment/loadPaymentMethods.d.ts +9 -0
- package/lib/shared/payment/loadPaymentMethods.js +40 -0
- package/lib/shared/receipt/customerInvoiceLanguages.d.ts +8 -0
- package/lib/shared/receipt/customerInvoiceLanguages.js +58 -0
- package/lib/shared/receipt/loadCustomerInvoiceLayout.d.ts +27 -0
- package/lib/shared/receipt/loadCustomerInvoiceLayout.js +124 -0
- package/lib/shared/receipt/small-ticket.d.ts +96 -0
- package/lib/shared/receipt/small-ticket.js +2546 -0
- package/lib/shared/receipt/templateVisibility.d.ts +23 -0
- package/lib/shared/receipt/templateVisibility.js +63 -0
- package/lib/shared/sales/loadSalesOrderSnapshot.d.ts +9 -0
- package/lib/shared/sales/loadSalesOrderSnapshot.js +22 -0
- package/lib/solution/BaseSales/fulfillmentItemRules.d.ts +27 -0
- package/lib/solution/BaseSales/fulfillmentItemRules.js +259 -0
- package/lib/solution/BaseSales/index.d.ts +39 -16
- package/lib/solution/BaseSales/index.js +333 -31
- package/lib/solution/BaseSales/pickupTiming.d.ts +47 -0
- package/lib/solution/BaseSales/pickupTiming.js +290 -0
- package/lib/solution/BaseSales/types.d.ts +11 -0
- package/lib/solution/BaseSales/utils/parseSalesResponse.js +3 -0
- package/lib/solution/BookingByStep/index.d.ts +3 -1
- package/lib/solution/BookingByStep/index.js +7 -3
- package/lib/solution/BookingTicket/index.d.ts +8 -2
- package/lib/solution/BookingTicket/index.js +65 -6
- package/lib/solution/BookingTicket/utils/addProductDecision.d.ts +0 -1
- package/lib/solution/BookingTicket/utils/addProductDecision.js +1 -1
- package/lib/solution/BookingTicket/utils/cartView.js +3 -1
- package/lib/solution/OrderInvoicePreview/index.d.ts +66 -0
- package/lib/solution/OrderInvoicePreview/index.js +246 -0
- package/lib/solution/RegisterAndLogin/config.js +57 -11
- package/lib/solution/RegisterAndLogin/index.d.ts +2 -0
- package/lib/solution/RegisterAndLogin/index.js +9 -1
- package/lib/solution/RegisterAndLogin/utils.d.ts +12 -4
- package/lib/solution/RegisterAndLogin/utils.js +24 -17
- package/lib/solution/UnifiedBookingSales/businessAvailability.d.ts +27 -0
- package/lib/solution/UnifiedBookingSales/businessAvailability.js +138 -0
- package/lib/solution/UnifiedBookingSales/index.d.ts +36 -4
- package/lib/solution/UnifiedBookingSales/index.js +561 -74
- package/lib/solution/UnifiedBookingSales/types.d.ts +16 -2
- package/lib/solution/VenueBooking/index.d.ts +15 -2
- package/lib/solution/VenueBooking/index.js +243 -96
- package/lib/solution/VenueBooking/types.d.ts +2 -1
- package/lib/solution/VenueBooking/types.js +1 -0
- package/lib/solution/VenueBooking/utils/dateSummary.d.ts +1 -0
- package/lib/solution/VenueBooking/utils/dateSummary.js +7 -4
- package/lib/solution/VenueBooking/utils/smartPricing.d.ts +71 -0
- package/lib/solution/VenueBooking/utils/smartPricing.js +224 -0
- package/lib/solution/VenueBooking/utils/timeSlot.d.ts +1 -0
- package/lib/solution/VenueBooking/utils/timeSlot.js +5 -2
- package/lib/solution/index.d.ts +1 -0
- package/lib/solution/index.js +11 -0
- package/package.json +1 -1
|
@@ -32,6 +32,8 @@ exports.normalizeProductForAvailability = normalizeProductForAvailability;
|
|
|
32
32
|
var _dayjs = _interopRequireDefault(require("dayjs"));
|
|
33
33
|
var _lodashEs = require("lodash-es");
|
|
34
34
|
var _modules = require("../../modules");
|
|
35
|
+
var _pickupTiming = require("../../modules/Order/pickupTiming");
|
|
36
|
+
var _cacheItemToBookingInput = require("../../modules/BookingContext/utils/cacheItemToBookingInput");
|
|
35
37
|
var _utils = require("../../modules/BookingContext/utils");
|
|
36
38
|
var _OpenData = require("../../modules/OpenData");
|
|
37
39
|
var _getDateIsInSchedule = require("../../modules/Schedule/getDateIsInSchedule");
|
|
@@ -49,13 +51,13 @@ Object.keys(_types).forEach(function (key) {
|
|
|
49
51
|
});
|
|
50
52
|
});
|
|
51
53
|
var _remoteOccupancyCache = require("./remoteOccupancyCache");
|
|
54
|
+
var _businessAvailability = require("./businessAvailability");
|
|
52
55
|
var _BookingTicket = require("../BookingTicket");
|
|
53
56
|
var _localClock = require("../../modules/ResourcePlanner/localClock");
|
|
54
57
|
var _platform = require("../../utils/platform");
|
|
55
58
|
var _itemRule = require("../../model/strategy/adapter/itemRule");
|
|
56
59
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
57
60
|
const OPEN_DATA_SECTION_CODES = ['sale', 'reservation', 'fulfillment', 'menu', 'workflow', 'documents', 'basic', 'checkout', 'availability'];
|
|
58
|
-
const OPEN_DATA_CACHE_TTL = 5 * 60 * 1000;
|
|
59
61
|
function normalizePositiveInteger(value) {
|
|
60
62
|
const normalized = Number(value);
|
|
61
63
|
if (!Number.isInteger(normalized) || normalized <= 0) return undefined;
|
|
@@ -865,6 +867,20 @@ function collectAvailabilityResourceIds(products, explicitResourceIds = []) {
|
|
|
865
867
|
function getProductLineUid(product) {
|
|
866
868
|
return String(product?.metadata?.unique_identification_number ?? product?.unique_identification_number ?? '');
|
|
867
869
|
}
|
|
870
|
+
function getVisibleCalendarDate(value) {
|
|
871
|
+
if (typeof value !== 'string') return null;
|
|
872
|
+
const raw = value.trim();
|
|
873
|
+
try {
|
|
874
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(raw)) {
|
|
875
|
+
(0, _localClock.parseLocalDate)(raw);
|
|
876
|
+
} else {
|
|
877
|
+
(0, _localClock.parseLocalDateTime)(raw);
|
|
878
|
+
}
|
|
879
|
+
return raw.slice(0, 10);
|
|
880
|
+
} catch (_error) {
|
|
881
|
+
return null;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
868
884
|
function buildAvailabilityLineIdentity(product) {
|
|
869
885
|
return {
|
|
870
886
|
product_id: product.product_id,
|
|
@@ -1198,6 +1214,14 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1198
1214
|
defaultName = 'unifiedBookingSales';
|
|
1199
1215
|
defaultVersion = '1.0.0';
|
|
1200
1216
|
store = {};
|
|
1217
|
+
pickupTimingEnabled = true;
|
|
1218
|
+
businessAvailabilityController;
|
|
1219
|
+
runtimeOpenDataScope = null;
|
|
1220
|
+
runtimeOpenDataSnapshot = null;
|
|
1221
|
+
runtimeOpenDataModule = null;
|
|
1222
|
+
runtimeOpenDataCore;
|
|
1223
|
+
runtimeOpenDataGeneration = 0;
|
|
1224
|
+
runtimeOpenDataDestroyed = false;
|
|
1201
1225
|
unifiedProductCatalog = [];
|
|
1202
1226
|
availabilityRuntimeOffsetMinutes = null;
|
|
1203
1227
|
availabilityScheduleCatalog = null;
|
|
@@ -1217,23 +1241,75 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1217
1241
|
allergyItemRuleConfigs = [];
|
|
1218
1242
|
allergyItemRuleConfigsPromise = null;
|
|
1219
1243
|
async initialize(core, options = {}) {
|
|
1244
|
+
this.resetRuntimeOpenData();
|
|
1245
|
+
this.runtimeOpenDataDestroyed = false;
|
|
1246
|
+
const openDataGeneration = this.runtimeOpenDataGeneration;
|
|
1220
1247
|
this.availabilityScheduleCatalog = null;
|
|
1221
|
-
this.openDataTarget = null;
|
|
1222
1248
|
this.availabilityRuntimeOffsetMinutes = (0, _localClock.captureRuntimeOffsetMinutes)();
|
|
1223
1249
|
this.clearAvailabilityRemoteResourceState();
|
|
1224
1250
|
this.resetAllergyItemRuleState();
|
|
1225
1251
|
await super.initialize(core, options);
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
if (this.otherParams?.openData && openDataBusinessCode && openDataChannel) {
|
|
1229
|
-
this.openDataTarget = `${openDataBusinessCode}+${openDataChannel}`;
|
|
1252
|
+
if (this.runtimeOpenDataDestroyed || openDataGeneration !== this.runtimeOpenDataGeneration) {
|
|
1253
|
+
throw new Error('Store settings changed. Please try again.');
|
|
1230
1254
|
}
|
|
1231
1255
|
this.captureAvailabilityScheduleCatalog();
|
|
1256
|
+
await this.ensureAllergyItemRuleConfigsLoaded();
|
|
1232
1257
|
}
|
|
1233
1258
|
getSubmitOrderSalesChannel() {
|
|
1234
1259
|
const channel = String(this.otherParams?.channel ?? '').trim();
|
|
1235
1260
|
return channel || super.getSubmitOrderSalesChannel();
|
|
1236
1261
|
}
|
|
1262
|
+
|
|
1263
|
+
/** Only the explicit Kiosk Sales consumer entry enables this policy; POS stays unchanged. */
|
|
1264
|
+
isBusinessAvailabilityEnabled() {
|
|
1265
|
+
return this.otherParams?.enableBusinessAvailability === true && ['pc', 'h5', 'kiosk'].includes((0, _platform.normalizeRuntimePlatform)(this.otherParams?.platform));
|
|
1266
|
+
}
|
|
1267
|
+
getBusinessAvailabilityScope() {
|
|
1268
|
+
return `${this.getRuntimeOpenDataScope()}:${this.runtimeOpenDataGeneration}`;
|
|
1269
|
+
}
|
|
1270
|
+
getBusinessAvailabilityController() {
|
|
1271
|
+
if (!this.businessAvailabilityController) {
|
|
1272
|
+
this.businessAvailabilityController = new _businessAvailability.BusinessAvailabilityController({
|
|
1273
|
+
loadOpenData: () => this.loadOpenDataConfig(),
|
|
1274
|
+
getSchedule: () => this.store.schedule,
|
|
1275
|
+
getTimezone: () => String(this.otherParams?.businessTimezone || this.getPickupTimingContextData('timezone') || ''),
|
|
1276
|
+
getScope: () => this.getBusinessAvailabilityScope()
|
|
1277
|
+
});
|
|
1278
|
+
}
|
|
1279
|
+
return this.businessAvailabilityController;
|
|
1280
|
+
}
|
|
1281
|
+
getBusinessAvailability() {
|
|
1282
|
+
return this.getBusinessAvailabilityController().getAvailability();
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
/** Internal final-write assertion; does not fetch OpenData or Schedule. */
|
|
1286
|
+
assertBusinessAvailability() {
|
|
1287
|
+
if (this.isBusinessAvailabilityEnabled()) this.getBusinessAvailabilityController().assertAvailable();
|
|
1288
|
+
}
|
|
1289
|
+
getSubModuleHooks(moduleName) {
|
|
1290
|
+
const hooks = super.getSubModuleHooks(moduleName);
|
|
1291
|
+
if (moduleName !== 'order' || !this.isBusinessAvailabilityEnabled()) return hooks;
|
|
1292
|
+
let lastScope;
|
|
1293
|
+
let lastExistingScope;
|
|
1294
|
+
let scopeToken = {};
|
|
1295
|
+
return {
|
|
1296
|
+
...hooks,
|
|
1297
|
+
getCartIntentScope: () => {
|
|
1298
|
+
const scope = this.getBusinessAvailabilityScope();
|
|
1299
|
+
const existingScope = hooks?.getCartIntentScope?.();
|
|
1300
|
+
if (scope !== lastScope || !Object.is(existingScope, lastExistingScope)) {
|
|
1301
|
+
lastScope = scope;
|
|
1302
|
+
lastExistingScope = existingScope;
|
|
1303
|
+
scopeToken = {};
|
|
1304
|
+
}
|
|
1305
|
+
return scopeToken;
|
|
1306
|
+
},
|
|
1307
|
+
beforeCartIntentCommit: context => {
|
|
1308
|
+
this.assertBusinessAvailability();
|
|
1309
|
+
hooks?.beforeCartIntentCommit?.(context);
|
|
1310
|
+
}
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
1237
1313
|
createAvailabilityFacadeError(code, message, details) {
|
|
1238
1314
|
return new _ResourcePlanner.AvailabilityError(code, message, details);
|
|
1239
1315
|
}
|
|
@@ -1246,6 +1322,26 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1246
1322
|
businessTimezone: String(otherParams.businessTimezone ?? '')
|
|
1247
1323
|
};
|
|
1248
1324
|
}
|
|
1325
|
+
usesDurationTimeGrid(projection) {
|
|
1326
|
+
const platform = String(this.otherParams?.platform ?? '').trim().toLowerCase();
|
|
1327
|
+
return (platform === 'pos' || platform === 'kiosk') && (!projection || projection.products.some(product => product.kind === 'duration'));
|
|
1328
|
+
}
|
|
1329
|
+
getCurrentAvailabilityBusinessDate(projection) {
|
|
1330
|
+
return (0, _ResourcePlanner.getDurationBusinessDate)(projection, this.getAvailabilityRuntimeDateTime());
|
|
1331
|
+
}
|
|
1332
|
+
getAvailabilityBusinessDateRange(projection) {
|
|
1333
|
+
const prepare = projection.meta.prepareParams;
|
|
1334
|
+
const range = prepare?.dateRange;
|
|
1335
|
+
if (!this.usesDurationTimeGrid(projection) || !range?.startDate || !range.endDate) {
|
|
1336
|
+
return projection.meta.coverage;
|
|
1337
|
+
}
|
|
1338
|
+
const businessDate = this.getCurrentAvailabilityBusinessDate(projection);
|
|
1339
|
+
const today = this.getAvailabilityRuntimeDateTime().slice(0, 10);
|
|
1340
|
+
return {
|
|
1341
|
+
startDate: range.startDate === today && businessDate < today ? businessDate : range.startDate,
|
|
1342
|
+
endDate: range.endDate
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1249
1345
|
normalizeAvailabilityIdList(values) {
|
|
1250
1346
|
if (!Array.isArray(values) || values.length === 0) return undefined;
|
|
1251
1347
|
const uniqueMap = new Map();
|
|
@@ -1289,6 +1385,90 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1289
1385
|
mode
|
|
1290
1386
|
});
|
|
1291
1387
|
}
|
|
1388
|
+
|
|
1389
|
+
/**
|
|
1390
|
+
* 列表直加没有经过 SKU availability flow,legacy cacheItem 的灵活时长可能落到
|
|
1391
|
+
* 23:59。提交前用同一套营业排期补齐结束时间,保留已选开始时间、资源和 Holder。
|
|
1392
|
+
* 仅供直加调用;手动时间选择和 availability candidate 不经过此方法。
|
|
1393
|
+
*/
|
|
1394
|
+
async prepareDirectAddFlexibleBooking(cacheItem) {
|
|
1395
|
+
if (!(0, _utils.isProductFlexibleDuration)(cacheItem?.duration) || (0, _utils.isSessionProduct)(cacheItem)) {
|
|
1396
|
+
return cacheItem;
|
|
1397
|
+
}
|
|
1398
|
+
const extend = cacheItem?._extend;
|
|
1399
|
+
const {
|
|
1400
|
+
startDate
|
|
1401
|
+
} = (0, _utils.getServiceTimes)(cacheItem);
|
|
1402
|
+
if (!extend?.start_date || !extend?.start_time || !startDate.isValid()) {
|
|
1403
|
+
throw new _ResourcePlanner.AvailabilityError('INVALID_TARGET_FILTER', '灵活时长商品缺少有效开始时间');
|
|
1404
|
+
}
|
|
1405
|
+
const date = startDate.format('YYYY-MM-DD');
|
|
1406
|
+
// 带上前一天,覆盖凌晨仍处在前一营业时段内的情况。
|
|
1407
|
+
const dateRange = {
|
|
1408
|
+
startDate: startDate.subtract(1, 'day').format('YYYY-MM-DD'),
|
|
1409
|
+
endDate: date
|
|
1410
|
+
};
|
|
1411
|
+
const prepare = await this.resolveAvailabilityPrepareDefaults({
|
|
1412
|
+
dateRange
|
|
1413
|
+
});
|
|
1414
|
+
if (prepare._availabilityOperatingHours?.source !== 'open_data_schedule') {
|
|
1415
|
+
return cacheItem;
|
|
1416
|
+
}
|
|
1417
|
+
const operatingHours = this.buildAvailabilityOperatingHoursSnapshot({
|
|
1418
|
+
prepare,
|
|
1419
|
+
dateRange,
|
|
1420
|
+
scheduleList: this.getAvailabilityScheduleCatalog(),
|
|
1421
|
+
businessHours: DEFAULT_AVAILABILITY_BUSINESS_HOURS
|
|
1422
|
+
});
|
|
1423
|
+
if (operatingHours.missingScheduleIds.length > 0) {
|
|
1424
|
+
throw new _ResourcePlanner.AvailabilityError('OPERATING_HOURS_RESOLVE_FAILED', '店铺营业时间排期缺失,请重试', {
|
|
1425
|
+
missingScheduleIds: operatingHours.missingScheduleIds
|
|
1426
|
+
});
|
|
1427
|
+
}
|
|
1428
|
+
const start = (0, _localClock.localDateTimeToScalar)((0, _localClock.parseLocalDateTime)(startDate.format('YYYY-MM-DDTHH:mm:00')));
|
|
1429
|
+
const nextDay = (0, _localClock.localDateTimeToScalar)((0, _localClock.parseLocalDateTime)(`${startDate.add(1, 'day').format('YYYY-MM-DD')}T00:00:00`));
|
|
1430
|
+
const windows = operatingHours.windows.map(window => ({
|
|
1431
|
+
start: (0, _localClock.localDateTimeToScalar)((0, _localClock.parseLocalDateTime)(window.startAt)),
|
|
1432
|
+
end: (0, _localClock.localDateTimeToScalar)((0, _localClock.parseLocalDateTime)(window.endAt))
|
|
1433
|
+
})).filter(window => window.end > start && window.start < nextDay).sort((left, right) => left.start - right.start);
|
|
1434
|
+
const merged = [];
|
|
1435
|
+
for (const window of windows) {
|
|
1436
|
+
const previous = merged[merged.length - 1];
|
|
1437
|
+
if (previous && window.start <= previous.end) {
|
|
1438
|
+
previous.end = Math.max(previous.end, window.end);
|
|
1439
|
+
} else {
|
|
1440
|
+
merged.push({
|
|
1441
|
+
...window
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
// POS 可以提前售票;保留开始分钟,结束取当前或当天下一段营业时间。
|
|
1446
|
+
// 不跨越午休,也不把闭店后的直加顺延到次日。
|
|
1447
|
+
let end = merged[0]?.end;
|
|
1448
|
+
const resourceEndAt = (0, _utils.getUsableTime)(cacheItem)?._extend?.usableTime?.end_at;
|
|
1449
|
+
if (resourceEndAt) {
|
|
1450
|
+
const resourceEnd = (0, _localClock.localDateTimeToScalar)((0, _localClock.parseLocalDateTime)((0, _dayjs.default)(resourceEndAt).format('YYYY-MM-DDTHH:mm:00')));
|
|
1451
|
+
if (end !== undefined) end = Math.min(end, resourceEnd);
|
|
1452
|
+
}
|
|
1453
|
+
if (end === undefined || end <= start) {
|
|
1454
|
+
throw new _ResourcePlanner.AvailabilityError('REVALIDATION_FAILED', '当前时间没有可用营业时段,请选择预约时间');
|
|
1455
|
+
}
|
|
1456
|
+
const duration = Math.floor((end - start) / 60000);
|
|
1457
|
+
if (duration <= 0) {
|
|
1458
|
+
throw new _ResourcePlanner.AvailabilityError('REVALIDATION_FAILED', '灵活时长商品的结束时间必须晚于开始时间');
|
|
1459
|
+
}
|
|
1460
|
+
const endDate = (0, _dayjs.default)((0, _localClock.formatLocalDateTime)(start + duration * 60000));
|
|
1461
|
+
return {
|
|
1462
|
+
...cacheItem,
|
|
1463
|
+
_extend: {
|
|
1464
|
+
...extend,
|
|
1465
|
+
duration: _utils.flexibleObj.create(duration),
|
|
1466
|
+
endDate,
|
|
1467
|
+
end_date: endDate.format('YYYY-MM-DD'),
|
|
1468
|
+
end_time: endDate.format('HH:mm')
|
|
1469
|
+
}
|
|
1470
|
+
};
|
|
1471
|
+
}
|
|
1292
1472
|
async resolveAvailabilityPrepareDefaults(params) {
|
|
1293
1473
|
const needsOperatingHours = params.businessHours === undefined;
|
|
1294
1474
|
const needsTimeSlice = params.slotStepMinutes === undefined;
|
|
@@ -1304,7 +1484,7 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1304
1484
|
const businessCode = String(this.otherParams?.businessCode ?? this.otherParams?.business_code ?? '').trim();
|
|
1305
1485
|
const channel = String(this.otherParams?.channel ?? '').trim();
|
|
1306
1486
|
const currentTarget = businessCode && channel ? `${businessCode}+${channel}` : null;
|
|
1307
|
-
let openData = !currentTarget || this.openDataTarget === currentTarget ? this.otherParams?.openData : null;
|
|
1487
|
+
let openData = (!currentTarget || this.openDataTarget === currentTarget) && (this.runtimeOpenDataScope === null || this.runtimeOpenDataScope === this.getRuntimeOpenDataScope()) ? this.otherParams?.openData : null;
|
|
1308
1488
|
if (!openData && this.store.openData) {
|
|
1309
1489
|
try {
|
|
1310
1490
|
openData = await this.getOpenData();
|
|
@@ -1368,6 +1548,13 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1368
1548
|
const previousScope = this.getAvailabilityScopeDescriptor(this.otherParams || {});
|
|
1369
1549
|
const previousItemRuleConfig = this.otherParams?.itemRule;
|
|
1370
1550
|
const explicitlySetsOpenData = Object.prototype.hasOwnProperty.call(params, 'openData');
|
|
1551
|
+
const nextParams = cover ? params : {
|
|
1552
|
+
...this.otherParams,
|
|
1553
|
+
...params
|
|
1554
|
+
};
|
|
1555
|
+
if (explicitlySetsOpenData || this.getRuntimeOpenDataScope() !== this.getRuntimeOpenDataScope(nextParams)) {
|
|
1556
|
+
this.clearOpenDataCache();
|
|
1557
|
+
}
|
|
1371
1558
|
await super.setOtherParams(params, {
|
|
1372
1559
|
cover
|
|
1373
1560
|
});
|
|
@@ -1384,6 +1571,8 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1384
1571
|
}
|
|
1385
1572
|
}
|
|
1386
1573
|
async destroy() {
|
|
1574
|
+
this.runtimeOpenDataDestroyed = true;
|
|
1575
|
+
this.clearOpenDataCache();
|
|
1387
1576
|
this.availabilityScheduleCatalog = null;
|
|
1388
1577
|
this.availabilityRuntimeOffsetMinutes = null;
|
|
1389
1578
|
await this.resetAvailabilityProjectionState('destroy');
|
|
@@ -1440,8 +1629,15 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1440
1629
|
return typeof value === 'string' && value.trim() ? value : undefined;
|
|
1441
1630
|
}
|
|
1442
1631
|
createAvailabilityContextRef(context) {
|
|
1632
|
+
const projection = this.getActiveAvailabilityProjectionEntry(context)?.projection;
|
|
1443
1633
|
return {
|
|
1444
|
-
contextId: context.contextId
|
|
1634
|
+
contextId: context.contextId,
|
|
1635
|
+
...(projection && this.usesDurationTimeGrid(projection) ? {
|
|
1636
|
+
durationTimeSelection: {
|
|
1637
|
+
version: 1,
|
|
1638
|
+
currentBusinessDate: this.getCurrentAvailabilityBusinessDate(projection)
|
|
1639
|
+
}
|
|
1640
|
+
} : {})
|
|
1445
1641
|
};
|
|
1446
1642
|
}
|
|
1447
1643
|
async emitAvailabilityDiagnostic(payload) {
|
|
@@ -1579,6 +1775,42 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1579
1775
|
this.store.order?.clearSessionStoragePersistedTempOrder?.();
|
|
1580
1776
|
}
|
|
1581
1777
|
|
|
1778
|
+
/** 在 Kiosk 拉起支付设备前补齐订单号,同一订单重试复用已分配的号码。 */
|
|
1779
|
+
async ensureLocalOrderNumbersForCheckout() {
|
|
1780
|
+
if ((0, _platform.normalizeRuntimePlatform)(this.otherParams?.platform) !== 'kiosk') return;
|
|
1781
|
+
const order = this.store.order;
|
|
1782
|
+
const tempOrder = order?.getTempOrder();
|
|
1783
|
+
if (!tempOrder) throw new Error('当前订单不存在,无法生成订单编号');
|
|
1784
|
+
if (tempOrder.shop_order_number && tempOrder.shop_full_order_number) return;
|
|
1785
|
+
const biz = this.getBookingTicketBusinessCode();
|
|
1786
|
+
if (!biz) throw new Error('当前订单缺少业务类型,无法生成订单编号');
|
|
1787
|
+
const idGenerator = this.appPlugin?.getApp?.()?.getPlugin?.('idGenerator');
|
|
1788
|
+
if (!tempOrder.shop_order_number && typeof idGenerator?.generateShopOrderNumber !== 'function' || !tempOrder.shop_full_order_number && typeof idGenerator?.generateReceiptId !== 'function') {
|
|
1789
|
+
throw new Error('订单编号生成器不可用,请重启应用后重试');
|
|
1790
|
+
}
|
|
1791
|
+
try {
|
|
1792
|
+
if (!tempOrder.shop_order_number) {
|
|
1793
|
+
tempOrder.shop_order_number = await idGenerator.generateShopOrderNumber({
|
|
1794
|
+
biz
|
|
1795
|
+
});
|
|
1796
|
+
}
|
|
1797
|
+
if (!tempOrder.shop_full_order_number) {
|
|
1798
|
+
tempOrder.shop_full_order_number = await idGenerator.generateReceiptId({
|
|
1799
|
+
biz
|
|
1800
|
+
});
|
|
1801
|
+
}
|
|
1802
|
+
if (order.getTempOrder() !== tempOrder) {
|
|
1803
|
+
throw new Error('当前订单已切换,请重新发起支付');
|
|
1804
|
+
}
|
|
1805
|
+
if (!tempOrder.shop_order_number || !tempOrder.shop_full_order_number) {
|
|
1806
|
+
throw new Error('订单编号生成失败,请重试');
|
|
1807
|
+
}
|
|
1808
|
+
} finally {
|
|
1809
|
+
// 部分生成成功时也保留号码,后续只补仍然缺失的字段。
|
|
1810
|
+
if (order.getTempOrder() === tempOrder) order.persistTempOrder();
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1582
1814
|
/**
|
|
1583
1815
|
* PC/H5/Kiosk 结账默认直接等待云端 checkout;POS/WebPOS 仍保持本地待同步。
|
|
1584
1816
|
* 显式传 checkoutSyncTaskEnabled: true 时仍可回退到本地同步模式。
|
|
@@ -1684,52 +1916,118 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1684
1916
|
action: this.resolveDiscountAction(this.resolveDiscountOrderIdentity(tempOrder))
|
|
1685
1917
|
});
|
|
1686
1918
|
}
|
|
1919
|
+
getRuntimeOpenDataScope(otherParams = this.otherParams) {
|
|
1920
|
+
return JSON.stringify({
|
|
1921
|
+
shop: this.getPickupTimingContextData('shop_id'),
|
|
1922
|
+
appShop: this.appPlugin ? this.getAppData('shop_id') : undefined,
|
|
1923
|
+
tenant: this.appPlugin ? this.getAppData('tenant_id') : undefined,
|
|
1924
|
+
businessCode: String(otherParams?.businessCode ?? otherParams?.business_code ?? '').trim(),
|
|
1925
|
+
channel: String(otherParams?.channel || super.getSubmitOrderSalesChannel()).trim(),
|
|
1926
|
+
platform: (0, _platform.normalizeRuntimePlatform)(otherParams?.platform),
|
|
1927
|
+
timezone: this.getPickupTimingContextData('timezone'),
|
|
1928
|
+
businessTimezone: otherParams?.businessTimezone,
|
|
1929
|
+
cacheId: otherParams === this.otherParams ? this.cacheId : otherParams?.cacheId
|
|
1930
|
+
});
|
|
1931
|
+
}
|
|
1932
|
+
resetRuntimeOpenData() {
|
|
1933
|
+
this.businessAvailabilityController?.invalidate();
|
|
1934
|
+
this.runtimeOpenDataGeneration += 1;
|
|
1935
|
+
this.runtimeOpenDataScope = null;
|
|
1936
|
+
this.runtimeOpenDataSnapshot = null;
|
|
1937
|
+
this.runtimeOpenDataModule = null;
|
|
1938
|
+
this.runtimeOpenDataCore = undefined;
|
|
1939
|
+
this.openDataTarget = null;
|
|
1940
|
+
this.loadOpenDataInFlight = null;
|
|
1941
|
+
this.loadOpenDataInFlightTarget = null;
|
|
1942
|
+
this.otherParams = {
|
|
1943
|
+
...this.otherParams,
|
|
1944
|
+
openData: null
|
|
1945
|
+
};
|
|
1946
|
+
this.store.openData?.clearCache?.();
|
|
1947
|
+
}
|
|
1948
|
+
async prepareInvoiceTemplateAtSolutionReady(injectedOpenData) {
|
|
1949
|
+
const businessCode = this.getBookingTicketBusinessCode();
|
|
1950
|
+
if (!businessCode || !this.store.openData) return;
|
|
1951
|
+
if (injectedOpenData) {
|
|
1952
|
+
this.otherParams.openData = injectedOpenData;
|
|
1953
|
+
this.openDataTarget = `${businessCode}+${this.getSubmitOrderSalesChannel()}`;
|
|
1954
|
+
}
|
|
1955
|
+
try {
|
|
1956
|
+
await this.loadOpenDataConfig();
|
|
1957
|
+
} catch (error) {
|
|
1958
|
+
console.warn('[UnifiedBookingSales] Invoice 模板预加载失败,使用默认配置', error);
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
async loadOpenDataConfig() {
|
|
1962
|
+
const businessCode = this.getBookingTicketBusinessCode();
|
|
1963
|
+
if (!businessCode) return null;
|
|
1964
|
+
return this.loadOpenData({
|
|
1965
|
+
businessCode,
|
|
1966
|
+
channel: this.getSubmitOrderSalesChannel()
|
|
1967
|
+
});
|
|
1968
|
+
}
|
|
1687
1969
|
|
|
1688
|
-
/**
|
|
1689
|
-
* Loads OpenData without borrowing BookingTicket. Cache ownership is tied to the exact
|
|
1690
|
-
* business/channel target so a reused solution instance cannot leak another entry's menus.
|
|
1691
|
-
*/
|
|
1970
|
+
/** A successful configuration belongs to this business runtime until an explicit refresh. */
|
|
1692
1971
|
async loadOpenData(params) {
|
|
1693
|
-
|
|
1972
|
+
const module = this.store.openData;
|
|
1973
|
+
if (!module) {
|
|
1694
1974
|
throw new Error('[UnifiedBookingSales] openData 模块未初始化');
|
|
1695
1975
|
}
|
|
1976
|
+
if (this.runtimeOpenDataDestroyed) {
|
|
1977
|
+
throw new Error('Store settings changed. Please try again.');
|
|
1978
|
+
}
|
|
1696
1979
|
const businessCode = String(params.businessCode || '').trim();
|
|
1697
1980
|
const channel = String(params.channel || '').trim();
|
|
1698
1981
|
if (!businessCode || !channel) {
|
|
1699
1982
|
throw new Error('[UnifiedBookingSales] OpenData 需要 businessCode 与 channel');
|
|
1700
1983
|
}
|
|
1701
1984
|
const target = `${businessCode}+${channel}`;
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1985
|
+
this.otherParams.businessCode = businessCode;
|
|
1986
|
+
this.otherParams.channel = channel;
|
|
1987
|
+
const scope = this.getRuntimeOpenDataScope();
|
|
1988
|
+
if (params.force) {
|
|
1989
|
+
this.clearOpenDataCache();
|
|
1990
|
+
}
|
|
1991
|
+
if (this.runtimeOpenDataScope !== scope || this.runtimeOpenDataModule !== module || this.runtimeOpenDataCore !== this.core) {
|
|
1992
|
+
// Only the first, explicitly injected target may seed a new runtime snapshot.
|
|
1993
|
+
const injectedOpenData = this.runtimeOpenDataScope === null && (!this.openDataTarget || this.openDataTarget === target) && isRecord(this.otherParams.openData) ? this.otherParams.openData : null;
|
|
1994
|
+
if (this.runtimeOpenDataScope !== null) {
|
|
1995
|
+
// The caller's Pickup Timing scope already changes with this runtime scope.
|
|
1996
|
+
this.resetRuntimeOpenData();
|
|
1707
1997
|
}
|
|
1708
|
-
|
|
1998
|
+
this.runtimeOpenDataScope = scope;
|
|
1999
|
+
this.runtimeOpenDataModule = module;
|
|
2000
|
+
this.runtimeOpenDataCore = this.core;
|
|
2001
|
+
this.runtimeOpenDataSnapshot = injectedOpenData;
|
|
1709
2002
|
}
|
|
1710
2003
|
if (this.loadOpenDataInFlight) {
|
|
1711
|
-
|
|
1712
|
-
|
|
2004
|
+
return this.loadOpenDataInFlight;
|
|
2005
|
+
}
|
|
2006
|
+
const generation = this.runtimeOpenDataGeneration;
|
|
2007
|
+
const core = this.core;
|
|
2008
|
+
const request = (async () => {
|
|
2009
|
+
let openData = this.runtimeOpenDataSnapshot;
|
|
2010
|
+
if (openData) {
|
|
2011
|
+
await module.ensureInvoiceTemplateSettings?.(target, openData);
|
|
2012
|
+
} else {
|
|
2013
|
+
openData = await module.fetchOpenData({
|
|
2014
|
+
scope: 'board',
|
|
2015
|
+
target,
|
|
2016
|
+
section_code: [...OPEN_DATA_SECTION_CODES]
|
|
2017
|
+
});
|
|
1713
2018
|
}
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
2019
|
+
if (this.runtimeOpenDataDestroyed || generation !== this.runtimeOpenDataGeneration || scope !== this.getRuntimeOpenDataScope() || module !== this.store.openData || core !== this.core) {
|
|
2020
|
+
throw new Error('Store settings changed. Please try again.');
|
|
2021
|
+
}
|
|
2022
|
+
this.runtimeOpenDataSnapshot = openData;
|
|
2023
|
+
this.openDataTarget = target;
|
|
2024
|
+
this.otherParams.openData = openData;
|
|
2025
|
+
return openData;
|
|
2026
|
+
})();
|
|
1717
2027
|
this.loadOpenDataInFlightTarget = target;
|
|
1718
|
-
const request = this.store.openData.fetchOpenData({
|
|
1719
|
-
scope: 'board',
|
|
1720
|
-
target,
|
|
1721
|
-
section_code: [...OPEN_DATA_SECTION_CODES]
|
|
1722
|
-
});
|
|
1723
2028
|
this.loadOpenDataInFlight = request;
|
|
1724
2029
|
try {
|
|
1725
|
-
|
|
1726
|
-
if (this.loadOpenDataInFlight === request) {
|
|
1727
|
-
this.openDataTarget = target;
|
|
1728
|
-
this.otherParams.openData = openData;
|
|
1729
|
-
this.otherParams.businessCode = businessCode;
|
|
1730
|
-
this.otherParams.channel = channel;
|
|
1731
|
-
}
|
|
1732
|
-
return openData;
|
|
2030
|
+
return await request;
|
|
1733
2031
|
} finally {
|
|
1734
2032
|
if (this.loadOpenDataInFlight === request) {
|
|
1735
2033
|
this.loadOpenDataInFlight = null;
|
|
@@ -1738,13 +2036,19 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1738
2036
|
}
|
|
1739
2037
|
}
|
|
1740
2038
|
async getOpenData() {
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
2039
|
+
return this.loadOpenDataConfig();
|
|
2040
|
+
}
|
|
2041
|
+
async loadPickupTimingOpenData(_force) {
|
|
2042
|
+
const scope = this.getPickupTimingScope();
|
|
2043
|
+
const businessCode = String(this.otherParams?.businessCode ?? this.otherParams?.business_code ?? '').trim();
|
|
2044
|
+
const config = await this.loadOpenData({
|
|
1745
2045
|
businessCode,
|
|
1746
|
-
channel
|
|
2046
|
+
channel: this.getSubmitOrderSalesChannel()
|
|
1747
2047
|
});
|
|
2048
|
+
if (scope !== this.getPickupTimingScope()) {
|
|
2049
|
+
throw new Error('Store settings changed. Please try again.');
|
|
2050
|
+
}
|
|
2051
|
+
return config;
|
|
1748
2052
|
}
|
|
1749
2053
|
resetAllergyItemRuleState() {
|
|
1750
2054
|
this.allergyItemRuleConfigs = [];
|
|
@@ -1797,7 +2101,7 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1797
2101
|
this.allergyItemRuleConfigs = loaded;
|
|
1798
2102
|
this.allergyItemRuleEvaluator.setStrategyConfigs(loaded);
|
|
1799
2103
|
return loaded;
|
|
1800
|
-
})();
|
|
2104
|
+
})().catch(() => []);
|
|
1801
2105
|
return this.allergyItemRuleConfigsPromise;
|
|
1802
2106
|
}
|
|
1803
2107
|
|
|
@@ -1839,10 +2143,8 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1839
2143
|
});
|
|
1840
2144
|
}
|
|
1841
2145
|
clearOpenDataCache() {
|
|
1842
|
-
this.
|
|
1843
|
-
this.
|
|
1844
|
-
this.loadOpenDataInFlightTarget = null;
|
|
1845
|
-
super.clearOpenDataCache();
|
|
2146
|
+
this.resetRuntimeOpenData();
|
|
2147
|
+
this.resetPickupTimingCache();
|
|
1846
2148
|
}
|
|
1847
2149
|
async getInvoiceLayoutConfig() {
|
|
1848
2150
|
const openData = await this.getOpenData();
|
|
@@ -1862,7 +2164,7 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1862
2164
|
const businessCode = String(this.otherParams?.businessCode || '').trim();
|
|
1863
2165
|
const channel = String(this.otherParams?.channel || '').trim();
|
|
1864
2166
|
if (!businessCode || !channel) return false;
|
|
1865
|
-
return this.openDataTarget === `${businessCode}+${channel}
|
|
2167
|
+
return this.openDataTarget === `${businessCode}+${channel}` && this.runtimeOpenDataScope === this.getRuntimeOpenDataScope();
|
|
1866
2168
|
}
|
|
1867
2169
|
|
|
1868
2170
|
/**
|
|
@@ -2274,7 +2576,9 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
2274
2576
|
return range ? [{
|
|
2275
2577
|
startAt: range.startAt,
|
|
2276
2578
|
endAt: range.endAt,
|
|
2277
|
-
source: descriptor.source
|
|
2579
|
+
source: descriptor.source,
|
|
2580
|
+
businessDate: date,
|
|
2581
|
+
openingAnchorAt: range.startAt
|
|
2278
2582
|
}] : [];
|
|
2279
2583
|
});
|
|
2280
2584
|
return {
|
|
@@ -2298,7 +2602,8 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
2298
2602
|
return range ? [{
|
|
2299
2603
|
startAt: range.startAt,
|
|
2300
2604
|
endAt: range.endAt,
|
|
2301
|
-
source: `operating_schedule:${descriptor.scheduleIds.map(String).join(',')}
|
|
2605
|
+
source: `operating_schedule:${descriptor.scheduleIds.map(String).join(',')}`,
|
|
2606
|
+
businessDate: date
|
|
2302
2607
|
}] : [];
|
|
2303
2608
|
}));
|
|
2304
2609
|
const excludedWindows = dateRangeDays(params.dateRange).flatMap(date => schedules.flatMap(schedule => getExcludedScheduleDateOverrideTimePoints(date, schedule).flatMap(slot => {
|
|
@@ -2312,12 +2617,23 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
2312
2617
|
startAt: range.startAt,
|
|
2313
2618
|
endAt: range.endAt,
|
|
2314
2619
|
source: `operating_schedule_excluded:${schedule.id}`,
|
|
2620
|
+
businessDate: date,
|
|
2315
2621
|
raw: {
|
|
2316
2622
|
scheduleId: schedule.id,
|
|
2317
2623
|
excludedDate: date
|
|
2318
2624
|
}
|
|
2319
2625
|
}] : [];
|
|
2320
2626
|
})));
|
|
2627
|
+
// All lunch-break windows share the first opening of their business day.
|
|
2628
|
+
const openings = new Map();
|
|
2629
|
+
[...windows, ...excludedWindows].forEach(window => {
|
|
2630
|
+
const date = window.businessDate || window.startAt.slice(0, 10);
|
|
2631
|
+
const previous = openings.get(date);
|
|
2632
|
+
if (!previous || window.startAt < previous) openings.set(date, window.startAt);
|
|
2633
|
+
});
|
|
2634
|
+
[...windows, ...excludedWindows].forEach(window => {
|
|
2635
|
+
window.openingAnchorAt = openings.get(window.businessDate || window.startAt.slice(0, 10));
|
|
2636
|
+
});
|
|
2321
2637
|
return {
|
|
2322
2638
|
source: descriptor.source,
|
|
2323
2639
|
scheduleIds: [...descriptor.scheduleIds],
|
|
@@ -2707,24 +3023,34 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
2707
3023
|
const normalizedPrepareParams = this.normalizeAvailabilityPrepareDescriptor(params);
|
|
2708
3024
|
const timezoneLabel = String(normalizedPrepareParams.timezone);
|
|
2709
3025
|
const dateRange = normalizedPrepareParams.dateRange;
|
|
3026
|
+
const scheduleList = this.getAvailabilityScheduleCatalog();
|
|
3027
|
+
const products = await this.loadAvailabilityProducts({
|
|
3028
|
+
prepare: normalizedPrepareParams,
|
|
3029
|
+
dateRange,
|
|
3030
|
+
scheduleList
|
|
3031
|
+
});
|
|
3032
|
+
// Coverage is physical local time; the caller's date range remains business
|
|
3033
|
+
// dates. Keep yesterday's opening and the last business day's overnight tail.
|
|
3034
|
+
const useDurationTimeGrid = this.usesDurationTimeGrid() && products.some(product => product.kind === 'duration');
|
|
3035
|
+
const coverageDateRange = useDurationTimeGrid ? {
|
|
3036
|
+
startDate: (0, _localClock.formatLocalDate)((0, _localClock.addLocalCalendarDays)((0, _localClock.localDateToScalarStart)((0, _localClock.parseLocalDate)(dateRange.startDate)), -1)),
|
|
3037
|
+
endDate: (0, _localClock.formatLocalDate)((0, _localClock.addLocalCalendarDays)((0, _localClock.localDateToScalarStart)((0, _localClock.parseLocalDate)(dateRange.endDate)), 1))
|
|
3038
|
+
} : dateRange;
|
|
2710
3039
|
const slotStepMinutes = normalizedPrepareParams.slotStepMinutes || DEFAULT_AVAILABILITY_SLOT_STEP_MINUTES;
|
|
2711
3040
|
const businessHours = normalizedPrepareParams.businessHours || DEFAULT_AVAILABILITY_BUSINESS_HOURS;
|
|
2712
3041
|
const mode = normalizedPrepareParams.mode || 'appointment';
|
|
2713
|
-
const scheduleList = this.getAvailabilityScheduleCatalog();
|
|
2714
3042
|
const operatingHours = this.buildAvailabilityOperatingHoursSnapshot({
|
|
2715
3043
|
prepare: normalizedPrepareParams,
|
|
2716
|
-
dateRange
|
|
3044
|
+
dateRange: useDurationTimeGrid ? {
|
|
3045
|
+
startDate: coverageDateRange.startDate,
|
|
3046
|
+
endDate: dateRange.endDate
|
|
3047
|
+
} : dateRange,
|
|
2717
3048
|
scheduleList,
|
|
2718
3049
|
businessHours
|
|
2719
3050
|
});
|
|
2720
|
-
const products = await this.loadAvailabilityProducts({
|
|
2721
|
-
prepare: normalizedPrepareParams,
|
|
2722
|
-
dateRange,
|
|
2723
|
-
scheduleList
|
|
2724
|
-
});
|
|
2725
3051
|
const loadedResources = await this.loadAvailabilityResources({
|
|
2726
3052
|
timezone: timezoneLabel,
|
|
2727
|
-
dateRange,
|
|
3053
|
+
dateRange: coverageDateRange,
|
|
2728
3054
|
resourceIds: normalizedPrepareParams.resourceIds,
|
|
2729
3055
|
products,
|
|
2730
3056
|
rawResources: normalizedPrepareParams.rawResources,
|
|
@@ -2760,7 +3086,7 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
2760
3086
|
resourceOccupancies: [...remoteOccupancies, ...cartOccupancies],
|
|
2761
3087
|
mode,
|
|
2762
3088
|
timezone: timezoneLabel,
|
|
2763
|
-
coverage:
|
|
3089
|
+
coverage: coverageDateRange,
|
|
2764
3090
|
slotStepMinutes,
|
|
2765
3091
|
businessHours,
|
|
2766
3092
|
operatingHours,
|
|
@@ -3015,14 +3341,47 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
3015
3341
|
} = await this.ensureAvailabilityContextProjection(contextId, 'get_product_time_ranges');
|
|
3016
3342
|
const contextVersion = context.activeVersion;
|
|
3017
3343
|
const isPosAvailabilityRequest = String(this.otherParams?.platform ?? '').trim().toLowerCase() === 'pos';
|
|
3018
|
-
const
|
|
3344
|
+
const isEditingContext = Boolean(context.prepareParams.editingProductLineUids?.length);
|
|
3345
|
+
const editingOffset = (0, _localClock.extractFixedOffsetMinutes)(projectionEntry.projection.meta.now) ?? this.getAvailabilityRuntimeOffsetMinutes();
|
|
3346
|
+
// This new editing input represents an absolute instant. Normalize it once
|
|
3347
|
+
// to the projection's fixed business clock before the wall-clock planner.
|
|
3348
|
+
const editingStartAtByProductId = isEditingContext && request.editingStartAtByProductId ? Object.fromEntries(Object.entries(request.editingStartAtByProductId).map(([productId, startAt]) => {
|
|
3349
|
+
try {
|
|
3350
|
+
const scalar = (0, _localClock.localDateTimeToScalar)((0, _localClock.parseLocalDateTime)(startAt));
|
|
3351
|
+
const inputOffset = (0, _localClock.extractFixedOffsetMinutes)(startAt) ?? editingOffset;
|
|
3352
|
+
return [productId, (0, _localClock.formatLocalDateTime)(scalar + (editingOffset - inputOffset) * 60000, editingOffset)];
|
|
3353
|
+
} catch {
|
|
3354
|
+
throw new _ResourcePlanner.AvailabilityError('INVALID_TARGET_FILTER', '编辑开始时间必须是完整有效日期时间');
|
|
3355
|
+
}
|
|
3356
|
+
})) : undefined;
|
|
3357
|
+
let dateRange = request.dateRange || this.getAvailabilityBusinessDateRange(projectionEntry.projection);
|
|
3358
|
+
// Older edit drafts carry the real calendar date, not a business-date label.
|
|
3359
|
+
// The explicit absolute start disambiguates the previous day's overnight tail.
|
|
3360
|
+
const editingStarts = Object.values(editingStartAtByProductId || {});
|
|
3361
|
+
if (this.usesDurationTimeGrid(projectionEntry.projection) && editingStarts.length === 1 && dateRange.startDate === dateRange.endDate && dateRange.startDate === (0, _localClock.formatLocalDate)((0, _localClock.parseLocalDateTime)(editingStarts[0]))) {
|
|
3362
|
+
const businessDate = (0, _ResourcePlanner.getDurationBusinessDate)(projectionEntry.projection, editingStarts[0]);
|
|
3363
|
+
dateRange = {
|
|
3364
|
+
startDate: businessDate,
|
|
3365
|
+
endDate: businessDate
|
|
3366
|
+
};
|
|
3367
|
+
}
|
|
3368
|
+
const rememberedStartAtByProductId = isPosAvailabilityRequest && !isEditingContext ? request.rememberedStartAtByProductId : undefined;
|
|
3369
|
+
const allowOutsideOperatingHoursForRequestedStartTimes = isPosAvailabilityRequest && !rememberedStartAtByProductId && (editingStarts.length > 0 || Object.values(request.durationStartTimesByProductId || {}).some(startTimes => Array.isArray(startTimes) && startTimes.length > 0));
|
|
3019
3370
|
return (0, _ResourcePlanner.getProductTimeRanges)(projectionEntry.projection, {
|
|
3020
3371
|
...request,
|
|
3372
|
+
dateRange,
|
|
3373
|
+
editingStartAtByProductId,
|
|
3374
|
+
rememberedStartAtByProductId,
|
|
3375
|
+
...(rememberedStartAtByProductId ? {
|
|
3376
|
+
allowOutsideOperatingHoursForRequestedStartTimes: false
|
|
3377
|
+
} : {}),
|
|
3021
3378
|
...(allowOutsideOperatingHoursForRequestedStartTimes ? {
|
|
3022
3379
|
allowOutsideOperatingHoursForRequestedStartTimes: true
|
|
3023
3380
|
} : {})
|
|
3024
3381
|
}, {
|
|
3025
3382
|
evaluatedAt: this.getAvailabilityRuntimeDateTime(),
|
|
3383
|
+
useDurationTimeGrid: this.usesDurationTimeGrid(projectionEntry.projection),
|
|
3384
|
+
enforceSaleCutoff: Boolean(rememberedStartAtByProductId),
|
|
3026
3385
|
useExactDurationCandidateMinute: isPosAvailabilityRequest,
|
|
3027
3386
|
allowSessionCustomRanges: isPosAvailabilityRequest,
|
|
3028
3387
|
allowExcludedScheduleDateOverride: isPosAvailabilityRequest
|
|
@@ -3041,7 +3400,13 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
3041
3400
|
} = await this.ensureAvailabilityContextProjection(contextId, 'query_booking_availability');
|
|
3042
3401
|
this.assertContextualQueryFreshness(context, request);
|
|
3043
3402
|
const isPosAvailabilityRequest = String(this.otherParams?.platform ?? '').trim().toLowerCase() === 'pos';
|
|
3044
|
-
const
|
|
3403
|
+
const queryRequest = request.target === 'date' || request.target === 'product' ? {
|
|
3404
|
+
...request,
|
|
3405
|
+
dateRange: request.dateRange || this.getAvailabilityBusinessDateRange(projectionEntry.projection)
|
|
3406
|
+
} : request;
|
|
3407
|
+
const result = (0, _ResourcePlanner.queryAvailabilityProjection)(projectionEntry.projection, queryRequest, {
|
|
3408
|
+
useDurationTimeGrid: this.usesDurationTimeGrid(projectionEntry.projection),
|
|
3409
|
+
enforceSaleCutoff: isPosAvailabilityRequest && !context.prepareParams.editingProductLineUids?.length && request.selectionIntent === 'remembered',
|
|
3045
3410
|
useExactDurationCandidateMinute: isPosAvailabilityRequest,
|
|
3046
3411
|
allowExcludedScheduleDateOverride: isPosAvailabilityRequest
|
|
3047
3412
|
});
|
|
@@ -3121,6 +3486,24 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
3121
3486
|
const bookingUid = String(product.booking_uid ?? product.metadata?.booking_uid ?? '');
|
|
3122
3487
|
return (this.getTempOrder()?.bookings || []).find(booking => bookingUid && getBookingUid(booking) === bookingUid || productUid && String(booking.product_uid ?? '') === productUid);
|
|
3123
3488
|
}
|
|
3489
|
+
async removeBookingProductsOutsideStartDate(startDate) {
|
|
3490
|
+
const products = this.getTempOrder()?.products || [];
|
|
3491
|
+
if (this.otherParams?.enforceSingleBookingStartDate !== true) {
|
|
3492
|
+
return products;
|
|
3493
|
+
}
|
|
3494
|
+
const targetDate = getVisibleCalendarDate(startDate);
|
|
3495
|
+
if (!targetDate) return products;
|
|
3496
|
+
const identities = products.flatMap(product => {
|
|
3497
|
+
if (!getProductLineUid(product) || product?.metadata?.product_add_schedule_time != null) {
|
|
3498
|
+
return [];
|
|
3499
|
+
}
|
|
3500
|
+
const booking = this.findLinkedBooking(product);
|
|
3501
|
+
if (!booking || (0, _pickupTiming.isFulfillmentTimingBooking)(booking)) return [];
|
|
3502
|
+
const bookingDate = getVisibleCalendarDate(booking.start_date);
|
|
3503
|
+
return bookingDate && bookingDate !== targetDate ? [buildAvailabilityLineIdentity(product)] : [];
|
|
3504
|
+
});
|
|
3505
|
+
return identities.length ? this.removeProductsFromOrder(identities) : products;
|
|
3506
|
+
}
|
|
3124
3507
|
|
|
3125
3508
|
/** Revalidates against the latest landed occupancy cache, then mutates exactly one product line. */
|
|
3126
3509
|
async commitAvailabilitySelection(contextId, params) {
|
|
@@ -3130,6 +3513,25 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
3130
3513
|
candidate: params.candidate
|
|
3131
3514
|
});
|
|
3132
3515
|
}
|
|
3516
|
+
const isRememberedSelection = params.selectionIntent === 'remembered';
|
|
3517
|
+
if (isRememberedSelection && (String(this.otherParams?.platform ?? '').trim().toLowerCase() !== 'pos' || params.mode !== 'add')) {
|
|
3518
|
+
throw new _ResourcePlanner.AvailabilityError('INVALID_TARGET_FILTER', '记忆时间自动提交仅支持 POS 新增 duration 商品');
|
|
3519
|
+
}
|
|
3520
|
+
const hasCommitGuard = isRememberedSelection || Boolean(params.availabilityCommitGuard);
|
|
3521
|
+
params.availabilityCommitGuard?.();
|
|
3522
|
+
// Stabilize the locally generated sale identity before taking the guard snapshot.
|
|
3523
|
+
if (hasCommitGuard) this.store.order?.ensureTempOrder?.();
|
|
3524
|
+
const rememberedOrder = hasCommitGuard ? this.getTempOrder() : null;
|
|
3525
|
+
const getRememberedCommitIdentity = () => JSON.stringify(stableValue({
|
|
3526
|
+
scope: this.getAvailabilityScopeDescriptor(),
|
|
3527
|
+
shopId: this.otherParams?.shop_id ?? this.otherParams?.shopId,
|
|
3528
|
+
contextShopId: this.core?.context?.shop_id,
|
|
3529
|
+
orderId: this.getTempOrder()?.order_id,
|
|
3530
|
+
externalSaleNumber: this.getTempOrder()?.external_sale_number
|
|
3531
|
+
}));
|
|
3532
|
+
const rememberedCommitIdentity = hasCommitGuard ? getRememberedCommitIdentity() : '';
|
|
3533
|
+
const availabilityCommitTargetGuard = () => this.getTempOrder() === rememberedOrder && getRememberedCommitIdentity() === rememberedCommitIdentity;
|
|
3534
|
+
let guardedCommitPublished = false;
|
|
3133
3535
|
await this.tryAcquireAvailabilityCommitLock(normalizedContextId);
|
|
3134
3536
|
try {
|
|
3135
3537
|
const bookingCount = Number(params.bookingCount ?? 1);
|
|
@@ -3235,6 +3637,10 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
3235
3637
|
});
|
|
3236
3638
|
}
|
|
3237
3639
|
const baseProjection = projectionEntry.projection;
|
|
3640
|
+
const selectedProduct = baseProjection.products.find(item => sameAvailabilityId(item.id, params.candidate.productId));
|
|
3641
|
+
if (isRememberedSelection && (String(this.otherParams?.platform ?? '').trim().toLowerCase() !== 'pos' || params.mode !== 'add' || selectedProduct?.kind !== 'duration')) {
|
|
3642
|
+
throw new _ResourcePlanner.AvailabilityError('INVALID_TARGET_FILTER', '记忆时间自动提交仅支持 POS 新增 duration 商品');
|
|
3643
|
+
}
|
|
3238
3644
|
let commitWriteGuard = {
|
|
3239
3645
|
contextId: normalizedContextId,
|
|
3240
3646
|
expectedVersion: context.activeVersion,
|
|
@@ -3280,17 +3686,32 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
3280
3686
|
this.assertAvailabilityCommitWriteCAS(commitWriteGuard);
|
|
3281
3687
|
const candidateDurationMinutes = getCandidateDurationMinutes(params.candidate);
|
|
3282
3688
|
const candidateStartTime = getCandidateStartTime(params.candidate);
|
|
3283
|
-
const allowPosAvailabilityOverride = String(this.otherParams?.platform ?? '').trim().toLowerCase() === 'pos';
|
|
3689
|
+
const allowPosAvailabilityOverride = String(this.otherParams?.platform ?? '').trim().toLowerCase() === 'pos' && !isRememberedSelection;
|
|
3690
|
+
const candidateBusinessDate = params.candidate.businessDate || (selectedProduct?.kind === 'duration' && this.usesDurationTimeGrid(refreshedProjection) ? (0, _ResourcePlanner.getDurationBusinessDate)(refreshedProjection, params.candidate.startAt) : params.candidate.startAt.slice(0, 10));
|
|
3284
3691
|
const refreshedRanges = (0, _ResourcePlanner.getProductTimeRanges)(refreshedProjection, {
|
|
3285
3692
|
productIds: [params.candidate.productId],
|
|
3286
3693
|
dateRange: {
|
|
3287
|
-
startDate:
|
|
3288
|
-
endDate:
|
|
3694
|
+
startDate: candidateBusinessDate,
|
|
3695
|
+
endDate: candidateBusinessDate
|
|
3289
3696
|
},
|
|
3290
|
-
...(
|
|
3697
|
+
...(isRememberedSelection ? {
|
|
3698
|
+
rememberedStartAtByProductId: {
|
|
3699
|
+
[String(params.candidate.productId)]: params.candidate.bookingStartAt
|
|
3700
|
+
},
|
|
3701
|
+
...(!selectedProduct?.isFlexibleDuration && candidateDurationMinutes ? {
|
|
3702
|
+
durationMinutesByProductId: {
|
|
3703
|
+
[String(params.candidate.productId)]: candidateDurationMinutes
|
|
3704
|
+
}
|
|
3705
|
+
} : {})
|
|
3706
|
+
} : candidateDurationMinutes ? {
|
|
3291
3707
|
durationMinutesByProductId: {
|
|
3292
3708
|
[String(params.candidate.productId)]: candidateDurationMinutes
|
|
3293
3709
|
},
|
|
3710
|
+
...(params.mode === 'edit' && selectedProduct?.kind === 'duration' ? {
|
|
3711
|
+
editingStartAtByProductId: {
|
|
3712
|
+
[String(params.candidate.productId)]: params.candidate.bookingStartAt
|
|
3713
|
+
}
|
|
3714
|
+
} : {}),
|
|
3294
3715
|
...(candidateStartTime ? {
|
|
3295
3716
|
durationStartTimesByProductId: {
|
|
3296
3717
|
[String(params.candidate.productId)]: [candidateStartTime]
|
|
@@ -3301,6 +3722,9 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
3301
3722
|
} : {})
|
|
3302
3723
|
} : {})
|
|
3303
3724
|
}, {
|
|
3725
|
+
evaluatedAt: this.getAvailabilityRuntimeDateTime(),
|
|
3726
|
+
useDurationTimeGrid: this.usesDurationTimeGrid(refreshedProjection),
|
|
3727
|
+
enforceSaleCutoff: isRememberedSelection,
|
|
3304
3728
|
useExactDurationCandidateMinute: allowPosAvailabilityOverride,
|
|
3305
3729
|
allowSessionCustomRanges: allowPosAvailabilityOverride,
|
|
3306
3730
|
allowExcludedScheduleDateOverride: allowPosAvailabilityOverride
|
|
@@ -3325,6 +3749,8 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
3325
3749
|
partySize,
|
|
3326
3750
|
bookingCount
|
|
3327
3751
|
}, {
|
|
3752
|
+
useDurationTimeGrid: this.usesDurationTimeGrid(refreshedProjection),
|
|
3753
|
+
enforceSaleCutoff: isRememberedSelection,
|
|
3328
3754
|
useExactDurationCandidateMinute: allowPosAvailabilityOverride,
|
|
3329
3755
|
allowExcludedScheduleDateOverride: allowPosAvailabilityOverride
|
|
3330
3756
|
});
|
|
@@ -3385,6 +3811,30 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
3385
3811
|
expectedOrderRevision: revalidationEntry.orderRevision
|
|
3386
3812
|
};
|
|
3387
3813
|
const refreshedCandidate = this.decorateAvailabilityCandidate(normalizedContextId, revalidatedContext.activeVersion, refreshedCandidateBase);
|
|
3814
|
+
// Order may await line/runtime setup. Its guarded transaction invokes this
|
|
3815
|
+
// again immediately before publishing, while the live cart is unchanged.
|
|
3816
|
+
const availabilityCommitGuard = hasCommitGuard ? () => {
|
|
3817
|
+
params.availabilityCommitGuard?.();
|
|
3818
|
+
this.assertAvailabilityCommitWriteCAS(commitWriteGuard);
|
|
3819
|
+
if (!availabilityCommitTargetGuard()) {
|
|
3820
|
+
throw this.createAvailabilityFacadeError('AVAILABILITY_SELECTION_STALE', '记忆时间加购期间订单或业务已变化');
|
|
3821
|
+
}
|
|
3822
|
+
if (!isRememberedSelection) return;
|
|
3823
|
+
const currentValidation = (0, _ResourcePlanner.validateAvailabilitySelection)(refreshedProjection, {
|
|
3824
|
+
candidate: refreshedCandidateBase,
|
|
3825
|
+
requirementSelections: params.requirementSelections,
|
|
3826
|
+
partySize,
|
|
3827
|
+
bookingCount
|
|
3828
|
+
}, {
|
|
3829
|
+
useDurationTimeGrid: true,
|
|
3830
|
+
enforceSaleCutoff: true
|
|
3831
|
+
});
|
|
3832
|
+
if (!currentValidation.ok) {
|
|
3833
|
+
throw new _ResourcePlanner.AvailabilityError('REVALIDATION_FAILED', '记忆时间在加购前已不可用,请重新选择', {
|
|
3834
|
+
validation: currentValidation
|
|
3835
|
+
});
|
|
3836
|
+
}
|
|
3837
|
+
} : undefined;
|
|
3388
3838
|
const existingBooking = editingLine ? this.findLinkedBooking(editingLine) : undefined;
|
|
3389
3839
|
const existingBookingUid = existingBooking ? getBookingUid(existingBooking) : '';
|
|
3390
3840
|
if (params.mode === 'edit' && !existingBookingUid) {
|
|
@@ -3402,6 +3852,23 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
3402
3852
|
holder: params.orderProduct?._extend?.holder ?? existingBooking?.holder ?? existingBooking?.metadata?.holder,
|
|
3403
3853
|
holderIds: params.orderProduct?._extend?.holder_id ?? params.orderProduct?._extend?.holder?.form_record ?? existingBooking?.metadata?.holder_id ?? existingBooking?.holder?.form_record
|
|
3404
3854
|
});
|
|
3855
|
+
if (isRememberedSelection && params.orderProduct?._extend?.resource) {
|
|
3856
|
+
// AutoClose 与编辑共用旧资源协议。仅复用其结构,实际资源及每份容量
|
|
3857
|
+
// 必须与本次严格校验结果一致;时间、Holder 和校验元数据仍使用上面的结果。
|
|
3858
|
+
const legacyBooking = (0, _cacheItemToBookingInput.cacheItemToBookingInput)(params.orderProduct);
|
|
3859
|
+
const legacyResources = legacyBooking.resources || [];
|
|
3860
|
+
const selectedResources = legacyResources.filter(resource => Number(resource.relation_id) !== 0);
|
|
3861
|
+
const resourceKeys = resources => resources.map(resource => JSON.stringify([String(resource.form_id), String(resource.relation_id), resource.relation_type, Number(resource.capacity)])).sort();
|
|
3862
|
+
if (JSON.stringify(resourceKeys(selectedResources)) !== JSON.stringify(resourceKeys(booking.resources)) || legacyResources.some(resource => Number(resource.relation_id) === 0 && !selectedResources.some(selected => sameAvailabilityId(selected.form_id, resource.form_id)))) {
|
|
3863
|
+
throw new _ResourcePlanner.AvailabilityError('REVALIDATION_FAILED', '记忆时间的资源与本次校验结果不一致');
|
|
3864
|
+
}
|
|
3865
|
+
Object.assign(booking, {
|
|
3866
|
+
relation_id: legacyBooking.relation_id,
|
|
3867
|
+
form_id: legacyBooking.form_id,
|
|
3868
|
+
relation_type: legacyBooking.relation_type,
|
|
3869
|
+
resources: (0, _lodashEs.cloneDeep)(legacyResources)
|
|
3870
|
+
});
|
|
3871
|
+
}
|
|
3405
3872
|
const transformed = this.buildUnifiedOrderProduct({
|
|
3406
3873
|
...params.orderProduct,
|
|
3407
3874
|
num: bookingCount,
|
|
@@ -3447,6 +3914,7 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
3447
3914
|
allow_booking_discount_reapply: params.allowBookingDiscountReapply
|
|
3448
3915
|
} : {})
|
|
3449
3916
|
});
|
|
3917
|
+
await this.removeBookingProductsOutsideStartDate(booking.start_date);
|
|
3450
3918
|
productLineUid = params.productLineUid;
|
|
3451
3919
|
bookingUid = existingBookingUid;
|
|
3452
3920
|
productLineUids = [productLineUid];
|
|
@@ -3454,12 +3922,21 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
3454
3922
|
} else {
|
|
3455
3923
|
const beforeProductLineUids = new Set((this.getTempOrder()?.products || []).map(line => getProductLineUid(line)).filter(Boolean));
|
|
3456
3924
|
const beforeBookingUids = new Set((this.getTempOrder()?.bookings || []).map(item => getBookingUid(item)).filter(Boolean));
|
|
3457
|
-
|
|
3458
|
-
|
|
3925
|
+
availabilityCommitGuard?.();
|
|
3926
|
+
let products = await this.addProductToOrder(transformed, booking, params.salesNotes?.length || availabilityCommitGuard ? {
|
|
3927
|
+
...(params.salesNotes?.length ? {
|
|
3928
|
+
salesNotes: params.salesNotes
|
|
3929
|
+
} : {}),
|
|
3930
|
+
...(availabilityCommitGuard ? {
|
|
3931
|
+
availabilityCommitGuard,
|
|
3932
|
+
availabilityCommitTargetGuard
|
|
3933
|
+
} : {})
|
|
3459
3934
|
} : undefined);
|
|
3935
|
+
products = await this.removeBookingProductsOutsideStartDate(booking.start_date);
|
|
3936
|
+
guardedCommitPublished = hasCommitGuard;
|
|
3460
3937
|
const addedLines = products.filter(line => sameAvailabilityId(line.product_id ?? line.id, refreshedCandidate.productId) && line.metadata?.availability_candidate_key === refreshedCandidate.key && !beforeProductLineUids.has(getProductLineUid(line)));
|
|
3461
3938
|
productLineUids = addedLines.map(line => getProductLineUid(line)).filter(Boolean);
|
|
3462
|
-
const addedBookings = (this.getTempOrder()?.bookings || []).filter(item => item.metadata?.availability_candidate_key === refreshedCandidate.key && !beforeBookingUids.has(getBookingUid(item)));
|
|
3939
|
+
const addedBookings = ((hasCommitGuard ? rememberedOrder : this.getTempOrder())?.bookings || []).filter(item => item.metadata?.availability_candidate_key === refreshedCandidate.key && !beforeBookingUids.has(getBookingUid(item)));
|
|
3463
3940
|
bookingUids = addedBookings.map(item => getBookingUid(item)).filter(Boolean);
|
|
3464
3941
|
if (bookingUids.length === 0) {
|
|
3465
3942
|
bookingUids = addedLines.map(line => String(line.booking_uid ?? line.metadata?.booking_uid ?? '')).filter(Boolean);
|
|
@@ -3467,12 +3944,13 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
3467
3944
|
productLineUid = productLineUids[productLineUids.length - 1] || '';
|
|
3468
3945
|
bookingUid = bookingUids[bookingUids.length - 1] || '';
|
|
3469
3946
|
}
|
|
3470
|
-
|
|
3471
|
-
|
|
3947
|
+
const targetStillCurrent = !hasCommitGuard || availabilityCommitTargetGuard();
|
|
3948
|
+
if (targetStillCurrent) this.invalidateAvailabilityContextsAfterCommit();else this.removeAvailabilityContext(normalizedContextId);
|
|
3949
|
+
const nextContext = hasCommitGuard ? this.availabilityContextRegistry.get(normalizedContextId) : this.getAvailabilityContextEntry(normalizedContextId);
|
|
3472
3950
|
const result = {
|
|
3473
3951
|
contextId: normalizedContextId,
|
|
3474
|
-
contextVersion: nextContext.
|
|
3475
|
-
tempOrder: this.getTempOrder(),
|
|
3952
|
+
contextVersion: nextContext?.activeVersion ?? refreshedCandidate.availabilityContextVersion,
|
|
3953
|
+
tempOrder: hasCommitGuard ? rememberedOrder : this.getTempOrder(),
|
|
3476
3954
|
validation,
|
|
3477
3955
|
candidate: refreshedCandidate,
|
|
3478
3956
|
productLineUid,
|
|
@@ -3480,10 +3958,19 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
3480
3958
|
productLineUids,
|
|
3481
3959
|
bookingUids
|
|
3482
3960
|
};
|
|
3483
|
-
|
|
3961
|
+
if (targetStillCurrent) {
|
|
3962
|
+
// A notification failure after publication must not invite a duplicate add.
|
|
3963
|
+
const emit = () => this.core.effects.emit(`${this.name}:${_types.UnifiedBookingSalesHooks.onAvailabilitySelectionCommitted}`, result);
|
|
3964
|
+
if (guardedCommitPublished) {
|
|
3965
|
+
try {
|
|
3966
|
+
await emit();
|
|
3967
|
+
} catch {/* The cart mutation has already succeeded. */}
|
|
3968
|
+
} else await emit();
|
|
3969
|
+
}
|
|
3484
3970
|
return result;
|
|
3485
3971
|
} finally {
|
|
3486
|
-
|
|
3972
|
+
const release = this.releaseAvailabilityCommitLock(normalizedContextId);
|
|
3973
|
+
if (guardedCommitPublished) await release.catch(() => undefined);else await release;
|
|
3487
3974
|
}
|
|
3488
3975
|
}
|
|
3489
3976
|
}
|