Seraya Psikologi — Documentation

Booking and payment MVP · Implementation baseline · 96 ADR

90. Couple Package Participant Model

Status

Accepted for MVP working model. Implements the participant, consent, visibility, notification, withdrawal, and ClientAccess decisions required to make the couple package (ADR 0074) bookable once business intake (TBC-INTAKE-01, TBC-MINOR-01, TBC-COUPLE-LAUNCH-01) is closed. This ADR does not close those intake TBCs and does not authorize couple day-one launch on its own; it defines the model that any couple booking will use.

Ringkasan eksekutif (Bahasa Indonesia)

Context

Launch catalog (ADR 0074, IMPLEMENTATION-GUIDE.md:68–73) mengkonfirmasi couple package dengan sequence A → B → joint, harga online Rp350.000 dan offline Rp550.000. Package ini confirmed launch scope, dan SERVICE-POLICY-MATRIX-v0.1 §4C TBC secara eksplisit menulis keputusan participant/consent/notification/withdrawal wajib sebelum couple live bookable.

Domain model saat ini (DOMAIN-MODEL.md, CONTEXT.md, IMPLEMENTATION-GUIDE.md §4) hanya mengenal satu Client per Booking. Tidak ada entitas yang menjelaskan:

  1. siapa payer versus dua orang service recipient (partner A dan partner B);
  2. bagaimana ConsentRecord dipisah untuk tiap partner dan untuk sesi joint;
  3. apakah partner A melihat jadwal partner B;
  4. siapa menerima konfirmasi, reminder, perubahan jadwal;
  5. apa efek jika satu partner tidak hadir atau menarik diri di tengah sesi joint;
  6. siapa berwenang mengajukan reschedule appointment A/B/joint;
  7. bagaimana ClientAccess di-scope untuk couple (per-participant atau per-booking).

Tanpa model ini, implementasi couple akan menebak keputusan bisnis, menggandakan Client record tanpa hubungan eksplisit, atau membocorkan jadwal individual partner ke partner lain. Round 3 (PRD-GUIDELINE-REVIEW.md) sudah memilih Mode A untuk cancellation/refund dan menjaga keputusan participant sebagai blocker terpisah. ADR ini menjawab blocker participant tanpa menyentuh blocker intake/minor/eligibility.

Diskusi multi-perspektif

Privacy (klinis/etis)

Operations (admin/finance)

Engineering (aggregate & schema)

UX (intake flow)

Decision

Model couple package menggunakan dua entitas baru (BookingParticipant, AppointmentParticipant), dua party role inti (payer, participant_a, participant_b), dan satu label relasional (joint_attendees). Spec lengkap:

1. Aggregate & entities

1.1 BookingParticipant

Child of Booking. Satu couple Booking memiliki tepat dua BookingParticipant rows untuk A dan B, dan satu di antaranya ditandai is_payer = true. Tiap row membawa:

Field Tipe Keterangan
id uuid primary key
booking_id uuid FK ke Booking, unique dengan (booking_id, party_role)
party_role enum participant_a | participant_b
is_payer bool tepat satu true per couple booking
display_name string nama tampilan (bukan nama legal)
contact_email string email terverifikasi, wajib untuk ClientAccess
contact_phone string | null opsional, untuk Admin WhatsApp manual support
age_at_booking int | null snapshot usia saat booking; tidak di-update
relationship_to_other enum | null spouse | partner | other — snapshot, tidak dimutasi
consent_version string versi consent yang di-accept participant ini
consent_record_id uuid FK ke ConsentRecord
consent_status enum pending | verified | withdrawn
created_at timestamp Asia/Jakarta
updated_at timestamp

Invariant: (booking_id, party_role) unique. Tidak boleh ada participant ketiga. Payer harus salah satu dari A atau B (default A); MVP tidak mendukung external payer.

1.2 AppointmentParticipant

Child of Appointment. Tiap Appointment memiliki rows:

Field:

Field Tipe Keterangan
id uuid primary key
appointment_id uuid FK ke Appointment
booking_participant_id uuid FK ke BookingParticipant
party_role enum participant_a | participant_b (denormalized untuk query)
attendance_mode enum individual_a | individual_b | joint_both
presence_status enum expected | present | absent | withdrawn_pre_session | withdrawn_mid_session
last_updated_by enum psychologist | admin | system
created_at / updated_at timestamp

Invariant: untuk appointment A/B, (appointment_id, party_role) unique. Untuk joint, (appointment_id, booking_participant_id) unique dan tepat dua rows.

2. Party roles & relationships

Role Definisi Boleh jadi payer? Boleh menerima notifikasi? Boleh memiliki ClientAccess?
payer label iuran (saat ini = partner A atau B) ya (wajib satu) ya ya
participant_a partner A, attend appointment A dan joint ya (default) ya ya
participant_b partner B, attend appointment B dan joint ya (DEFERRED ke post-MVP) ya ya
joint_attendees label relasional, bukan role; menandai bahwa appointment joint dihadiri A & B n/a n/a n/a

MVP: payer = participant_a by default. Mengubah payer ke participant_b adalah Admin-only action yang memerlukan field reason dan audit; ini DEFERRED.

Tiga ConsentRecord per couple booking:

  1. couple_consent: consent umum couple package (tujuan, kerahasiaan, data, batasan). Ditandatangani payer pada saat checkout.
  2. participant_consent_a / participant_consent_b: consent individu partner A atau B, termasuk verifikasi identitas via email magic link (ADR 0020). Consent B tidak menunggu consent A; dan sebaliknya.
  3. joint_session_consent: consent khusus sesi joint, ditandatangani kedua partner sebelum appointment joint dimulai (cut-off default = sebelum reminder 24 jam). Wording harus secara eksplisit menyebut:
    - sesi joint dihadiri A dan B bersama psikolog;
    - yang dicatat hanya metadata kehadiran dan outcome administratif;
    - sesi bukan catatan klinis gabungan;
    - partner boleh menarik consent dan membatalkan sesi joint tanpa efek ke sesi A/B individual yang sudah selesai.

Semua wording tetap mengikuti sign-off klinis/etis (ADR 0082, TBC-CONSENT-01). ConsentRecord immutable per IMPLEMENTATION-GUIDE.md §8.1.

4. Visibility & notification

Visibility antar partner (read scope):

Data Partner A melihat? Partner B melihat? Admin melihat? Psychologist melihat?
Jadwal appointment A ya (own) tidak ya ya (assigned)
Jadwal appointment B tidak ya (own) ya ya (assigned)
Jadwal appointment joint ya ya ya ya
Display name partner lain ya (untuk joint prep) ya (untuk joint prep) ya ya (untuk joint prep)
Email/phone partner lain tidak tidak ya tidak
Consent record partner lain tidak tidak ya (audit) tidak
Outcome appointment individual partner lain tidak tidak ya tidak
Payment/PaymentEvent amount ya (payer) ya (payer) ya tidak

Notification mengikuti IMPLEMENTATION-GUIDE.md §10:

5. Withdrawal / no-show effects

Event Appointment state Entitlement effect Package effect Notification
Partner A tidak hadir di appointment A Appointment A → no_show Entitlement #1 ter-konsume (ADR 0027) none reminder normal, hasil ke Admin
Partner B tidak hadir di appointment B Appointment B → no_show Entitlement #2 ter-konsume none sama
Salah satu partner mundur dari appointment joint sebelum mulai Appointment joint → cancelled (via CancellationDecision) Entitlement #3 di-restore (joint cancellation di luar ADR 0027 grace; keputusan Admin) none notifikasi ke kedua partner + Admin
Salah satu partner mundur dari appointment joint di tengah sesi Appointment joint → completed (oleh psikolog) Entitlement #3 ter-konsume none notifikasi outcome ke kedua partner
Kedua partner tidak hadir di joint Appointment joint → no_show Entitlement #3 ter-konsume none notifikasi ke Admin
Withdrawal sebelum appointment (tanpa hadiri) mengikuti CancellationRequest → CancellationDecision (approve/deny) ikuti cancellation matrix ADR 0025/ADR 0051/CancellationDecision atomic none sesuai cancellation decision

Refunds tetap purchase-level decision (ADR 0063). Withdrawal/no-show tidak otomatis menghasilkan refund; Admin dapat memutuskan full_refund atau no_refund setelah review kasus per kasus (ADR 0076, ADR 0077).

6. Reschedule authority

7. ClientAccess scope

8. Commands baru

Tambah dua command ke IMPLEMENTATION-GUIDE.md §5.2:

Konsistensi dengan command existing: tidak menghapus/mengubah CreateBooking, CreatePackagePurchase, ScheduleNextEntitlement, RequestCancellation, DecideCancellation. Command baru hanya mendaftarkan participant dan consent.

9. Transition matrix Appointment A/B/joint

Untuk Appointment A atau B (mode individual):

Dari Event Ke Atomic effect
(none) ScheduleNextEntitlement untuk A atau B scheduled buat AppointmentParticipant row untuk participant yang relevan
scheduled verified PaymentEvent untuk purchase awal confirmed tidak ada perubahan participant
scheduled RescheduleAction rescheduled + replacement scheduled pindahkan AppointmentParticipant.attendance_mode ke replacement
confirmed psychologist menandai completed completed entitlement unit terkait ter-konsume; presence_status = present
confirmed psychologist menandai no_show setelah grace 15 menit (ADR 0028) no_show entitlement ter-konsume; presence_status = absent
confirmed / scheduled CancellationDecision approve cancelled entitlement di-restore jika package masih valid
confirmed / scheduled CancellationDecision deny tidak berubah tidak ada mutation

Untuk Appointment joint (mode joint_both, dua AppointmentParticipant rows):

Dari Event Ke Atomic effect untuk A Atomic effect untuk B
(none) ScheduleNextEntitlement untuk joint scheduled buat row joint_both untuk A buat row joint_both untuk B
scheduled joint_session_consent untuk A & B verified confirmed presence_status = expected presence_status = expected
confirmed psychologist menandai completed completed presence_status = present presence_status = present
confirmed satu partner absent di mid-session, psikolog tandai completed completed presence_status = present atau withdrawn_mid_session presence_status = present atau withdrawn_mid_session
confirmed satu partner mundur sebelum mulai, CancellationDecision approve cancelled presence_status = withdrawn_pre_session presence_status = withdrawn_pre_session atau present (tergantung partner mana yang mundur)
confirmed kedua partner absent, no-show grace 15 menit no_show presence_status = absent presence_status = absent

Entitlement #3 (joint) ter-konsume oleh completed atau no_show sesuai ADR 0027. Joint cancellation sebelum mulai me-restore entitlement #3 (di luar default grace consumption), melalui CancellationDecision yang explicit, dengan audit reason.

10. Audit

Setiap mutasi couple state menulis ke AuditRecord:

Pesan WhatsApp tidak menjadi audit source. Pesan admin dapat diringkas ke field reason (non-klinis, max 200 karakter).

Open follow-up

Reference