@final-commerce/common 2.4.1-staging2.1 → 2.4.1-staging2.2
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/bin/gate-done.mjs +5 -4
- package/dist/index.d.mts +201 -2
- package/dist/index.d.ts +201 -2
- package/dist/index.js +491 -10
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +471 -9
- package/dist/index.mjs.map +1 -1
- package/dist/pos-types/index.d.mts +151 -18
- package/dist/pos-types/index.d.ts +151 -18
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -27,6 +27,11 @@ __export(index_exports, {
|
|
|
27
27
|
ADYEN_TAX_NUMBER_FORMATS: () => ADYEN_TAX_NUMBER_FORMATS,
|
|
28
28
|
ADYEN_TAX_NUMBER_MIN_LENGTH: () => ADYEN_TAX_NUMBER_MIN_LENGTH,
|
|
29
29
|
AttributeType: () => AttributeType,
|
|
30
|
+
BookingRatePeriod: () => BookingRatePeriod,
|
|
31
|
+
BookingResourceKind: () => BookingResourceKind,
|
|
32
|
+
BookingResourceStatus: () => BookingResourceStatus,
|
|
33
|
+
BookingRulesError: () => BookingRulesError,
|
|
34
|
+
BookingType: () => BookingType,
|
|
30
35
|
CONDITION_OPERATORS: () => CONDITION_OPERATORS,
|
|
31
36
|
COUNTRY_CURRENCIES: () => COUNTRY_CURRENCIES,
|
|
32
37
|
CURRENCY_CONFIG: () => CURRENCY_CONFIG,
|
|
@@ -37,6 +42,7 @@ __export(index_exports, {
|
|
|
37
42
|
DEFAULT_CONFIG: () => DEFAULT_CONFIG,
|
|
38
43
|
DEFAULT_CROSS_AXIS_RULES: () => DEFAULT_CROSS_AXIS_RULES,
|
|
39
44
|
DEFAULT_DISPLAY_STATE_MAP: () => DEFAULT_DISPLAY_STATE_MAP,
|
|
45
|
+
DEFAULT_HOLD_MINUTES: () => DEFAULT_HOLD_MINUTES,
|
|
40
46
|
DEFAULT_RESOLVED_CONFIG: () => DEFAULT_RESOLVED_CONFIG,
|
|
41
47
|
DEFAULT_UNIT_ID: () => DEFAULT_UNIT_ID,
|
|
42
48
|
DualWriteStrategy: () => DualWriteStrategy,
|
|
@@ -80,8 +86,10 @@ __export(index_exports, {
|
|
|
80
86
|
REPORT_NEGATE_PATHS: () => REPORT_NEGATE_PATHS,
|
|
81
87
|
ReceiptSendChannel: () => ReceiptSendChannel,
|
|
82
88
|
ReceiptType: () => ReceiptType,
|
|
89
|
+
ReservationStatus: () => ReservationStatus,
|
|
83
90
|
ResidualPaymentMethod: () => ResidualPaymentMethod,
|
|
84
91
|
ResidualStatus: () => ResidualStatus,
|
|
92
|
+
ResourceAvailabilityState: () => ResourceAvailabilityState,
|
|
85
93
|
STATE_SCHEMA: () => STATE_SCHEMA,
|
|
86
94
|
STRIPE_SUPPORTED_COUNTRIES: () => STRIPE_SUPPORTED_COUNTRIES,
|
|
87
95
|
SYSTEM_FAMILIES: () => SYSTEM_FAMILIES,
|
|
@@ -107,6 +115,8 @@ __export(index_exports, {
|
|
|
107
115
|
baseFulfillmentDefinitions: () => baseFulfillmentDefinitions,
|
|
108
116
|
baseOf: () => baseOf,
|
|
109
117
|
basePaymentDefinitions: () => basePaymentDefinitions,
|
|
118
|
+
buildAvailability: () => buildAvailability,
|
|
119
|
+
buildGrid: () => buildGrid,
|
|
110
120
|
buildOrderContext: () => buildOrderContext,
|
|
111
121
|
buildPosPresetConfig: () => buildPosPresetConfig,
|
|
112
122
|
buildResolvedConfig: () => buildResolvedConfig,
|
|
@@ -146,6 +156,7 @@ __export(index_exports, {
|
|
|
146
156
|
getPricingRegionFromCountry: () => getPricingRegionFromCountry,
|
|
147
157
|
getSettlementTotal: () => getSettlementTotal,
|
|
148
158
|
getTotalCollected: () => getTotalCollected,
|
|
159
|
+
holdExpiresAt: () => holdExpiresAt,
|
|
149
160
|
inferStatesFromLegacyStatus: () => inferStatesFromLegacyStatus,
|
|
150
161
|
isAdyenRolloutCountry: () => isAdyenRolloutCountry,
|
|
151
162
|
isConditionOperator: () => isConditionOperator,
|
|
@@ -160,9 +171,12 @@ __export(index_exports, {
|
|
|
160
171
|
mergeFragment: () => mergeFragment,
|
|
161
172
|
multiplyAmount: () => multiplyAmount,
|
|
162
173
|
normalizeMongoIdString: () => normalizeMongoIdString,
|
|
174
|
+
occupancyIn: () => occupancyIn,
|
|
163
175
|
parseAmountToMinorUnits: () => parseAmountToMinorUnits,
|
|
164
176
|
parseCurrencyCode: () => parseCurrencyCode,
|
|
165
177
|
parseStripeCurrencyCode: () => parseStripeCurrencyCode,
|
|
178
|
+
planWindow: () => planWindow,
|
|
179
|
+
presentAt: () => presentAt,
|
|
166
180
|
removeFragment: () => removeFragment,
|
|
167
181
|
resolveFamily: () => resolveFamily,
|
|
168
182
|
resolveTemplate: () => resolveTemplate,
|
|
@@ -170,14 +184,19 @@ __export(index_exports, {
|
|
|
170
184
|
roundToPrecision: () => roundToPrecision,
|
|
171
185
|
sameFamily: () => sameFamily,
|
|
172
186
|
setOrderState: () => setOrderState,
|
|
187
|
+
shopDayKey: () => shopDayKey,
|
|
188
|
+
shopDayRange: () => shopDayRange,
|
|
173
189
|
signReportForDisplay: () => signReportForDisplay,
|
|
190
|
+
slotsForWindow: () => slotsForWindow,
|
|
174
191
|
stringifyMinorUnits: () => stringifyMinorUnits,
|
|
175
192
|
subtractAmounts: () => subtractAmounts,
|
|
176
193
|
toBase: () => toBase,
|
|
177
194
|
toMinorUnits: () => toMinorUnits,
|
|
178
195
|
validateConfig: () => validateConfig,
|
|
179
196
|
validateFragmentCompatibility: () => validateFragmentCompatibility,
|
|
180
|
-
violatesFinancialInvariant: () => violatesFinancialInvariant
|
|
197
|
+
violatesFinancialInvariant: () => violatesFinancialInvariant,
|
|
198
|
+
windowEndsAt: () => windowEndsAt,
|
|
199
|
+
withinPresence: () => withinPresence
|
|
181
200
|
});
|
|
182
201
|
module.exports = __toCommonJS(index_exports);
|
|
183
202
|
|
|
@@ -605,6 +624,7 @@ var StripeCurrencyCode = /* @__PURE__ */ ((StripeCurrencyCode2) => {
|
|
|
605
624
|
var ProductType = /* @__PURE__ */ ((ProductType2) => {
|
|
606
625
|
ProductType2["SIMPLE"] = "simple";
|
|
607
626
|
ProductType2["VARIABLE"] = "variable";
|
|
627
|
+
ProductType2["BOOKING"] = "booking";
|
|
608
628
|
return ProductType2;
|
|
609
629
|
})(ProductType || {});
|
|
610
630
|
var ProductStatus = /* @__PURE__ */ ((ProductStatus2) => {
|
|
@@ -622,6 +642,50 @@ var PaymentProcessor = /* @__PURE__ */ ((PaymentProcessor2) => {
|
|
|
622
642
|
return PaymentProcessor2;
|
|
623
643
|
})(PaymentProcessor || {});
|
|
624
644
|
|
|
645
|
+
// enums/booking.enum.ts
|
|
646
|
+
var BookingType = /* @__PURE__ */ ((BookingType3) => {
|
|
647
|
+
BookingType3["RENTAL"] = "rental";
|
|
648
|
+
BookingType3["APPOINTMENT"] = "appointment";
|
|
649
|
+
BookingType3["OVERNIGHT"] = "overnight";
|
|
650
|
+
BookingType3["GROUP"] = "group";
|
|
651
|
+
return BookingType3;
|
|
652
|
+
})(BookingType || {});
|
|
653
|
+
var BookingRatePeriod = /* @__PURE__ */ ((BookingRatePeriod2) => {
|
|
654
|
+
BookingRatePeriod2["SLOT"] = "slot";
|
|
655
|
+
BookingRatePeriod2["DAY"] = "day";
|
|
656
|
+
BookingRatePeriod2["NIGHT"] = "night";
|
|
657
|
+
BookingRatePeriod2["WEEK"] = "week";
|
|
658
|
+
BookingRatePeriod2["MONTH"] = "month";
|
|
659
|
+
return BookingRatePeriod2;
|
|
660
|
+
})(BookingRatePeriod || {});
|
|
661
|
+
var BookingResourceKind = /* @__PURE__ */ ((BookingResourceKind2) => {
|
|
662
|
+
BookingResourceKind2["ASSET"] = "asset";
|
|
663
|
+
BookingResourceKind2["ROOM"] = "room";
|
|
664
|
+
BookingResourceKind2["PROPERTY"] = "property";
|
|
665
|
+
BookingResourceKind2["SPACE"] = "space";
|
|
666
|
+
BookingResourceKind2["STAFF"] = "staff";
|
|
667
|
+
BookingResourceKind2["TABLE"] = "table";
|
|
668
|
+
return BookingResourceKind2;
|
|
669
|
+
})(BookingResourceKind || {});
|
|
670
|
+
var ReservationStatus = /* @__PURE__ */ ((ReservationStatus2) => {
|
|
671
|
+
ReservationStatus2["HELD"] = "held";
|
|
672
|
+
ReservationStatus2["CONFIRMED"] = "confirmed";
|
|
673
|
+
ReservationStatus2["CANCELLED"] = "cancelled";
|
|
674
|
+
ReservationStatus2["EXPIRED"] = "expired";
|
|
675
|
+
return ReservationStatus2;
|
|
676
|
+
})(ReservationStatus || {});
|
|
677
|
+
var BookingResourceStatus = /* @__PURE__ */ ((BookingResourceStatus2) => {
|
|
678
|
+
BookingResourceStatus2["ACTIVE"] = "active";
|
|
679
|
+
BookingResourceStatus2["INACTIVE"] = "inactive";
|
|
680
|
+
return BookingResourceStatus2;
|
|
681
|
+
})(BookingResourceStatus || {});
|
|
682
|
+
var ResourceAvailabilityState = /* @__PURE__ */ ((ResourceAvailabilityState2) => {
|
|
683
|
+
ResourceAvailabilityState2["AVAILABLE"] = "available";
|
|
684
|
+
ResourceAvailabilityState2["FULLY_BOOKED"] = "fullyBooked";
|
|
685
|
+
ResourceAvailabilityState2["NO_HOURS_IN_RANGE"] = "noHoursInRange";
|
|
686
|
+
return ResourceAvailabilityState2;
|
|
687
|
+
})(ResourceAvailabilityState || {});
|
|
688
|
+
|
|
625
689
|
// utils/currency.util.ts
|
|
626
690
|
var CURRENCY_CONFIG = {
|
|
627
691
|
// Major currencies with 2 decimal places
|
|
@@ -1833,6 +1897,15 @@ function sumRefundQuantities(refunds) {
|
|
|
1833
1897
|
}
|
|
1834
1898
|
return t;
|
|
1835
1899
|
}
|
|
1900
|
+
var asCount = (value) => typeof value === "number" && Number.isFinite(value) ? Math.max(0, value) : 0;
|
|
1901
|
+
function countsOf(row) {
|
|
1902
|
+
const quantity = asCount(row.quantity);
|
|
1903
|
+
return {
|
|
1904
|
+
quantity,
|
|
1905
|
+
fulfilled: Math.min(asCount(row.fulfilledQuantity), quantity),
|
|
1906
|
+
returned: Math.min(asCount(row.returnedQuantity), quantity)
|
|
1907
|
+
};
|
|
1908
|
+
}
|
|
1836
1909
|
function buildOrderContext(order, extras) {
|
|
1837
1910
|
const o = order ?? {};
|
|
1838
1911
|
const total = typeof o.total === "number" && Number.isFinite(o.total) ? o.total : 0;
|
|
@@ -1840,6 +1913,7 @@ function buildOrderContext(order, extras) {
|
|
|
1840
1913
|
const paymentMethods = o.paymentMethods ?? [];
|
|
1841
1914
|
const refunds = o.refunds ?? o.refund ?? [];
|
|
1842
1915
|
const lineItems = o.lineItems ?? [];
|
|
1916
|
+
const reservations = o.reservations ?? [];
|
|
1843
1917
|
const totalPaid = sumNumbers(paymentMethods);
|
|
1844
1918
|
const totalRefunded = sumRefundQuantities(refunds);
|
|
1845
1919
|
const isFullyPaid = total > 0 ? balance <= 0 : totalPaid > 0 && balance <= 0;
|
|
@@ -1847,13 +1921,11 @@ function buildOrderContext(order, extras) {
|
|
|
1847
1921
|
let itemCount = 0;
|
|
1848
1922
|
let fulfilledItemCount = 0;
|
|
1849
1923
|
let returnedItemCount = 0;
|
|
1850
|
-
for (const
|
|
1851
|
-
const
|
|
1852
|
-
itemCount +=
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
const rq = typeof li.returnedQuantity === "number" && Number.isFinite(li.returnedQuantity) ? Math.max(0, li.returnedQuantity) : 0;
|
|
1856
|
-
returnedItemCount += Math.min(rq, q);
|
|
1924
|
+
for (const row of [...lineItems, ...reservations]) {
|
|
1925
|
+
const { quantity, fulfilled, returned } = countsOf(row);
|
|
1926
|
+
itemCount += quantity;
|
|
1927
|
+
fulfilledItemCount += fulfilled;
|
|
1928
|
+
returnedItemCount += returned;
|
|
1857
1929
|
}
|
|
1858
1930
|
const isFullyReturned = itemCount > 0 ? returnedItemCount >= itemCount : returnedItemCount > 0;
|
|
1859
1931
|
const hasPartialReturns = returnedItemCount > 0 && !isFullyReturned;
|
|
@@ -1863,7 +1935,8 @@ function buildOrderContext(order, extras) {
|
|
|
1863
1935
|
balance,
|
|
1864
1936
|
paymentMethods,
|
|
1865
1937
|
refunds,
|
|
1866
|
-
lineItems
|
|
1938
|
+
lineItems,
|
|
1939
|
+
reservations
|
|
1867
1940
|
},
|
|
1868
1941
|
computed: {
|
|
1869
1942
|
balance,
|
|
@@ -2943,6 +3016,395 @@ var ORDER_STATE_EVENTS = {
|
|
|
2943
3016
|
configUpdated: "config-updated"
|
|
2944
3017
|
};
|
|
2945
3018
|
|
|
3019
|
+
// booking/types.ts
|
|
3020
|
+
var BookingRulesError = class extends Error {
|
|
3021
|
+
constructor(message) {
|
|
3022
|
+
super(message);
|
|
3023
|
+
this.name = "BookingRulesError";
|
|
3024
|
+
}
|
|
3025
|
+
};
|
|
3026
|
+
|
|
3027
|
+
// booking/zoned-time.ts
|
|
3028
|
+
var MINUTE = 6e4;
|
|
3029
|
+
var formatters = /* @__PURE__ */ new Map();
|
|
3030
|
+
var formatterFor = (timeZone) => {
|
|
3031
|
+
const existing = formatters.get(timeZone);
|
|
3032
|
+
if (existing) return existing;
|
|
3033
|
+
let created;
|
|
3034
|
+
try {
|
|
3035
|
+
created = new Intl.DateTimeFormat("en-US", {
|
|
3036
|
+
timeZone,
|
|
3037
|
+
hour12: false,
|
|
3038
|
+
year: "numeric",
|
|
3039
|
+
month: "2-digit",
|
|
3040
|
+
day: "2-digit",
|
|
3041
|
+
hour: "2-digit",
|
|
3042
|
+
minute: "2-digit",
|
|
3043
|
+
second: "2-digit",
|
|
3044
|
+
weekday: "short"
|
|
3045
|
+
});
|
|
3046
|
+
} catch {
|
|
3047
|
+
throw new BookingRulesError(`Booking rules: "${timeZone}" is not a known time zone`);
|
|
3048
|
+
}
|
|
3049
|
+
formatters.set(timeZone, created);
|
|
3050
|
+
return created;
|
|
3051
|
+
};
|
|
3052
|
+
var WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
3053
|
+
function zonedParts(instant, timeZone) {
|
|
3054
|
+
const parts = {};
|
|
3055
|
+
for (const { type, value } of formatterFor(timeZone).formatToParts(instant)) parts[type] = value;
|
|
3056
|
+
const hour = Number(parts.hour) % 24;
|
|
3057
|
+
return {
|
|
3058
|
+
year: Number(parts.year),
|
|
3059
|
+
month: Number(parts.month),
|
|
3060
|
+
day: Number(parts.day),
|
|
3061
|
+
hour,
|
|
3062
|
+
minute: Number(parts.minute),
|
|
3063
|
+
second: Number(parts.second),
|
|
3064
|
+
weekday: WEEKDAYS.indexOf(parts.weekday)
|
|
3065
|
+
};
|
|
3066
|
+
}
|
|
3067
|
+
function offsetAt(instant, timeZone) {
|
|
3068
|
+
const { year, month, day, hour, minute, second } = zonedParts(instant, timeZone);
|
|
3069
|
+
return Date.UTC(year, month - 1, day, hour, minute, second) - Math.floor(instant.getTime() / 1e3) * 1e3;
|
|
3070
|
+
}
|
|
3071
|
+
function instantFromLocal(year, month, day, minutes, timeZone) {
|
|
3072
|
+
const naive = Date.UTC(year, month - 1, day) + minutes * MINUTE;
|
|
3073
|
+
const firstGuess = naive - offsetAt(new Date(naive), timeZone);
|
|
3074
|
+
return new Date(naive - offsetAt(new Date(firstGuess), timeZone));
|
|
3075
|
+
}
|
|
3076
|
+
function startOfZonedDay(instant, timeZone) {
|
|
3077
|
+
const { year, month, day } = zonedParts(instant, timeZone);
|
|
3078
|
+
return instantFromLocal(year, month, day, 0, timeZone);
|
|
3079
|
+
}
|
|
3080
|
+
function nextZonedDay(dayStart, timeZone) {
|
|
3081
|
+
const { year, month, day } = zonedParts(dayStart, timeZone);
|
|
3082
|
+
return instantFromLocal(year, month, day + 1, 0, timeZone);
|
|
3083
|
+
}
|
|
3084
|
+
function atZonedMinute(dayStart, minutes, timeZone) {
|
|
3085
|
+
const { year, month, day } = zonedParts(dayStart, timeZone);
|
|
3086
|
+
return instantFromLocal(year, month, day, minutes, timeZone);
|
|
3087
|
+
}
|
|
3088
|
+
|
|
3089
|
+
// booking/grid.ts
|
|
3090
|
+
var MINUTE2 = 6e4;
|
|
3091
|
+
var MAX_RANGE_DAYS = 366;
|
|
3092
|
+
var asTime = (value, field) => {
|
|
3093
|
+
if (value == null) return void 0;
|
|
3094
|
+
const time = new Date(value).getTime();
|
|
3095
|
+
if (Number.isNaN(time)) throw new BookingRulesError(`Booking rules: ${field} is not a date, got "${String(value)}"`);
|
|
3096
|
+
return time;
|
|
3097
|
+
};
|
|
3098
|
+
function parseHhMm(value, field) {
|
|
3099
|
+
const [hours, minutes] = value.split(":").map(Number);
|
|
3100
|
+
if (!Number.isInteger(hours) || !Number.isInteger(minutes)) {
|
|
3101
|
+
throw new BookingRulesError(`Booking rules: ${field} must be "HH:mm", got "${value}"`);
|
|
3102
|
+
}
|
|
3103
|
+
return hours * 60 + minutes;
|
|
3104
|
+
}
|
|
3105
|
+
function windowEndsAt(rule, now) {
|
|
3106
|
+
const { maxAdvanceDays, bookableTo } = rule.window;
|
|
3107
|
+
const ends = Math.min(
|
|
3108
|
+
maxAdvanceDays ? now.getTime() + maxAdvanceDays * 1440 * MINUTE2 : Number.POSITIVE_INFINITY,
|
|
3109
|
+
asTime(bookableTo, "window.bookableTo") ?? Number.POSITIVE_INFINITY
|
|
3110
|
+
);
|
|
3111
|
+
return Number.isFinite(ends) ? new Date(ends) : null;
|
|
3112
|
+
}
|
|
3113
|
+
function periodSlot(dayStart, timeZone, rule, openMinute, closeMinute) {
|
|
3114
|
+
const { ratePeriod, slot, checkInTime, checkOutTime } = rule;
|
|
3115
|
+
const at = (minutes) => atZonedMinute(dayStart, minutes, timeZone);
|
|
3116
|
+
if (ratePeriod === "night" /* NIGHT */) {
|
|
3117
|
+
if (!checkInTime || !checkOutTime) {
|
|
3118
|
+
throw new BookingRulesError("Overnight booking rules need both checkInTime and checkOutTime");
|
|
3119
|
+
}
|
|
3120
|
+
const checkIn = parseHhMm(checkInTime, "checkInTime");
|
|
3121
|
+
const checkOut = parseHhMm(checkOutTime, "checkOutTime");
|
|
3122
|
+
return {
|
|
3123
|
+
startAt: at(checkIn),
|
|
3124
|
+
endAt: at(1440 + checkOut),
|
|
3125
|
+
// Housekeeping sits between check-out and the next check-in: it costs no night.
|
|
3126
|
+
bufferEndAt: at(1440 + checkIn),
|
|
3127
|
+
canStart: true
|
|
3128
|
+
};
|
|
3129
|
+
}
|
|
3130
|
+
const from = ratePeriod === "day" /* DAY */ ? openMinute ?? 0 : 0;
|
|
3131
|
+
const to = ratePeriod === "day" /* DAY */ ? closeMinute ?? 1440 : 1440;
|
|
3132
|
+
return {
|
|
3133
|
+
startAt: at(from),
|
|
3134
|
+
endAt: at(to),
|
|
3135
|
+
bufferEndAt: at(to + (slot.bufferMinutes ?? 0)),
|
|
3136
|
+
canStart: true
|
|
3137
|
+
};
|
|
3138
|
+
}
|
|
3139
|
+
function freeGaps(openAt, closeAt, busy) {
|
|
3140
|
+
const inside = busy.map(({ startAt, bufferEndAt }) => ({ from: +startAt, to: +bufferEndAt })).filter(({ from, to }) => to > openAt && from < closeAt).sort((first, second) => first.from - second.from);
|
|
3141
|
+
const gaps = [];
|
|
3142
|
+
let cursor = openAt;
|
|
3143
|
+
for (const { from, to } of inside) {
|
|
3144
|
+
if (from > cursor) gaps.push({ from: cursor, to: from, endsAtBooking: true });
|
|
3145
|
+
cursor = Math.max(cursor, to);
|
|
3146
|
+
if (cursor >= closeAt) break;
|
|
3147
|
+
}
|
|
3148
|
+
if (cursor < closeAt) gaps.push({ from: cursor, to: closeAt, endsAtBooking: false });
|
|
3149
|
+
return gaps;
|
|
3150
|
+
}
|
|
3151
|
+
function buildGrid(rule, timeZone, from, to, now, busy = []) {
|
|
3152
|
+
if (to.getTime() - from.getTime() > MAX_RANGE_DAYS * 1440 * MINUTE2) {
|
|
3153
|
+
throw new BookingRulesError(`A booking range may span at most ${MAX_RANGE_DAYS} days`);
|
|
3154
|
+
}
|
|
3155
|
+
const { availability, slot, window, ratePeriod } = rule;
|
|
3156
|
+
const periodMode = ratePeriod !== "slot" /* SLOT */;
|
|
3157
|
+
const weeklyHours = availability.weeklyHours ?? [];
|
|
3158
|
+
const notBefore = Math.max(
|
|
3159
|
+
now.getTime() + (window.minNoticeMinutes ?? 0) * MINUTE2,
|
|
3160
|
+
asTime(window.bookableFrom, "window.bookableFrom") ?? 0,
|
|
3161
|
+
asTime(availability.availableFrom, "availability.availableFrom") ?? 0
|
|
3162
|
+
);
|
|
3163
|
+
const notAfter = windowEndsAt(rule, now)?.getTime() ?? Number.POSITIVE_INFINITY;
|
|
3164
|
+
const blocked = (availability.offMarket ?? []).map(({ from: blockFrom, to: blockTo }) => ({
|
|
3165
|
+
from: asTime(blockFrom, "availability.offMarket.from"),
|
|
3166
|
+
to: asTime(blockTo, "availability.offMarket.to")
|
|
3167
|
+
}));
|
|
3168
|
+
const arrivalDays = availability.arrivalDays;
|
|
3169
|
+
const step = slot.lengthMinutes + (slot.bufferMinutes ?? 0);
|
|
3170
|
+
const slots = [];
|
|
3171
|
+
const lastDay = startOfZonedDay(to, timeZone).getTime();
|
|
3172
|
+
let dayStart = startOfZonedDay(from, timeZone);
|
|
3173
|
+
while (dayStart.getTime() <= lastDay) {
|
|
3174
|
+
const weekday = zonedParts(dayStart, timeZone).weekday;
|
|
3175
|
+
const rows = weeklyHours.filter((entry) => entry.weekday === weekday);
|
|
3176
|
+
if (periodMode) {
|
|
3177
|
+
const openToday = weeklyHours.length === 0 || rows.length > 0;
|
|
3178
|
+
if (openToday) {
|
|
3179
|
+
const built = periodSlot(dayStart, timeZone, rule, rows[0]?.openMinute, rows[0]?.closeMinute);
|
|
3180
|
+
slots.push({ ...built, canStart: !arrivalDays?.length || arrivalDays.includes(weekday) });
|
|
3181
|
+
}
|
|
3182
|
+
} else {
|
|
3183
|
+
for (const row of rows) {
|
|
3184
|
+
const openAt = +atZonedMinute(dayStart, row.openMinute, timeZone);
|
|
3185
|
+
const closeAt = +atZonedMinute(dayStart, row.closeMinute, timeZone);
|
|
3186
|
+
for (const gap of freeGaps(openAt, closeAt, busy)) {
|
|
3187
|
+
const mustFit = (gap.endsAtBooking ? step : slot.lengthMinutes) * MINUTE2;
|
|
3188
|
+
for (let start = gap.from; start + mustFit <= gap.to; start += step * MINUTE2) {
|
|
3189
|
+
slots.push({
|
|
3190
|
+
startAt: new Date(start),
|
|
3191
|
+
endAt: new Date(start + slot.lengthMinutes * MINUTE2),
|
|
3192
|
+
bufferEndAt: new Date(start + step * MINUTE2),
|
|
3193
|
+
canStart: true
|
|
3194
|
+
});
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
dayStart = nextZonedDay(dayStart, timeZone);
|
|
3200
|
+
}
|
|
3201
|
+
const askedFrom = from.getTime();
|
|
3202
|
+
const askedTo = to.getTime();
|
|
3203
|
+
return slots.filter(({ startAt, bufferEndAt }) => {
|
|
3204
|
+
const start = startAt.getTime();
|
|
3205
|
+
if (start < askedFrom || start >= askedTo) return false;
|
|
3206
|
+
if (start < notBefore || start > notAfter) return false;
|
|
3207
|
+
return !blocked.some(
|
|
3208
|
+
({ from: blockFrom, to: blockTo }) => start < blockTo && bufferEndAt.getTime() > blockFrom
|
|
3209
|
+
);
|
|
3210
|
+
}).sort((first, second) => +first.startAt - +second.startAt);
|
|
3211
|
+
}
|
|
3212
|
+
function slotsForWindow(grid, startAt, endAt) {
|
|
3213
|
+
const startIndex = grid.findIndex((entry) => entry.startAt.getTime() === startAt.getTime());
|
|
3214
|
+
if (startIndex < 0) {
|
|
3215
|
+
throw new BookingRulesError(`${startAt.toISOString()} is not a bookable slot start for these rules`);
|
|
3216
|
+
}
|
|
3217
|
+
if (!grid[startIndex].canStart) {
|
|
3218
|
+
throw new BookingRulesError(`${startAt.toISOString()} is not an arrival day for these rules`);
|
|
3219
|
+
}
|
|
3220
|
+
const covered = [];
|
|
3221
|
+
for (let index = startIndex; index < grid.length; index++) {
|
|
3222
|
+
const entry = grid[index];
|
|
3223
|
+
const previous = covered[covered.length - 1];
|
|
3224
|
+
if (previous && entry.startAt.getTime() !== previous.bufferEndAt.getTime()) break;
|
|
3225
|
+
covered.push(entry);
|
|
3226
|
+
if (entry.endAt.getTime() === endAt.getTime()) return covered;
|
|
3227
|
+
if (entry.endAt.getTime() > endAt.getTime()) break;
|
|
3228
|
+
}
|
|
3229
|
+
throw new BookingRulesError(
|
|
3230
|
+
`${startAt.toISOString()} \u2192 ${endAt.toISOString()} does not line up with the bookable grid`
|
|
3231
|
+
);
|
|
3232
|
+
}
|
|
3233
|
+
|
|
3234
|
+
// booking/availability.ts
|
|
3235
|
+
var withinPresence = (slot, timeZone, presence) => {
|
|
3236
|
+
if (!presence?.length) return true;
|
|
3237
|
+
const dayStart = startOfZonedDay(slot.startAt, timeZone);
|
|
3238
|
+
const weekday = zonedParts(dayStart, timeZone).weekday;
|
|
3239
|
+
return presence.some(
|
|
3240
|
+
(shift) => shift.weekday === weekday && slot.startAt.getTime() >= atZonedMinute(dayStart, shift.openMinute, timeZone).getTime() && slot.endAt.getTime() <= atZonedMinute(dayStart, shift.closeMinute, timeZone).getTime()
|
|
3241
|
+
);
|
|
3242
|
+
};
|
|
3243
|
+
var overlaps = (entry, startAt, bufferEndAt) => entry.startAt.getTime() < bufferEndAt.getTime() && entry.bufferEndAt.getTime() > startAt.getTime();
|
|
3244
|
+
function occupancyIn(occupied, productId, startAt, bufferEndAt, shared) {
|
|
3245
|
+
const clashing = occupied.filter((entry) => overlaps(entry, startAt, bufferEndAt));
|
|
3246
|
+
const foreign = shared ? void 0 : clashing.find((entry) => entry.productId !== productId);
|
|
3247
|
+
return {
|
|
3248
|
+
taken: clashing.filter((entry) => shared || entry.productId === productId).reduce((sum, entry) => sum + entry.qty, 0),
|
|
3249
|
+
foreignProductId: foreign?.productId
|
|
3250
|
+
};
|
|
3251
|
+
}
|
|
3252
|
+
var stateFor = (hasShift, offerable, free) => {
|
|
3253
|
+
if (offerable === 0) {
|
|
3254
|
+
return hasShift() ? "fullyBooked" /* FULLY_BOOKED */ : "noHoursInRange" /* NO_HOURS_IN_RANGE */;
|
|
3255
|
+
}
|
|
3256
|
+
return free === 0 ? "fullyBooked" /* FULLY_BOOKED */ : "available" /* AVAILABLE */;
|
|
3257
|
+
};
|
|
3258
|
+
function shopDayKey(instant, timeZone) {
|
|
3259
|
+
const { year, month, day } = zonedParts(instant, timeZone);
|
|
3260
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
3261
|
+
return `${year}-${pad(month)}-${pad(day)}`;
|
|
3262
|
+
}
|
|
3263
|
+
function shopDayRange(fromDayKey, count, timeZone) {
|
|
3264
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(fromDayKey ?? "");
|
|
3265
|
+
if (!match) {
|
|
3266
|
+
throw new BookingRulesError(`Booking rules: "${fromDayKey}" is not a shop day (YYYY-MM-DD)`);
|
|
3267
|
+
}
|
|
3268
|
+
if (!Number.isInteger(count) || count < 1) {
|
|
3269
|
+
throw new BookingRulesError(`Booking rules: "${count}" is not a number of days`);
|
|
3270
|
+
}
|
|
3271
|
+
const [, year, month, day] = match.map(Number);
|
|
3272
|
+
const from = instantFromLocal(year, month, day, 0, timeZone);
|
|
3273
|
+
if (shopDayKey(from, timeZone) !== fromDayKey) {
|
|
3274
|
+
throw new BookingRulesError(`Booking rules: "${fromDayKey}" is not a real day`);
|
|
3275
|
+
}
|
|
3276
|
+
return { from, to: instantFromLocal(year, month, day + count, 0, timeZone) };
|
|
3277
|
+
}
|
|
3278
|
+
function buildAvailability({
|
|
3279
|
+
productId,
|
|
3280
|
+
bookingRulesId,
|
|
3281
|
+
bookingType,
|
|
3282
|
+
rule,
|
|
3283
|
+
resources,
|
|
3284
|
+
occupied,
|
|
3285
|
+
from,
|
|
3286
|
+
to,
|
|
3287
|
+
now
|
|
3288
|
+
}) {
|
|
3289
|
+
const shared = rule.slot.sharedAcrossProducts ?? false;
|
|
3290
|
+
const bookableUntil = windowEndsAt(rule, now);
|
|
3291
|
+
const header = {
|
|
3292
|
+
productId,
|
|
3293
|
+
bookingRulesId,
|
|
3294
|
+
bookingType,
|
|
3295
|
+
ratePeriod: rule.ratePeriod,
|
|
3296
|
+
// Passed through for the UI to label: a group slot under minimumToRun may not run.
|
|
3297
|
+
minimumToRun: rule.slot.minimumToRun,
|
|
3298
|
+
minStay: rule.slot.minStay,
|
|
3299
|
+
maxStay: rule.slot.maxStay,
|
|
3300
|
+
// Why a day can come back empty. Past this instant the rule refuses everything, so the UI
|
|
3301
|
+
// can say "beyond the booking window" where it would otherwise claim "nothing free".
|
|
3302
|
+
bookableUntil,
|
|
3303
|
+
// The comparison itself, not just the boundary: deciding whether a day lies past the
|
|
3304
|
+
// window is a rule, and a UI that re-derives it owns a copy of that rule — which is how
|
|
3305
|
+
// the same empty grid ends up explained one way on the till and another in a flow.
|
|
3306
|
+
beyondWindow: !!bookableUntil && from.getTime() > bookableUntil.getTime()
|
|
3307
|
+
};
|
|
3308
|
+
const byResource = /* @__PURE__ */ new Map();
|
|
3309
|
+
for (const entry of occupied) {
|
|
3310
|
+
byResource.set(entry.resourceId, [...byResource.get(entry.resourceId) ?? [], entry]);
|
|
3311
|
+
}
|
|
3312
|
+
const grids = resources.map((resource) => {
|
|
3313
|
+
const mine = byResource.get(resource.id) ?? [];
|
|
3314
|
+
const busy = (resource.seats ?? 1) === 1 ? mine : [];
|
|
3315
|
+
const slots2 = buildGrid(rule, resource.timeZone, from, to, now, busy).filter(
|
|
3316
|
+
(slot) => withinPresence(slot, resource.timeZone, resource.presence)
|
|
3317
|
+
);
|
|
3318
|
+
return { resource, slots: slots2 };
|
|
3319
|
+
});
|
|
3320
|
+
const byInstant = /* @__PURE__ */ new Map();
|
|
3321
|
+
const freeSlotsPerResource = /* @__PURE__ */ new Map();
|
|
3322
|
+
for (const { resource, slots: slots2 } of grids) {
|
|
3323
|
+
const capacity = resource.seats ?? 1;
|
|
3324
|
+
const mine = byResource.get(resource.id) ?? [];
|
|
3325
|
+
for (const slot of slots2) {
|
|
3326
|
+
const { taken, foreignProductId } = occupancyIn(mine, productId, slot.startAt, slot.bufferEndAt, shared);
|
|
3327
|
+
const free = foreignProductId ? 0 : Math.max(capacity - taken, 0);
|
|
3328
|
+
const entry = byInstant.get(+slot.startAt) ?? {
|
|
3329
|
+
...slot,
|
|
3330
|
+
capacity: 0,
|
|
3331
|
+
booked: 0,
|
|
3332
|
+
free: 0,
|
|
3333
|
+
resources: [],
|
|
3334
|
+
dayKey: shopDayKey(slot.startAt, resource.timeZone)
|
|
3335
|
+
};
|
|
3336
|
+
entry.capacity += capacity;
|
|
3337
|
+
entry.booked += foreignProductId ? capacity : taken;
|
|
3338
|
+
entry.free += free;
|
|
3339
|
+
entry.canStart = entry.canStart || slot.canStart;
|
|
3340
|
+
entry.resources.push({ resourceId: resource.id, name: resource.name, capacity, booked: taken, free });
|
|
3341
|
+
if (free > 0) freeSlotsPerResource.set(resource.id, (freeSlotsPerResource.get(resource.id) ?? 0) + 1);
|
|
3342
|
+
byInstant.set(+slot.startAt, entry);
|
|
3343
|
+
}
|
|
3344
|
+
}
|
|
3345
|
+
const inWindow = grids.map(({ resource, slots: slots2 }) => {
|
|
3346
|
+
const free = freeSlotsPerResource.get(resource.id) ?? 0;
|
|
3347
|
+
return {
|
|
3348
|
+
resourceId: resource.id,
|
|
3349
|
+
name: resource.name,
|
|
3350
|
+
slots: slots2.length,
|
|
3351
|
+
free,
|
|
3352
|
+
// Lazily: the shift grid is a second walk, and only an empty answer needs it.
|
|
3353
|
+
state: stateFor(
|
|
3354
|
+
() => buildGrid(rule, resource.timeZone, from, to, now).filter(
|
|
3355
|
+
(slot) => withinPresence(slot, resource.timeZone, resource.presence)
|
|
3356
|
+
).length > 0,
|
|
3357
|
+
slots2.length,
|
|
3358
|
+
free
|
|
3359
|
+
)
|
|
3360
|
+
};
|
|
3361
|
+
});
|
|
3362
|
+
const slots = [...byInstant.values()].sort((first, second) => +first.startAt - +second.startAt);
|
|
3363
|
+
return {
|
|
3364
|
+
...header,
|
|
3365
|
+
resources: inWindow,
|
|
3366
|
+
slots,
|
|
3367
|
+
/**
|
|
3368
|
+
* The clock every instant above reads on. Returned rather than assumed: without it a caller
|
|
3369
|
+
* has to fetch the zone from somewhere else and hope it is the same one the grid used.
|
|
3370
|
+
*/
|
|
3371
|
+
timeZone: resources[0]?.timeZone ?? null,
|
|
3372
|
+
/** The shop days this answer covers, in order — what a day picker lists. */
|
|
3373
|
+
days: [...new Set(slots.map((slot) => slot.dayKey))]
|
|
3374
|
+
};
|
|
3375
|
+
}
|
|
3376
|
+
function presentAt(resources, rows, outletId) {
|
|
3377
|
+
const byResource = /* @__PURE__ */ new Map();
|
|
3378
|
+
for (const row of rows) {
|
|
3379
|
+
byResource.set(row.resourceId, [...byResource.get(row.resourceId) ?? [], row]);
|
|
3380
|
+
}
|
|
3381
|
+
return resources.flatMap((resource) => {
|
|
3382
|
+
const own = byResource.get(resource.id) ?? [];
|
|
3383
|
+
if (own.length === 0 || !outletId) return [{ resource }];
|
|
3384
|
+
const here = own.find((row) => row.outletId === outletId);
|
|
3385
|
+
return here ? [{ resource, presence: here.weeklyHours }] : [];
|
|
3386
|
+
});
|
|
3387
|
+
}
|
|
3388
|
+
|
|
3389
|
+
// booking/plan.ts
|
|
3390
|
+
var DEFAULT_HOLD_MINUTES = 10;
|
|
3391
|
+
function planWindow({ rule, timeZone, presence, startAt, endAt, now, busy = [] }) {
|
|
3392
|
+
const grid = buildGrid(rule, timeZone, startAt, endAt, now, busy).filter(
|
|
3393
|
+
(slot) => withinPresence(slot, timeZone, presence)
|
|
3394
|
+
);
|
|
3395
|
+
const slots = slotsForWindow(grid, startAt, endAt);
|
|
3396
|
+
const { minStay, maxStay } = rule.slot;
|
|
3397
|
+
if (minStay && slots.length < minStay) {
|
|
3398
|
+
throw new BookingRulesError(`These rules ask for at least ${minStay} period(s), got ${slots.length}`);
|
|
3399
|
+
}
|
|
3400
|
+
if (maxStay && slots.length > maxStay) {
|
|
3401
|
+
throw new BookingRulesError(`These rules allow at most ${maxStay} period(s), got ${slots.length}`);
|
|
3402
|
+
}
|
|
3403
|
+
const last = slots[slots.length - 1];
|
|
3404
|
+
return { slots, endAt: last.endAt, bufferEndAt: last.bufferEndAt };
|
|
3405
|
+
}
|
|
3406
|
+
var holdExpiresAt = (rule, now) => new Date(now.getTime() + (rule.window.holdMinutes ?? DEFAULT_HOLD_MINUTES) * 6e4);
|
|
3407
|
+
|
|
2946
3408
|
// constants/adyen.constants.ts
|
|
2947
3409
|
var ADYEN_ROLLOUT_COUNTRY_CODES = /* @__PURE__ */ new Set(["CA"]);
|
|
2948
3410
|
function isAdyenRolloutCountry(alpha2) {
|
|
@@ -2981,6 +3443,11 @@ function adyenRequiresJobTitle(associationType) {
|
|
|
2981
3443
|
ADYEN_TAX_NUMBER_FORMATS,
|
|
2982
3444
|
ADYEN_TAX_NUMBER_MIN_LENGTH,
|
|
2983
3445
|
AttributeType,
|
|
3446
|
+
BookingRatePeriod,
|
|
3447
|
+
BookingResourceKind,
|
|
3448
|
+
BookingResourceStatus,
|
|
3449
|
+
BookingRulesError,
|
|
3450
|
+
BookingType,
|
|
2984
3451
|
CONDITION_OPERATORS,
|
|
2985
3452
|
COUNTRY_CURRENCIES,
|
|
2986
3453
|
CURRENCY_CONFIG,
|
|
@@ -2991,6 +3458,7 @@ function adyenRequiresJobTitle(associationType) {
|
|
|
2991
3458
|
DEFAULT_CONFIG,
|
|
2992
3459
|
DEFAULT_CROSS_AXIS_RULES,
|
|
2993
3460
|
DEFAULT_DISPLAY_STATE_MAP,
|
|
3461
|
+
DEFAULT_HOLD_MINUTES,
|
|
2994
3462
|
DEFAULT_RESOLVED_CONFIG,
|
|
2995
3463
|
DEFAULT_UNIT_ID,
|
|
2996
3464
|
DualWriteStrategy,
|
|
@@ -3034,8 +3502,10 @@ function adyenRequiresJobTitle(associationType) {
|
|
|
3034
3502
|
REPORT_NEGATE_PATHS,
|
|
3035
3503
|
ReceiptSendChannel,
|
|
3036
3504
|
ReceiptType,
|
|
3505
|
+
ReservationStatus,
|
|
3037
3506
|
ResidualPaymentMethod,
|
|
3038
3507
|
ResidualStatus,
|
|
3508
|
+
ResourceAvailabilityState,
|
|
3039
3509
|
STATE_SCHEMA,
|
|
3040
3510
|
STRIPE_SUPPORTED_COUNTRIES,
|
|
3041
3511
|
SYSTEM_FAMILIES,
|
|
@@ -3061,6 +3531,8 @@ function adyenRequiresJobTitle(associationType) {
|
|
|
3061
3531
|
baseFulfillmentDefinitions,
|
|
3062
3532
|
baseOf,
|
|
3063
3533
|
basePaymentDefinitions,
|
|
3534
|
+
buildAvailability,
|
|
3535
|
+
buildGrid,
|
|
3064
3536
|
buildOrderContext,
|
|
3065
3537
|
buildPosPresetConfig,
|
|
3066
3538
|
buildResolvedConfig,
|
|
@@ -3100,6 +3572,7 @@ function adyenRequiresJobTitle(associationType) {
|
|
|
3100
3572
|
getPricingRegionFromCountry,
|
|
3101
3573
|
getSettlementTotal,
|
|
3102
3574
|
getTotalCollected,
|
|
3575
|
+
holdExpiresAt,
|
|
3103
3576
|
inferStatesFromLegacyStatus,
|
|
3104
3577
|
isAdyenRolloutCountry,
|
|
3105
3578
|
isConditionOperator,
|
|
@@ -3114,9 +3587,12 @@ function adyenRequiresJobTitle(associationType) {
|
|
|
3114
3587
|
mergeFragment,
|
|
3115
3588
|
multiplyAmount,
|
|
3116
3589
|
normalizeMongoIdString,
|
|
3590
|
+
occupancyIn,
|
|
3117
3591
|
parseAmountToMinorUnits,
|
|
3118
3592
|
parseCurrencyCode,
|
|
3119
3593
|
parseStripeCurrencyCode,
|
|
3594
|
+
planWindow,
|
|
3595
|
+
presentAt,
|
|
3120
3596
|
removeFragment,
|
|
3121
3597
|
resolveFamily,
|
|
3122
3598
|
resolveTemplate,
|
|
@@ -3124,13 +3600,18 @@ function adyenRequiresJobTitle(associationType) {
|
|
|
3124
3600
|
roundToPrecision,
|
|
3125
3601
|
sameFamily,
|
|
3126
3602
|
setOrderState,
|
|
3603
|
+
shopDayKey,
|
|
3604
|
+
shopDayRange,
|
|
3127
3605
|
signReportForDisplay,
|
|
3606
|
+
slotsForWindow,
|
|
3128
3607
|
stringifyMinorUnits,
|
|
3129
3608
|
subtractAmounts,
|
|
3130
3609
|
toBase,
|
|
3131
3610
|
toMinorUnits,
|
|
3132
3611
|
validateConfig,
|
|
3133
3612
|
validateFragmentCompatibility,
|
|
3134
|
-
violatesFinancialInvariant
|
|
3613
|
+
violatesFinancialInvariant,
|
|
3614
|
+
windowEndsAt,
|
|
3615
|
+
withinPresence
|
|
3135
3616
|
});
|
|
3136
3617
|
//# sourceMappingURL=index.js.map
|