Files
triqura-ecd/supabase/migrations/20260720000003_create_referral_domain.sql
colinislit ea4c0c836f feat(db): technisch datamodel instroom/intake/behandeladvies (SQL-migraties)
Vertaalt de FCO-IM-feitenrondes en logische modellen naar 37 nieuwe
PostgreSQL-tabellen, verdeeld over vier migraties:

- create_reference_data: één generieke value_list_item-tabel (spelregel
  3.1 #1) i.p.v. ~30 losse waardelijst-tabellen, met alle startwaarden uit
  model-aanmelding.md/model-instroom.md/model-intake-behandeladvies.md.
- create_person_client_domain: person, address, contact_detail, client,
  client_relation, referrer, practice_organization,
  client_general_practitioner, insurance, consent, client_portal_account
  (model-aanmelding.md §2.1-2.9, 2.14, 2.16). BSN als bsn_hash (SHA-256),
  conform de platform-privacyregel — geen raw BSN, geen reversibele
  encryptie.
- create_referral_domain: 20 entiteiten uit model-instroom.md (referral_
  request t/m episode_team_involvement), inclusief de vervangt-constraints
  als partial unique index (voorkomt vertakking) en de XOR-check op
  clinical_care_episode tussen acceptatiebesluit en Wvggz-mandaat.
- create_intake_treatment_advice_domain: 5 entiteiten uit
  model-intake-behandeladvies.md.

Scope: dit zijn nieuwe, additieve tabellen naast de bestaande prototype-
tabellen (patients, screenings, intakes, patients.is_john_doe,
intakes.kindcheck_data) — die tabellen zijn bewust niet aangeraakt.
Wiring van de live app op dit schema, en migratie van prototype-data, is
een aparte, nog te nemen beslissing. Migraties zijn nog niet toegepast op
een database.

decision_authority_id (care_acceptance_decision) is tijdelijk nullable
zonder FK — het doeltabel volgt uit de bevoegdheidsronde (B16). Perifere
waardelijst-kolommen zijn TEXT met een verwijzing naar de bedoelde lijst in
een comment, geen harde FK naar value_list_item — applicatievalidatie (Zod)
dekt die laag; business-kritieke uitkomsten (decision_outcome,
treatment_advice_outcome, mandate_type) hebben wel een CHECK-constraint.
2026-07-20 08:57:12 +02:00

568 lines
32 KiB
SQL

-- ============================================================================
-- REFERRAL / INSTROOM DOMAIN
-- ============================================================================
-- Created: 2026-07-20
-- Source: docs/datamodel/deelmodellen/model-instroom.md (20 entiteiten),
-- docs/datamodel/feitenmodellen/feitenmodel-instroom.md, besluiten B21-B30.
--
-- Design principles carried over from the model documents:
-- - referral_case has NO status column — the current state is derived from
-- the event timeline (signal_case_assignment/screening_activity/
-- case_information_request/care_acceptance_decision/case_withdrawal/
-- case_consolidation). See model-instroom.md §5. Application code (or a
-- read-model view, per the UX review) computes "actuele stand", not this
-- schema.
-- - "vervangt"-relations (care_acceptance_decision, professional_referral,
-- municipal_care_assignment) are enforced unique-if-set (partial unique
-- index) so the chain can never fork.
-- - `medewerker`/beslisser references point at the existing `practitioners`
-- table (already used by the prototype screening/intake schema).
-- - `decision_authority_id` on care_acceptance_decision is left nullable
-- for now: the target table for decision authority does not exist yet
-- (bevoegdheidsronde, B16/B33). The model calls it "verplicht"; enforcing
-- NOT NULL today would make every insert impossible. Tighten once that
-- ronde delivers a real authority table.
-- ============================================================================
-- ============================================================================
-- referral_request
-- ============================================================================
CREATE TABLE referral_request (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
person_id UUID REFERENCES person(id) ON DELETE RESTRICT,
person_identified_at TIMESTAMPTZ,
person_identified_by UUID REFERENCES practitioners(id),
initiated_at TIMESTAMPTZ NOT NULL,
initiator_type TEXT NOT NULL, -- waardelijst initiator_type
referrer_id UUID REFERENCES referrer(id),
requested_scope TEXT NOT NULL,
presenting_need_text TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ,
CONSTRAINT referral_request_person_identification CHECK (
(person_id IS NULL AND person_identified_at IS NULL AND person_identified_by IS NULL)
OR (person_id IS NOT NULL AND person_identified_at IS NOT NULL AND person_identified_by IS NOT NULL)
)
);
COMMENT ON TABLE referral_request IS 'model-instroom.md §2.1. person_id optioneel: crisisaanmelding met onbekende identiteit (B26) — geen placeholder-persoon, de koppeling is een eigen feit.';
ALTER TABLE referral_request ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON referral_request FOR ALL USING (auth.role() = 'authenticated');
CREATE TRIGGER update_referral_request_updated_at BEFORE UPDATE ON referral_request FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================================================
-- referral_submission
-- ============================================================================
CREATE TABLE referral_submission (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
received_at TIMESTAMPTZ NOT NULL,
channel TEXT NOT NULL, -- waardelijst submission_channel
source_reference TEXT, -- vrije tekst of naam verwijzer; "onbekend" toegestaan
recorded_by UUID REFERENCES practitioners(id),
raw_note TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ
);
COMMENT ON TABLE referral_submission IS 'model-instroom.md §2.2. Nooit overschreven door een latere aanvulling — een aanvulling is een nieuwe submission.';
ALTER TABLE referral_submission ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON referral_submission FOR ALL USING (auth.role() = 'authenticated');
CREATE TRIGGER update_referral_submission_updated_at BEFORE UPDATE ON referral_submission FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================================================
-- submission_document
-- ============================================================================
CREATE TABLE submission_document (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
submission_id UUID NOT NULL REFERENCES referral_submission(id) ON DELETE CASCADE,
document_type TEXT NOT NULL, -- waardelijst document_type
document_reference UUID NOT NULL, -- documentopslag
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ
);
CREATE INDEX idx_submission_document_submission ON submission_document(submission_id) WHERE deleted_at IS NULL;
COMMENT ON TABLE submission_document IS 'model-instroom.md §2.3.';
ALTER TABLE submission_document ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON submission_document FOR ALL USING (auth.role() = 'authenticated');
CREATE TRIGGER update_submission_document_updated_at BEFORE UPDATE ON submission_document FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================================================
-- submission_request_link
-- ============================================================================
CREATE TABLE submission_request_link (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
submission_id UUID NOT NULL REFERENCES referral_submission(id) ON DELETE CASCADE,
referral_request_id UUID NOT NULL REFERENCES referral_request(id) ON DELETE CASCADE,
established_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ,
UNIQUE (submission_id, referral_request_id)
);
CREATE INDEX idx_submission_request_link_request ON submission_request_link(referral_request_id) WHERE deleted_at IS NULL;
COMMENT ON TABLE submission_request_link IS 'model-instroom.md §2.4. "representeert" vs "vult_aan" is niet opgeslagen — af te leiden als vroegste established_at per referral_request_id.';
ALTER TABLE submission_request_link ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON submission_request_link FOR ALL USING (auth.role() = 'authenticated');
-- ============================================================================
-- submission_duplicate_assessment
-- ============================================================================
CREATE TABLE submission_duplicate_assessment (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
submission_id UUID NOT NULL REFERENCES referral_submission(id) ON DELETE CASCADE,
duplicate_of_submission_id UUID NOT NULL REFERENCES referral_submission(id) ON DELETE CASCADE,
assessed_by UUID NOT NULL REFERENCES practitioners(id),
assessed_at TIMESTAMPTZ NOT NULL,
reason TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ,
CONSTRAINT submission_duplicate_not_self CHECK (submission_id <> duplicate_of_submission_id)
);
COMMENT ON TABLE submission_duplicate_assessment IS 'model-instroom.md §2.5. Niet-destructief: beide submissions blijven bestaan.';
ALTER TABLE submission_duplicate_assessment ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON submission_duplicate_assessment FOR ALL USING (auth.role() = 'authenticated');
-- ============================================================================
-- referral_case
-- ============================================================================
CREATE TABLE referral_case (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
person_id UUID REFERENCES person(id) ON DELETE RESTRICT,
person_identified_at TIMESTAMPTZ,
person_identified_by UUID REFERENCES practitioners(id),
opened_at TIMESTAMPTZ NOT NULL,
presenting_need_summary TEXT NOT NULL,
legal_framework TEXT NOT NULL, -- waardelijst wettelijk_kader
zpm_referral_type TEXT, -- waardelijst zpm_verwijstype
gp_informed_at DATE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ,
CONSTRAINT referral_case_person_identification CHECK (
(person_id IS NULL AND person_identified_at IS NULL AND person_identified_by IS NULL)
OR (person_id IS NOT NULL AND person_identified_at IS NOT NULL AND person_identified_by IS NOT NULL)
)
);
COMMENT ON TABLE referral_case IS 'model-instroom.md §2.6. Geen statusveld — zie §5 aldaar en de opmerking bovenaan dit bestand. legal_framework/zpm_referral_type/gp_informed_at hersteld van het oudere AANMELDING (B21-B27).';
ALTER TABLE referral_case ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON referral_case FOR ALL USING (auth.role() = 'authenticated');
CREATE TRIGGER update_referral_case_updated_at BEFORE UPDATE ON referral_case FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- Now that referral_case exists: wire the consent scope FK from the person/client migration
ALTER TABLE consent
ADD CONSTRAINT consent_scope_referral_case_fk FOREIGN KEY (scope_referral_case_id) REFERENCES referral_case(id) ON DELETE SET NULL;
-- ============================================================================
-- submission_case_assignment
-- ============================================================================
CREATE TABLE submission_case_assignment (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
submission_id UUID NOT NULL REFERENCES referral_submission(id) ON DELETE CASCADE,
referral_case_id UUID NOT NULL REFERENCES referral_case(id) ON DELETE CASCADE,
assigned_at TIMESTAMPTZ NOT NULL,
assigned_by UUID NOT NULL REFERENCES practitioners(id),
reason TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ,
UNIQUE (submission_id, referral_case_id)
);
CREATE INDEX idx_submission_case_assignment_case ON submission_case_assignment(referral_case_id) WHERE deleted_at IS NULL;
COMMENT ON TABLE submission_case_assignment IS 'model-instroom.md §2.7 (hernoemd van signal_case_assignment). Toewijzing is een gebeurtenis, geen overschrijfbaar veld.';
ALTER TABLE submission_case_assignment ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON submission_case_assignment FOR ALL USING (auth.role() = 'authenticated');
-- ============================================================================
-- request_case_link
-- ============================================================================
CREATE TABLE request_case_link (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
referral_request_id UUID NOT NULL REFERENCES referral_request(id) ON DELETE CASCADE,
referral_case_id UUID NOT NULL REFERENCES referral_case(id) ON DELETE CASCADE,
since_date DATE NOT NULL,
reason TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ,
UNIQUE (referral_request_id, referral_case_id)
);
COMMENT ON TABLE request_case_link IS 'model-instroom.md §2.8. reason verplicht (applicatieniveau) zodra een request aan >1 case gekoppeld is.';
ALTER TABLE request_case_link ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON request_case_link FOR ALL USING (auth.role() = 'authenticated');
-- ============================================================================
-- case_consolidation
-- ============================================================================
CREATE TABLE case_consolidation (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
primary_case_id UUID NOT NULL REFERENCES referral_case(id) ON DELETE RESTRICT,
related_case_id UUID NOT NULL REFERENCES referral_case(id) ON DELETE RESTRICT,
overlap_assessed_at TIMESTAMPTZ,
designated_at TIMESTAMPTZ NOT NULL,
designated_by UUID NOT NULL REFERENCES practitioners(id),
reason TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ,
CONSTRAINT case_consolidation_no_self CHECK (primary_case_id <> related_case_id)
);
-- een case kan hoogstens één keer de niet-leidende rol vervullen
CREATE UNIQUE INDEX idx_case_consolidation_related_unique ON case_consolidation(related_case_id) WHERE deleted_at IS NULL;
COMMENT ON TABLE case_consolidation IS 'model-instroom.md §2.9 (hernoemd van access_case_consolidation). Geen speciale decision_authority vereist (administratieve correctie). Transitieve cykels niet uitgesloten op schemaniveau — applicatieverantwoordelijkheid.';
ALTER TABLE case_consolidation ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON case_consolidation FOR ALL USING (auth.role() = 'authenticated');
-- ============================================================================
-- screening_activity
-- ============================================================================
CREATE TABLE screening_activity (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
referral_case_id UUID NOT NULL REFERENCES referral_case(id) ON DELETE CASCADE,
performed_at TIMESTAMPTZ NOT NULL,
activity_type TEXT NOT NULL, -- waardelijst screening_activity_type
performed_by UUID NOT NULL REFERENCES practitioners(id),
notes TEXT,
funded BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ
);
CREATE INDEX idx_screening_activity_case ON screening_activity(referral_case_id) WHERE deleted_at IS NULL;
COMMENT ON TABLE screening_activity IS 'model-instroom.md §2.10. Directe koppeling aan referral_case — de aparte "screening"-groeperingsentiteit is vervallen (reviewronde 19 juli).';
ALTER TABLE screening_activity ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON screening_activity FOR ALL USING (auth.role() = 'authenticated');
CREATE TRIGGER update_screening_activity_updated_at BEFORE UPDATE ON screening_activity FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================================================
-- case_information_request
-- ============================================================================
CREATE TABLE case_information_request (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
referral_case_id UUID NOT NULL REFERENCES referral_case(id) ON DELETE CASCADE,
requested_at TIMESTAMPTZ NOT NULL,
requested_by UUID NOT NULL REFERENCES practitioners(id),
requested_information TEXT NOT NULL,
resolved_by_submission_id UUID REFERENCES referral_submission(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ
);
CREATE UNIQUE INDEX idx_case_information_request_resolved_submission ON case_information_request(resolved_by_submission_id) WHERE resolved_by_submission_id IS NOT NULL AND deleted_at IS NULL;
CREATE INDEX idx_case_information_request_case ON case_information_request(referral_case_id) WHERE deleted_at IS NULL;
COMMENT ON TABLE case_information_request IS 'model-instroom.md §2.11. Geen besluituitkomst en geen statusfase (B21/vraag 5) — een gedateerd gebeurtenisfeit; kan zich ook ná een eerder besluit voordoen tijdens heroverweging.';
ALTER TABLE case_information_request ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON case_information_request FOR ALL USING (auth.role() = 'authenticated');
CREATE TRIGGER update_case_information_request_updated_at BEFORE UPDATE ON case_information_request FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================================================
-- care_acceptance_decision
-- ============================================================================
CREATE TABLE care_acceptance_decision (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
referral_case_id UUID NOT NULL REFERENCES referral_case(id) ON DELETE RESTRICT,
decided_at TIMESTAMPTZ NOT NULL,
decided_by UUID NOT NULL REFERENCES practitioners(id),
decision_authority_id UUID, -- ref decision_authority — tabel volgt uit bevoegdheidsronde (B16); tijdelijk geen FK/NOT NULL
content_match BOOLEAN NOT NULL,
capacity_available BOOLEAN,
program_context TEXT, -- waardelijst care_program of vrije tekst
decision_outcome TEXT NOT NULL, -- waardelijst decision_outcome
follow_up_route TEXT NOT NULL, -- waardelijst follow_up_route
rationale TEXT,
replaces_decision_id UUID REFERENCES care_acceptance_decision(id),
replacement_reason TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ,
CONSTRAINT care_acceptance_decision_outcome_check CHECK (decision_outcome IN ('geaccepteerd', 'afgewezen')),
CONSTRAINT care_acceptance_decision_rationale_required CHECK (
decision_outcome <> 'afgewezen' OR (rationale IS NOT NULL AND length(trim(rationale)) > 0)
),
CONSTRAINT care_acceptance_decision_replacement_reason_required CHECK (
replaces_decision_id IS NULL OR (replacement_reason IS NOT NULL AND length(trim(replacement_reason)) > 0)
),
CONSTRAINT care_acceptance_decision_no_self_replace CHECK (replaces_decision_id <> id)
);
-- vervangt-keten kan nooit vertakken (B23 / reviewronde 19 juli, kritieke bevinding)
CREATE UNIQUE INDEX idx_care_acceptance_decision_replaces_unique ON care_acceptance_decision(replaces_decision_id) WHERE replaces_decision_id IS NOT NULL AND deleted_at IS NULL;
CREATE INDEX idx_care_acceptance_decision_case ON care_acceptance_decision(referral_case_id) WHERE deleted_at IS NULL;
COMMENT ON TABLE care_acceptance_decision IS 'model-instroom.md §2.12. Geen apart voorafgaand screeningsadvies (B21). Concurrency bij gelijktijdige heroverwegingen (SELECT ... FOR UPDATE op de keten-tail) is applicatieverantwoordelijkheid.';
ALTER TABLE care_acceptance_decision ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON care_acceptance_decision FOR ALL USING (auth.role() = 'authenticated');
CREATE TRIGGER update_care_acceptance_decision_updated_at BEFORE UPDATE ON care_acceptance_decision FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================================================
-- case_withdrawal
-- ============================================================================
CREATE TABLE case_withdrawal (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
referral_case_id UUID NOT NULL REFERENCES referral_case(id) ON DELETE CASCADE,
withdrawn_at TIMESTAMPTZ NOT NULL,
recorded_by UUID NOT NULL REFERENCES practitioners(id),
note TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ
);
CREATE INDEX idx_case_withdrawal_case ON case_withdrawal(referral_case_id) WHERE deleted_at IS NULL;
COMMENT ON TABLE case_withdrawal IS 'model-instroom.md §2.13. Geen beslisbevoegdheid vereist; kan op elk moment, ook ná een positief besluit (B24).';
ALTER TABLE case_withdrawal ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON case_withdrawal FOR ALL USING (auth.role() = 'authenticated');
-- ============================================================================
-- professional_referral
-- ============================================================================
CREATE TABLE professional_referral (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
referral_request_id UUID NOT NULL REFERENCES referral_request(id) ON DELETE RESTRICT,
referrer_id UUID NOT NULL REFERENCES referrer(id),
referral_date DATE NOT NULL,
echelon TEXT, -- waardelijst echelon
suspected_condition TEXT,
is_readmission BOOLEAN NOT NULL DEFAULT false,
replaces_referral_id UUID REFERENCES professional_referral(id),
replacement_reason TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ,
CONSTRAINT professional_referral_replacement_reason_required CHECK (
replaces_referral_id IS NULL OR (replacement_reason IS NOT NULL AND length(trim(replacement_reason)) > 0)
),
CONSTRAINT professional_referral_no_self_replace CHECK (replaces_referral_id <> id)
);
CREATE UNIQUE INDEX idx_professional_referral_replaces_unique ON professional_referral(replaces_referral_id) WHERE replaces_referral_id IS NOT NULL AND deleted_at IS NULL;
CREATE INDEX idx_professional_referral_request ON professional_referral(referral_request_id) WHERE deleted_at IS NULL;
COMMENT ON TABLE professional_referral IS 'model-instroom.md §2.14. Correctie/aanvulling = nieuw, vervangend record, geen nieuw request (was open punt 1, opgelost).';
ALTER TABLE professional_referral ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON professional_referral FOR ALL USING (auth.role() = 'authenticated');
CREATE TRIGGER update_professional_referral_updated_at BEFORE UPDATE ON professional_referral FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================================================
-- municipal_care_assignment
-- ============================================================================
CREATE TABLE municipal_care_assignment (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
referral_request_id UUID NOT NULL REFERENCES referral_request(id) ON DELETE RESTRICT,
legal_framework TEXT NOT NULL, -- waardelijst municipal_legal_framework
municipality_code TEXT NOT NULL,
assignment_number TEXT NOT NULL,
product_category TEXT NOT NULL, -- waardelijst municipal_product_category
product_code TEXT NOT NULL,
volume NUMERIC NOT NULL,
unit TEXT NOT NULL, -- waardelijst municipal_product_unit
frequency TEXT NOT NULL,
start_date DATE NOT NULL,
end_date DATE,
issued_at TIMESTAMPTZ NOT NULL,
replaces_assignment_id UUID REFERENCES municipal_care_assignment(id),
replacement_reason TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ,
CONSTRAINT municipal_care_assignment_replacement_reason_required CHECK (
replaces_assignment_id IS NULL OR (replacement_reason IS NOT NULL AND length(trim(replacement_reason)) > 0)
),
CONSTRAINT municipal_care_assignment_no_self_replace CHECK (replaces_assignment_id <> id)
);
CREATE UNIQUE INDEX idx_municipal_care_assignment_replaces_unique ON municipal_care_assignment(replaces_assignment_id) WHERE replaces_assignment_id IS NOT NULL AND deleted_at IS NULL;
CREATE UNIQUE INDEX idx_municipal_care_assignment_number ON municipal_care_assignment(municipality_code, assignment_number) WHERE deleted_at IS NULL;
COMMENT ON TABLE municipal_care_assignment IS 'model-instroom.md §2.15 (voorstel, juridisch onderzoek 19 juli). Naast, niet in plaats van professional_referral (Jeugdwet kent een eigen professionele verwijsroute).';
ALTER TABLE municipal_care_assignment ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON municipal_care_assignment FOR ALL USING (auth.role() = 'authenticated');
CREATE TRIGGER update_municipal_care_assignment_updated_at BEFORE UPDATE ON municipal_care_assignment FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================================================
-- legal_mandate
-- ============================================================================
CREATE TABLE legal_mandate (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
referral_case_id UUID NOT NULL REFERENCES referral_case(id) ON DELETE RESTRICT,
mandate_type TEXT NOT NULL, -- waardelijst mandate_type
decision_reference TEXT NOT NULL,
decided_by_role TEXT NOT NULL, -- waardelijst mandate_decider_role
decided_at TIMESTAMPTZ NOT NULL,
medical_statement_reference UUID, -- documentopslag
effectuated_at TIMESTAMPTZ NOT NULL,
valid_from TIMESTAMPTZ NOT NULL,
valid_until TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ,
CONSTRAINT legal_mandate_type_check CHECK (mandate_type IN ('crisismaatregel', 'zorgmachtiging')),
CONSTRAINT legal_mandate_medical_statement_required CHECK (
mandate_type <> 'crisismaatregel' OR medical_statement_reference IS NOT NULL
)
);
CREATE INDEX idx_legal_mandate_case ON legal_mandate(referral_case_id) WHERE deleted_at IS NULL;
COMMENT ON TABLE legal_mandate IS 'model-instroom.md §2.16 (voorstel, juridisch onderzoek 19 juli). Geen vervangt-relatie: een crisismaatregel die overgaat in een zorgmachtiging is een nieuw mandaat.';
ALTER TABLE legal_mandate ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON legal_mandate FOR ALL USING (auth.role() = 'authenticated');
CREATE TRIGGER update_legal_mandate_updated_at BEFORE UPDATE ON legal_mandate FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================================================
-- crisis_encounter_note
-- ============================================================================
CREATE TABLE crisis_encounter_note (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
referral_case_id UUID NOT NULL REFERENCES referral_case(id) ON DELETE CASCADE,
occurred_at TIMESTAMPTZ NOT NULL,
recorded_by UUID NOT NULL REFERENCES practitioners(id),
fact_type TEXT NOT NULL, -- waardelijst crisis_fact_type
description TEXT NOT NULL,
structured_value JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ
);
CREATE INDEX idx_crisis_encounter_note_case ON crisis_encounter_note(referral_case_id) WHERE deleted_at IS NULL;
COMMENT ON TABLE crisis_encounter_note IS 'model-instroom.md §2.17 (voorstel). Werkt al zonder geïdentificeerde persoon. 14-dagen-identificatienudge hangt aan referral_request.person_identified_at IS NULL, geen constraint hier.';
ALTER TABLE crisis_encounter_note ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON crisis_encounter_note FOR ALL USING (auth.role() = 'authenticated');
-- ============================================================================
-- clinical_care_episode
-- ============================================================================
CREATE TABLE clinical_care_episode (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
originating_decision_id UUID REFERENCES care_acceptance_decision(id) ON DELETE RESTRICT,
originating_mandate_id UUID REFERENCES legal_mandate(id) ON DELETE RESTRICT,
started_at TIMESTAMPTZ NOT NULL,
ended_at TIMESTAMPTZ,
end_reason TEXT, -- waardelijst episode_end_reason
possible_continuation_of_episode_id UUID REFERENCES clinical_care_episode(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ,
CONSTRAINT clinical_care_episode_origin_xor CHECK (
(originating_decision_id IS NOT NULL AND originating_mandate_id IS NULL)
OR (originating_decision_id IS NULL AND originating_mandate_id IS NOT NULL)
),
CONSTRAINT clinical_care_episode_end_reason_required CHECK (
ended_at IS NULL OR end_reason IS NOT NULL
),
CONSTRAINT clinical_care_episode_no_self_continuation CHECK (possible_continuation_of_episode_id <> id)
);
CREATE UNIQUE INDEX idx_clinical_care_episode_decision ON clinical_care_episode(originating_decision_id) WHERE originating_decision_id IS NOT NULL AND deleted_at IS NULL;
CREATE UNIQUE INDEX idx_clinical_care_episode_mandate ON clinical_care_episode(originating_mandate_id) WHERE originating_mandate_id IS NOT NULL AND deleted_at IS NULL;
COMMENT ON TABLE clinical_care_episode IS 'model-instroom.md §2.18 (alleen ontstaan/einde — volledig episodemodel is een ander deelgebied). XOR: ontstaat óf uit een acceptatiebesluit óf uit een mandaat. possible_continuation_of_episode_id is een zorginhoudelijke aanwijzing, GEEN NZa-trajectnummertoets.';
ALTER TABLE clinical_care_episode ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON clinical_care_episode FOR ALL USING (auth.role() = 'authenticated');
CREATE TRIGGER update_clinical_care_episode_updated_at BEFORE UPDATE ON clinical_care_episode FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================================================
-- case_urgency_assessment
-- ============================================================================
CREATE TABLE case_urgency_assessment (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
referral_case_id UUID NOT NULL REFERENCES referral_case(id) ON DELETE CASCADE,
assessed_at TIMESTAMPTZ NOT NULL,
assessed_by UUID NOT NULL REFERENCES practitioners(id),
assessed_in_role TEXT NOT NULL, -- waardelijst urgency_assessor_role
urgency_level TEXT NOT NULL, -- waardelijst urgency_level
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ
);
CREATE INDEX idx_case_urgency_assessment_case ON case_urgency_assessment(referral_case_id) WHERE deleted_at IS NULL;
COMMENT ON TABLE case_urgency_assessment IS 'model-instroom.md §2.19. Herhaalbaar, laatste assessed_at geldt; geen vervangt-relatie (lagere inzet dan een acceptatiebesluit).';
ALTER TABLE case_urgency_assessment ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON case_urgency_assessment FOR ALL USING (auth.role() = 'authenticated');
-- ============================================================================
-- episode_team_involvement (minimale, voorlopige hook — zie besluitenlog §10 punt 3)
-- ============================================================================
CREATE TABLE episode_team_involvement (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
clinical_care_episode_id UUID NOT NULL REFERENCES clinical_care_episode(id) ON DELETE CASCADE,
team_reference TEXT NOT NULL, -- vrije tekst; geen Team-entiteit vóór de zorgteam-ronde
involved_since TIMESTAMPTZ NOT NULL,
recorded_by UUID NOT NULL REFERENCES practitioners(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ
);
CREATE INDEX idx_episode_team_involvement_episode ON episode_team_involvement(clinical_care_episode_id) WHERE deleted_at IS NULL;
COMMENT ON TABLE episode_team_involvement IS 'model-instroom.md §2.20. Bewust minimaal — geen rollen, geen bevoegdheid, geen individueel lidmaatschap. Wordt vervangen/geabsorbeerd door het volledige zorgteam-model (besluitenlog §10 punt 3).';
ALTER TABLE episode_team_involvement ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON episode_team_involvement FOR ALL USING (auth.role() = 'authenticated');