Kaizen
Browse modulesBookingsbookings/serverClasses

Class: SeriesService

Defined in: server/bookings/series-service.ts:127

Constructors

Constructor

new SeriesService(deps: SeriesServiceDeps): SeriesService;

Defined in: server/bookings/series-service.ts:128

Parameters

ParameterType
depsSeriesServiceDeps

Returns

SeriesService

Properties

deps

protected readonly deps: SeriesServiceDeps;

Defined in: server/bookings/series-service.ts:128

Methods

cancelOccurrence()

cancelOccurrence(bookingId: string, opts: {
  actorId: string;
  allowLateCancel?: boolean;
  reason?: string;
}): Promise<{
  amountOwedCents: number | null;
  bookingTypeId: string;
  bookingTypeVersionId: string;
  capacityUnits: number;
  createdAt: Date;
  createdBy: string | null;
  currency: string | null;
  deletedAt: Date | null;
  deletedBy: string | null;
  groupId: string | null;
  holdExpiresAt: Date | null;
  id: string;
  organizationId: string;
  origin: BookingOrigin;
  paid: boolean;
  partyEmail: string | null;
  partyName: string | null;
  partyRef: string | null;
  payload: JsonValue;
  paymentRef: string | null;
  pendingExpiresAt: Date | null;
  status: BookingStatus;
  statusUpdatedAt: Date;
  statusUpdatedBy: string | null;
  supersededById: string | null;
  uid: string;
  updatedAt: Date;
  updatedBy: string | null;
  version: number;
}>;

Defined in: server/bookings/series-service.ts:202

THIS-OCCURRENCE skip (the EXDATE analogue, §6.8): cancel the one child and flag its schedule isException — the cancelled-but-present row keeps occupying its (series_id, occurrence_at) slot, so the occurrence is never regenerated. Cancellation policy (and allowLateCancel) applies exactly as for a standalone booking.

Parameters

ParameterType
bookingIdstring
opts{ actorId: string; allowLateCancel?: boolean; reason?: string; }
opts.actorIdstring
opts.allowLateCancel?boolean
opts.reason?string

Returns

Promise<{ amountOwedCents: number | null; bookingTypeId: string; bookingTypeVersionId: string; capacityUnits: number; createdAt: Date; createdBy: string | null; currency: string | null; deletedAt: Date | null; deletedBy: string | null; groupId: string | null; holdExpiresAt: Date | null; id: string; organizationId: string; origin: BookingOrigin; paid: boolean; partyEmail: string | null; partyName: string | null; partyRef: string | null; payload: JsonValue; paymentRef: string | null; pendingExpiresAt: Date | null; status: BookingStatus; statusUpdatedAt: Date; statusUpdatedBy: string | null; supersededById: string | null; uid: string; updatedAt: Date; updatedBy: string | null; version: number; }>


createSeries()

createSeries(raw: {
  actorId: string;
  allowOutsideHours?: boolean;
  allowOverbook?: boolean;
  bookingTypeId: string;
  dtstartLocal: string;
  durationMinutes: number;
  horizon: unknown;
  maxAdvanceMs: number;
  organizationId: string;
  partyRef?: string | null;
  recurrenceRule: string;
  resourceId: string;
  timeZone: string;
}): Promise<CreateSeriesResult>;

Defined in: server/bookings/series-service.ts:142

Create a BookingSeries and fan it out through horizon in ONE transaction (spec §6.8): there is never a window where a confirmed series exists with no materialized children blocking its slots.

The §6.8 fail-open guard: horizon must reach now + maxAdvanceMs, or the create is REJECTED (SeriesHorizonError) — beyond the horizon the series' occurrences are not rows and block nothing, so a short horizon sells slots the series already owns. maxAdvanceMs is a per-call policy value (mirroring SchedulerHealth.staleness), not a column — the max-advance window is consumer-owned configuration.

Parameters

ParameterTypeDescription
raw{ actorId: string; allowOutsideHours?: boolean; allowOverbook?: boolean; bookingTypeId: string; dtstartLocal: string; durationMinutes: number; horizon: unknown; maxAdvanceMs: number; organizationId: string; partyRef?: string | null; recurrenceRule: string; resourceId: string; timeZone: string; }-
raw.actorIdstring-
raw.allowOutsideHours?boolean§6.9 bypass-and-record — stamped onto each materialized child's claim.
raw.allowOverbook?boolean§6.6 bypass-and-record — stamped onto each materialized child's claim.
raw.bookingTypeIdstring-
raw.dtstartLocalstringWall-clock ISO date-time, NO offset — e.g. 2026-06-15T17:00:00.
raw.durationMinutesnumber-
raw.horizonunknownInitial materialization horizon — children are fanned out through this instant at create.
raw.maxAdvanceMsnumberThe org's max-advance booking window; horizon must reach now + maxAdvanceMs.
raw.organizationIdstring-
raw.partyRef?string | nullNull/omitted = a system/offering occupancy series (children get origin: "system").
raw.recurrenceRulestring-
raw.resourceIdstring-
raw.timeZonestring-

Returns

Promise<CreateSeriesResult>


editAllFuture()

editAllFuture(
   seriesId: string, 
   patch: {
  resourceId: string;
}, 
   opts: {
  actorId: string;
  allowOutsideHours?: boolean;
  allowOverbook?: boolean;
}
): Promise<{
  reconciled: number;
  series: {
     bookingTypeId: string;
     bookingTypeVersionId: string;
     createdAt: Date;
     createdBy: string | null;
     deletedAt: Date | null;
     deletedBy: string | null;
     dtstartLocal: string;
     durationMinutes: number;
     id: string;
     materializedThrough: Date | null;
     organizationId: string;
     partyRef: string | null;
     recurrenceRule: string;
     resourceId: string;
     status: string;
     supersedesSeriesId: string | null;
     timeZone: string;
     updatedAt: Date;
     updatedBy: string | null;
  };
}>;

Defined in: server/bookings/series-service.ts:465

ALL-FUTURE bulk edit (spec §6.8 table): apply a resource move to every future non-exception, non-terminal child AND renew the series template so newly materialized children follow. Time/rule changes are a this-and-following split — use editThisAndFollowing.

Parameters

ParameterType
seriesIdstring
patch{ resourceId: string; }
patch.resourceIdstring
opts{ actorId: string; allowOutsideHours?: boolean; allowOverbook?: boolean; }
opts.actorIdstring
opts.allowOutsideHours?boolean
opts.allowOverbook?boolean

Returns

Promise<{ reconciled: number; series: { bookingTypeId: string; bookingTypeVersionId: string; createdAt: Date; createdBy: string | null; deletedAt: Date | null; deletedBy: string | null; dtstartLocal: string; durationMinutes: number; id: string; materializedThrough: Date | null; organizationId: string; partyRef: string | null; recurrenceRule: string; resourceId: string; status: string; supersedesSeriesId: string | null; timeZone: string; updatedAt: Date; updatedBy: string | null; }; }>


editOccurrence()

editOccurrence(
   bookingId: string, 
   patch: {
  endsAt?: Date;
  resourceId?: string;
  startsAt?: Date;
}, 
   opts: {
  actorId: string;
  allowOutsideHours?: boolean;
  allowOverbook?: boolean;
}
): Promise<{
  bookingId: string;
  capacityModel: string;
  endsAt: Date;
  id: string;
  isBlocking: boolean;
  isException: boolean;
  occurrenceAt: Date | null;
  overbooked: boolean;
  resourceId: string;
  seriesId: string | null;
  startsAt: Date;
  timeZone: string;
}>;

Defined in: server/bookings/series-service.ts:230

THIS-OCCURRENCE edit (the RECURRENCE-ID analogue, §6.8): mutate the one child's slot and flag isException so series-wide edits skip it. The new slot is capacity- and window-checked under the §6.6 locks. System children mutate in place; customer children route through the §7.1 transfers seam (see reconcileCustomerChild).

Parameters

ParameterType
bookingIdstring
patch{ endsAt?: Date; resourceId?: string; startsAt?: Date; }
patch.endsAt?Date
patch.resourceId?string
patch.startsAt?Date
opts{ actorId: string; allowOutsideHours?: boolean; allowOverbook?: boolean; }
opts.actorIdstring
opts.allowOutsideHours?boolean
opts.allowOverbook?boolean

Returns

Promise<{ bookingId: string; capacityModel: string; endsAt: Date; id: string; isBlocking: boolean; isException: boolean; occurrenceAt: Date | null; overbooked: boolean; resourceId: string; seriesId: string | null; startsAt: Date; timeZone: string; }>


editThisAndFollowing()

editThisAndFollowing(seriesId: string, input: SeriesSplitInput): Promise<SeriesSplitResult>;

Defined in: server/bookings/series-service.ts:303

THIS-AND-FOLLOWING split (spec §6.8 table). In ONE transaction:

  1. cap the old rule — lower COUNT when COUNT-bounded (counting EVERY rule occurrence before the split: a cancelled exception consumed one of the purchased N), else cap with UNTIL; old series → ended;
  2. mint the successor (supersedesSeriesId → old; COUNT = remaining purchased occurrences);
  3. reconcile already-materialized future children by PAIRING them, in occurrence order, with the successor's first instants: non-exception children take the new slot (capacity/window-checked; system in place, customer via the §7.1 seam); exception children keep their diverged content (a skipped week stays skipped, special hours stay special) but re-point their (series_id, occurrence_at) identity so the successor never regenerates over them — which is what makes "10 lessons, week 3 skipped, move at week 5" yield exactly 10;
  4. materialize the successor through the old cursor (no fail-open gap).

Parameters

ParameterType
seriesIdstring
inputSeriesSplitInput

Returns

Promise<SeriesSplitResult>

On this page