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.
This commit is contained in:
293
supabase/migrations/20260720000001_create_reference_data.sql
Normal file
293
supabase/migrations/20260720000001_create_reference_data.sql
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
-- ============================================================================
|
||||||
|
-- REFERENCE DATA (WAARDELIJSTEN) — instroom / intake / behandeladvies
|
||||||
|
-- ============================================================================
|
||||||
|
-- Created: 2026-07-20
|
||||||
|
-- Purpose: shared reference-list table for the redesigned instroom/intake/
|
||||||
|
-- behandeladvies domain (docs/datamodel/deelmodellen/model-instroom.md,
|
||||||
|
-- model-intake-behandeladvies.md, model-aanmelding.md §4).
|
||||||
|
--
|
||||||
|
-- Design note: spelregel 3.1 #1 ("elke waardelijst is een referentietabel")
|
||||||
|
-- is honoured with ONE shared, generic table (list_name-discriminated)
|
||||||
|
-- instead of ~30 near-identical single-purpose tables. Each row still
|
||||||
|
-- carries code/label/external_code/code_system/geldig_van/geldig_tot/actief.
|
||||||
|
-- List-specific extra attributes (agb_verplicht, verwijsnudge_severity, ...)
|
||||||
|
-- live in `extra` (JSONB) rather than one-off columns.
|
||||||
|
--
|
||||||
|
-- Scope note: this migration and the ones that follow (create_person_client_
|
||||||
|
-- domain, create_referral_domain, create_intake_treatment_advice_domain)
|
||||||
|
-- implement the REDESIGNED target schema from the 19-20 juli 2026 FCO-IM
|
||||||
|
-- rounds. They are new, additive tables — the existing prototype tables
|
||||||
|
-- (patients, screenings, intakes, patients.is_john_doe, intakes.kindcheck_data)
|
||||||
|
-- are intentionally left untouched. Wiring the live app to this schema, and
|
||||||
|
-- migrating prototype data into it, is a separate follow-up decision.
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
CREATE TABLE value_list_item (
|
||||||
|
list_name TEXT NOT NULL,
|
||||||
|
code TEXT NOT NULL,
|
||||||
|
label TEXT NOT NULL,
|
||||||
|
external_code TEXT,
|
||||||
|
code_system TEXT,
|
||||||
|
extra JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
valid_from DATE,
|
||||||
|
valid_to DATE,
|
||||||
|
active BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
|
||||||
|
PRIMARY KEY (list_name, code)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_value_list_item_list_active ON value_list_item(list_name, active);
|
||||||
|
|
||||||
|
COMMENT ON TABLE value_list_item IS 'Generieke waardelijst-tabel (spelregel 3.1 #1): één rij per (list_name, code). Startlijsten, geen definitieve besluiten (zie modeldocumenten).';
|
||||||
|
COMMENT ON COLUMN value_list_item.extra IS 'List-specifieke attributen, bv. agb_verplicht (verwijzertype), verwijsnudge_severity (wettelijk_kader).';
|
||||||
|
|
||||||
|
ALTER TABLE value_list_item ENABLE ROW LEVEL SECURITY;
|
||||||
|
|
||||||
|
CREATE POLICY "Enable read for authenticated users" ON value_list_item
|
||||||
|
FOR SELECT USING (auth.role() = 'authenticated');
|
||||||
|
|
||||||
|
CREATE TRIGGER update_value_list_item_updated_at
|
||||||
|
BEFORE UPDATE ON value_list_item
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- Seed data — startlijsten uit de modeldocumenten, geen definitieve besluiten
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
INSERT INTO value_list_item (list_name, code, label, extra) VALUES
|
||||||
|
-- geslacht (model-aanmelding.md §4.1) — externe code: HL7 AdministrativeGender
|
||||||
|
('geslacht', 'man', 'Man', '{}'),
|
||||||
|
('geslacht', 'vrouw', 'Vrouw', '{}'),
|
||||||
|
('geslacht', 'anders', 'Anders', '{}'),
|
||||||
|
('geslacht', 'onbekend', 'Onbekend', '{}'),
|
||||||
|
|
||||||
|
-- genderidentiteit (zib Patient v4.3)
|
||||||
|
('genderidentiteit', 'man', 'Man', '{}'),
|
||||||
|
('genderidentiteit', 'vrouw', 'Vrouw', '{}'),
|
||||||
|
('genderidentiteit', 'non_binair', 'Non-binair', '{}'),
|
||||||
|
('genderidentiteit', 'anders', 'Anders', '{}'),
|
||||||
|
('genderidentiteit', 'zegt_het_niet', 'Zegt het niet', '{}'),
|
||||||
|
|
||||||
|
-- adrestype
|
||||||
|
('adrestype', 'woonadres', 'Woonadres', '{}'),
|
||||||
|
('adrestype', 'postadres', 'Postadres', '{}'),
|
||||||
|
('adrestype', 'tijdelijk_verblijf', 'Tijdelijk verblijf', '{}'),
|
||||||
|
|
||||||
|
-- contactgegeven_type
|
||||||
|
('contactgegeven_type', 'telefoon', 'Telefoon', '{}'),
|
||||||
|
('contactgegeven_type', 'email', 'E-mail', '{}'),
|
||||||
|
|
||||||
|
-- contactgegeven_soort
|
||||||
|
('contactgegeven_soort', 'mobiel_prive', 'Mobiel privé', '{}'),
|
||||||
|
('contactgegeven_soort', 'vast_prive', 'Vast privé', '{}'),
|
||||||
|
('contactgegeven_soort', 'werk', 'Werk', '{}'),
|
||||||
|
('contactgegeven_soort', 'overig', 'Overig', '{}'),
|
||||||
|
|
||||||
|
-- relatie
|
||||||
|
('relatie', 'partner', 'Partner', '{}'),
|
||||||
|
('relatie', 'ouder', 'Ouder', '{}'),
|
||||||
|
('relatie', 'kind', 'Kind', '{}'),
|
||||||
|
('relatie', 'broer_zus', 'Broer/zus', '{}'),
|
||||||
|
('relatie', 'familielid_overig', 'Familielid overig', '{}'),
|
||||||
|
('relatie', 'vriend_kennis', 'Vriend/kennis', '{}'),
|
||||||
|
('relatie', 'professional', 'Professional', '{}'),
|
||||||
|
('relatie', 'overig', 'Overig', '{}'),
|
||||||
|
|
||||||
|
-- relatierol (twee assen relatie x rol — zib Contactpersoon)
|
||||||
|
('relatierol', 'eerste_contactpersoon', 'Eerste contactpersoon', '{}'),
|
||||||
|
('relatierol', 'wettelijk_vertegenwoordiger', 'Wettelijk vertegenwoordiger', '{}'),
|
||||||
|
('relatierol', 'gezaghebbende_ouder', 'Gezaghebbende ouder', '{}'),
|
||||||
|
('relatierol', 'mantelzorger', 'Mantelzorger', '{}'),
|
||||||
|
('relatierol', 'naaste', 'Naaste', '{}'),
|
||||||
|
|
||||||
|
-- vertegenwoordigingsgrond (WGBO-volgorde)
|
||||||
|
('vertegenwoordigingsgrond', 'curator', 'Curator', '{}'),
|
||||||
|
('vertegenwoordigingsgrond', 'mentor', 'Mentor', '{}'),
|
||||||
|
('vertegenwoordigingsgrond', 'schriftelijk_gemachtigde', 'Schriftelijk gemachtigde', '{}'),
|
||||||
|
('vertegenwoordigingsgrond', 'ouder_voogd', 'Ouder/voogd', '{}'),
|
||||||
|
('vertegenwoordigingsgrond', 'partner_familie', 'Partner/familie', '{}'),
|
||||||
|
|
||||||
|
-- huisarts_situatie (voedt zpm_verwijstype 04/05)
|
||||||
|
('huisarts_situatie', 'bekend', 'Bekend', '{}'),
|
||||||
|
('huisarts_situatie', 'geen_huisarts', 'Geen huisarts', '{}'),
|
||||||
|
('huisarts_situatie', 'geen_toestemming_correspondentie', 'Geen toestemming correspondentie', '{}'),
|
||||||
|
('huisarts_situatie', 'onbekend', 'Onbekend', '{}'),
|
||||||
|
|
||||||
|
-- verwijzertype (extra.agb_verplicht — discovery §5.0 besluit 3)
|
||||||
|
('verwijzertype', 'huisarts', 'Huisarts', '{"agb_verplicht": true}'),
|
||||||
|
('verwijzertype', 'medisch_specialist', 'Medisch specialist', '{"agb_verplicht": true}'),
|
||||||
|
('verwijzertype', 'straatdokter', 'Straatdokter', '{"agb_verplicht": true}'),
|
||||||
|
('verwijzertype', 'bedrijfsarts', 'Bedrijfsarts', '{"agb_verplicht": true}'),
|
||||||
|
('verwijzertype', 'regiebehandelaar', 'Regiebehandelaar', '{"agb_verplicht": true}'),
|
||||||
|
('verwijzertype', 'ggz_instelling', 'GGZ-instelling', '{"agb_verplicht": true}'),
|
||||||
|
('verwijzertype', 'gemeente', 'Gemeente', '{"agb_verplicht": false}'),
|
||||||
|
('verwijzertype', 'zelfaanmelding', 'Zelfaanmelding', '{"agb_verplicht": false}'),
|
||||||
|
('verwijzertype', 'crisis', 'Crisis', '{"agb_verplicht": false}'),
|
||||||
|
|
||||||
|
-- praktijk_soort
|
||||||
|
('praktijk_soort', 'huisartsenpraktijk', 'Huisartsenpraktijk', '{}'),
|
||||||
|
('praktijk_soort', 'ziekenhuis', 'Ziekenhuis', '{}'),
|
||||||
|
('praktijk_soort', 'ggz_instelling', 'GGZ-instelling', '{}'),
|
||||||
|
('praktijk_soort', 'gemeente', 'Gemeente', '{}'),
|
||||||
|
('praktijk_soort', 'arbodienst', 'Arbodienst', '{}'),
|
||||||
|
('praktijk_soort', 'overig', 'Overig', '{}'),
|
||||||
|
|
||||||
|
-- wettelijk_kader (extra.verwijsnudge_severity — te bevestigen in de nudge-/declaratieronde; wvggz toegevoegd bij B27)
|
||||||
|
('wettelijk_kader', 'zvw', 'Zorgverzekeringswet', '{"verwijsnudge_severity": "blokkade"}'),
|
||||||
|
('wettelijk_kader', 'jeugdwet', 'Jeugdwet', '{"verwijsnudge_severity": "signaal"}'),
|
||||||
|
('wettelijk_kader', 'wmo', 'Wmo', '{"verwijsnudge_severity": "signaal"}'),
|
||||||
|
('wettelijk_kader', 'wlz', 'Wlz', '{"verwijsnudge_severity": "waarschuwing"}'),
|
||||||
|
('wettelijk_kader', 'forensisch', 'Forensisch', '{"verwijsnudge_severity": "waarschuwing"}'),
|
||||||
|
('wettelijk_kader', 'wvggz', 'Wvggz', '{"verwijsnudge_severity": null}'),
|
||||||
|
|
||||||
|
-- submission_channel (was aanmeldkanaal — model-instroom.md §4)
|
||||||
|
('submission_channel', 'zorgdomein', 'ZorgDomein', '{}'),
|
||||||
|
('submission_channel', 'e_mail', 'E-mail', '{}'),
|
||||||
|
('submission_channel', 'telefoon', 'Telefoon', '{}'),
|
||||||
|
('submission_channel', 'portaal', 'Cliëntportaal', '{}'),
|
||||||
|
('submission_channel', 'post', 'Post', '{}'),
|
||||||
|
('submission_channel', 'overig', 'Overig', '{}'),
|
||||||
|
|
||||||
|
-- document_type (model-instroom.md §4)
|
||||||
|
('document_type', 'verwijsbrief', 'Verwijsbrief', '{}'),
|
||||||
|
('document_type', 'diagnostische_aanvulling', 'Diagnostische aanvulling', '{}'),
|
||||||
|
('document_type', 'beschikking', 'Beschikking', '{}'),
|
||||||
|
('document_type', 'overig', 'Overig', '{}'),
|
||||||
|
|
||||||
|
-- screening_activity_type
|
||||||
|
('screening_activity_type', 'telefonisch_contact', 'Telefonisch contact', '{}'),
|
||||||
|
('screening_activity_type', 'dossieronderzoek', 'Dossieronderzoek', '{}'),
|
||||||
|
('screening_activity_type', 'vragenlijst', 'Vragenlijst', '{}'),
|
||||||
|
('screening_activity_type', 'overig', 'Overig', '{}'),
|
||||||
|
|
||||||
|
-- decision_outcome (B22)
|
||||||
|
('decision_outcome', 'geaccepteerd', 'Geaccepteerd', '{}'),
|
||||||
|
('decision_outcome', 'afgewezen', 'Afgewezen', '{}'),
|
||||||
|
|
||||||
|
-- follow_up_route
|
||||||
|
('follow_up_route', 'intake_direct', 'Intake direct plannen', '{}'),
|
||||||
|
('follow_up_route', 'intakewachtlijst', 'Intakewachtlijst', '{}'),
|
||||||
|
('follow_up_route', 'spoedroute', 'Spoedroute', '{}'),
|
||||||
|
('follow_up_route', 'doorverwijzen', 'Doorverwijzen', '{}'),
|
||||||
|
('follow_up_route', 'case_afsluiten', 'Case afsluiten', '{}'),
|
||||||
|
|
||||||
|
-- episode_end_reason (model-aanmelding.md §4.24 + B24-aanvullingen)
|
||||||
|
('episode_end_reason', 'ingetrokken_door_client', 'Ingetrokken door cliënt', '{}'),
|
||||||
|
('episode_end_reason', 'acceptatiebesluit_herzien', 'Acceptatiebesluit herzien', '{}'),
|
||||||
|
('episode_end_reason', 'behandeling_afgerond', 'Behandeling afgerond', '{}'),
|
||||||
|
('episode_end_reason', 'doorverwezen', 'Doorverwezen', '{}'),
|
||||||
|
('episode_end_reason', 'client_beeindigt', 'Cliënt beëindigt', '{}'),
|
||||||
|
('episode_end_reason', 'geen_contact', 'Geen contact meer', '{}'),
|
||||||
|
('episode_end_reason', 'overleden', 'Overleden', '{}'),
|
||||||
|
('episode_end_reason', 'overig', 'Overig', '{}'),
|
||||||
|
|
||||||
|
-- echelon
|
||||||
|
('echelon', 'gb_ggz', 'Generalistische basis-ggz', '{}'),
|
||||||
|
('echelon', 'g_ggz', 'Gespecialiseerde ggz', '{}'),
|
||||||
|
('echelon', 'onbekend', 'Onbekend', '{}'),
|
||||||
|
|
||||||
|
-- zpm_verwijstype (Vektis/ZPM-code als external_code)
|
||||||
|
('zpm_verwijstype', '01', 'Verwijzing aanwezig', '{}'),
|
||||||
|
('zpm_verwijstype', '02', 'Doorverwijzing regiebehandelaar', '{}'),
|
||||||
|
('zpm_verwijstype', '03', 'Geen verwijzing, verlate correspondentie', '{}'),
|
||||||
|
('zpm_verwijstype', '04', 'Geen verwijzing, correspondentie niet toegestaan', '{}'),
|
||||||
|
('zpm_verwijstype', '05', 'Geen verwijzing, niet declarabel', '{}'),
|
||||||
|
('zpm_verwijstype', '06', 'Geen verwijzing, andere grond fz', '{}'),
|
||||||
|
('zpm_verwijstype', '07', 'Verwijzing zonder AGB', '{}'),
|
||||||
|
|
||||||
|
-- municipal_legal_framework (B27)
|
||||||
|
('municipal_legal_framework', 'wmo', 'Wmo', '{}'),
|
||||||
|
('municipal_legal_framework', 'jeugdwet', 'Jeugdwet', '{}'),
|
||||||
|
|
||||||
|
-- municipal_product_category / municipal_product_unit — minimale startwaarden (declaratie-ronde volgt)
|
||||||
|
('municipal_product_category', 'overig', 'Overig (nader te bepalen)', '{}'),
|
||||||
|
('municipal_product_unit', 'overig', 'Overig (nader te bepalen)', '{}'),
|
||||||
|
|
||||||
|
-- mandate_type / mandate_decider_role (B27, Wvggz)
|
||||||
|
('mandate_type', 'crisismaatregel', 'Crisismaatregel', '{}'),
|
||||||
|
('mandate_type', 'zorgmachtiging', 'Zorgmachtiging', '{}'),
|
||||||
|
('mandate_decider_role', 'burgemeester', 'Burgemeester', '{}'),
|
||||||
|
('mandate_decider_role', 'rechter', 'Rechter', '{}'),
|
||||||
|
|
||||||
|
-- crisis_fact_type
|
||||||
|
('crisis_fact_type', 'medicatie_toegediend', 'Medicatie toegediend', '{}'),
|
||||||
|
('crisis_fact_type', 'risico_inschatting', 'Risico-inschatting', '{}'),
|
||||||
|
('crisis_fact_type', 'vitale_functie', 'Vitale functie', '{}'),
|
||||||
|
('crisis_fact_type', 'dwangmaatregel', 'Dwangmaatregel', '{}'),
|
||||||
|
('crisis_fact_type', 'overig', 'Overig', '{}'),
|
||||||
|
|
||||||
|
-- urgency_assessor_role / urgency_level
|
||||||
|
('urgency_assessor_role', 'screener', 'Screener', '{}'),
|
||||||
|
('urgency_assessor_role', 'triagist', 'Triagist', '{}'),
|
||||||
|
('urgency_assessor_role', 'intake_team', 'Intake-team', '{}'),
|
||||||
|
('urgency_assessor_role', 'mdo', 'MDO', '{}'),
|
||||||
|
('urgency_level', 'laag', 'Laag', '{}'),
|
||||||
|
('urgency_level', 'normaal', 'Normaal', '{}'),
|
||||||
|
('urgency_level', 'hoog', 'Hoog', '{}'),
|
||||||
|
('urgency_level', 'spoed', 'Spoed', '{}'),
|
||||||
|
|
||||||
|
-- initiator_type
|
||||||
|
('initiator_type', 'professional', 'Professional', '{}'),
|
||||||
|
('initiator_type', 'organisatie', 'Organisatie', '{}'),
|
||||||
|
('initiator_type', 'client', 'Cliënt', '{}'),
|
||||||
|
('initiator_type', 'vertegenwoordiger', 'Vertegenwoordiger', '{}'),
|
||||||
|
|
||||||
|
-- intake_initiation_reason
|
||||||
|
('intake_initiation_reason', 'regulier', 'Regulier', '{}'),
|
||||||
|
('intake_initiation_reason', 'intern', 'Intern', '{}'),
|
||||||
|
('intake_initiation_reason', 'crisis', 'Crisis', '{}'),
|
||||||
|
|
||||||
|
-- intake_status
|
||||||
|
('intake_status', 'gepland', 'Gepland', '{}'),
|
||||||
|
('intake_status', 'bezig', 'Bezig', '{}'),
|
||||||
|
('intake_status', 'afgerond', 'Afgerond', '{}'),
|
||||||
|
('intake_status', 'afgebroken', 'Afgebroken', '{}'),
|
||||||
|
|
||||||
|
-- intake_abort_reason
|
||||||
|
('intake_abort_reason', 'client_trekt_terug', 'Cliënt trekt zich terug', '{}'),
|
||||||
|
('intake_abort_reason', 'geen_contact_meer', 'Geen contact meer', '{}'),
|
||||||
|
('intake_abort_reason', 'overleden', 'Overleden', '{}'),
|
||||||
|
('intake_abort_reason', 'overig', 'Overig', '{}'),
|
||||||
|
|
||||||
|
-- intake_contact_type
|
||||||
|
('intake_contact_type', 'intakegesprek', 'Intakegesprek', '{}'),
|
||||||
|
('intake_contact_type', 'aanvullend_onderzoek', 'Aanvullend onderzoek', '{}'),
|
||||||
|
('intake_contact_type', 'telefonisch_contact', 'Telefonisch contact', '{}'),
|
||||||
|
('intake_contact_type', 'huisbezoek', 'Huisbezoek', '{}'),
|
||||||
|
('intake_contact_type', 'beeldcontact', 'Beeldcontact', '{}'),
|
||||||
|
('intake_contact_type', 'overig', 'Overig', '{}'),
|
||||||
|
|
||||||
|
-- treatment_advice_outcome (B33 — doorverwijzing/terugverwijzing volgtijdelijk, extra_diagnostiek praktijkgefundeerd)
|
||||||
|
('treatment_advice_outcome', 'in_zorg', 'In zorg', '{}'),
|
||||||
|
('treatment_advice_outcome', 'terugverwijzing', 'Terugverwijzing', '{"lks_genormeerd": true, "volgt_op": "doorverwijzing"}'),
|
||||||
|
('treatment_advice_outcome', 'doorverwijzing', 'Doorverwijzing', '{"lks_genormeerd": true}'),
|
||||||
|
('treatment_advice_outcome', 'extra_diagnostiek', 'Extra diagnostiek', '{"lks_genormeerd": false}'),
|
||||||
|
|
||||||
|
-- department / care_program — instellingsconfigureerbaar, minimale startwaarden
|
||||||
|
('department', 'volwassenen', 'Volwassenen', '{}'),
|
||||||
|
('department', 'jeugd', 'Jeugd', '{}'),
|
||||||
|
('department', 'ouderen', 'Ouderen', '{}'),
|
||||||
|
('department', 'forensisch', 'Forensisch', '{}'),
|
||||||
|
('department', 'overig', 'Overig', '{}'),
|
||||||
|
('care_program', 'algemeen_ggz', 'Algemeen GGZ', '{}'),
|
||||||
|
('care_program', 'stemming', 'Stemming', '{}'),
|
||||||
|
('care_program', 'trauma', 'Trauma', '{}'),
|
||||||
|
('care_program', 'verslaving', 'Verslaving', '{}'),
|
||||||
|
('care_program', 'overig', 'Overig', '{}'),
|
||||||
|
|
||||||
|
-- toestemming_type (extra.grondslag)
|
||||||
|
('toestemming_type', 'correspondentie_huisarts_verwijzer', 'Correspondentie huisarts/verwijzer', '{"grondslag": "WGBO/verwijsafspraken"}'),
|
||||||
|
('toestemming_type', 'dsm_hoofdgroep_op_factuur', 'DSM-hoofdgroep op factuur', '{"grondslag": "Stcrt. 2025-11955, opt-in"}'),
|
||||||
|
('toestemming_type', 'privacyverklaring_zorgvraagtypering', 'Privacyverklaring zorgvraagtypering', '{"grondslag": "NZa"}'),
|
||||||
|
('toestemming_type', 'gegevensdeling_derden', 'Gegevensdeling derden', '{"grondslag": "AVG"}'),
|
||||||
|
('toestemming_type', 'inzage_naasten', 'Inzage naasten', '{"grondslag": "WGBO"}'),
|
||||||
|
|
||||||
|
-- toestemming_status / toestemming_wijze
|
||||||
|
('toestemming_status', 'verleend', 'Verleend', '{}'),
|
||||||
|
('toestemming_status', 'geweigerd', 'Geweigerd', '{}'),
|
||||||
|
('toestemming_status', 'ingetrokken', 'Ingetrokken', '{}'),
|
||||||
|
('toestemming_wijze', 'mondeling', 'Mondeling', '{}'),
|
||||||
|
('toestemming_wijze', 'schriftelijk', 'Schriftelijk', '{}'),
|
||||||
|
('toestemming_wijze', 'portaal', 'Portaal', '{}');
|
||||||
@@ -0,0 +1,349 @@
|
|||||||
|
-- ============================================================================
|
||||||
|
-- PERSON / CLIENT DOMAIN
|
||||||
|
-- ============================================================================
|
||||||
|
-- Created: 2026-07-20
|
||||||
|
-- Source: docs/datamodel/deelmodellen/model-aanmelding.md §2.1-2.9, §2.14, §2.16
|
||||||
|
-- (PERSOON, ADRES, CONTACTGEGEVEN, CLIENT, CLIENTRELATIE, VERWIJZER,
|
||||||
|
-- PRAKTIJK_INSTELLING, CLIENT_HUISARTS, VERZEKERING, TOESTEMMING,
|
||||||
|
-- CLIENTPORTAAL_ACCOUNT) — the AANMELDING/VERWIJZING/ZORGEPISODE part of
|
||||||
|
-- that document is superseded by the referral/intake domains that follow
|
||||||
|
-- this migration, not translated here.
|
||||||
|
--
|
||||||
|
-- Naming: English identifiers per besluit B20. Dutch field names in the
|
||||||
|
-- source document are translated 1:1 in meaning; nothing renamed to a
|
||||||
|
-- different concept.
|
||||||
|
--
|
||||||
|
-- Privacy: raw BSN is never stored, only a SHA-256 hash (bsn_hash) — this
|
||||||
|
-- follows the platform-wide privacy rule (root CLAUDE.md), which is a
|
||||||
|
-- stricter requirement than the source document's "versleuteld (pgcrypto)"
|
||||||
|
-- wording. Reversible BSN storage for declaratie/Vecozo purposes, if ever
|
||||||
|
-- needed, is a separate, explicitly-scoped decision — not implemented here.
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- person
|
||||||
|
-- ============================================================================
|
||||||
|
CREATE TABLE person (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
family_name TEXT NOT NULL,
|
||||||
|
name_prefix TEXT,
|
||||||
|
given_names TEXT,
|
||||||
|
initials TEXT,
|
||||||
|
preferred_name TEXT,
|
||||||
|
birth_date DATE NOT NULL,
|
||||||
|
gender TEXT NOT NULL, -- waardelijst geslacht
|
||||||
|
gender_identity TEXT, -- waardelijst genderidentiteit
|
||||||
|
bsn_hash TEXT, -- SHA-256, optioneel op PERSOON (rolgebonden eis via nudge)
|
||||||
|
deceased BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
deceased_at DATE,
|
||||||
|
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
deleted_at TIMESTAMPTZ,
|
||||||
|
|
||||||
|
CONSTRAINT person_family_name_not_empty CHECK (length(trim(family_name)) > 0),
|
||||||
|
CONSTRAINT person_deceased_at_requires_flag CHECK (deceased_at IS NULL OR deceased)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX idx_person_bsn_hash ON person(bsn_hash) WHERE bsn_hash IS NOT NULL AND deleted_at IS NULL;
|
||||||
|
CREATE INDEX idx_person_name ON person(family_name, given_names) WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
COMMENT ON TABLE person IS 'Natuurlijke persoon, onafhankelijk van rollen (cliënt, contactpersoon, vertegenwoordiger, medewerker). model-aanmelding.md §2.1.';
|
||||||
|
COMMENT ON COLUMN person.bsn_hash IS 'SHA-256 hash van het BSN; geen raw BSN opgeslagen (platform-privacyregel). Naam+geboortedatum-duplicaten zijn een nudge, geen constraint.';
|
||||||
|
|
||||||
|
ALTER TABLE person ENABLE ROW LEVEL SECURITY;
|
||||||
|
CREATE POLICY "Enable all for authenticated users" ON person FOR ALL USING (auth.role() = 'authenticated');
|
||||||
|
CREATE TRIGGER update_person_updated_at BEFORE UPDATE ON person FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- address
|
||||||
|
-- ============================================================================
|
||||||
|
CREATE TABLE address (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
person_id UUID NOT NULL REFERENCES person(id) ON DELETE CASCADE,
|
||||||
|
address_type TEXT NOT NULL, -- waardelijst adrestype
|
||||||
|
street TEXT NOT NULL,
|
||||||
|
house_number TEXT NOT NULL,
|
||||||
|
house_number_addition TEXT,
|
||||||
|
postal_code TEXT NOT NULL,
|
||||||
|
city TEXT NOT NULL,
|
||||||
|
country TEXT NOT NULL DEFAULT 'NL',
|
||||||
|
valid_from DATE,
|
||||||
|
valid_to DATE,
|
||||||
|
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
deleted_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
-- max één actueel (valid_to leeg) adres per persoon per adrestype
|
||||||
|
CREATE UNIQUE INDEX idx_address_current_per_type ON address(person_id, address_type)
|
||||||
|
WHERE valid_to IS NULL AND deleted_at IS NULL;
|
||||||
|
CREATE INDEX idx_address_person ON address(person_id) WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
COMMENT ON TABLE address IS 'model-aanmelding.md §2.2 — 0..n adressen per persoon (zib Patient).';
|
||||||
|
|
||||||
|
ALTER TABLE address ENABLE ROW LEVEL SECURITY;
|
||||||
|
CREATE POLICY "Enable all for authenticated users" ON address FOR ALL USING (auth.role() = 'authenticated');
|
||||||
|
CREATE TRIGGER update_address_updated_at BEFORE UPDATE ON address FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- contact_detail
|
||||||
|
-- ============================================================================
|
||||||
|
CREATE TABLE contact_detail (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
person_id UUID NOT NULL REFERENCES person(id) ON DELETE CASCADE,
|
||||||
|
contact_type TEXT NOT NULL, -- waardelijst contactgegeven_type (telefoon/e-mail)
|
||||||
|
contact_subtype TEXT, -- waardelijst contactgegeven_soort
|
||||||
|
value TEXT NOT NULL,
|
||||||
|
is_preferred BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
deleted_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
-- max één voorkeursgegeven per persoon per contacttype
|
||||||
|
CREATE UNIQUE INDEX idx_contact_detail_preferred ON contact_detail(person_id, contact_type)
|
||||||
|
WHERE is_preferred AND deleted_at IS NULL;
|
||||||
|
CREATE INDEX idx_contact_detail_person ON contact_detail(person_id) WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
COMMENT ON TABLE contact_detail IS 'model-aanmelding.md §2.3 — telefoonnummers en e-mailadressen per persoon.';
|
||||||
|
|
||||||
|
ALTER TABLE contact_detail ENABLE ROW LEVEL SECURITY;
|
||||||
|
CREATE POLICY "Enable all for authenticated users" ON contact_detail FOR ALL USING (auth.role() = 'authenticated');
|
||||||
|
CREATE TRIGGER update_contact_detail_updated_at BEFORE UPDATE ON contact_detail FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- client (rol op person)
|
||||||
|
-- ============================================================================
|
||||||
|
CREATE TABLE client (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
person_id UUID NOT NULL REFERENCES person(id) ON DELETE RESTRICT,
|
||||||
|
client_number BIGINT NOT NULL,
|
||||||
|
client_since DATE NOT NULL,
|
||||||
|
gp_situation TEXT NOT NULL DEFAULT 'onbekend', -- waardelijst huisarts_situatie
|
||||||
|
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
deleted_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
-- max één CLIENT-rol per PERSOON
|
||||||
|
CREATE UNIQUE INDEX idx_client_person ON client(person_id) WHERE deleted_at IS NULL;
|
||||||
|
CREATE UNIQUE INDEX idx_client_number ON client(client_number) WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
COMMENT ON TABLE client IS 'model-aanmelding.md §2.4 — instellingsgebonden rol op PERSOON. Nudge (geen constraint): cliënt zonder bsn_hash (Wabvpz).';
|
||||||
|
|
||||||
|
ALTER TABLE client ENABLE ROW LEVEL SECURITY;
|
||||||
|
CREATE POLICY "Enable all for authenticated users" ON client FOR ALL USING (auth.role() = 'authenticated');
|
||||||
|
CREATE TRIGGER update_client_updated_at BEFORE UPDATE ON client FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- client_relation (contactpersoon / wettelijk vertegenwoordiger)
|
||||||
|
-- ============================================================================
|
||||||
|
CREATE TABLE client_relation (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
client_id UUID NOT NULL REFERENCES client(id) ON DELETE CASCADE,
|
||||||
|
person_id UUID NOT NULL REFERENCES person(id) ON DELETE RESTRICT,
|
||||||
|
relation_type TEXT, -- waardelijst relatie (partner, ouder, kind, ...)
|
||||||
|
relation_role TEXT NOT NULL, -- waardelijst relatierol
|
||||||
|
representation_basis TEXT, -- waardelijst vertegenwoordigingsgrond (alleen bij rol wettelijk_vertegenwoordiger)
|
||||||
|
valid_from DATE NOT NULL,
|
||||||
|
valid_to DATE,
|
||||||
|
notes TEXT,
|
||||||
|
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
deleted_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
-- max één actuele rij per (client, persoon, rol)
|
||||||
|
CREATE UNIQUE INDEX idx_client_relation_current ON client_relation(client_id, person_id, relation_role)
|
||||||
|
WHERE valid_to IS NULL AND deleted_at IS NULL;
|
||||||
|
CREATE INDEX idx_client_relation_client ON client_relation(client_id) WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
COMMENT ON TABLE client_relation IS 'model-aanmelding.md §2.5 — contactpersonen/vertegenwoordigers, twee assen relatie x rol (zib Contactpersoon). Leeftijdsafhankelijke rechten (12/16 WGBO) zijn autorisatie, geen schema.';
|
||||||
|
|
||||||
|
ALTER TABLE client_relation ENABLE ROW LEVEL SECURITY;
|
||||||
|
CREATE POLICY "Enable all for authenticated users" ON client_relation FOR ALL USING (auth.role() = 'authenticated');
|
||||||
|
CREATE TRIGGER update_client_relation_updated_at BEFORE UPDATE ON client_relation FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
-- Zelfrelatie (persoon = cliënt-persoon) niet toegestaan. CHECK-constraints
|
||||||
|
-- kunnen geen subquery bevatten in Postgres, dus dit is een trigger i.p.v.
|
||||||
|
-- een CHECK — functioneel identiek aan wat model-aanmelding.md §2.5 vraagt.
|
||||||
|
CREATE OR REPLACE FUNCTION prevent_client_relation_self_reference()
|
||||||
|
RETURNS TRIGGER AS $$
|
||||||
|
BEGIN
|
||||||
|
IF NEW.person_id = (SELECT person_id FROM client WHERE client.id = NEW.client_id) THEN
|
||||||
|
RAISE EXCEPTION 'client_relation: person_id mag niet gelijk zijn aan de cliënt-persoon zelf';
|
||||||
|
END IF;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE TRIGGER client_relation_no_self_reference
|
||||||
|
BEFORE INSERT OR UPDATE ON client_relation
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION prevent_client_relation_self_reference();
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- practice_organization (moet vóór referrer bestaan i.v.m. FK)
|
||||||
|
-- ============================================================================
|
||||||
|
CREATE TABLE practice_organization (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
practice_type TEXT, -- waardelijst praktijk_soort
|
||||||
|
agb_code TEXT,
|
||||||
|
address_text TEXT,
|
||||||
|
phone TEXT,
|
||||||
|
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
deleted_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX idx_practice_organization_agb ON practice_organization(agb_code) WHERE agb_code IS NOT NULL AND deleted_at IS NULL;
|
||||||
|
|
||||||
|
COMMENT ON TABLE practice_organization IS 'model-aanmelding.md §2.7 — organisatie waaraan verwijzers/huisartsen verbonden zijn.';
|
||||||
|
|
||||||
|
ALTER TABLE practice_organization ENABLE ROW LEVEL SECURITY;
|
||||||
|
CREATE POLICY "Enable all for authenticated users" ON practice_organization FOR ALL USING (auth.role() = 'authenticated');
|
||||||
|
CREATE TRIGGER update_practice_organization_updated_at BEFORE UPDATE ON practice_organization FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- referrer
|
||||||
|
-- ============================================================================
|
||||||
|
CREATE TABLE referrer (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
referrer_type TEXT NOT NULL, -- waardelijst verwijzertype (extra.agb_verplicht bepaalt nudge)
|
||||||
|
agb_code TEXT,
|
||||||
|
practice_organization_id UUID REFERENCES practice_organization(id) ON DELETE SET NULL,
|
||||||
|
phone TEXT,
|
||||||
|
email TEXT,
|
||||||
|
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
deleted_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX idx_referrer_agb ON referrer(agb_code) WHERE agb_code IS NOT NULL AND deleted_at IS NULL;
|
||||||
|
CREATE INDEX idx_referrer_practice ON referrer(practice_organization_id) WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
COMMENT ON TABLE referrer IS 'model-aanmelding.md §2.6 — persoon/functionaris die verwijst, los van practice_organization. Kan zonder praktijk bestaan (gemeente-ambtenaar). Ook referent voor de vaste huisarts.';
|
||||||
|
|
||||||
|
ALTER TABLE referrer ENABLE ROW LEVEL SECURITY;
|
||||||
|
CREATE POLICY "Enable all for authenticated users" ON referrer FOR ALL USING (auth.role() = 'authenticated');
|
||||||
|
CREATE TRIGGER update_referrer_updated_at BEFORE UPDATE ON referrer FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- client_general_practitioner (vaste huisarts)
|
||||||
|
-- ============================================================================
|
||||||
|
CREATE TABLE client_general_practitioner (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
client_id UUID NOT NULL REFERENCES client(id) ON DELETE CASCADE,
|
||||||
|
referrer_id UUID REFERENCES referrer(id) ON DELETE SET NULL,
|
||||||
|
practice_organization_id UUID REFERENCES practice_organization(id) ON DELETE SET NULL,
|
||||||
|
valid_from DATE NOT NULL,
|
||||||
|
valid_to DATE,
|
||||||
|
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
deleted_at TIMESTAMPTZ,
|
||||||
|
|
||||||
|
CONSTRAINT client_gp_at_least_one_ref CHECK (referrer_id IS NOT NULL OR practice_organization_id IS NOT NULL)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- max één actuele registratie per cliënt
|
||||||
|
CREATE UNIQUE INDEX idx_client_gp_current ON client_general_practitioner(client_id)
|
||||||
|
WHERE valid_to IS NULL AND deleted_at IS NULL;
|
||||||
|
|
||||||
|
COMMENT ON TABLE client_general_practitioner IS 'model-aanmelding.md §2.8 — vaste huisarts, los van de incidentele verwijzer. "Geen huisarts"/"geen toestemming" staat op client.gp_situation, niet hier.';
|
||||||
|
|
||||||
|
ALTER TABLE client_general_practitioner ENABLE ROW LEVEL SECURITY;
|
||||||
|
CREATE POLICY "Enable all for authenticated users" ON client_general_practitioner FOR ALL USING (auth.role() = 'authenticated');
|
||||||
|
CREATE TRIGGER update_client_gp_updated_at BEFORE UPDATE ON client_general_practitioner FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- insurance
|
||||||
|
-- ============================================================================
|
||||||
|
CREATE TABLE insurance (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
client_id UUID NOT NULL REFERENCES client(id) ON DELETE CASCADE,
|
||||||
|
uzovi_code TEXT NOT NULL,
|
||||||
|
insurer_name TEXT,
|
||||||
|
policy_number TEXT,
|
||||||
|
valid_from DATE NOT NULL,
|
||||||
|
valid_to DATE,
|
||||||
|
cov_checked_at DATE,
|
||||||
|
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
deleted_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
-- max één actuele verzekering per cliënt
|
||||||
|
CREATE UNIQUE INDEX idx_insurance_current ON insurance(client_id) WHERE valid_to IS NULL AND deleted_at IS NULL;
|
||||||
|
|
||||||
|
COMMENT ON TABLE insurance IS 'model-aanmelding.md §2.9 — Zvw-verzekeringsgegevens. Nudge: Zvw-aanmelding zonder actuele COV-controle. Wlz/forensisch: declaratie-ronde.';
|
||||||
|
|
||||||
|
ALTER TABLE insurance ENABLE ROW LEVEL SECURITY;
|
||||||
|
CREATE POLICY "Enable all for authenticated users" ON insurance FOR ALL USING (auth.role() = 'authenticated');
|
||||||
|
CREATE TRIGGER update_insurance_updated_at BEFORE UPDATE ON insurance FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- consent
|
||||||
|
-- ============================================================================
|
||||||
|
-- referral_case_id/clinical_care_episode_id FKs are added by later migrations
|
||||||
|
-- (create_referral_domain, create_intake_treatment_advice_domain) once those
|
||||||
|
-- tables exist — see ALTER TABLE at the bottom of create_referral_domain.
|
||||||
|
CREATE TABLE consent (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
client_id UUID NOT NULL REFERENCES client(id) ON DELETE CASCADE,
|
||||||
|
consent_type TEXT NOT NULL, -- waardelijst toestemming_type (extra.grondslag)
|
||||||
|
status TEXT NOT NULL, -- waardelijst toestemming_status
|
||||||
|
consent_date DATE NOT NULL,
|
||||||
|
method TEXT, -- waardelijst toestemming_wijze
|
||||||
|
recorded_by UUID REFERENCES practitioners(id),
|
||||||
|
valid_to DATE,
|
||||||
|
revoked_at DATE,
|
||||||
|
scope_referral_case_id UUID, -- optioneel; default cliëntbreed
|
||||||
|
notes TEXT,
|
||||||
|
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
deleted_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
-- max één actuele (niet-ingetrokken) rij per (client, type, scope)
|
||||||
|
CREATE UNIQUE INDEX idx_consent_current ON consent(client_id, consent_type, COALESCE(scope_referral_case_id, '00000000-0000-0000-0000-000000000000'))
|
||||||
|
WHERE status <> 'ingetrokken' AND deleted_at IS NULL;
|
||||||
|
CREATE INDEX idx_consent_client ON consent(client_id) WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
COMMENT ON TABLE consent IS 'model-aanmelding.md §2.14 — generieke WGBO/AVG-toestemming. Cliëntakkoord op het behandelplan is een apart feit bij het behandelplan, geen consent-rij. scope_referral_case_id FK volgt in create_referral_domain.sql.';
|
||||||
|
|
||||||
|
ALTER TABLE consent ENABLE ROW LEVEL SECURITY;
|
||||||
|
CREATE POLICY "Enable all for authenticated users" ON consent FOR ALL USING (auth.role() = 'authenticated');
|
||||||
|
CREATE TRIGGER update_consent_updated_at BEFORE UPDATE ON consent FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- client_portal_account
|
||||||
|
-- ============================================================================
|
||||||
|
CREATE TABLE client_portal_account (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
client_id UUID NOT NULL REFERENCES client(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
deleted_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX idx_client_portal_account_client ON client_portal_account(client_id) WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
COMMENT ON TABLE client_portal_account IS 'model-aanmelding.md §2.16 — relatie-placeholder (Wabvpz elektronische inzage). Authenticatiedetails: auth/ADM-ronde.';
|
||||||
|
|
||||||
|
ALTER TABLE client_portal_account ENABLE ROW LEVEL SECURITY;
|
||||||
|
CREATE POLICY "Enable all for authenticated users" ON client_portal_account FOR ALL USING (auth.role() = 'authenticated');
|
||||||
|
CREATE TRIGGER update_client_portal_account_updated_at BEFORE UPDATE ON client_portal_account FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||||
567
supabase/migrations/20260720000003_create_referral_domain.sql
Normal file
567
supabase/migrations/20260720000003_create_referral_domain.sql
Normal file
@@ -0,0 +1,567 @@
|
|||||||
|
-- ============================================================================
|
||||||
|
-- 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');
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
-- ============================================================================
|
||||||
|
-- INTAKE / TREATMENT ADVICE DOMAIN
|
||||||
|
-- ============================================================================
|
||||||
|
-- Created: 2026-07-20
|
||||||
|
-- Source: docs/datamodel/deelmodellen/model-intake-behandeladvies.md (5
|
||||||
|
-- entiteiten), docs/datamodel/feitenmodellen/feitenmodel-intake-behandeladvies.md,
|
||||||
|
-- besluiten B31-B34.
|
||||||
|
--
|
||||||
|
-- clinical_intake_assessment hangs off clinical_care_episode (not off
|
||||||
|
-- referral_case) — it starts at some point after the acceptance decision,
|
||||||
|
-- possibly after a wait-list period (status = 'gepland' covers that).
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- clinical_intake_assessment
|
||||||
|
-- ============================================================================
|
||||||
|
CREATE TABLE clinical_intake_assessment (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
clinical_care_episode_id UUID NOT NULL REFERENCES clinical_care_episode(id) ON DELETE RESTRICT,
|
||||||
|
initiation_reason TEXT NOT NULL, -- waardelijst intake_initiation_reason
|
||||||
|
department TEXT NOT NULL, -- waardelijst department
|
||||||
|
status TEXT NOT NULL DEFAULT 'gepland', -- waardelijst intake_status
|
||||||
|
planned_at TIMESTAMPTZ NOT NULL,
|
||||||
|
started_at TIMESTAMPTZ,
|
||||||
|
ended_at TIMESTAMPTZ,
|
||||||
|
abort_reason TEXT, -- waardelijst intake_abort_reason
|
||||||
|
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
deleted_at TIMESTAMPTZ,
|
||||||
|
|
||||||
|
CONSTRAINT clinical_intake_assessment_status_check CHECK (status IN ('gepland', 'bezig', 'afgerond', 'afgebroken')),
|
||||||
|
CONSTRAINT clinical_intake_assessment_started_required CHECK (
|
||||||
|
status NOT IN ('bezig', 'afgerond', 'afgebroken') OR started_at IS NOT NULL
|
||||||
|
),
|
||||||
|
CONSTRAINT clinical_intake_assessment_ended_required CHECK (
|
||||||
|
status NOT IN ('afgerond', 'afgebroken') OR ended_at IS NOT NULL
|
||||||
|
),
|
||||||
|
CONSTRAINT clinical_intake_assessment_abort_reason_required CHECK (
|
||||||
|
status <> 'afgebroken' OR abort_reason IS NOT NULL
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_clinical_intake_assessment_episode ON clinical_intake_assessment(clinical_care_episode_id) WHERE deleted_at IS NULL;
|
||||||
|
CREATE INDEX idx_clinical_intake_assessment_status ON clinical_intake_assessment(status) WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
COMMENT ON TABLE clinical_intake_assessment IS 'model-intake-behandeladvies.md §1.1. Start ná het acceptatiebesluit (B31), niet gelijktijdig met episode-ontstaan — planned_at dekt een eventuele intakewachtlijst-periode. "Maximaal één lopende intake per episode" is een aan/uit-zetbare bedrijfsregel, geen schemabeperking.';
|
||||||
|
|
||||||
|
ALTER TABLE clinical_intake_assessment ENABLE ROW LEVEL SECURITY;
|
||||||
|
CREATE POLICY "Enable all for authenticated users" ON clinical_intake_assessment FOR ALL USING (auth.role() = 'authenticated');
|
||||||
|
CREATE TRIGGER update_clinical_intake_assessment_updated_at BEFORE UPDATE ON clinical_intake_assessment FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- intake_contact
|
||||||
|
-- ============================================================================
|
||||||
|
CREATE TABLE intake_contact (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
clinical_intake_assessment_id UUID NOT NULL REFERENCES clinical_intake_assessment(id) ON DELETE CASCADE,
|
||||||
|
occurred_at TIMESTAMPTZ NOT NULL,
|
||||||
|
contact_type TEXT NOT NULL, -- waardelijst intake_contact_type
|
||||||
|
performed_by UUID NOT NULL REFERENCES practitioners(id),
|
||||||
|
notes TEXT,
|
||||||
|
planned_duration_minutes INTEGER,
|
||||||
|
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
deleted_at TIMESTAMPTZ,
|
||||||
|
|
||||||
|
CONSTRAINT intake_contact_duration_non_negative CHECK (planned_duration_minutes IS NULL OR planned_duration_minutes >= 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_intake_contact_assessment ON intake_contact(clinical_intake_assessment_id) WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
COMMENT ON TABLE intake_contact IS 'model-intake-behandeladvies.md §1.2. Exclusief aan één clinical_intake_assessment; generalisatie naar een bredere consult-/afspraakstructuur is de agenda-ronde.';
|
||||||
|
|
||||||
|
ALTER TABLE intake_contact ENABLE ROW LEVEL SECURITY;
|
||||||
|
CREATE POLICY "Enable all for authenticated users" ON intake_contact FOR ALL USING (auth.role() = 'authenticated');
|
||||||
|
CREATE TRIGGER update_intake_contact_updated_at BEFORE UPDATE ON intake_contact FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- child_safety_check
|
||||||
|
-- ============================================================================
|
||||||
|
CREATE TABLE child_safety_check (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
clinical_intake_assessment_id UUID NOT NULL REFERENCES clinical_intake_assessment(id) ON DELETE CASCADE,
|
||||||
|
performed_at TIMESTAMPTZ NOT NULL,
|
||||||
|
performed_by UUID NOT NULL REFERENCES practitioners(id),
|
||||||
|
responsible_for_minors BOOLEAN NOT NULL,
|
||||||
|
minor_count INTEGER,
|
||||||
|
minor_ages TEXT, -- vrije tekst, bewust geen kindrecords (dataminimalisatie)
|
||||||
|
safety_concern BOOLEAN NOT NULL,
|
||||||
|
safety_concern_notes TEXT,
|
||||||
|
action_taken BOOLEAN NOT NULL,
|
||||||
|
action_taken_notes TEXT,
|
||||||
|
pregnancy BOOLEAN NOT NULL, -- vierde vlag, te verifiëren tegen KNMG-meldcode (B34)
|
||||||
|
notes TEXT,
|
||||||
|
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
deleted_at TIMESTAMPTZ,
|
||||||
|
|
||||||
|
CONSTRAINT child_safety_check_safety_notes_required CHECK (NOT safety_concern OR safety_concern_notes IS NOT NULL),
|
||||||
|
CONSTRAINT child_safety_check_action_notes_required CHECK (NOT action_taken OR action_taken_notes IS NOT NULL)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- maximaal één per intake
|
||||||
|
CREATE UNIQUE INDEX idx_child_safety_check_assessment ON child_safety_check(clinical_intake_assessment_id) WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
COMMENT ON TABLE child_safety_check IS 'model-intake-behandeladvies.md §1.3 (B34). Wet verplichte meldcode / KNMG-kindcheck.';
|
||||||
|
|
||||||
|
ALTER TABLE child_safety_check ENABLE ROW LEVEL SECURITY;
|
||||||
|
CREATE POLICY "Enable all for authenticated users" ON child_safety_check FOR ALL USING (auth.role() = 'authenticated');
|
||||||
|
CREATE TRIGGER update_child_safety_check_updated_at BEFORE UPDATE ON child_safety_check FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- intake_mdt_review (minimale, voorlopige hook — zie §6 aldaar)
|
||||||
|
-- ============================================================================
|
||||||
|
CREATE TABLE intake_mdt_review (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
occurred_at TIMESTAMPTZ NOT NULL,
|
||||||
|
participants TEXT NOT NULL, -- vrije tekst; structurering wacht op de MDO-ronde (B14-B15)
|
||||||
|
notes TEXT,
|
||||||
|
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
deleted_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMENT ON TABLE intake_mdt_review IS 'model-intake-behandeladvies.md §1.4. Geen eigen FK naar clinical_intake_assessment — gekoppeld via treatment_advice.mdt_review_id, telt pas zodra hij aan een vastgesteld advies hangt. LKS 4.0: dwingend voor settings 3-8, optioneel setting 2, n.v.t. vrijgevestigden.';
|
||||||
|
|
||||||
|
ALTER TABLE intake_mdt_review ENABLE ROW LEVEL SECURITY;
|
||||||
|
CREATE POLICY "Enable all for authenticated users" ON intake_mdt_review FOR ALL USING (auth.role() = 'authenticated');
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- treatment_advice
|
||||||
|
-- ============================================================================
|
||||||
|
CREATE TABLE treatment_advice (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
clinical_intake_assessment_id UUID NOT NULL REFERENCES clinical_intake_assessment(id) ON DELETE RESTRICT,
|
||||||
|
decided_at TIMESTAMPTZ NOT NULL,
|
||||||
|
decided_by UUID NOT NULL REFERENCES practitioners(id), -- indicerende regiebehandelaar
|
||||||
|
outcome TEXT NOT NULL, -- waardelijst treatment_advice_outcome
|
||||||
|
recommended_care_program TEXT, -- waardelijst care_program
|
||||||
|
rationale TEXT,
|
||||||
|
mdt_review_id UUID REFERENCES intake_mdt_review(id),
|
||||||
|
replaces_advice_id UUID REFERENCES treatment_advice(id),
|
||||||
|
replacement_reason TEXT,
|
||||||
|
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
deleted_at TIMESTAMPTZ,
|
||||||
|
|
||||||
|
CONSTRAINT treatment_advice_outcome_check CHECK (outcome IN ('in_zorg', 'terugverwijzing', 'doorverwijzing', 'extra_diagnostiek')),
|
||||||
|
CONSTRAINT treatment_advice_care_program_required CHECK (
|
||||||
|
outcome <> 'in_zorg' OR recommended_care_program IS NOT NULL
|
||||||
|
),
|
||||||
|
CONSTRAINT treatment_advice_rationale_required CHECK (
|
||||||
|
outcome = 'in_zorg' OR (rationale IS NOT NULL AND length(trim(rationale)) > 0)
|
||||||
|
),
|
||||||
|
CONSTRAINT treatment_advice_replacement_reason_required CHECK (
|
||||||
|
replaces_advice_id IS NULL OR (replacement_reason IS NOT NULL AND length(trim(replacement_reason)) > 0)
|
||||||
|
),
|
||||||
|
CONSTRAINT treatment_advice_no_self_replace CHECK (replaces_advice_id <> id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- vervangt-keten kan nooit vertakken, zelfde patroon als care_acceptance_decision
|
||||||
|
CREATE UNIQUE INDEX idx_treatment_advice_replaces_unique ON treatment_advice(replaces_advice_id) WHERE replaces_advice_id IS NOT NULL AND deleted_at IS NULL;
|
||||||
|
CREATE INDEX idx_treatment_advice_assessment ON treatment_advice(clinical_intake_assessment_id) WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
COMMENT ON TABLE treatment_advice IS 'model-intake-behandeladvies.md §1.5 (B32-B33). Geen apart voorafgaand advies — dit object zelf geeft de vervolgrichting. doorverwijzing/terugverwijzing zijn LKS-genormeerd volgtijdelijk (via replaces_advice_id); extra_diagnostiek is praktijkgefundeerd, niet LKS-genormeerd.';
|
||||||
|
|
||||||
|
ALTER TABLE treatment_advice ENABLE ROW LEVEL SECURITY;
|
||||||
|
CREATE POLICY "Enable all for authenticated users" ON treatment_advice FOR ALL USING (auth.role() = 'authenticated');
|
||||||
|
CREATE TRIGGER update_treatment_advice_updated_at BEFORE UPDATE ON treatment_advice FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
-- Een afgeronde intake vereist minimaal één (niet noodzakelijk het laatst
|
||||||
|
-- vervangen) treatment_advice — afgedwongen op applicatieniveau, niet als
|
||||||
|
-- schema-constraint (zou een circulaire FK-afhankelijkheid vereisen).
|
||||||
|
COMMENT ON COLUMN clinical_intake_assessment.status IS 'gepland/bezig/afgerond/afgebroken. Bij afgerond hoort minimaal één treatment_advice (applicatieniveau, geen FK-constraint i.v.m. circulaire afhankelijkheid).';
|
||||||
Reference in New Issue
Block a user