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.
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.
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
| Concern | Implementation | What it does |
|---|---|---|
| Workflow module | workers_nivaa/src/lib/workflows/trial-workflow/ | Owns trial aggregate loading, guards, commands, projections, and effects. |
| Commands | commands.ts | Implements observeTrialStatus, listTrialMatches, proposeTrialDates, respondTrialDates, confirmTrialPayment, recordTrialFeedback, approveTrialSchedule, and convertTrial. |
| Duration source | repository.ts + posting/session duration lookups | Derives trial end times from authoritative duration instead of trusting the client. |
| Effects | effects.ts | Emits in-app/Telegram notifications for proposals, counters, accepts, payment, feedback, and conversion. |
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
| Phase | Meaning |
|---|---|
Date_Pending | A proposal exists from tutor or no proposal exists yet; parent is the next valid responder when tutor has counter-proposed. |
Date_Proposed | Parent has proposed dates and tutor is the next valid responder. |
Invoiced | Dates are accepted, trial appointments and invoices exist, payment confirmation is pending. |
Active | Trial invoices are paid and trial appointments can proceed. |
Feedback_Pending | Trial sessions are complete and parent feedback is required. |
Converting | Parent wants to continue; schedule confirmation / conversion work remains. |
Complete | Trial finished either by rejection/withdrawal path or regular conversion. |
Negotiation Contract
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 proposalDetailed Lifecycle
| Step | Trigger | Workflow write | Notifications / fanout | Next state |
|---|---|---|---|---|
| 1. Counterparty proposes dates | POST /trial/:matchId/propose-dates | proposeTrialDates() 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 proposal | POST /trial/:matchId/respond-dates with action = accept | respondTrialDates() 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 proposal | POST /trial/:matchId/respond-dates with action = counter | Current 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 payment | POST /trial/:matchId/confirm-payment | confirmTrialPayment() 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 feedback | POST /trial/:matchId/feedback | recordTrialFeedback() 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 schedule | POST /trial/:matchId/approve-schedule | approveTrialSchedule() marks the proposed regular schedule approved. | Parent + admin are notified. | Admin can convert to regular sessions. |
| 7. Admin converts trial | POST /trial/:matchId/convert | convertTrial() 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.