Trials — Code Map
API
Module — apps/api/src/trials/
| File | Purpose |
|---|---|
trials.module.ts | TrialsModule: roster, notes, brief, time-change listener, member trial bookings and their controllers; imports FormsModule (health declaration), BookingsModule (the booking engine) and TrialSeatsModule (the class picker). Imported by ClassSessionsModule, which hosts the session-scoped routes. |
trial-seats.module.ts | TrialSeatsModule: TrialSeatsService alone, imported by SalesWorkspaceModule and OrganizationLeadsModule. Kept out of TrialsModule so Sales does not pull in Forms: Sales → Trials → Forms → Notifications → … → Sales would be a module cycle. |
session-roster.service.ts | SessionRosterService: roster (API-internal TrialSource per entry: lead > staff > plan; canMarkTrial / canUnmarkTrial), markBookingTrial (409 trial_not_markable for a Sales or plan trial and for cancelled / no-show bookings), checkInTrial / undoTrialCheckIn (evidence + sales_trial activity), createTrialNote, trialsToday, trialsFor (shared with the brief and the Today rows). |
staff-notes.service.ts | StaffNotesService: staff check (requireStaff), member and lead note CRUD, notesForSubjects (batched, with converted-from lead notes), canEdit. |
staff-notes.controller.ts | organizations/:orgId/members/:membershipId/notes and organizations/:orgId/sales-workspace/leads/:leadId/notes. |
member-trial-bookings.service.ts | MemberTrialBookingsService: book (marks a live booking under its row lock, else BookingsService.bookForMember with trialMarkedByUserId), list (upcoming trials for any reason, canUnmark), sessionOptions (listTrialSessions). |
member-trial-bookings.controller.ts | organizations/:orgId/members/:membershipId/trial-bookings — GET, POST, GET session-options. |
trial-seats.service.ts | TrialSeatsService: listTrialSessions (Sales picker), claimSeat (row lock + capacity), release (waitlist refill, trigger trial_released), moveIntoBooking (a converting lead’s seat becomes their booking, plan picked and charged in a savepoint; a revived row drops any stale staff mark). |
trial-session-sync.listener.ts | TrialSessionSyncListener on SESSION_TIME_CHANGED: moves pending trials, the lead’s trial_date and the follow_up_trial task. |
trial-brief.service.ts | TrialBriefService.sendDueBriefs — @ScheduledJob every 5 min; due query joins the coach’s membership in the session’s org; marker claim, push enqueue, re-arm only if the enqueue throws. |
trials.driver.ts | Integration driver for the specs below (session-roster, trial-seats, trial-brief, staff-notes, member-trial-bookings). |
Seats — apps/api/src/bookings/
| File | Purpose |
|---|---|
trial-seats.ts | SEAT_HOLDING_TRIAL_OUTCOMES, holdsTrialSeat, trialSeatCounts, endPendingTrials (a lead’s trials), endSessionTrials (a cancelled/deleted/overwritten class’s trials, with sales_trial activity and task close). |
booking-enforcement.util.ts | sessionOccupancy / seatsTaken include trial seats; promotionVerdict judges a seat taken now (waitlist promotion, a moved trial seat). |
booking-entitlement-sweep.service.ts | loadCandidates skips bookings with trial_appointment_id. |
booking-events.ts | Promotion trigger trial_released. |
bookings.service.ts | bookForMember / bookForSubject take BookingWriteOptions.trialMarkedByUserId, written with the booking (insert or revive; a revive resets trial_appointment_id and any old mark). Member-facing responses (bookForSubject, getBookingsForUser) omit both trial columns. |
Callers elsewhere
| File | What |
|---|---|
class-sessions/class-sessions.controller.ts | GET trials-today, GET :id/roster, PUT :id/bookings/:bookingId/trial, POST|DELETE :id/trials/:appointmentId/check-in, POST :id/trials/:appointmentId/notes. |
class-sessions/class-sessions.service.ts | getBookingCounts and findUpcomingToday add trial seats; findUpcomingToday rows carry trialCount; resolveBulkFilter counts trial seats as bookings; cancel, deleteSession, bulkDelete call endSessionTrials. |
sales-workspace/sales-workspace.controller.ts | GET trial-sessions. |
sales-workspace/sales-workspace.service.ts | scheduleTrialInTx (end pending trials + claim), refill after a no_show/cancelled outcome, Not relevant; closeAcquisitionAsConverted moves the seat (moveIntoBooking) then ends the rest; lead detail classSession / checkedInAt / checkedInBy, convertedMember. |
organization-leads/organization-leads.service.ts | Archive and bulk archive end pending trials (endPendingTrials) and refill freed seats; convertLead and attachLeadToMembership (CRM link, self-signup) run the conversion writes and closeTrialsOnConversion (moveIntoBooking, then endPendingTrials for the rest) in one transaction, then release. |
program-templates/schedule-planner.ts, program-templates/program-templates.service.ts | loadExistingSessions counts trial seats as bookings (overwrite blocks such a class); applyToSchedule calls endSessionTrials on the sessions it replaces. |
arbox-bridge/arbox-schedule-import.service.ts | cancelDisappeared calls endSessionTrials on the classes it cancels. |
plans/plans.service.ts, plans/plans.controller.ts, plans/dto/*, subscriptions/subscriptions.controller.ts | isTrial, trialOncePerPerson and the trial expiresInDays on create, update and every plan response; update refuses un-flagging a sold trial (400 trial_plan_in_use) and resets trialOncePerPerson on un-flag; purchase calls assertTrialNotUsed for non-staff actors. |
plans/trial-once-per-person.util.ts | assertTrialNotUsed — the once-per-person rule (409 trial_already_used; held, or a pending checkout of another trial plan). |
subscriptions/subscriptions.service.ts, payments/services/{webhook-processing.service,billing-settlement,recurring-charge.service,cash-plan-coverage.service,manual-payment.service}.ts | Stamp the trial’s use-within window on activation via consumableValidityEnd. |
forms/forms.service.ts | healthDeclarationStates. |
push-notifications/notification-copy.ts | pushCopy.trialBrief. |
users/dto/update-notification-prefs.dto.ts | Accepts trials. |
DB — libs/db
src/lib/schema/staff-notes.ts—staffNotes.src/lib/schema/sales-workspace.ts—leadTrialAppointments.classSessionId,checkedInAt,checkedInByUserId.src/lib/schema/scheduling.ts—classSessions.trialBriefSentFor,bookings.trialMarkedByUserId,bookings.trialAppointmentId.src/lib/schema/payments.ts—plans.isTrial,plans.trialOncePerPerson.drizzle/0184_trials.sql.
Shared — libs/shared
src/lib/schemas/trials.schema.ts— notes, roster (rosterTrialSchema={ lead },canMarkTrial,canUnmarkTrial), mark-trial body, trials today, trial-session option, member trial booking (createMemberTrialBookingSchema,memberTrialBookingSchema). No trial source: it is API-internal.src/lib/schemas/plan.schema.ts—isTrial,trialOncePerPerson,isExpiryAllowedForPlan,consumableValidityEnd,planKind,TRIAL_REQUIRES_DROP_IN,TRIAL_PLAN_IN_USE.src/lib/constants/payment-error-codes.ts—TRIAL_ALREADY_USED.src/lib/schemas/sales-workspace.schema.ts—trial.classSessionId.src/lib/schemas/notification-prefs.ts—trials.src/lib/i18n/dictionaries/{en,he,ru}.json—trials(incl.trials.planSheet,trials.alreadyUsed,trials.memberTrials),trialsPush,pushNotifications.categories.trials,plans.types.trial,plans.dialog.typeHints.trial.
Web — apps/web/src
| File | What |
|---|---|
components/overview/schedule/session-trials.tsx | SessionTrials block, TrialBadge, TrialMarkButton (Mark on canMarkTrial, Unmark on canUnmarkTrial). |
components/overview/schedule/session-detail-sheet.tsx | Roster from GET …/roster; mark / unmark; lead trial check-in / undo; add note. |
components/staff-notes/staff-notes.tsx | StaffNotes — list, add, important toggle, edit/delete by canEdit. |
components/overview/members/member-detail-page.tsx | Member notes and the Trial classes card (overview tab). |
components/trials/member-trials-card.tsx | MemberTrialsCard — a member’s upcoming trials (status, coach), Unmark when canUnmark, “Book trial class”. |
components/trials/book-trial-dialog.tsx | BookTrialDialog — class picker (…/trial-bookings/session-options), plan picker on plan_selection_required, refusal copy. |
components/overview/plans/plan-dialog.tsx | Trial workout kind: trial-only fields (price help, Use within, One per person), hidden recurring/pack/presale/group/limit fields. |
app/[lang]/(protected)/dashboard/plans/plans-content.tsx, components/overview/plans/plan-detail-page.tsx | ”Trial workout” type label / header badge (planKind). |
app/[lang]/(protected)/(member)/shop/page.tsx, components/member/family/add-child/plan-step.tsx, app/[lang]/join/[token]/join-continue.tsx | trial_already_used → trials.alreadyUsed. |
components/overview/v2/today-panel.tsx | Trial count chip. |
app/[lang]/(protected)/dashboard/leads/sales-workspace.tsx | Trial session picker in the result editor. |
app/[lang]/(protected)/dashboard/leads/sales-lead-sheet.tsx | Trial class/coach/check-in line, converted trials; lead notes; the converted member’s Trial classes card (convertedMember). |
app/[lang]/(protected)/dashboard/leads/sales-row-editors.tsx, sales-trial-outcome.tsx | Outcome editor preselects attended when checked in. |
Mobile — taikan-mobile
| File | What |
|---|---|
src/hooks/use-trials.ts | Mirrored types; useIsStaff, useTrialsToday, useSessionRoster, useRosterCheckIn, useAddRosterNote, useMarkBookingTrial. In-memory only (persist: false). |
src/components/home/trials-today-card.tsx | TrialsTodayCard on the home screen. |
src/components/schedule/session-roster.tsx | Roster section on the session screen. |
src/components/schedule/trial-badge.tsx | TrialBadge. |
src/i18n/trials-strings.ts, src/i18n/use-trials-strings.ts | he/en/ru strings. |
app/(tabs)/index.tsx, app/(tabs)/schedule/[id].tsx | Mount the card and the roster (staff only). |
src/hooks/use-push-notifications.ts | A push for another org the user belongs to (the trials brief) switches the active org before navigating. |
app/(tabs)/profile/notifications.tsx | Hides trials from non-staff. |
src/lib/query-keys.ts | sessions.roster, sessions.trialsToday under sessions.all. |