Workflow: Trial Lifecycle

Technical reference for the canonical trial-workflow module. Trial workflow owns trial date negotiation, symmetric accept/counter rules, lowercase proposal/invoice status values, invoice/payment progression, parent feedback, schedule approval, and conversion to regular sessions.

Entry point

Trial workflow begins once posting-workflow has created a matches row with match_type = 'Trial'. The accept route may optionally hand off bundled first-round slots into proposeTrialDates(), but all trial negotiation semantics belong to trial-workflow after that boundary.

Start-time-only slot contract

Parent and tutor submit only { date, start_time } slots. Worker derives end_time from postings.session_duration_mins (or the linked appointment duration later in lifecycle) and validates 15-minute start increments.

Module Contract

ConcernImplementationWhat it does
Workflow moduleworkers_nivaa/src/lib/workflows/trial-workflow/Owns trial aggregate loading, guards, commands, projections, and effects.
Commandscommands.tsImplements observeTrialStatus, listTrialMatches, proposeTrialDates, respondTrialDates, confirmTrialPayment, recordTrialFeedback, approveTrialSchedule, and convertTrial.
Duration sourcerepository.ts + posting/session duration lookupsDerives trial end times from authoritative duration instead of trusting the client.
Effectseffects.tsEmits in-app/Telegram notifications for proposals, counters, accepts, payment, feedback, and conversion.
Lowercase table statuses

trial_date_proposals.status uses pending, accepted, counter_proposed, expired, or coordinator_needed. trial_invoices.status uses pending, paid, overdue, or waived.

Canonical Trial Phases

PhaseMeaning
Date_PendingA proposal exists from tutor or no proposal exists yet; parent is the next valid responder when tutor has counter-proposed.
Date_ProposedParent has proposed dates and tutor is the next valid responder.
InvoicedDates are accepted, trial appointments and invoices exist, payment confirmation is pending.
ActiveTrial invoices are paid and trial appointments can proceed.
Feedback_PendingTrial sessions are complete and parent feedback is required.
ConvertingParent wants to continue; schedule confirmation / conversion work remains.
CompleteTrial finished either by rejection/withdrawal path or regular conversion.

Negotiation Contract

Symmetric counterparty rule
latest pending proposal by Parent
  -> Tutor may accept or counter

latest pending proposal by Tutor
  -> Parent may accept or counter

proposer may not decide their own latest proposal

Detailed Lifecycle

StepTriggerWorkflow writeNotifications / fanoutNext state
1. Counterparty proposes datesPOST /trial/:matchId/propose-datesproposeTrialDates() inserts a proposal row using start-time-only slots, derives end times server-side, and validates 15-minute increments.Counterparty + admin notifications are emitted.Match moves to Date_Proposed when parent proposed, or remains under responder control based on latest proposal ownership.
2. Counterparty accepts latest proposalPOST /trial/:matchId/respond-dates with action = acceptrespondTrialDates() marks the pending proposal accepted, creates trial appointments, creates trial_invoices, and sets trial_phase = 'Invoiced'.Parent, tutor, and admin are notified.Finance / ops must confirm payment.
3. Counterparty counters latest proposalPOST /trial/:matchId/respond-dates with action = counterCurrent pending proposal is resolved, a new proposal row is inserted from the responding actor, and phase ownership flips to the other side.New responder + admin are notified.Negotiation continues without forcing a coordinator-only fixup.
4. Admin confirms paymentPOST /trial/:matchId/confirm-paymentconfirmTrialPayment() marks invoice rows paid and moves the trial to Active once all pending invoices are paid.Parent and tutor are notified that trial is active.Session execution moves under appointment/session workflow.
5. Parent records feedbackPOST /trial/:matchId/feedbackrecordTrialFeedback() either completes/rejects the trial or moves it to Converting.Tutor + admin fanout depends on continue/not-continue path.Either conversion begins or trial ends.
6. Tutor approves recurring schedulePOST /trial/:matchId/approve-scheduleapproveTrialSchedule() marks the proposed regular schedule approved.Parent + admin are notified.Admin can convert to regular sessions.
7. Admin converts trialPOST /trial/:matchId/convertconvertTrial() creates regular appointments and finalizes the trial branch.Parent, tutor, and admin receive conversion fanout.Control returns to session workflow for regular lessons.

Tables And Fields Touched

  • matches: match_type, trial_phase, status, billing_model, trial_sessions_total, trial_sessions_completed.
  • trial_date_proposals: latest proposal ownership, round count, and serialized start-time-only slots.
  • trial_invoices: trial payment state.
  • trial_feedback: continuation choice, same-schedule choice, proposed regular schedule, tutor approval state.
  • appointments: trial appointments are created here with server-derived end times.