@final-commerce/common 2.4.0-preprod.1 → 2.4.0-preprod.3

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.mjs CHANGED
@@ -3051,6 +3051,26 @@ var stateFor = (hasShift, offerable, free) => {
3051
3051
  }
3052
3052
  return free === 0 ? "fullyBooked" /* FULLY_BOOKED */ : "available" /* AVAILABLE */;
3053
3053
  };
3054
+ function shopDayKey(instant, timeZone) {
3055
+ const { year, month, day } = zonedParts(instant, timeZone);
3056
+ const pad = (value) => String(value).padStart(2, "0");
3057
+ return `${year}-${pad(month)}-${pad(day)}`;
3058
+ }
3059
+ function shopDayRange(fromDayKey, count, timeZone) {
3060
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(fromDayKey ?? "");
3061
+ if (!match) {
3062
+ throw new BookingRulesError(`Booking rules: "${fromDayKey}" is not a shop day (YYYY-MM-DD)`);
3063
+ }
3064
+ if (!Number.isInteger(count) || count < 1) {
3065
+ throw new BookingRulesError(`Booking rules: "${count}" is not a number of days`);
3066
+ }
3067
+ const [, year, month, day] = match.map(Number);
3068
+ const from = instantFromLocal(year, month, day, 0, timeZone);
3069
+ if (shopDayKey(from, timeZone) !== fromDayKey) {
3070
+ throw new BookingRulesError(`Booking rules: "${fromDayKey}" is not a real day`);
3071
+ }
3072
+ return { from, to: instantFromLocal(year, month, day + count, 0, timeZone) };
3073
+ }
3054
3074
  function buildAvailability({
3055
3075
  productId,
3056
3076
  bookingRulesId,
@@ -3088,20 +3108,20 @@ function buildAvailability({
3088
3108
  const grids = resources.map((resource) => {
3089
3109
  const mine = byResource.get(resource.id) ?? [];
3090
3110
  const busy = (resource.seats ?? 1) === 1 ? mine : [];
3091
- const slots = buildGrid(rule, resource.timeZone, from, to, now, busy).filter(
3111
+ const slots2 = buildGrid(rule, resource.timeZone, from, to, now, busy).filter(
3092
3112
  (slot) => withinPresence(slot, resource.timeZone, resource.presence)
3093
3113
  );
3094
- return { resource, slots };
3114
+ return { resource, slots: slots2 };
3095
3115
  });
3096
3116
  const byInstant = /* @__PURE__ */ new Map();
3097
3117
  const freeSlotsPerResource = /* @__PURE__ */ new Map();
3098
- for (const { resource, slots } of grids) {
3118
+ for (const { resource, slots: slots2 } of grids) {
3099
3119
  const capacity = resource.seats ?? 1;
3100
3120
  const mine = byResource.get(resource.id) ?? [];
3101
- for (const slot of slots) {
3121
+ for (const slot of slots2) {
3102
3122
  const { taken, foreignProductId } = occupancyIn(mine, productId, slot.startAt, slot.bufferEndAt, shared);
3103
3123
  const free = foreignProductId ? 0 : Math.max(capacity - taken, 0);
3104
- const entry = byInstant.get(+slot.startAt) ?? { ...slot, capacity: 0, booked: 0, free: 0, resources: [] };
3124
+ const entry = byInstant.get(+slot.startAt) ?? { ...slot, capacity: 0, booked: 0, free: 0, resources: [], dayKey: shopDayKey(slot.startAt, resource.timeZone) };
3105
3125
  entry.capacity += capacity;
3106
3126
  entry.booked += foreignProductId ? capacity : taken;
3107
3127
  entry.free += free;
@@ -3111,27 +3131,35 @@ function buildAvailability({
3111
3131
  byInstant.set(+slot.startAt, entry);
3112
3132
  }
3113
3133
  }
3114
- const inWindow = grids.map(({ resource, slots }) => {
3134
+ const inWindow = grids.map(({ resource, slots: slots2 }) => {
3115
3135
  const free = freeSlotsPerResource.get(resource.id) ?? 0;
3116
3136
  return {
3117
3137
  resourceId: resource.id,
3118
3138
  name: resource.name,
3119
- slots: slots.length,
3139
+ slots: slots2.length,
3120
3140
  free,
3121
3141
  // Lazily: the shift grid is a second walk, and only an empty answer needs it.
3122
3142
  state: stateFor(
3123
3143
  () => buildGrid(rule, resource.timeZone, from, to, now).filter(
3124
3144
  (slot) => withinPresence(slot, resource.timeZone, resource.presence)
3125
3145
  ).length > 0,
3126
- slots.length,
3146
+ slots2.length,
3127
3147
  free
3128
3148
  )
3129
3149
  };
3130
3150
  });
3151
+ const slots = [...byInstant.values()].sort((first, second) => +first.startAt - +second.startAt);
3131
3152
  return {
3132
3153
  ...header,
3133
3154
  resources: inWindow,
3134
- slots: [...byInstant.values()].sort((first, second) => +first.startAt - +second.startAt)
3155
+ slots,
3156
+ /**
3157
+ * The clock every instant above reads on. Returned rather than assumed: without it a caller
3158
+ * has to fetch the zone from somewhere else and hope it is the same one the grid used.
3159
+ */
3160
+ timeZone: resources[0]?.timeZone ?? null,
3161
+ /** The shop days this answer covers, in order — what a day picker lists. */
3162
+ days: [...new Set(slots.map((slot) => slot.dayKey))]
3135
3163
  };
3136
3164
  }
3137
3165
  function presentAt(resources, rows, outletId) {
@@ -3326,6 +3354,8 @@ export {
3326
3354
  roundToPrecision,
3327
3355
  sameFamily,
3328
3356
  setOrderState,
3357
+ shopDayKey,
3358
+ shopDayRange,
3329
3359
  signReportForDisplay,
3330
3360
  slotsForWindow,
3331
3361
  stringifyMinorUnits,