@final-commerce/common 2.3.0 → 2.4.0-preprod.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/dist/index.d.mts +201 -2
- package/dist/index.d.ts +201 -2
- package/dist/index.js +474 -10
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +454 -9
- package/dist/index.mjs.map +1 -1
- package/dist/pos-types/index.d.mts +147 -18
- package/dist/pos-types/index.d.ts +147 -18
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -21,6 +21,11 @@ var index_exports = {};
|
|
|
21
21
|
__export(index_exports, {
|
|
22
22
|
ADYEN_ROLLOUT_COUNTRY_CODES: () => ADYEN_ROLLOUT_COUNTRY_CODES,
|
|
23
23
|
AttributeType: () => AttributeType,
|
|
24
|
+
BookingRatePeriod: () => BookingRatePeriod,
|
|
25
|
+
BookingResourceKind: () => BookingResourceKind,
|
|
26
|
+
BookingResourceStatus: () => BookingResourceStatus,
|
|
27
|
+
BookingRulesError: () => BookingRulesError,
|
|
28
|
+
BookingType: () => BookingType,
|
|
24
29
|
CONDITION_OPERATORS: () => CONDITION_OPERATORS,
|
|
25
30
|
COUNTRY_CURRENCIES: () => COUNTRY_CURRENCIES,
|
|
26
31
|
CURRENCY_CONFIG: () => CURRENCY_CONFIG,
|
|
@@ -31,6 +36,7 @@ __export(index_exports, {
|
|
|
31
36
|
DEFAULT_CONFIG: () => DEFAULT_CONFIG,
|
|
32
37
|
DEFAULT_CROSS_AXIS_RULES: () => DEFAULT_CROSS_AXIS_RULES,
|
|
33
38
|
DEFAULT_DISPLAY_STATE_MAP: () => DEFAULT_DISPLAY_STATE_MAP,
|
|
39
|
+
DEFAULT_HOLD_MINUTES: () => DEFAULT_HOLD_MINUTES,
|
|
34
40
|
DEFAULT_RESOLVED_CONFIG: () => DEFAULT_RESOLVED_CONFIG,
|
|
35
41
|
DEFAULT_UNIT_ID: () => DEFAULT_UNIT_ID,
|
|
36
42
|
DualWriteStrategy: () => DualWriteStrategy,
|
|
@@ -74,8 +80,10 @@ __export(index_exports, {
|
|
|
74
80
|
REPORT_NEGATE_PATHS: () => REPORT_NEGATE_PATHS,
|
|
75
81
|
ReceiptSendChannel: () => ReceiptSendChannel,
|
|
76
82
|
ReceiptType: () => ReceiptType,
|
|
83
|
+
ReservationStatus: () => ReservationStatus,
|
|
77
84
|
ResidualPaymentMethod: () => ResidualPaymentMethod,
|
|
78
85
|
ResidualStatus: () => ResidualStatus,
|
|
86
|
+
ResourceAvailabilityState: () => ResourceAvailabilityState,
|
|
79
87
|
STATE_SCHEMA: () => STATE_SCHEMA,
|
|
80
88
|
STRIPE_SUPPORTED_COUNTRIES: () => STRIPE_SUPPORTED_COUNTRIES,
|
|
81
89
|
SYSTEM_FAMILIES: () => SYSTEM_FAMILIES,
|
|
@@ -96,6 +104,8 @@ __export(index_exports, {
|
|
|
96
104
|
baseFulfillmentDefinitions: () => baseFulfillmentDefinitions,
|
|
97
105
|
baseOf: () => baseOf,
|
|
98
106
|
basePaymentDefinitions: () => basePaymentDefinitions,
|
|
107
|
+
buildAvailability: () => buildAvailability,
|
|
108
|
+
buildGrid: () => buildGrid,
|
|
99
109
|
buildOrderContext: () => buildOrderContext,
|
|
100
110
|
buildPosPresetConfig: () => buildPosPresetConfig,
|
|
101
111
|
buildResolvedConfig: () => buildResolvedConfig,
|
|
@@ -135,6 +145,7 @@ __export(index_exports, {
|
|
|
135
145
|
getPricingRegionFromCountry: () => getPricingRegionFromCountry,
|
|
136
146
|
getSettlementTotal: () => getSettlementTotal,
|
|
137
147
|
getTotalCollected: () => getTotalCollected,
|
|
148
|
+
holdExpiresAt: () => holdExpiresAt,
|
|
138
149
|
inferStatesFromLegacyStatus: () => inferStatesFromLegacyStatus,
|
|
139
150
|
isAdyenRolloutCountry: () => isAdyenRolloutCountry,
|
|
140
151
|
isConditionOperator: () => isConditionOperator,
|
|
@@ -149,9 +160,12 @@ __export(index_exports, {
|
|
|
149
160
|
mergeFragment: () => mergeFragment,
|
|
150
161
|
multiplyAmount: () => multiplyAmount,
|
|
151
162
|
normalizeMongoIdString: () => normalizeMongoIdString,
|
|
163
|
+
occupancyIn: () => occupancyIn,
|
|
152
164
|
parseAmountToMinorUnits: () => parseAmountToMinorUnits,
|
|
153
165
|
parseCurrencyCode: () => parseCurrencyCode,
|
|
154
166
|
parseStripeCurrencyCode: () => parseStripeCurrencyCode,
|
|
167
|
+
planWindow: () => planWindow,
|
|
168
|
+
presentAt: () => presentAt,
|
|
155
169
|
removeFragment: () => removeFragment,
|
|
156
170
|
resolveFamily: () => resolveFamily,
|
|
157
171
|
resolveTemplate: () => resolveTemplate,
|
|
@@ -159,14 +173,19 @@ __export(index_exports, {
|
|
|
159
173
|
roundToPrecision: () => roundToPrecision,
|
|
160
174
|
sameFamily: () => sameFamily,
|
|
161
175
|
setOrderState: () => setOrderState,
|
|
176
|
+
shopDayKey: () => shopDayKey,
|
|
177
|
+
shopDayRange: () => shopDayRange,
|
|
162
178
|
signReportForDisplay: () => signReportForDisplay,
|
|
179
|
+
slotsForWindow: () => slotsForWindow,
|
|
163
180
|
stringifyMinorUnits: () => stringifyMinorUnits,
|
|
164
181
|
subtractAmounts: () => subtractAmounts,
|
|
165
182
|
toBase: () => toBase,
|
|
166
183
|
toMinorUnits: () => toMinorUnits,
|
|
167
184
|
validateConfig: () => validateConfig,
|
|
168
185
|
validateFragmentCompatibility: () => validateFragmentCompatibility,
|
|
169
|
-
violatesFinancialInvariant: () => violatesFinancialInvariant
|
|
186
|
+
violatesFinancialInvariant: () => violatesFinancialInvariant,
|
|
187
|
+
windowEndsAt: () => windowEndsAt,
|
|
188
|
+
withinPresence: () => withinPresence
|
|
170
189
|
});
|
|
171
190
|
module.exports = __toCommonJS(index_exports);
|
|
172
191
|
|
|
@@ -594,6 +613,7 @@ var StripeCurrencyCode = /* @__PURE__ */ ((StripeCurrencyCode2) => {
|
|
|
594
613
|
var ProductType = /* @__PURE__ */ ((ProductType2) => {
|
|
595
614
|
ProductType2["SIMPLE"] = "simple";
|
|
596
615
|
ProductType2["VARIABLE"] = "variable";
|
|
616
|
+
ProductType2["BOOKING"] = "booking";
|
|
597
617
|
return ProductType2;
|
|
598
618
|
})(ProductType || {});
|
|
599
619
|
var ProductStatus = /* @__PURE__ */ ((ProductStatus2) => {
|
|
@@ -611,6 +631,50 @@ var PaymentProcessor = /* @__PURE__ */ ((PaymentProcessor2) => {
|
|
|
611
631
|
return PaymentProcessor2;
|
|
612
632
|
})(PaymentProcessor || {});
|
|
613
633
|
|
|
634
|
+
// enums/booking.enum.ts
|
|
635
|
+
var BookingType = /* @__PURE__ */ ((BookingType3) => {
|
|
636
|
+
BookingType3["RENTAL"] = "rental";
|
|
637
|
+
BookingType3["APPOINTMENT"] = "appointment";
|
|
638
|
+
BookingType3["OVERNIGHT"] = "overnight";
|
|
639
|
+
BookingType3["GROUP"] = "group";
|
|
640
|
+
return BookingType3;
|
|
641
|
+
})(BookingType || {});
|
|
642
|
+
var BookingRatePeriod = /* @__PURE__ */ ((BookingRatePeriod2) => {
|
|
643
|
+
BookingRatePeriod2["SLOT"] = "slot";
|
|
644
|
+
BookingRatePeriod2["DAY"] = "day";
|
|
645
|
+
BookingRatePeriod2["NIGHT"] = "night";
|
|
646
|
+
BookingRatePeriod2["WEEK"] = "week";
|
|
647
|
+
BookingRatePeriod2["MONTH"] = "month";
|
|
648
|
+
return BookingRatePeriod2;
|
|
649
|
+
})(BookingRatePeriod || {});
|
|
650
|
+
var BookingResourceKind = /* @__PURE__ */ ((BookingResourceKind2) => {
|
|
651
|
+
BookingResourceKind2["ASSET"] = "asset";
|
|
652
|
+
BookingResourceKind2["ROOM"] = "room";
|
|
653
|
+
BookingResourceKind2["PROPERTY"] = "property";
|
|
654
|
+
BookingResourceKind2["SPACE"] = "space";
|
|
655
|
+
BookingResourceKind2["STAFF"] = "staff";
|
|
656
|
+
BookingResourceKind2["TABLE"] = "table";
|
|
657
|
+
return BookingResourceKind2;
|
|
658
|
+
})(BookingResourceKind || {});
|
|
659
|
+
var ReservationStatus = /* @__PURE__ */ ((ReservationStatus2) => {
|
|
660
|
+
ReservationStatus2["HELD"] = "held";
|
|
661
|
+
ReservationStatus2["CONFIRMED"] = "confirmed";
|
|
662
|
+
ReservationStatus2["CANCELLED"] = "cancelled";
|
|
663
|
+
ReservationStatus2["EXPIRED"] = "expired";
|
|
664
|
+
return ReservationStatus2;
|
|
665
|
+
})(ReservationStatus || {});
|
|
666
|
+
var BookingResourceStatus = /* @__PURE__ */ ((BookingResourceStatus2) => {
|
|
667
|
+
BookingResourceStatus2["ACTIVE"] = "active";
|
|
668
|
+
BookingResourceStatus2["INACTIVE"] = "inactive";
|
|
669
|
+
return BookingResourceStatus2;
|
|
670
|
+
})(BookingResourceStatus || {});
|
|
671
|
+
var ResourceAvailabilityState = /* @__PURE__ */ ((ResourceAvailabilityState2) => {
|
|
672
|
+
ResourceAvailabilityState2["AVAILABLE"] = "available";
|
|
673
|
+
ResourceAvailabilityState2["FULLY_BOOKED"] = "fullyBooked";
|
|
674
|
+
ResourceAvailabilityState2["NO_HOURS_IN_RANGE"] = "noHoursInRange";
|
|
675
|
+
return ResourceAvailabilityState2;
|
|
676
|
+
})(ResourceAvailabilityState || {});
|
|
677
|
+
|
|
614
678
|
// utils/currency.util.ts
|
|
615
679
|
var CURRENCY_CONFIG = {
|
|
616
680
|
// Major currencies with 2 decimal places
|
|
@@ -1822,6 +1886,15 @@ function sumRefundQuantities(refunds) {
|
|
|
1822
1886
|
}
|
|
1823
1887
|
return t;
|
|
1824
1888
|
}
|
|
1889
|
+
var asCount = (value) => typeof value === "number" && Number.isFinite(value) ? Math.max(0, value) : 0;
|
|
1890
|
+
function countsOf(row) {
|
|
1891
|
+
const quantity = asCount(row.quantity);
|
|
1892
|
+
return {
|
|
1893
|
+
quantity,
|
|
1894
|
+
fulfilled: Math.min(asCount(row.fulfilledQuantity), quantity),
|
|
1895
|
+
returned: Math.min(asCount(row.returnedQuantity), quantity)
|
|
1896
|
+
};
|
|
1897
|
+
}
|
|
1825
1898
|
function buildOrderContext(order, extras) {
|
|
1826
1899
|
const o = order ?? {};
|
|
1827
1900
|
const total = typeof o.total === "number" && Number.isFinite(o.total) ? o.total : 0;
|
|
@@ -1829,6 +1902,7 @@ function buildOrderContext(order, extras) {
|
|
|
1829
1902
|
const paymentMethods = o.paymentMethods ?? [];
|
|
1830
1903
|
const refunds = o.refunds ?? o.refund ?? [];
|
|
1831
1904
|
const lineItems = o.lineItems ?? [];
|
|
1905
|
+
const reservations = o.reservations ?? [];
|
|
1832
1906
|
const totalPaid = sumNumbers(paymentMethods);
|
|
1833
1907
|
const totalRefunded = sumRefundQuantities(refunds);
|
|
1834
1908
|
const isFullyPaid = total > 0 ? balance <= 0 : totalPaid > 0 && balance <= 0;
|
|
@@ -1836,13 +1910,11 @@ function buildOrderContext(order, extras) {
|
|
|
1836
1910
|
let itemCount = 0;
|
|
1837
1911
|
let fulfilledItemCount = 0;
|
|
1838
1912
|
let returnedItemCount = 0;
|
|
1839
|
-
for (const
|
|
1840
|
-
const
|
|
1841
|
-
itemCount +=
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
const rq = typeof li.returnedQuantity === "number" && Number.isFinite(li.returnedQuantity) ? Math.max(0, li.returnedQuantity) : 0;
|
|
1845
|
-
returnedItemCount += Math.min(rq, q);
|
|
1913
|
+
for (const row of [...lineItems, ...reservations]) {
|
|
1914
|
+
const { quantity, fulfilled, returned } = countsOf(row);
|
|
1915
|
+
itemCount += quantity;
|
|
1916
|
+
fulfilledItemCount += fulfilled;
|
|
1917
|
+
returnedItemCount += returned;
|
|
1846
1918
|
}
|
|
1847
1919
|
const isFullyReturned = itemCount > 0 ? returnedItemCount >= itemCount : returnedItemCount > 0;
|
|
1848
1920
|
const hasPartialReturns = returnedItemCount > 0 && !isFullyReturned;
|
|
@@ -1852,7 +1924,8 @@ function buildOrderContext(order, extras) {
|
|
|
1852
1924
|
balance,
|
|
1853
1925
|
paymentMethods,
|
|
1854
1926
|
refunds,
|
|
1855
|
-
lineItems
|
|
1927
|
+
lineItems,
|
|
1928
|
+
reservations
|
|
1856
1929
|
},
|
|
1857
1930
|
computed: {
|
|
1858
1931
|
balance,
|
|
@@ -2932,6 +3005,378 @@ var ORDER_STATE_EVENTS = {
|
|
|
2932
3005
|
configUpdated: "config-updated"
|
|
2933
3006
|
};
|
|
2934
3007
|
|
|
3008
|
+
// booking/types.ts
|
|
3009
|
+
var BookingRulesError = class extends Error {
|
|
3010
|
+
constructor(message) {
|
|
3011
|
+
super(message);
|
|
3012
|
+
this.name = "BookingRulesError";
|
|
3013
|
+
}
|
|
3014
|
+
};
|
|
3015
|
+
|
|
3016
|
+
// booking/zoned-time.ts
|
|
3017
|
+
var MINUTE = 6e4;
|
|
3018
|
+
var formatters = /* @__PURE__ */ new Map();
|
|
3019
|
+
var formatterFor = (timeZone) => {
|
|
3020
|
+
const existing = formatters.get(timeZone);
|
|
3021
|
+
if (existing) return existing;
|
|
3022
|
+
let created;
|
|
3023
|
+
try {
|
|
3024
|
+
created = new Intl.DateTimeFormat("en-US", {
|
|
3025
|
+
timeZone,
|
|
3026
|
+
hour12: false,
|
|
3027
|
+
year: "numeric",
|
|
3028
|
+
month: "2-digit",
|
|
3029
|
+
day: "2-digit",
|
|
3030
|
+
hour: "2-digit",
|
|
3031
|
+
minute: "2-digit",
|
|
3032
|
+
second: "2-digit",
|
|
3033
|
+
weekday: "short"
|
|
3034
|
+
});
|
|
3035
|
+
} catch {
|
|
3036
|
+
throw new BookingRulesError(`Booking rules: "${timeZone}" is not a known time zone`);
|
|
3037
|
+
}
|
|
3038
|
+
formatters.set(timeZone, created);
|
|
3039
|
+
return created;
|
|
3040
|
+
};
|
|
3041
|
+
var WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
3042
|
+
function zonedParts(instant, timeZone) {
|
|
3043
|
+
const parts = {};
|
|
3044
|
+
for (const { type, value } of formatterFor(timeZone).formatToParts(instant)) parts[type] = value;
|
|
3045
|
+
const hour = Number(parts.hour) % 24;
|
|
3046
|
+
return {
|
|
3047
|
+
year: Number(parts.year),
|
|
3048
|
+
month: Number(parts.month),
|
|
3049
|
+
day: Number(parts.day),
|
|
3050
|
+
hour,
|
|
3051
|
+
minute: Number(parts.minute),
|
|
3052
|
+
second: Number(parts.second),
|
|
3053
|
+
weekday: WEEKDAYS.indexOf(parts.weekday)
|
|
3054
|
+
};
|
|
3055
|
+
}
|
|
3056
|
+
function offsetAt(instant, timeZone) {
|
|
3057
|
+
const { year, month, day, hour, minute, second } = zonedParts(instant, timeZone);
|
|
3058
|
+
return Date.UTC(year, month - 1, day, hour, minute, second) - Math.floor(instant.getTime() / 1e3) * 1e3;
|
|
3059
|
+
}
|
|
3060
|
+
function instantFromLocal(year, month, day, minutes, timeZone) {
|
|
3061
|
+
const naive = Date.UTC(year, month - 1, day) + minutes * MINUTE;
|
|
3062
|
+
const firstGuess = naive - offsetAt(new Date(naive), timeZone);
|
|
3063
|
+
return new Date(naive - offsetAt(new Date(firstGuess), timeZone));
|
|
3064
|
+
}
|
|
3065
|
+
function startOfZonedDay(instant, timeZone) {
|
|
3066
|
+
const { year, month, day } = zonedParts(instant, timeZone);
|
|
3067
|
+
return instantFromLocal(year, month, day, 0, timeZone);
|
|
3068
|
+
}
|
|
3069
|
+
function nextZonedDay(dayStart, timeZone) {
|
|
3070
|
+
const { year, month, day } = zonedParts(dayStart, timeZone);
|
|
3071
|
+
return instantFromLocal(year, month, day + 1, 0, timeZone);
|
|
3072
|
+
}
|
|
3073
|
+
function atZonedMinute(dayStart, minutes, timeZone) {
|
|
3074
|
+
const { year, month, day } = zonedParts(dayStart, timeZone);
|
|
3075
|
+
return instantFromLocal(year, month, day, minutes, timeZone);
|
|
3076
|
+
}
|
|
3077
|
+
|
|
3078
|
+
// booking/grid.ts
|
|
3079
|
+
var MINUTE2 = 6e4;
|
|
3080
|
+
var MAX_RANGE_DAYS = 366;
|
|
3081
|
+
var asTime = (value, field) => {
|
|
3082
|
+
if (value == null) return void 0;
|
|
3083
|
+
const time = new Date(value).getTime();
|
|
3084
|
+
if (Number.isNaN(time)) throw new BookingRulesError(`Booking rules: ${field} is not a date, got "${String(value)}"`);
|
|
3085
|
+
return time;
|
|
3086
|
+
};
|
|
3087
|
+
function parseHhMm(value, field) {
|
|
3088
|
+
const [hours, minutes] = value.split(":").map(Number);
|
|
3089
|
+
if (!Number.isInteger(hours) || !Number.isInteger(minutes)) {
|
|
3090
|
+
throw new BookingRulesError(`Booking rules: ${field} must be "HH:mm", got "${value}"`);
|
|
3091
|
+
}
|
|
3092
|
+
return hours * 60 + minutes;
|
|
3093
|
+
}
|
|
3094
|
+
function windowEndsAt(rule, now) {
|
|
3095
|
+
const { maxAdvanceDays, bookableTo } = rule.window;
|
|
3096
|
+
const ends = Math.min(
|
|
3097
|
+
maxAdvanceDays ? now.getTime() + maxAdvanceDays * 1440 * MINUTE2 : Number.POSITIVE_INFINITY,
|
|
3098
|
+
asTime(bookableTo, "window.bookableTo") ?? Number.POSITIVE_INFINITY
|
|
3099
|
+
);
|
|
3100
|
+
return Number.isFinite(ends) ? new Date(ends) : null;
|
|
3101
|
+
}
|
|
3102
|
+
function periodSlot(dayStart, timeZone, rule, openMinute, closeMinute) {
|
|
3103
|
+
const { ratePeriod, slot, checkInTime, checkOutTime } = rule;
|
|
3104
|
+
const at = (minutes) => atZonedMinute(dayStart, minutes, timeZone);
|
|
3105
|
+
if (ratePeriod === "night" /* NIGHT */) {
|
|
3106
|
+
if (!checkInTime || !checkOutTime) {
|
|
3107
|
+
throw new BookingRulesError("Overnight booking rules need both checkInTime and checkOutTime");
|
|
3108
|
+
}
|
|
3109
|
+
const checkIn = parseHhMm(checkInTime, "checkInTime");
|
|
3110
|
+
const checkOut = parseHhMm(checkOutTime, "checkOutTime");
|
|
3111
|
+
return {
|
|
3112
|
+
startAt: at(checkIn),
|
|
3113
|
+
endAt: at(1440 + checkOut),
|
|
3114
|
+
// Housekeeping sits between check-out and the next check-in: it costs no night.
|
|
3115
|
+
bufferEndAt: at(1440 + checkIn),
|
|
3116
|
+
canStart: true
|
|
3117
|
+
};
|
|
3118
|
+
}
|
|
3119
|
+
const from = ratePeriod === "day" /* DAY */ ? openMinute ?? 0 : 0;
|
|
3120
|
+
const to = ratePeriod === "day" /* DAY */ ? closeMinute ?? 1440 : 1440;
|
|
3121
|
+
return {
|
|
3122
|
+
startAt: at(from),
|
|
3123
|
+
endAt: at(to),
|
|
3124
|
+
bufferEndAt: at(to + (slot.bufferMinutes ?? 0)),
|
|
3125
|
+
canStart: true
|
|
3126
|
+
};
|
|
3127
|
+
}
|
|
3128
|
+
function freeGaps(openAt, closeAt, busy) {
|
|
3129
|
+
const inside = busy.map(({ startAt, bufferEndAt }) => ({ from: +startAt, to: +bufferEndAt })).filter(({ from, to }) => to > openAt && from < closeAt).sort((first, second) => first.from - second.from);
|
|
3130
|
+
const gaps = [];
|
|
3131
|
+
let cursor = openAt;
|
|
3132
|
+
for (const { from, to } of inside) {
|
|
3133
|
+
if (from > cursor) gaps.push({ from: cursor, to: from, endsAtBooking: true });
|
|
3134
|
+
cursor = Math.max(cursor, to);
|
|
3135
|
+
if (cursor >= closeAt) break;
|
|
3136
|
+
}
|
|
3137
|
+
if (cursor < closeAt) gaps.push({ from: cursor, to: closeAt, endsAtBooking: false });
|
|
3138
|
+
return gaps;
|
|
3139
|
+
}
|
|
3140
|
+
function buildGrid(rule, timeZone, from, to, now, busy = []) {
|
|
3141
|
+
if (to.getTime() - from.getTime() > MAX_RANGE_DAYS * 1440 * MINUTE2) {
|
|
3142
|
+
throw new BookingRulesError(`A booking range may span at most ${MAX_RANGE_DAYS} days`);
|
|
3143
|
+
}
|
|
3144
|
+
const { availability, slot, window, ratePeriod } = rule;
|
|
3145
|
+
const periodMode = ratePeriod !== "slot" /* SLOT */;
|
|
3146
|
+
const weeklyHours = availability.weeklyHours ?? [];
|
|
3147
|
+
const notBefore = Math.max(
|
|
3148
|
+
now.getTime() + (window.minNoticeMinutes ?? 0) * MINUTE2,
|
|
3149
|
+
asTime(window.bookableFrom, "window.bookableFrom") ?? 0,
|
|
3150
|
+
asTime(availability.availableFrom, "availability.availableFrom") ?? 0
|
|
3151
|
+
);
|
|
3152
|
+
const notAfter = windowEndsAt(rule, now)?.getTime() ?? Number.POSITIVE_INFINITY;
|
|
3153
|
+
const blocked = (availability.offMarket ?? []).map(({ from: blockFrom, to: blockTo }) => ({
|
|
3154
|
+
from: asTime(blockFrom, "availability.offMarket.from"),
|
|
3155
|
+
to: asTime(blockTo, "availability.offMarket.to")
|
|
3156
|
+
}));
|
|
3157
|
+
const arrivalDays = availability.arrivalDays;
|
|
3158
|
+
const step = slot.lengthMinutes + (slot.bufferMinutes ?? 0);
|
|
3159
|
+
const slots = [];
|
|
3160
|
+
const lastDay = startOfZonedDay(to, timeZone).getTime();
|
|
3161
|
+
let dayStart = startOfZonedDay(from, timeZone);
|
|
3162
|
+
while (dayStart.getTime() <= lastDay) {
|
|
3163
|
+
const weekday = zonedParts(dayStart, timeZone).weekday;
|
|
3164
|
+
const rows = weeklyHours.filter((entry) => entry.weekday === weekday);
|
|
3165
|
+
if (periodMode) {
|
|
3166
|
+
const openToday = weeklyHours.length === 0 || rows.length > 0;
|
|
3167
|
+
if (openToday) {
|
|
3168
|
+
const built = periodSlot(dayStart, timeZone, rule, rows[0]?.openMinute, rows[0]?.closeMinute);
|
|
3169
|
+
slots.push({ ...built, canStart: !arrivalDays?.length || arrivalDays.includes(weekday) });
|
|
3170
|
+
}
|
|
3171
|
+
} else {
|
|
3172
|
+
for (const row of rows) {
|
|
3173
|
+
const openAt = +atZonedMinute(dayStart, row.openMinute, timeZone);
|
|
3174
|
+
const closeAt = +atZonedMinute(dayStart, row.closeMinute, timeZone);
|
|
3175
|
+
for (const gap of freeGaps(openAt, closeAt, busy)) {
|
|
3176
|
+
const mustFit = (gap.endsAtBooking ? step : slot.lengthMinutes) * MINUTE2;
|
|
3177
|
+
for (let start = gap.from; start + mustFit <= gap.to; start += step * MINUTE2) {
|
|
3178
|
+
slots.push({
|
|
3179
|
+
startAt: new Date(start),
|
|
3180
|
+
endAt: new Date(start + slot.lengthMinutes * MINUTE2),
|
|
3181
|
+
bufferEndAt: new Date(start + step * MINUTE2),
|
|
3182
|
+
canStart: true
|
|
3183
|
+
});
|
|
3184
|
+
}
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
3187
|
+
}
|
|
3188
|
+
dayStart = nextZonedDay(dayStart, timeZone);
|
|
3189
|
+
}
|
|
3190
|
+
const askedFrom = from.getTime();
|
|
3191
|
+
const askedTo = to.getTime();
|
|
3192
|
+
return slots.filter(({ startAt, bufferEndAt }) => {
|
|
3193
|
+
const start = startAt.getTime();
|
|
3194
|
+
if (start < askedFrom || start >= askedTo) return false;
|
|
3195
|
+
if (start < notBefore || start > notAfter) return false;
|
|
3196
|
+
return !blocked.some(({ from: blockFrom, to: blockTo }) => start < blockTo && bufferEndAt.getTime() > blockFrom);
|
|
3197
|
+
}).sort((first, second) => +first.startAt - +second.startAt);
|
|
3198
|
+
}
|
|
3199
|
+
function slotsForWindow(grid, startAt, endAt) {
|
|
3200
|
+
const startIndex = grid.findIndex((entry) => entry.startAt.getTime() === startAt.getTime());
|
|
3201
|
+
if (startIndex < 0) {
|
|
3202
|
+
throw new BookingRulesError(`${startAt.toISOString()} is not a bookable slot start for these rules`);
|
|
3203
|
+
}
|
|
3204
|
+
if (!grid[startIndex].canStart) {
|
|
3205
|
+
throw new BookingRulesError(`${startAt.toISOString()} is not an arrival day for these rules`);
|
|
3206
|
+
}
|
|
3207
|
+
const covered = [];
|
|
3208
|
+
for (let index = startIndex; index < grid.length; index++) {
|
|
3209
|
+
const entry = grid[index];
|
|
3210
|
+
const previous = covered[covered.length - 1];
|
|
3211
|
+
if (previous && entry.startAt.getTime() !== previous.bufferEndAt.getTime()) break;
|
|
3212
|
+
covered.push(entry);
|
|
3213
|
+
if (entry.endAt.getTime() === endAt.getTime()) return covered;
|
|
3214
|
+
if (entry.endAt.getTime() > endAt.getTime()) break;
|
|
3215
|
+
}
|
|
3216
|
+
throw new BookingRulesError(
|
|
3217
|
+
`${startAt.toISOString()} \u2192 ${endAt.toISOString()} does not line up with the bookable grid`
|
|
3218
|
+
);
|
|
3219
|
+
}
|
|
3220
|
+
|
|
3221
|
+
// booking/availability.ts
|
|
3222
|
+
var withinPresence = (slot, timeZone, presence) => {
|
|
3223
|
+
if (!presence?.length) return true;
|
|
3224
|
+
const dayStart = startOfZonedDay(slot.startAt, timeZone);
|
|
3225
|
+
const weekday = zonedParts(dayStart, timeZone).weekday;
|
|
3226
|
+
return presence.some(
|
|
3227
|
+
(shift) => shift.weekday === weekday && slot.startAt.getTime() >= atZonedMinute(dayStart, shift.openMinute, timeZone).getTime() && slot.endAt.getTime() <= atZonedMinute(dayStart, shift.closeMinute, timeZone).getTime()
|
|
3228
|
+
);
|
|
3229
|
+
};
|
|
3230
|
+
var overlaps = (entry, startAt, bufferEndAt) => entry.startAt.getTime() < bufferEndAt.getTime() && entry.bufferEndAt.getTime() > startAt.getTime();
|
|
3231
|
+
function occupancyIn(occupied, productId, startAt, bufferEndAt, shared) {
|
|
3232
|
+
const clashing = occupied.filter((entry) => overlaps(entry, startAt, bufferEndAt));
|
|
3233
|
+
const foreign = shared ? void 0 : clashing.find((entry) => entry.productId !== productId);
|
|
3234
|
+
return {
|
|
3235
|
+
taken: clashing.filter((entry) => shared || entry.productId === productId).reduce((sum, entry) => sum + entry.qty, 0),
|
|
3236
|
+
foreignProductId: foreign?.productId
|
|
3237
|
+
};
|
|
3238
|
+
}
|
|
3239
|
+
var stateFor = (hasShift, offerable, free) => {
|
|
3240
|
+
if (offerable === 0) {
|
|
3241
|
+
return hasShift() ? "fullyBooked" /* FULLY_BOOKED */ : "noHoursInRange" /* NO_HOURS_IN_RANGE */;
|
|
3242
|
+
}
|
|
3243
|
+
return free === 0 ? "fullyBooked" /* FULLY_BOOKED */ : "available" /* AVAILABLE */;
|
|
3244
|
+
};
|
|
3245
|
+
function shopDayKey(instant, timeZone) {
|
|
3246
|
+
const { year, month, day } = zonedParts(instant, timeZone);
|
|
3247
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
3248
|
+
return `${year}-${pad(month)}-${pad(day)}`;
|
|
3249
|
+
}
|
|
3250
|
+
function shopDayRange(fromDayKey, count, timeZone) {
|
|
3251
|
+
const [year, month, day] = fromDayKey.split("-").map(Number);
|
|
3252
|
+
return {
|
|
3253
|
+
from: instantFromLocal(year, month, day, 0, timeZone),
|
|
3254
|
+
to: instantFromLocal(year, month, day + Math.max(1, count), 0, timeZone)
|
|
3255
|
+
};
|
|
3256
|
+
}
|
|
3257
|
+
function buildAvailability({
|
|
3258
|
+
productId,
|
|
3259
|
+
bookingRulesId,
|
|
3260
|
+
bookingType,
|
|
3261
|
+
rule,
|
|
3262
|
+
resources,
|
|
3263
|
+
occupied,
|
|
3264
|
+
from,
|
|
3265
|
+
to,
|
|
3266
|
+
now
|
|
3267
|
+
}) {
|
|
3268
|
+
const shared = rule.slot.sharedAcrossProducts ?? false;
|
|
3269
|
+
const bookableUntil = windowEndsAt(rule, now);
|
|
3270
|
+
const header = {
|
|
3271
|
+
productId,
|
|
3272
|
+
bookingRulesId,
|
|
3273
|
+
bookingType,
|
|
3274
|
+
ratePeriod: rule.ratePeriod,
|
|
3275
|
+
// Passed through for the UI to label: a group slot under minimumToRun may not run.
|
|
3276
|
+
minimumToRun: rule.slot.minimumToRun,
|
|
3277
|
+
minStay: rule.slot.minStay,
|
|
3278
|
+
maxStay: rule.slot.maxStay,
|
|
3279
|
+
// Why a day can come back empty. Past this instant the rule refuses everything, so the UI
|
|
3280
|
+
// can say "beyond the booking window" where it would otherwise claim "nothing free".
|
|
3281
|
+
bookableUntil,
|
|
3282
|
+
// The comparison itself, not just the boundary: deciding whether a day lies past the
|
|
3283
|
+
// window is a rule, and a UI that re-derives it owns a copy of that rule — which is how
|
|
3284
|
+
// the same empty grid ends up explained one way on the till and another in a flow.
|
|
3285
|
+
beyondWindow: !!bookableUntil && from.getTime() > bookableUntil.getTime()
|
|
3286
|
+
};
|
|
3287
|
+
const byResource = /* @__PURE__ */ new Map();
|
|
3288
|
+
for (const entry of occupied) {
|
|
3289
|
+
byResource.set(entry.resourceId, [...byResource.get(entry.resourceId) ?? [], entry]);
|
|
3290
|
+
}
|
|
3291
|
+
const grids = resources.map((resource) => {
|
|
3292
|
+
const mine = byResource.get(resource.id) ?? [];
|
|
3293
|
+
const busy = (resource.seats ?? 1) === 1 ? mine : [];
|
|
3294
|
+
const slots2 = buildGrid(rule, resource.timeZone, from, to, now, busy).filter(
|
|
3295
|
+
(slot) => withinPresence(slot, resource.timeZone, resource.presence)
|
|
3296
|
+
);
|
|
3297
|
+
return { resource, slots: slots2 };
|
|
3298
|
+
});
|
|
3299
|
+
const byInstant = /* @__PURE__ */ new Map();
|
|
3300
|
+
const freeSlotsPerResource = /* @__PURE__ */ new Map();
|
|
3301
|
+
for (const { resource, slots: slots2 } of grids) {
|
|
3302
|
+
const capacity = resource.seats ?? 1;
|
|
3303
|
+
const mine = byResource.get(resource.id) ?? [];
|
|
3304
|
+
for (const slot of slots2) {
|
|
3305
|
+
const { taken, foreignProductId } = occupancyIn(mine, productId, slot.startAt, slot.bufferEndAt, shared);
|
|
3306
|
+
const free = foreignProductId ? 0 : Math.max(capacity - taken, 0);
|
|
3307
|
+
const entry = byInstant.get(+slot.startAt) ?? { ...slot, capacity: 0, booked: 0, free: 0, resources: [], dayKey: shopDayKey(slot.startAt, resource.timeZone) };
|
|
3308
|
+
entry.capacity += capacity;
|
|
3309
|
+
entry.booked += foreignProductId ? capacity : taken;
|
|
3310
|
+
entry.free += free;
|
|
3311
|
+
entry.canStart = entry.canStart || slot.canStart;
|
|
3312
|
+
entry.resources.push({ resourceId: resource.id, name: resource.name, capacity, booked: taken, free });
|
|
3313
|
+
if (free > 0) freeSlotsPerResource.set(resource.id, (freeSlotsPerResource.get(resource.id) ?? 0) + 1);
|
|
3314
|
+
byInstant.set(+slot.startAt, entry);
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3317
|
+
const inWindow = grids.map(({ resource, slots: slots2 }) => {
|
|
3318
|
+
const free = freeSlotsPerResource.get(resource.id) ?? 0;
|
|
3319
|
+
return {
|
|
3320
|
+
resourceId: resource.id,
|
|
3321
|
+
name: resource.name,
|
|
3322
|
+
slots: slots2.length,
|
|
3323
|
+
free,
|
|
3324
|
+
// Lazily: the shift grid is a second walk, and only an empty answer needs it.
|
|
3325
|
+
state: stateFor(
|
|
3326
|
+
() => buildGrid(rule, resource.timeZone, from, to, now).filter(
|
|
3327
|
+
(slot) => withinPresence(slot, resource.timeZone, resource.presence)
|
|
3328
|
+
).length > 0,
|
|
3329
|
+
slots2.length,
|
|
3330
|
+
free
|
|
3331
|
+
)
|
|
3332
|
+
};
|
|
3333
|
+
});
|
|
3334
|
+
const slots = [...byInstant.values()].sort((first, second) => +first.startAt - +second.startAt);
|
|
3335
|
+
return {
|
|
3336
|
+
...header,
|
|
3337
|
+
resources: inWindow,
|
|
3338
|
+
slots,
|
|
3339
|
+
/**
|
|
3340
|
+
* The clock every instant above reads on. Returned rather than assumed: without it a caller
|
|
3341
|
+
* has to fetch the zone from somewhere else and hope it is the same one the grid used.
|
|
3342
|
+
*/
|
|
3343
|
+
timeZone: resources[0]?.timeZone ?? null,
|
|
3344
|
+
/** The shop days this answer covers, in order — what a day picker lists. */
|
|
3345
|
+
days: [...new Set(slots.map((slot) => slot.dayKey))]
|
|
3346
|
+
};
|
|
3347
|
+
}
|
|
3348
|
+
function presentAt(resources, rows, outletId) {
|
|
3349
|
+
const byResource = /* @__PURE__ */ new Map();
|
|
3350
|
+
for (const row of rows) {
|
|
3351
|
+
byResource.set(row.resourceId, [...byResource.get(row.resourceId) ?? [], row]);
|
|
3352
|
+
}
|
|
3353
|
+
return resources.flatMap((resource) => {
|
|
3354
|
+
const own = byResource.get(resource.id) ?? [];
|
|
3355
|
+
if (own.length === 0 || !outletId) return [{ resource }];
|
|
3356
|
+
const here = own.find((row) => row.outletId === outletId);
|
|
3357
|
+
return here ? [{ resource, presence: here.weeklyHours }] : [];
|
|
3358
|
+
});
|
|
3359
|
+
}
|
|
3360
|
+
|
|
3361
|
+
// booking/plan.ts
|
|
3362
|
+
var DEFAULT_HOLD_MINUTES = 10;
|
|
3363
|
+
function planWindow({ rule, timeZone, presence, startAt, endAt, now, busy = [] }) {
|
|
3364
|
+
const grid = buildGrid(rule, timeZone, startAt, endAt, now, busy).filter(
|
|
3365
|
+
(slot) => withinPresence(slot, timeZone, presence)
|
|
3366
|
+
);
|
|
3367
|
+
const slots = slotsForWindow(grid, startAt, endAt);
|
|
3368
|
+
const { minStay, maxStay } = rule.slot;
|
|
3369
|
+
if (minStay && slots.length < minStay) {
|
|
3370
|
+
throw new BookingRulesError(`These rules ask for at least ${minStay} period(s), got ${slots.length}`);
|
|
3371
|
+
}
|
|
3372
|
+
if (maxStay && slots.length > maxStay) {
|
|
3373
|
+
throw new BookingRulesError(`These rules allow at most ${maxStay} period(s), got ${slots.length}`);
|
|
3374
|
+
}
|
|
3375
|
+
const last = slots[slots.length - 1];
|
|
3376
|
+
return { slots, endAt: last.endAt, bufferEndAt: last.bufferEndAt };
|
|
3377
|
+
}
|
|
3378
|
+
var holdExpiresAt = (rule, now) => new Date(now.getTime() + (rule.window.holdMinutes ?? DEFAULT_HOLD_MINUTES) * 6e4);
|
|
3379
|
+
|
|
2935
3380
|
// constants/adyen.constants.ts
|
|
2936
3381
|
var ADYEN_ROLLOUT_COUNTRY_CODES = /* @__PURE__ */ new Set(["CA"]);
|
|
2937
3382
|
function isAdyenRolloutCountry(alpha2) {
|
|
@@ -2941,6 +3386,11 @@ function isAdyenRolloutCountry(alpha2) {
|
|
|
2941
3386
|
0 && (module.exports = {
|
|
2942
3387
|
ADYEN_ROLLOUT_COUNTRY_CODES,
|
|
2943
3388
|
AttributeType,
|
|
3389
|
+
BookingRatePeriod,
|
|
3390
|
+
BookingResourceKind,
|
|
3391
|
+
BookingResourceStatus,
|
|
3392
|
+
BookingRulesError,
|
|
3393
|
+
BookingType,
|
|
2944
3394
|
CONDITION_OPERATORS,
|
|
2945
3395
|
COUNTRY_CURRENCIES,
|
|
2946
3396
|
CURRENCY_CONFIG,
|
|
@@ -2951,6 +3401,7 @@ function isAdyenRolloutCountry(alpha2) {
|
|
|
2951
3401
|
DEFAULT_CONFIG,
|
|
2952
3402
|
DEFAULT_CROSS_AXIS_RULES,
|
|
2953
3403
|
DEFAULT_DISPLAY_STATE_MAP,
|
|
3404
|
+
DEFAULT_HOLD_MINUTES,
|
|
2954
3405
|
DEFAULT_RESOLVED_CONFIG,
|
|
2955
3406
|
DEFAULT_UNIT_ID,
|
|
2956
3407
|
DualWriteStrategy,
|
|
@@ -2994,8 +3445,10 @@ function isAdyenRolloutCountry(alpha2) {
|
|
|
2994
3445
|
REPORT_NEGATE_PATHS,
|
|
2995
3446
|
ReceiptSendChannel,
|
|
2996
3447
|
ReceiptType,
|
|
3448
|
+
ReservationStatus,
|
|
2997
3449
|
ResidualPaymentMethod,
|
|
2998
3450
|
ResidualStatus,
|
|
3451
|
+
ResourceAvailabilityState,
|
|
2999
3452
|
STATE_SCHEMA,
|
|
3000
3453
|
STRIPE_SUPPORTED_COUNTRIES,
|
|
3001
3454
|
SYSTEM_FAMILIES,
|
|
@@ -3016,6 +3469,8 @@ function isAdyenRolloutCountry(alpha2) {
|
|
|
3016
3469
|
baseFulfillmentDefinitions,
|
|
3017
3470
|
baseOf,
|
|
3018
3471
|
basePaymentDefinitions,
|
|
3472
|
+
buildAvailability,
|
|
3473
|
+
buildGrid,
|
|
3019
3474
|
buildOrderContext,
|
|
3020
3475
|
buildPosPresetConfig,
|
|
3021
3476
|
buildResolvedConfig,
|
|
@@ -3055,6 +3510,7 @@ function isAdyenRolloutCountry(alpha2) {
|
|
|
3055
3510
|
getPricingRegionFromCountry,
|
|
3056
3511
|
getSettlementTotal,
|
|
3057
3512
|
getTotalCollected,
|
|
3513
|
+
holdExpiresAt,
|
|
3058
3514
|
inferStatesFromLegacyStatus,
|
|
3059
3515
|
isAdyenRolloutCountry,
|
|
3060
3516
|
isConditionOperator,
|
|
@@ -3069,9 +3525,12 @@ function isAdyenRolloutCountry(alpha2) {
|
|
|
3069
3525
|
mergeFragment,
|
|
3070
3526
|
multiplyAmount,
|
|
3071
3527
|
normalizeMongoIdString,
|
|
3528
|
+
occupancyIn,
|
|
3072
3529
|
parseAmountToMinorUnits,
|
|
3073
3530
|
parseCurrencyCode,
|
|
3074
3531
|
parseStripeCurrencyCode,
|
|
3532
|
+
planWindow,
|
|
3533
|
+
presentAt,
|
|
3075
3534
|
removeFragment,
|
|
3076
3535
|
resolveFamily,
|
|
3077
3536
|
resolveTemplate,
|
|
@@ -3079,13 +3538,18 @@ function isAdyenRolloutCountry(alpha2) {
|
|
|
3079
3538
|
roundToPrecision,
|
|
3080
3539
|
sameFamily,
|
|
3081
3540
|
setOrderState,
|
|
3541
|
+
shopDayKey,
|
|
3542
|
+
shopDayRange,
|
|
3082
3543
|
signReportForDisplay,
|
|
3544
|
+
slotsForWindow,
|
|
3083
3545
|
stringifyMinorUnits,
|
|
3084
3546
|
subtractAmounts,
|
|
3085
3547
|
toBase,
|
|
3086
3548
|
toMinorUnits,
|
|
3087
3549
|
validateConfig,
|
|
3088
3550
|
validateFragmentCompatibility,
|
|
3089
|
-
violatesFinancialInvariant
|
|
3551
|
+
violatesFinancialInvariant,
|
|
3552
|
+
windowEndsAt,
|
|
3553
|
+
withinPresence
|
|
3090
3554
|
});
|
|
3091
3555
|
//# sourceMappingURL=index.js.map
|