@final-commerce/common 2.2.3-staging3.2 → 2.3.0-staging.1
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.mts +192 -4
- package/dist/index.d.ts +192 -4
- package/dist/index.js +448 -18
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +430 -15
- package/dist/index.mjs.map +1 -1
- package/dist/pos-types/index.d.mts +122 -113
- package/dist/pos-types/index.d.ts +122 -113
- package/dist/pos-types/index.js +0 -1
- package/dist/pos-types/index.js.map +1 -1
- package/dist/pos-types/index.mjs +0 -1
- package/dist/pos-types/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -19,8 +19,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
19
19
|
// index.ts
|
|
20
20
|
var index_exports = {};
|
|
21
21
|
__export(index_exports, {
|
|
22
|
-
ADYEN_ROLLOUT_COUNTRY_CODES: () => ADYEN_ROLLOUT_COUNTRY_CODES,
|
|
23
22
|
AttributeType: () => AttributeType,
|
|
23
|
+
BookingRatePeriod: () => BookingRatePeriod,
|
|
24
|
+
BookingResourceKind: () => BookingResourceKind,
|
|
25
|
+
BookingResourceStatus: () => BookingResourceStatus,
|
|
26
|
+
BookingRulesError: () => BookingRulesError,
|
|
27
|
+
BookingType: () => BookingType,
|
|
24
28
|
CONDITION_OPERATORS: () => CONDITION_OPERATORS,
|
|
25
29
|
COUNTRY_CURRENCIES: () => COUNTRY_CURRENCIES,
|
|
26
30
|
CURRENCY_CONFIG: () => CURRENCY_CONFIG,
|
|
@@ -31,6 +35,7 @@ __export(index_exports, {
|
|
|
31
35
|
DEFAULT_CONFIG: () => DEFAULT_CONFIG,
|
|
32
36
|
DEFAULT_CROSS_AXIS_RULES: () => DEFAULT_CROSS_AXIS_RULES,
|
|
33
37
|
DEFAULT_DISPLAY_STATE_MAP: () => DEFAULT_DISPLAY_STATE_MAP,
|
|
38
|
+
DEFAULT_HOLD_MINUTES: () => DEFAULT_HOLD_MINUTES,
|
|
34
39
|
DEFAULT_RESOLVED_CONFIG: () => DEFAULT_RESOLVED_CONFIG,
|
|
35
40
|
DEFAULT_UNIT_ID: () => DEFAULT_UNIT_ID,
|
|
36
41
|
DualWriteStrategy: () => DualWriteStrategy,
|
|
@@ -74,8 +79,10 @@ __export(index_exports, {
|
|
|
74
79
|
REPORT_NEGATE_PATHS: () => REPORT_NEGATE_PATHS,
|
|
75
80
|
ReceiptSendChannel: () => ReceiptSendChannel,
|
|
76
81
|
ReceiptType: () => ReceiptType,
|
|
82
|
+
ReservationStatus: () => ReservationStatus,
|
|
77
83
|
ResidualPaymentMethod: () => ResidualPaymentMethod,
|
|
78
84
|
ResidualStatus: () => ResidualStatus,
|
|
85
|
+
ResourceAvailabilityState: () => ResourceAvailabilityState,
|
|
79
86
|
STATE_SCHEMA: () => STATE_SCHEMA,
|
|
80
87
|
STRIPE_SUPPORTED_COUNTRIES: () => STRIPE_SUPPORTED_COUNTRIES,
|
|
81
88
|
SYSTEM_FAMILIES: () => SYSTEM_FAMILIES,
|
|
@@ -96,6 +103,8 @@ __export(index_exports, {
|
|
|
96
103
|
baseFulfillmentDefinitions: () => baseFulfillmentDefinitions,
|
|
97
104
|
baseOf: () => baseOf,
|
|
98
105
|
basePaymentDefinitions: () => basePaymentDefinitions,
|
|
106
|
+
buildAvailability: () => buildAvailability,
|
|
107
|
+
buildGrid: () => buildGrid,
|
|
99
108
|
buildOrderContext: () => buildOrderContext,
|
|
100
109
|
buildPosPresetConfig: () => buildPosPresetConfig,
|
|
101
110
|
buildResolvedConfig: () => buildResolvedConfig,
|
|
@@ -135,8 +144,8 @@ __export(index_exports, {
|
|
|
135
144
|
getPricingRegionFromCountry: () => getPricingRegionFromCountry,
|
|
136
145
|
getSettlementTotal: () => getSettlementTotal,
|
|
137
146
|
getTotalCollected: () => getTotalCollected,
|
|
147
|
+
holdExpiresAt: () => holdExpiresAt,
|
|
138
148
|
inferStatesFromLegacyStatus: () => inferStatesFromLegacyStatus,
|
|
139
|
-
isAdyenRolloutCountry: () => isAdyenRolloutCountry,
|
|
140
149
|
isConditionOperator: () => isConditionOperator,
|
|
141
150
|
isFulfillmentState: () => isFulfillmentState,
|
|
142
151
|
isPaymentState: () => isPaymentState,
|
|
@@ -149,9 +158,12 @@ __export(index_exports, {
|
|
|
149
158
|
mergeFragment: () => mergeFragment,
|
|
150
159
|
multiplyAmount: () => multiplyAmount,
|
|
151
160
|
normalizeMongoIdString: () => normalizeMongoIdString,
|
|
161
|
+
occupancyIn: () => occupancyIn,
|
|
152
162
|
parseAmountToMinorUnits: () => parseAmountToMinorUnits,
|
|
153
163
|
parseCurrencyCode: () => parseCurrencyCode,
|
|
154
164
|
parseStripeCurrencyCode: () => parseStripeCurrencyCode,
|
|
165
|
+
planWindow: () => planWindow,
|
|
166
|
+
presentAt: () => presentAt,
|
|
155
167
|
removeFragment: () => removeFragment,
|
|
156
168
|
resolveFamily: () => resolveFamily,
|
|
157
169
|
resolveTemplate: () => resolveTemplate,
|
|
@@ -160,13 +172,16 @@ __export(index_exports, {
|
|
|
160
172
|
sameFamily: () => sameFamily,
|
|
161
173
|
setOrderState: () => setOrderState,
|
|
162
174
|
signReportForDisplay: () => signReportForDisplay,
|
|
175
|
+
slotsForWindow: () => slotsForWindow,
|
|
163
176
|
stringifyMinorUnits: () => stringifyMinorUnits,
|
|
164
177
|
subtractAmounts: () => subtractAmounts,
|
|
165
178
|
toBase: () => toBase,
|
|
166
179
|
toMinorUnits: () => toMinorUnits,
|
|
167
180
|
validateConfig: () => validateConfig,
|
|
168
181
|
validateFragmentCompatibility: () => validateFragmentCompatibility,
|
|
169
|
-
violatesFinancialInvariant: () => violatesFinancialInvariant
|
|
182
|
+
violatesFinancialInvariant: () => violatesFinancialInvariant,
|
|
183
|
+
windowEndsAt: () => windowEndsAt,
|
|
184
|
+
withinPresence: () => withinPresence
|
|
170
185
|
});
|
|
171
186
|
module.exports = __toCommonJS(index_exports);
|
|
172
187
|
|
|
@@ -592,6 +607,7 @@ var StripeCurrencyCode = /* @__PURE__ */ ((StripeCurrencyCode2) => {
|
|
|
592
607
|
var ProductType = /* @__PURE__ */ ((ProductType2) => {
|
|
593
608
|
ProductType2["SIMPLE"] = "simple";
|
|
594
609
|
ProductType2["VARIABLE"] = "variable";
|
|
610
|
+
ProductType2["BOOKING"] = "booking";
|
|
595
611
|
return ProductType2;
|
|
596
612
|
})(ProductType || {});
|
|
597
613
|
var ProductStatus = /* @__PURE__ */ ((ProductStatus2) => {
|
|
@@ -609,6 +625,50 @@ var PaymentProcessor = /* @__PURE__ */ ((PaymentProcessor2) => {
|
|
|
609
625
|
return PaymentProcessor2;
|
|
610
626
|
})(PaymentProcessor || {});
|
|
611
627
|
|
|
628
|
+
// enums/booking.enum.ts
|
|
629
|
+
var BookingType = /* @__PURE__ */ ((BookingType3) => {
|
|
630
|
+
BookingType3["RENTAL"] = "rental";
|
|
631
|
+
BookingType3["APPOINTMENT"] = "appointment";
|
|
632
|
+
BookingType3["OVERNIGHT"] = "overnight";
|
|
633
|
+
BookingType3["GROUP"] = "group";
|
|
634
|
+
return BookingType3;
|
|
635
|
+
})(BookingType || {});
|
|
636
|
+
var BookingRatePeriod = /* @__PURE__ */ ((BookingRatePeriod2) => {
|
|
637
|
+
BookingRatePeriod2["SLOT"] = "slot";
|
|
638
|
+
BookingRatePeriod2["DAY"] = "day";
|
|
639
|
+
BookingRatePeriod2["NIGHT"] = "night";
|
|
640
|
+
BookingRatePeriod2["WEEK"] = "week";
|
|
641
|
+
BookingRatePeriod2["MONTH"] = "month";
|
|
642
|
+
return BookingRatePeriod2;
|
|
643
|
+
})(BookingRatePeriod || {});
|
|
644
|
+
var BookingResourceKind = /* @__PURE__ */ ((BookingResourceKind2) => {
|
|
645
|
+
BookingResourceKind2["ASSET"] = "asset";
|
|
646
|
+
BookingResourceKind2["ROOM"] = "room";
|
|
647
|
+
BookingResourceKind2["PROPERTY"] = "property";
|
|
648
|
+
BookingResourceKind2["SPACE"] = "space";
|
|
649
|
+
BookingResourceKind2["STAFF"] = "staff";
|
|
650
|
+
BookingResourceKind2["TABLE"] = "table";
|
|
651
|
+
return BookingResourceKind2;
|
|
652
|
+
})(BookingResourceKind || {});
|
|
653
|
+
var ReservationStatus = /* @__PURE__ */ ((ReservationStatus2) => {
|
|
654
|
+
ReservationStatus2["HELD"] = "held";
|
|
655
|
+
ReservationStatus2["CONFIRMED"] = "confirmed";
|
|
656
|
+
ReservationStatus2["CANCELLED"] = "cancelled";
|
|
657
|
+
ReservationStatus2["EXPIRED"] = "expired";
|
|
658
|
+
return ReservationStatus2;
|
|
659
|
+
})(ReservationStatus || {});
|
|
660
|
+
var BookingResourceStatus = /* @__PURE__ */ ((BookingResourceStatus2) => {
|
|
661
|
+
BookingResourceStatus2["ACTIVE"] = "active";
|
|
662
|
+
BookingResourceStatus2["INACTIVE"] = "inactive";
|
|
663
|
+
return BookingResourceStatus2;
|
|
664
|
+
})(BookingResourceStatus || {});
|
|
665
|
+
var ResourceAvailabilityState = /* @__PURE__ */ ((ResourceAvailabilityState2) => {
|
|
666
|
+
ResourceAvailabilityState2["AVAILABLE"] = "available";
|
|
667
|
+
ResourceAvailabilityState2["FULLY_BOOKED"] = "fullyBooked";
|
|
668
|
+
ResourceAvailabilityState2["NO_HOURS_IN_RANGE"] = "noHoursInRange";
|
|
669
|
+
return ResourceAvailabilityState2;
|
|
670
|
+
})(ResourceAvailabilityState || {});
|
|
671
|
+
|
|
612
672
|
// utils/currency.util.ts
|
|
613
673
|
var CURRENCY_CONFIG = {
|
|
614
674
|
// Major currencies with 2 decimal places
|
|
@@ -1818,6 +1878,15 @@ function sumRefundQuantities(refunds) {
|
|
|
1818
1878
|
}
|
|
1819
1879
|
return t;
|
|
1820
1880
|
}
|
|
1881
|
+
var asCount = (value) => typeof value === "number" && Number.isFinite(value) ? Math.max(0, value) : 0;
|
|
1882
|
+
function countsOf(row) {
|
|
1883
|
+
const quantity = asCount(row.quantity);
|
|
1884
|
+
return {
|
|
1885
|
+
quantity,
|
|
1886
|
+
fulfilled: Math.min(asCount(row.fulfilledQuantity), quantity),
|
|
1887
|
+
returned: Math.min(asCount(row.returnedQuantity), quantity)
|
|
1888
|
+
};
|
|
1889
|
+
}
|
|
1821
1890
|
function buildOrderContext(order, extras) {
|
|
1822
1891
|
const o = order ?? {};
|
|
1823
1892
|
const total = typeof o.total === "number" && Number.isFinite(o.total) ? o.total : 0;
|
|
@@ -1825,6 +1894,7 @@ function buildOrderContext(order, extras) {
|
|
|
1825
1894
|
const paymentMethods = o.paymentMethods ?? [];
|
|
1826
1895
|
const refunds = o.refunds ?? o.refund ?? [];
|
|
1827
1896
|
const lineItems = o.lineItems ?? [];
|
|
1897
|
+
const reservations = o.reservations ?? [];
|
|
1828
1898
|
const totalPaid = sumNumbers(paymentMethods);
|
|
1829
1899
|
const totalRefunded = sumRefundQuantities(refunds);
|
|
1830
1900
|
const isFullyPaid = total > 0 ? balance <= 0 : totalPaid > 0 && balance <= 0;
|
|
@@ -1832,13 +1902,11 @@ function buildOrderContext(order, extras) {
|
|
|
1832
1902
|
let itemCount = 0;
|
|
1833
1903
|
let fulfilledItemCount = 0;
|
|
1834
1904
|
let returnedItemCount = 0;
|
|
1835
|
-
for (const
|
|
1836
|
-
const
|
|
1837
|
-
itemCount +=
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
const rq = typeof li.returnedQuantity === "number" && Number.isFinite(li.returnedQuantity) ? Math.max(0, li.returnedQuantity) : 0;
|
|
1841
|
-
returnedItemCount += Math.min(rq, q);
|
|
1905
|
+
for (const row of [...lineItems, ...reservations]) {
|
|
1906
|
+
const { quantity, fulfilled, returned } = countsOf(row);
|
|
1907
|
+
itemCount += quantity;
|
|
1908
|
+
fulfilledItemCount += fulfilled;
|
|
1909
|
+
returnedItemCount += returned;
|
|
1842
1910
|
}
|
|
1843
1911
|
const isFullyReturned = itemCount > 0 ? returnedItemCount >= itemCount : returnedItemCount > 0;
|
|
1844
1912
|
const hasPartialReturns = returnedItemCount > 0 && !isFullyReturned;
|
|
@@ -1848,7 +1916,8 @@ function buildOrderContext(order, extras) {
|
|
|
1848
1916
|
balance,
|
|
1849
1917
|
paymentMethods,
|
|
1850
1918
|
refunds,
|
|
1851
|
-
lineItems
|
|
1919
|
+
lineItems,
|
|
1920
|
+
reservations
|
|
1852
1921
|
},
|
|
1853
1922
|
computed: {
|
|
1854
1923
|
balance,
|
|
@@ -2928,15 +2997,365 @@ var ORDER_STATE_EVENTS = {
|
|
|
2928
2997
|
configUpdated: "config-updated"
|
|
2929
2998
|
};
|
|
2930
2999
|
|
|
2931
|
-
//
|
|
2932
|
-
var
|
|
2933
|
-
|
|
2934
|
-
|
|
3000
|
+
// booking/types.ts
|
|
3001
|
+
var BookingRulesError = class extends Error {
|
|
3002
|
+
constructor(message) {
|
|
3003
|
+
super(message);
|
|
3004
|
+
this.name = "BookingRulesError";
|
|
3005
|
+
}
|
|
3006
|
+
};
|
|
3007
|
+
|
|
3008
|
+
// booking/zoned-time.ts
|
|
3009
|
+
var MINUTE = 6e4;
|
|
3010
|
+
var formatters = /* @__PURE__ */ new Map();
|
|
3011
|
+
var formatterFor = (timeZone) => {
|
|
3012
|
+
const existing = formatters.get(timeZone);
|
|
3013
|
+
if (existing) return existing;
|
|
3014
|
+
let created;
|
|
3015
|
+
try {
|
|
3016
|
+
created = new Intl.DateTimeFormat("en-US", {
|
|
3017
|
+
timeZone,
|
|
3018
|
+
hour12: false,
|
|
3019
|
+
year: "numeric",
|
|
3020
|
+
month: "2-digit",
|
|
3021
|
+
day: "2-digit",
|
|
3022
|
+
hour: "2-digit",
|
|
3023
|
+
minute: "2-digit",
|
|
3024
|
+
second: "2-digit",
|
|
3025
|
+
weekday: "short"
|
|
3026
|
+
});
|
|
3027
|
+
} catch {
|
|
3028
|
+
throw new BookingRulesError(`Booking rules: "${timeZone}" is not a known time zone`);
|
|
3029
|
+
}
|
|
3030
|
+
formatters.set(timeZone, created);
|
|
3031
|
+
return created;
|
|
3032
|
+
};
|
|
3033
|
+
var WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
3034
|
+
function zonedParts(instant, timeZone) {
|
|
3035
|
+
const parts = {};
|
|
3036
|
+
for (const { type, value } of formatterFor(timeZone).formatToParts(instant)) parts[type] = value;
|
|
3037
|
+
const hour = Number(parts.hour) % 24;
|
|
3038
|
+
return {
|
|
3039
|
+
year: Number(parts.year),
|
|
3040
|
+
month: Number(parts.month),
|
|
3041
|
+
day: Number(parts.day),
|
|
3042
|
+
hour,
|
|
3043
|
+
minute: Number(parts.minute),
|
|
3044
|
+
second: Number(parts.second),
|
|
3045
|
+
weekday: WEEKDAYS.indexOf(parts.weekday)
|
|
3046
|
+
};
|
|
3047
|
+
}
|
|
3048
|
+
function offsetAt(instant, timeZone) {
|
|
3049
|
+
const { year, month, day, hour, minute, second } = zonedParts(instant, timeZone);
|
|
3050
|
+
return Date.UTC(year, month - 1, day, hour, minute, second) - Math.floor(instant.getTime() / 1e3) * 1e3;
|
|
3051
|
+
}
|
|
3052
|
+
function instantFromLocal(year, month, day, minutes, timeZone) {
|
|
3053
|
+
const naive = Date.UTC(year, month - 1, day) + minutes * MINUTE;
|
|
3054
|
+
const firstGuess = naive - offsetAt(new Date(naive), timeZone);
|
|
3055
|
+
return new Date(naive - offsetAt(new Date(firstGuess), timeZone));
|
|
3056
|
+
}
|
|
3057
|
+
function startOfZonedDay(instant, timeZone) {
|
|
3058
|
+
const { year, month, day } = zonedParts(instant, timeZone);
|
|
3059
|
+
return instantFromLocal(year, month, day, 0, timeZone);
|
|
3060
|
+
}
|
|
3061
|
+
function nextZonedDay(dayStart, timeZone) {
|
|
3062
|
+
const { year, month, day } = zonedParts(dayStart, timeZone);
|
|
3063
|
+
return instantFromLocal(year, month, day + 1, 0, timeZone);
|
|
3064
|
+
}
|
|
3065
|
+
function atZonedMinute(dayStart, minutes, timeZone) {
|
|
3066
|
+
const { year, month, day } = zonedParts(dayStart, timeZone);
|
|
3067
|
+
return instantFromLocal(year, month, day, minutes, timeZone);
|
|
3068
|
+
}
|
|
3069
|
+
|
|
3070
|
+
// booking/grid.ts
|
|
3071
|
+
var MINUTE2 = 6e4;
|
|
3072
|
+
var MAX_RANGE_DAYS = 366;
|
|
3073
|
+
var asTime = (value, field) => {
|
|
3074
|
+
if (value == null) return void 0;
|
|
3075
|
+
const time = new Date(value).getTime();
|
|
3076
|
+
if (Number.isNaN(time)) throw new BookingRulesError(`Booking rules: ${field} is not a date, got "${String(value)}"`);
|
|
3077
|
+
return time;
|
|
3078
|
+
};
|
|
3079
|
+
function parseHhMm(value, field) {
|
|
3080
|
+
const [hours, minutes] = value.split(":").map(Number);
|
|
3081
|
+
if (!Number.isInteger(hours) || !Number.isInteger(minutes)) {
|
|
3082
|
+
throw new BookingRulesError(`Booking rules: ${field} must be "HH:mm", got "${value}"`);
|
|
3083
|
+
}
|
|
3084
|
+
return hours * 60 + minutes;
|
|
3085
|
+
}
|
|
3086
|
+
function windowEndsAt(rule, now) {
|
|
3087
|
+
const { maxAdvanceDays, bookableTo } = rule.window;
|
|
3088
|
+
const ends = Math.min(
|
|
3089
|
+
maxAdvanceDays ? now.getTime() + maxAdvanceDays * 1440 * MINUTE2 : Number.POSITIVE_INFINITY,
|
|
3090
|
+
asTime(bookableTo, "window.bookableTo") ?? Number.POSITIVE_INFINITY
|
|
3091
|
+
);
|
|
3092
|
+
return Number.isFinite(ends) ? new Date(ends) : null;
|
|
3093
|
+
}
|
|
3094
|
+
function periodSlot(dayStart, timeZone, rule, openMinute, closeMinute) {
|
|
3095
|
+
const { ratePeriod, slot, checkInTime, checkOutTime } = rule;
|
|
3096
|
+
const at = (minutes) => atZonedMinute(dayStart, minutes, timeZone);
|
|
3097
|
+
if (ratePeriod === "night" /* NIGHT */) {
|
|
3098
|
+
if (!checkInTime || !checkOutTime) {
|
|
3099
|
+
throw new BookingRulesError("Overnight booking rules need both checkInTime and checkOutTime");
|
|
3100
|
+
}
|
|
3101
|
+
const checkIn = parseHhMm(checkInTime, "checkInTime");
|
|
3102
|
+
const checkOut = parseHhMm(checkOutTime, "checkOutTime");
|
|
3103
|
+
return {
|
|
3104
|
+
startAt: at(checkIn),
|
|
3105
|
+
endAt: at(1440 + checkOut),
|
|
3106
|
+
// Housekeeping sits between check-out and the next check-in: it costs no night.
|
|
3107
|
+
bufferEndAt: at(1440 + checkIn),
|
|
3108
|
+
canStart: true
|
|
3109
|
+
};
|
|
3110
|
+
}
|
|
3111
|
+
const from = ratePeriod === "day" /* DAY */ ? openMinute ?? 0 : 0;
|
|
3112
|
+
const to = ratePeriod === "day" /* DAY */ ? closeMinute ?? 1440 : 1440;
|
|
3113
|
+
return {
|
|
3114
|
+
startAt: at(from),
|
|
3115
|
+
endAt: at(to),
|
|
3116
|
+
bufferEndAt: at(to + (slot.bufferMinutes ?? 0)),
|
|
3117
|
+
canStart: true
|
|
3118
|
+
};
|
|
3119
|
+
}
|
|
3120
|
+
function freeGaps(openAt, closeAt, busy) {
|
|
3121
|
+
const inside = busy.map(({ startAt, bufferEndAt }) => ({ from: +startAt, to: +bufferEndAt })).filter(({ from, to }) => to > openAt && from < closeAt).sort((first, second) => first.from - second.from);
|
|
3122
|
+
const gaps = [];
|
|
3123
|
+
let cursor = openAt;
|
|
3124
|
+
for (const { from, to } of inside) {
|
|
3125
|
+
if (from > cursor) gaps.push({ from: cursor, to: from, endsAtBooking: true });
|
|
3126
|
+
cursor = Math.max(cursor, to);
|
|
3127
|
+
if (cursor >= closeAt) break;
|
|
3128
|
+
}
|
|
3129
|
+
if (cursor < closeAt) gaps.push({ from: cursor, to: closeAt, endsAtBooking: false });
|
|
3130
|
+
return gaps;
|
|
3131
|
+
}
|
|
3132
|
+
function buildGrid(rule, timeZone, from, to, now, busy = []) {
|
|
3133
|
+
if (to.getTime() - from.getTime() > MAX_RANGE_DAYS * 1440 * MINUTE2) {
|
|
3134
|
+
throw new BookingRulesError(`A booking range may span at most ${MAX_RANGE_DAYS} days`);
|
|
3135
|
+
}
|
|
3136
|
+
const { availability, slot, window, ratePeriod } = rule;
|
|
3137
|
+
const periodMode = ratePeriod !== "slot" /* SLOT */;
|
|
3138
|
+
const weeklyHours = availability.weeklyHours ?? [];
|
|
3139
|
+
const notBefore = Math.max(
|
|
3140
|
+
now.getTime() + (window.minNoticeMinutes ?? 0) * MINUTE2,
|
|
3141
|
+
asTime(window.bookableFrom, "window.bookableFrom") ?? 0,
|
|
3142
|
+
asTime(availability.availableFrom, "availability.availableFrom") ?? 0
|
|
3143
|
+
);
|
|
3144
|
+
const notAfter = windowEndsAt(rule, now)?.getTime() ?? Number.POSITIVE_INFINITY;
|
|
3145
|
+
const blocked = (availability.offMarket ?? []).map(({ from: blockFrom, to: blockTo }) => ({
|
|
3146
|
+
from: asTime(blockFrom, "availability.offMarket.from"),
|
|
3147
|
+
to: asTime(blockTo, "availability.offMarket.to")
|
|
3148
|
+
}));
|
|
3149
|
+
const arrivalDays = availability.arrivalDays;
|
|
3150
|
+
const step = slot.lengthMinutes + (slot.bufferMinutes ?? 0);
|
|
3151
|
+
const slots = [];
|
|
3152
|
+
const lastDay = startOfZonedDay(to, timeZone).getTime();
|
|
3153
|
+
let dayStart = startOfZonedDay(from, timeZone);
|
|
3154
|
+
while (dayStart.getTime() <= lastDay) {
|
|
3155
|
+
const weekday = zonedParts(dayStart, timeZone).weekday;
|
|
3156
|
+
const rows = weeklyHours.filter((entry) => entry.weekday === weekday);
|
|
3157
|
+
if (periodMode) {
|
|
3158
|
+
const openToday = weeklyHours.length === 0 || rows.length > 0;
|
|
3159
|
+
if (openToday) {
|
|
3160
|
+
const built = periodSlot(dayStart, timeZone, rule, rows[0]?.openMinute, rows[0]?.closeMinute);
|
|
3161
|
+
slots.push({ ...built, canStart: !arrivalDays?.length || arrivalDays.includes(weekday) });
|
|
3162
|
+
}
|
|
3163
|
+
} else {
|
|
3164
|
+
for (const row of rows) {
|
|
3165
|
+
const openAt = +atZonedMinute(dayStart, row.openMinute, timeZone);
|
|
3166
|
+
const closeAt = +atZonedMinute(dayStart, row.closeMinute, timeZone);
|
|
3167
|
+
for (const gap of freeGaps(openAt, closeAt, busy)) {
|
|
3168
|
+
const mustFit = (gap.endsAtBooking ? step : slot.lengthMinutes) * MINUTE2;
|
|
3169
|
+
for (let start = gap.from; start + mustFit <= gap.to; start += step * MINUTE2) {
|
|
3170
|
+
slots.push({
|
|
3171
|
+
startAt: new Date(start),
|
|
3172
|
+
endAt: new Date(start + slot.lengthMinutes * MINUTE2),
|
|
3173
|
+
bufferEndAt: new Date(start + step * MINUTE2),
|
|
3174
|
+
canStart: true
|
|
3175
|
+
});
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
}
|
|
3179
|
+
}
|
|
3180
|
+
dayStart = nextZonedDay(dayStart, timeZone);
|
|
3181
|
+
}
|
|
3182
|
+
const askedFrom = from.getTime();
|
|
3183
|
+
const askedTo = to.getTime();
|
|
3184
|
+
return slots.filter(({ startAt, bufferEndAt }) => {
|
|
3185
|
+
const start = startAt.getTime();
|
|
3186
|
+
if (start < askedFrom || start >= askedTo) return false;
|
|
3187
|
+
if (start < notBefore || start > notAfter) return false;
|
|
3188
|
+
return !blocked.some(({ from: blockFrom, to: blockTo }) => start < blockTo && bufferEndAt.getTime() > blockFrom);
|
|
3189
|
+
}).sort((first, second) => +first.startAt - +second.startAt);
|
|
3190
|
+
}
|
|
3191
|
+
function slotsForWindow(grid, startAt, endAt) {
|
|
3192
|
+
const startIndex = grid.findIndex((entry) => entry.startAt.getTime() === startAt.getTime());
|
|
3193
|
+
if (startIndex < 0) {
|
|
3194
|
+
throw new BookingRulesError(`${startAt.toISOString()} is not a bookable slot start for these rules`);
|
|
3195
|
+
}
|
|
3196
|
+
if (!grid[startIndex].canStart) {
|
|
3197
|
+
throw new BookingRulesError(`${startAt.toISOString()} is not an arrival day for these rules`);
|
|
3198
|
+
}
|
|
3199
|
+
const covered = [];
|
|
3200
|
+
for (let index = startIndex; index < grid.length; index++) {
|
|
3201
|
+
const entry = grid[index];
|
|
3202
|
+
const previous = covered[covered.length - 1];
|
|
3203
|
+
if (previous && entry.startAt.getTime() !== previous.bufferEndAt.getTime()) break;
|
|
3204
|
+
covered.push(entry);
|
|
3205
|
+
if (entry.endAt.getTime() === endAt.getTime()) return covered;
|
|
3206
|
+
if (entry.endAt.getTime() > endAt.getTime()) break;
|
|
3207
|
+
}
|
|
3208
|
+
throw new BookingRulesError(
|
|
3209
|
+
`${startAt.toISOString()} \u2192 ${endAt.toISOString()} does not line up with the bookable grid`
|
|
3210
|
+
);
|
|
3211
|
+
}
|
|
3212
|
+
|
|
3213
|
+
// booking/availability.ts
|
|
3214
|
+
var withinPresence = (slot, timeZone, presence) => {
|
|
3215
|
+
if (!presence?.length) return true;
|
|
3216
|
+
const dayStart = startOfZonedDay(slot.startAt, timeZone);
|
|
3217
|
+
const weekday = zonedParts(dayStart, timeZone).weekday;
|
|
3218
|
+
return presence.some(
|
|
3219
|
+
(shift) => shift.weekday === weekday && slot.startAt.getTime() >= atZonedMinute(dayStart, shift.openMinute, timeZone).getTime() && slot.endAt.getTime() <= atZonedMinute(dayStart, shift.closeMinute, timeZone).getTime()
|
|
3220
|
+
);
|
|
3221
|
+
};
|
|
3222
|
+
var overlaps = (entry, startAt, bufferEndAt) => entry.startAt.getTime() < bufferEndAt.getTime() && entry.bufferEndAt.getTime() > startAt.getTime();
|
|
3223
|
+
function occupancyIn(occupied, productId, startAt, bufferEndAt, shared) {
|
|
3224
|
+
const clashing = occupied.filter((entry) => overlaps(entry, startAt, bufferEndAt));
|
|
3225
|
+
const foreign = shared ? void 0 : clashing.find((entry) => entry.productId !== productId);
|
|
3226
|
+
return {
|
|
3227
|
+
taken: clashing.filter((entry) => shared || entry.productId === productId).reduce((sum, entry) => sum + entry.qty, 0),
|
|
3228
|
+
foreignProductId: foreign?.productId
|
|
3229
|
+
};
|
|
3230
|
+
}
|
|
3231
|
+
var stateFor = (hasShift, offerable, free) => {
|
|
3232
|
+
if (offerable === 0) {
|
|
3233
|
+
return hasShift() ? "fullyBooked" /* FULLY_BOOKED */ : "noHoursInRange" /* NO_HOURS_IN_RANGE */;
|
|
3234
|
+
}
|
|
3235
|
+
return free === 0 ? "fullyBooked" /* FULLY_BOOKED */ : "available" /* AVAILABLE */;
|
|
3236
|
+
};
|
|
3237
|
+
function buildAvailability({
|
|
3238
|
+
productId,
|
|
3239
|
+
bookingRulesId,
|
|
3240
|
+
bookingType,
|
|
3241
|
+
rule,
|
|
3242
|
+
resources,
|
|
3243
|
+
occupied,
|
|
3244
|
+
from,
|
|
3245
|
+
to,
|
|
3246
|
+
now
|
|
3247
|
+
}) {
|
|
3248
|
+
const shared = rule.slot.sharedAcrossProducts ?? false;
|
|
3249
|
+
const bookableUntil = windowEndsAt(rule, now);
|
|
3250
|
+
const header = {
|
|
3251
|
+
productId,
|
|
3252
|
+
bookingRulesId,
|
|
3253
|
+
bookingType,
|
|
3254
|
+
ratePeriod: rule.ratePeriod,
|
|
3255
|
+
// Passed through for the UI to label: a group slot under minimumToRun may not run.
|
|
3256
|
+
minimumToRun: rule.slot.minimumToRun,
|
|
3257
|
+
minStay: rule.slot.minStay,
|
|
3258
|
+
maxStay: rule.slot.maxStay,
|
|
3259
|
+
// Why a day can come back empty. Past this instant the rule refuses everything, so the UI
|
|
3260
|
+
// can say "beyond the booking window" where it would otherwise claim "nothing free".
|
|
3261
|
+
bookableUntil,
|
|
3262
|
+
// The comparison itself, not just the boundary: deciding whether a day lies past the
|
|
3263
|
+
// window is a rule, and a UI that re-derives it owns a copy of that rule — which is how
|
|
3264
|
+
// the same empty grid ends up explained one way on the till and another in a flow.
|
|
3265
|
+
beyondWindow: !!bookableUntil && from.getTime() > bookableUntil.getTime()
|
|
3266
|
+
};
|
|
3267
|
+
const byResource = /* @__PURE__ */ new Map();
|
|
3268
|
+
for (const entry of occupied) {
|
|
3269
|
+
byResource.set(entry.resourceId, [...byResource.get(entry.resourceId) ?? [], entry]);
|
|
3270
|
+
}
|
|
3271
|
+
const grids = resources.map((resource) => {
|
|
3272
|
+
const mine = byResource.get(resource.id) ?? [];
|
|
3273
|
+
const busy = (resource.seats ?? 1) === 1 ? mine : [];
|
|
3274
|
+
const slots = buildGrid(rule, resource.timeZone, from, to, now, busy).filter(
|
|
3275
|
+
(slot) => withinPresence(slot, resource.timeZone, resource.presence)
|
|
3276
|
+
);
|
|
3277
|
+
return { resource, slots };
|
|
3278
|
+
});
|
|
3279
|
+
const byInstant = /* @__PURE__ */ new Map();
|
|
3280
|
+
const freeSlotsPerResource = /* @__PURE__ */ new Map();
|
|
3281
|
+
for (const { resource, slots } of grids) {
|
|
3282
|
+
const capacity = resource.seats ?? 1;
|
|
3283
|
+
const mine = byResource.get(resource.id) ?? [];
|
|
3284
|
+
for (const slot of slots) {
|
|
3285
|
+
const { taken, foreignProductId } = occupancyIn(mine, productId, slot.startAt, slot.bufferEndAt, shared);
|
|
3286
|
+
const free = foreignProductId ? 0 : Math.max(capacity - taken, 0);
|
|
3287
|
+
const entry = byInstant.get(+slot.startAt) ?? { ...slot, capacity: 0, booked: 0, free: 0, resources: [] };
|
|
3288
|
+
entry.capacity += capacity;
|
|
3289
|
+
entry.booked += foreignProductId ? capacity : taken;
|
|
3290
|
+
entry.free += free;
|
|
3291
|
+
entry.canStart = entry.canStart || slot.canStart;
|
|
3292
|
+
entry.resources.push({ resourceId: resource.id, name: resource.name, capacity, booked: taken, free });
|
|
3293
|
+
if (free > 0) freeSlotsPerResource.set(resource.id, (freeSlotsPerResource.get(resource.id) ?? 0) + 1);
|
|
3294
|
+
byInstant.set(+slot.startAt, entry);
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3297
|
+
const inWindow = grids.map(({ resource, slots }) => {
|
|
3298
|
+
const free = freeSlotsPerResource.get(resource.id) ?? 0;
|
|
3299
|
+
return {
|
|
3300
|
+
resourceId: resource.id,
|
|
3301
|
+
name: resource.name,
|
|
3302
|
+
slots: slots.length,
|
|
3303
|
+
free,
|
|
3304
|
+
// Lazily: the shift grid is a second walk, and only an empty answer needs it.
|
|
3305
|
+
state: stateFor(
|
|
3306
|
+
() => buildGrid(rule, resource.timeZone, from, to, now).filter(
|
|
3307
|
+
(slot) => withinPresence(slot, resource.timeZone, resource.presence)
|
|
3308
|
+
).length > 0,
|
|
3309
|
+
slots.length,
|
|
3310
|
+
free
|
|
3311
|
+
)
|
|
3312
|
+
};
|
|
3313
|
+
});
|
|
3314
|
+
return {
|
|
3315
|
+
...header,
|
|
3316
|
+
resources: inWindow,
|
|
3317
|
+
slots: [...byInstant.values()].sort((first, second) => +first.startAt - +second.startAt)
|
|
3318
|
+
};
|
|
3319
|
+
}
|
|
3320
|
+
function presentAt(resources, rows, outletId) {
|
|
3321
|
+
const byResource = /* @__PURE__ */ new Map();
|
|
3322
|
+
for (const row of rows) {
|
|
3323
|
+
byResource.set(row.resourceId, [...byResource.get(row.resourceId) ?? [], row]);
|
|
3324
|
+
}
|
|
3325
|
+
return resources.flatMap((resource) => {
|
|
3326
|
+
const own = byResource.get(resource.id) ?? [];
|
|
3327
|
+
if (own.length === 0 || !outletId) return [{ resource }];
|
|
3328
|
+
const here = own.find((row) => row.outletId === outletId);
|
|
3329
|
+
return here ? [{ resource, presence: here.weeklyHours }] : [];
|
|
3330
|
+
});
|
|
3331
|
+
}
|
|
3332
|
+
|
|
3333
|
+
// booking/plan.ts
|
|
3334
|
+
var DEFAULT_HOLD_MINUTES = 10;
|
|
3335
|
+
function planWindow({ rule, timeZone, presence, startAt, endAt, now, busy = [] }) {
|
|
3336
|
+
const grid = buildGrid(rule, timeZone, startAt, endAt, now, busy).filter(
|
|
3337
|
+
(slot) => withinPresence(slot, timeZone, presence)
|
|
3338
|
+
);
|
|
3339
|
+
const slots = slotsForWindow(grid, startAt, endAt);
|
|
3340
|
+
const { minStay, maxStay } = rule.slot;
|
|
3341
|
+
if (minStay && slots.length < minStay) {
|
|
3342
|
+
throw new BookingRulesError(`These rules ask for at least ${minStay} period(s), got ${slots.length}`);
|
|
3343
|
+
}
|
|
3344
|
+
if (maxStay && slots.length > maxStay) {
|
|
3345
|
+
throw new BookingRulesError(`These rules allow at most ${maxStay} period(s), got ${slots.length}`);
|
|
3346
|
+
}
|
|
3347
|
+
const last = slots[slots.length - 1];
|
|
3348
|
+
return { slots, endAt: last.endAt, bufferEndAt: last.bufferEndAt };
|
|
2935
3349
|
}
|
|
3350
|
+
var holdExpiresAt = (rule, now) => new Date(now.getTime() + (rule.window.holdMinutes ?? DEFAULT_HOLD_MINUTES) * 6e4);
|
|
2936
3351
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2937
3352
|
0 && (module.exports = {
|
|
2938
|
-
ADYEN_ROLLOUT_COUNTRY_CODES,
|
|
2939
3353
|
AttributeType,
|
|
3354
|
+
BookingRatePeriod,
|
|
3355
|
+
BookingResourceKind,
|
|
3356
|
+
BookingResourceStatus,
|
|
3357
|
+
BookingRulesError,
|
|
3358
|
+
BookingType,
|
|
2940
3359
|
CONDITION_OPERATORS,
|
|
2941
3360
|
COUNTRY_CURRENCIES,
|
|
2942
3361
|
CURRENCY_CONFIG,
|
|
@@ -2947,6 +3366,7 @@ function isAdyenRolloutCountry(alpha2) {
|
|
|
2947
3366
|
DEFAULT_CONFIG,
|
|
2948
3367
|
DEFAULT_CROSS_AXIS_RULES,
|
|
2949
3368
|
DEFAULT_DISPLAY_STATE_MAP,
|
|
3369
|
+
DEFAULT_HOLD_MINUTES,
|
|
2950
3370
|
DEFAULT_RESOLVED_CONFIG,
|
|
2951
3371
|
DEFAULT_UNIT_ID,
|
|
2952
3372
|
DualWriteStrategy,
|
|
@@ -2990,8 +3410,10 @@ function isAdyenRolloutCountry(alpha2) {
|
|
|
2990
3410
|
REPORT_NEGATE_PATHS,
|
|
2991
3411
|
ReceiptSendChannel,
|
|
2992
3412
|
ReceiptType,
|
|
3413
|
+
ReservationStatus,
|
|
2993
3414
|
ResidualPaymentMethod,
|
|
2994
3415
|
ResidualStatus,
|
|
3416
|
+
ResourceAvailabilityState,
|
|
2995
3417
|
STATE_SCHEMA,
|
|
2996
3418
|
STRIPE_SUPPORTED_COUNTRIES,
|
|
2997
3419
|
SYSTEM_FAMILIES,
|
|
@@ -3012,6 +3434,8 @@ function isAdyenRolloutCountry(alpha2) {
|
|
|
3012
3434
|
baseFulfillmentDefinitions,
|
|
3013
3435
|
baseOf,
|
|
3014
3436
|
basePaymentDefinitions,
|
|
3437
|
+
buildAvailability,
|
|
3438
|
+
buildGrid,
|
|
3015
3439
|
buildOrderContext,
|
|
3016
3440
|
buildPosPresetConfig,
|
|
3017
3441
|
buildResolvedConfig,
|
|
@@ -3051,8 +3475,8 @@ function isAdyenRolloutCountry(alpha2) {
|
|
|
3051
3475
|
getPricingRegionFromCountry,
|
|
3052
3476
|
getSettlementTotal,
|
|
3053
3477
|
getTotalCollected,
|
|
3478
|
+
holdExpiresAt,
|
|
3054
3479
|
inferStatesFromLegacyStatus,
|
|
3055
|
-
isAdyenRolloutCountry,
|
|
3056
3480
|
isConditionOperator,
|
|
3057
3481
|
isFulfillmentState,
|
|
3058
3482
|
isPaymentState,
|
|
@@ -3065,9 +3489,12 @@ function isAdyenRolloutCountry(alpha2) {
|
|
|
3065
3489
|
mergeFragment,
|
|
3066
3490
|
multiplyAmount,
|
|
3067
3491
|
normalizeMongoIdString,
|
|
3492
|
+
occupancyIn,
|
|
3068
3493
|
parseAmountToMinorUnits,
|
|
3069
3494
|
parseCurrencyCode,
|
|
3070
3495
|
parseStripeCurrencyCode,
|
|
3496
|
+
planWindow,
|
|
3497
|
+
presentAt,
|
|
3071
3498
|
removeFragment,
|
|
3072
3499
|
resolveFamily,
|
|
3073
3500
|
resolveTemplate,
|
|
@@ -3076,12 +3503,15 @@ function isAdyenRolloutCountry(alpha2) {
|
|
|
3076
3503
|
sameFamily,
|
|
3077
3504
|
setOrderState,
|
|
3078
3505
|
signReportForDisplay,
|
|
3506
|
+
slotsForWindow,
|
|
3079
3507
|
stringifyMinorUnits,
|
|
3080
3508
|
subtractAmounts,
|
|
3081
3509
|
toBase,
|
|
3082
3510
|
toMinorUnits,
|
|
3083
3511
|
validateConfig,
|
|
3084
3512
|
validateFragmentCompatibility,
|
|
3085
|
-
violatesFinancialInvariant
|
|
3513
|
+
violatesFinancialInvariant,
|
|
3514
|
+
windowEndsAt,
|
|
3515
|
+
withinPresence
|
|
3086
3516
|
});
|
|
3087
3517
|
//# sourceMappingURL=index.js.map
|