= Advanced Database Development = ---- == Allergy–prescription safety enforcement == A patient should never be prescribed a medication that conflicts with one of their documented allergies. This requirement blocks the unsafe prescription outright, at the database level: a new or changed **prescription_medical_records** row is rejected if the prescription is linked, via **prescription_restriction** → **allergy_prescription_restrictions**, to an allergy the patient has on file, either in **patient_allergies** or in **medical_record_allergies** on any of the patient's medical records. The trigger only prevents new conflicts; prescriptions that were recorded before it existed are listed by the **v_prescription_allergy_conflicts** view so they can be reviewed. === Custom domains === {{{ CREATE DOMAIN non_negative_cost AS DECIMAL(12,2) CHECK (VALUE >= 0); ALTER TABLE procedures ALTER COLUMN cost TYPE non_negative_cost USING cost::non_negative_cost; ALTER TABLE lab_tests ALTER COLUMN cost TYPE non_negative_cost USING cost::non_negative_cost; ALTER TABLE billing ALTER COLUMN total_cost TYPE non_negative_cost USING total_cost::non_negative_cost; ALTER TABLE procedures DROP CONSTRAINT IF EXISTS procedures_cost_chk; ALTER TABLE lab_tests DROP CONSTRAINT IF EXISTS lab_tests_cost_chk; ALTER TABLE billing DROP CONSTRAINT IF EXISTS billing_cost_chk; }}} === Triggers === {{{ CREATE OR REPLACE FUNCTION t1_prescription_allergy_check() RETURNS TRIGGER LANGUAGE plpgsql AS $$ DECLARE v_patient_id BIGINT; v_conflict_allergy_id BIGINT; v_allergy_name TEXT; BEGIN SELECT patient_id INTO v_patient_id FROM medical_records WHERE record_id = NEW.record_id; IF v_patient_id IS NULL THEN RAISE EXCEPTION 'Medical record % not found', NEW.record_id; END IF; SELECT a.allergy_id, a.name INTO v_conflict_allergy_id, v_allergy_name FROM prescription_restriction pr_rest JOIN allergy_prescription_restrictions apr ON apr.restriction_id = pr_rest.restriction_id JOIN allergies a ON a.allergy_id = apr.allergy_id WHERE pr_rest.prescription_id = NEW.prescription_id AND ( EXISTS (SELECT 1 FROM patient_allergies pa WHERE pa.patient_id = v_patient_id AND pa.allergy_id = a.allergy_id) OR EXISTS (SELECT 1 FROM medical_record_allergies mra JOIN medical_records mr2 ON mr2.record_id = mra.record_id WHERE mr2.patient_id = v_patient_id AND mra.allergy_id = a.allergy_id) ) LIMIT 1; IF v_conflict_allergy_id IS NOT NULL THEN RAISE EXCEPTION 'PRESCRIPTION_ALLERGY_CONFLICT: Prescription % conflicts with allergy % (%) of patient %', NEW.prescription_id, v_conflict_allergy_id, v_allergy_name, v_patient_id; END IF; RETURN NEW; END; $$; DROP TRIGGER IF EXISTS trg_prescription_allergy_check ON prescription_medical_records; CREATE TRIGGER trg_prescription_allergy_check BEFORE INSERT OR UPDATE OF prescription_id, record_id ON prescription_medical_records FOR EACH ROW EXECUTE FUNCTION t1_prescription_allergy_check(); }}} === Views === {{{ CREATE OR REPLACE VIEW v_prescription_allergy_conflicts AS SELECT mr.record_id, p.patient_id, p.first_name, p.last_name, pmr.prescription_id, pr.medication_name, a.allergy_id, a.name AS allergy_name, a.allergy_severity, apr.restriction_id, pr_rest.description AS restriction_description FROM prescription_medical_records pmr JOIN medical_records mr ON pmr.record_id = mr.record_id JOIN patients p ON mr.patient_id = p.patient_id JOIN prescriptions pr ON pmr.prescription_id = pr.prescription_id JOIN prescription_restriction pr_rest ON pr.prescription_id = pr_rest.prescription_id JOIN allergy_prescription_restrictions apr ON pr_rest.restriction_id = apr.restriction_id JOIN allergies a ON apr.allergy_id = a.allergy_id WHERE EXISTS (SELECT 1 FROM patient_allergies pa WHERE pa.patient_id = p.patient_id AND pa.allergy_id = a.allergy_id) OR EXISTS (SELECT 1 FROM medical_record_allergies mra JOIN medical_records mr2 ON mr2.record_id = mra.record_id WHERE mr2.patient_id = p.patient_id AND mra.allergy_id = a.allergy_id) ORDER BY p.patient_id, CASE a.allergy_severity WHEN 'CRITICAL' THEN 1 WHEN 'HIGH' THEN 2 WHEN 'MEDIUM' THEN 3 ELSE 4 END; }}} ---- == Appointment scheduling integrity == Appointments must obey real scheduling constraints: no scheduling in the past (when an appointment is created or moved to a new date or time), no double-booking the same doctor or patient in an overlapping time, no marking an appointment `COMPLETED` before its scheduled time, and valid status transitions only (`SCHEDULED → IN_PROGRESS/COMPLETED/CANCELLED/NO_SHOW`, `IN_PROGRESS → COMPLETED/CANCELLED`, nothing further out of `COMPLETED`/`CANCELLED`/`NO_SHOW`). Each appointment is treated as a 30-minute slot, and only `SCHEDULED`, `IN_PROGRESS` and `COMPLETED` appointments occupy a slot. The **job_mark_no_show** procedure moves appointments that are still `SCHEDULED` 45+ minutes past their time to `NO_SHOW`; it is run with `CALL job_mark_no_show();`, either manually or from a scheduler such as pg_cron, and **v_overdue_appointments** shows the appointments it will pick up. === Schema change === {{{ ALTER TABLE appointments DROP CONSTRAINT appointments_status_chk; ALTER TABLE appointments ADD CONSTRAINT appointments_status_chk CHECK (status IN ('SCHEDULED', 'COMPLETED', 'CANCELLED', 'IN_PROGRESS', 'NO_SHOW')); }}} === Triggers === {{{ CREATE OR REPLACE FUNCTION is_valid_appointment_transition(p_old TEXT, p_new TEXT) RETURNS BOOLEAN LANGUAGE sql IMMUTABLE AS $$ SELECT CASE WHEN p_old = p_new THEN TRUE WHEN p_old = 'SCHEDULED' AND p_new IN ('IN_PROGRESS', 'COMPLETED', 'CANCELLED', 'NO_SHOW') THEN TRUE WHEN p_old = 'IN_PROGRESS' AND p_new IN ('COMPLETED', 'CANCELLED') THEN TRUE ELSE FALSE END; $$; CREATE OR REPLACE FUNCTION trigger_appointments_enforce() RETURNS TRIGGER LANGUAGE plpgsql AS $$ DECLARE v_combined_datetime TIMESTAMP; BEGIN v_combined_datetime := NEW.appointment_date::TIMESTAMP + NEW.appointment_time; IF TG_OP = 'INSERT' AND v_combined_datetime < NOW() THEN RAISE EXCEPTION 'Cannot schedule appointment in the past (appointment_date=%, appointment_time=%)', NEW.appointment_date, NEW.appointment_time; END IF; IF TG_OP = 'UPDATE' THEN IF (NEW.appointment_date, NEW.appointment_time) IS DISTINCT FROM (OLD.appointment_date, OLD.appointment_time) AND v_combined_datetime < NOW() THEN RAISE EXCEPTION 'Cannot move appointment % into the past (appointment_date=%, appointment_time=%)', NEW.appointment_id, NEW.appointment_date, NEW.appointment_time; END IF; IF NOT is_valid_appointment_transition(OLD.status, NEW.status) THEN RAISE EXCEPTION 'Appointment status cannot transition from % to %', OLD.status, NEW.status; END IF; END IF; IF NEW.status = 'COMPLETED' AND v_combined_datetime > NOW() THEN RAISE EXCEPTION 'Cannot mark appointment COMPLETED before its scheduled time (scheduled for %)', v_combined_datetime; END IF; RETURN NEW; END; $$; DROP TRIGGER IF EXISTS trigger_appointments_enforce ON appointments; CREATE TRIGGER trigger_appointments_enforce BEFORE INSERT OR UPDATE ON appointments FOR EACH ROW EXECUTE FUNCTION trigger_appointments_enforce(); CREATE OR REPLACE FUNCTION t1_appointments_no_overlap() RETURNS TRIGGER LANGUAGE plpgsql AS $$ DECLARE v_new_start TIMESTAMP; v_new_end TIMESTAMP; BEGIN IF NEW.status NOT IN ('SCHEDULED', 'IN_PROGRESS', 'COMPLETED') THEN RETURN NEW; END IF; v_new_start := NEW.appointment_date::TIMESTAMP + NEW.appointment_time; v_new_end := v_new_start + INTERVAL '30 minutes'; IF EXISTS ( SELECT 1 FROM appointments a WHERE a.doctor_id = NEW.doctor_id AND a.status IN ('SCHEDULED', 'IN_PROGRESS', 'COMPLETED') AND (a.appointment_date::TIMESTAMP + a.appointment_time) < v_new_end AND v_new_start < (a.appointment_date::TIMESTAMP + a.appointment_time + INTERVAL '30 minutes') AND (TG_OP <> 'UPDATE' OR a.appointment_id <> NEW.appointment_id) ) THEN RAISE EXCEPTION 'Doctor % has an overlapping appointment at % %', NEW.doctor_id, NEW.appointment_date, NEW.appointment_time; END IF; IF EXISTS ( SELECT 1 FROM appointments a WHERE a.patient_id = NEW.patient_id AND a.status IN ('SCHEDULED', 'IN_PROGRESS', 'COMPLETED') AND (a.appointment_date::TIMESTAMP + a.appointment_time) < v_new_end AND v_new_start < (a.appointment_date::TIMESTAMP + a.appointment_time + INTERVAL '30 minutes') AND (TG_OP <> 'UPDATE' OR a.appointment_id <> NEW.appointment_id) ) THEN RAISE EXCEPTION 'Patient % has an overlapping appointment at % %', NEW.patient_id, NEW.appointment_date, NEW.appointment_time; END IF; RETURN NEW; END; $$; DROP TRIGGER IF EXISTS trigger_appointments_no_overlap ON appointments; CREATE TRIGGER trigger_appointments_no_overlap BEFORE INSERT OR UPDATE ON appointments FOR EACH ROW EXECUTE FUNCTION t1_appointments_no_overlap(); }}} === Stored procedures/functions === {{{ CREATE OR REPLACE PROCEDURE job_mark_no_show() LANGUAGE plpgsql AS $$ BEGIN UPDATE appointments SET status = 'NO_SHOW' WHERE status = 'SCHEDULED' AND (appointment_date::TIMESTAMP + appointment_time) < (NOW() - INTERVAL '45 minutes'); END; $$; }}} === Views === {{{ CREATE OR REPLACE VIEW v_overdue_appointments AS SELECT a.appointment_id, a.patient_id, p.first_name, p.last_name, a.doctor_id, d.first_name AS doctor_first_name, d.last_name AS doctor_last_name, a.appointment_date, a.appointment_time, a.status, NOW() - (a.appointment_date::TIMESTAMP + a.appointment_time) AS time_overdue FROM appointments a JOIN patients p ON a.patient_id = p.patient_id JOIN doctors d ON a.doctor_id = d.doctor_id WHERE a.status = 'SCHEDULED' AND (a.appointment_date::TIMESTAMP + a.appointment_time) < (NOW() - INTERVAL '45 minutes') ORDER BY time_overdue DESC; }}} ---- == Medical record consistency == **Diagnoses, procedures, and referrals** can be created independently but must stay consistent with the patient they actually serve. Linking a diagnosis to a different patient's medical record, linking a performed procedure to a diagnosis that belongs to another patient, or a doctor referring a patient to themselves is both an **integrity problem** and a clinical **safety risk**, so these are rejected on insert and on update. The **v_medical_record_overview** view summarises each medical record and flags diagnosis mismatches and self-referrals that were recorded before the triggers existed. === Triggers === {{{ CREATE OR REPLACE FUNCTION t1_diagnosis_record_consistency() RETURNS TRIGGER LANGUAGE plpgsql AS $$ DECLARE v_record_patient BIGINT; v_diagnosis_patient BIGINT; BEGIN SELECT patient_id INTO v_record_patient FROM medical_records WHERE record_id = NEW.record_id; SELECT patient_id INTO v_diagnosis_patient FROM diagnosis WHERE diagnosis_id = NEW.diagnosis_id; IF v_record_patient IS NULL THEN RAISE EXCEPTION 'Medical record % not found', NEW.record_id; END IF; IF v_diagnosis_patient IS NULL THEN RAISE EXCEPTION 'Diagnosis % not found', NEW.diagnosis_id; END IF; IF v_record_patient <> v_diagnosis_patient THEN RAISE EXCEPTION 'Diagnosis % belongs to patient %, but medical record % belongs to patient %', NEW.diagnosis_id, v_diagnosis_patient, NEW.record_id, v_record_patient; END IF; RETURN NEW; END; $$; DROP TRIGGER IF EXISTS trg_diagnosis_record_consistency ON diagnosis_medical_records; CREATE TRIGGER trg_diagnosis_record_consistency BEFORE INSERT OR UPDATE ON diagnosis_medical_records FOR EACH ROW EXECUTE FUNCTION t1_diagnosis_record_consistency(); CREATE OR REPLACE FUNCTION t2_procedure_diagnosis_consistency() RETURNS TRIGGER LANGUAGE plpgsql AS $$ DECLARE v_diagnosis_patient BIGINT; BEGIN IF NEW.diagnosis_id IS NOT NULL THEN SELECT patient_id INTO v_diagnosis_patient FROM diagnosis WHERE diagnosis_id = NEW.diagnosis_id; IF v_diagnosis_patient IS NULL THEN RAISE EXCEPTION 'Diagnosis % not found', NEW.diagnosis_id; END IF; IF NEW.patient_id <> v_diagnosis_patient THEN RAISE EXCEPTION 'Procedure belongs to patient %, but diagnosis % belongs to patient %', NEW.patient_id, NEW.diagnosis_id, v_diagnosis_patient; END IF; END IF; RETURN NEW; END; $$; DROP TRIGGER IF EXISTS trg_procedure_diagnosis_consistency ON performed_procedures; CREATE TRIGGER trg_procedure_diagnosis_consistency BEFORE INSERT OR UPDATE ON performed_procedures FOR EACH ROW EXECUTE FUNCTION t2_procedure_diagnosis_consistency(); CREATE OR REPLACE FUNCTION t3_referral_consistency() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN IF NEW.from_doctor_id = NEW.to_doctor_id THEN RAISE EXCEPTION 'Doctor % cannot refer a patient to themselves', NEW.from_doctor_id; END IF; RETURN NEW; END; $$; DROP TRIGGER IF EXISTS trg_referral_consistency ON referrals; CREATE TRIGGER trg_referral_consistency BEFORE INSERT OR UPDATE ON referrals FOR EACH ROW EXECUTE FUNCTION t3_referral_consistency(); }}} === Views === {{{ CREATE OR REPLACE VIEW v_medical_record_overview AS SELECT mr.record_id, p.patient_id, p.first_name, p.last_name, p.embg, COUNT(DISTINCT dmr.diagnosis_id) AS diagnosis_count, COUNT(DISTINCT CASE WHEN d.patient_id <> p.patient_id THEN dmr.diagnosis_id END) AS diagnosis_mismatches, COUNT(DISTINCT mrp.procedure_id) AS procedures_count, COUNT(DISTINCT mrl.result_id) AS lab_results_count, COUNT(DISTINCT ref.referral_id) AS referrals_count, COUNT(DISTINCT mra.allergy_id) AS allergies_count, COUNT(DISTINCT CASE WHEN ref.from_doctor_id = ref.to_doctor_id THEN ref.referral_id END) AS self_referrals_detected FROM medical_records mr JOIN patients p ON mr.patient_id = p.patient_id LEFT JOIN diagnosis_medical_records dmr ON dmr.record_id = mr.record_id LEFT JOIN diagnosis d ON d.diagnosis_id = dmr.diagnosis_id LEFT JOIN medical_record_procedures mrp ON mrp.record_id = mr.record_id LEFT JOIN medical_record_lab_results mrl ON mrl.record_id = mr.record_id LEFT JOIN referrals ref ON ref.record_id = mr.record_id LEFT JOIN medical_record_allergies mra ON mra.record_id = mr.record_id GROUP BY mr.record_id, p.patient_id, p.first_name, p.last_name, p.embg; }}} ---- == Billing integrity == Billing records must stay in sync with their line items, and payment status must follow a valid state machine. billing.total_cost is recalculated automatically whenever a procedure or lab test is added to or removed from a bill, as the sum of the list prices (`cost`) of its line items, rather than relying on the application to keep it in sync. Line items can only be changed while the bill is `PENDING`, so a `PAID` or `CANCELLED` bill can no longer change its amount. payment_status may only move `PENDING → PAID` or `PENDING → CANCELLED`, never backwards once finalized; when a bill becomes `PAID` without a payment_date, the current date is filled in. Every billing status change and every change to the calculated total caused by adding or removing a billing line item is recorded in billing_audit_log. The existing created_at column is used to track outstanding `PENDING` bills: **v_overdue_billings** lists bills that have been pending for more than 30 days (`CRITICAL` after 60 days), and job_billing_alerts reports how many need follow-up. === Schema change === {{{ CREATE TABLE IF NOT EXISTS billing_audit_log ( audit_id BIGSERIAL PRIMARY KEY, bill_id BIGINT NOT NULL REFERENCES billing(bill_id), old_amount DECIMAL(12,2), new_amount DECIMAL(12,2), old_status TEXT, new_status TEXT, change_type TEXT CHECK (change_type IN ('INSERT', 'UPDATE', 'LINE_ITEM_ADD', 'LINE_ITEM_REMOVE')), changed_at TIMESTAMP DEFAULT NOW() ); }}} === Stored procedures/functions === {{{ CREATE OR REPLACE FUNCTION recalculate_billing_total(p_bill_id BIGINT, p_change_type TEXT) RETURNS VOID LANGUAGE plpgsql AS $$ DECLARE v_old_total DECIMAL; v_status TEXT; v_procedure_total DECIMAL; v_lab_total DECIMAL; v_new_total DECIMAL; BEGIN SELECT total_cost, payment_status INTO v_old_total, v_status FROM billing WHERE bill_id = p_bill_id; IF v_status IS NULL THEN RAISE EXCEPTION 'Bill % not found', p_bill_id; END IF; IF v_status <> 'PENDING' THEN RAISE EXCEPTION 'Bill % is % and its line items can no longer be changed', p_bill_id, v_status; END IF; SELECT COALESCE(SUM(p.cost), 0) INTO v_procedure_total FROM billing_procedures bp JOIN procedures p ON p.procedure_id = bp.procedure_id WHERE bp.bill_id = p_bill_id; SELECT COALESCE(SUM(lt.cost), 0) INTO v_lab_total FROM billing_lab_tests blt JOIN lab_tests lt ON lt.test_id = blt.test_id WHERE blt.bill_id = p_bill_id; v_new_total := v_procedure_total + v_lab_total; UPDATE billing SET total_cost = v_new_total WHERE bill_id = p_bill_id; INSERT INTO billing_audit_log (bill_id, old_amount, new_amount, change_type) VALUES (p_bill_id, v_old_total, v_new_total, p_change_type); END; $$; CREATE OR REPLACE FUNCTION is_valid_billing_transition(p_old TEXT, p_new TEXT) RETURNS BOOLEAN LANGUAGE sql IMMUTABLE AS $$ SELECT CASE WHEN p_old = p_new THEN TRUE WHEN p_old = 'PENDING' AND p_new IN ('PAID', 'CANCELLED') THEN TRUE ELSE FALSE END; $$; CREATE OR REPLACE PROCEDURE job_billing_alerts() LANGUAGE plpgsql AS $$ DECLARE v_overdue_count INT; BEGIN SELECT COUNT(*) INTO v_overdue_count FROM v_overdue_billings; RAISE NOTICE 'Found % overdue billing records requiring follow-up', v_overdue_count; END; $$; }}} === Triggers === {{{ CREATE OR REPLACE FUNCTION t1_billing_line_item_changed() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN IF TG_OP = 'DELETE' THEN PERFORM recalculate_billing_total(OLD.bill_id, 'LINE_ITEM_REMOVE'); RETURN OLD; ELSE PERFORM recalculate_billing_total(NEW.bill_id, 'LINE_ITEM_ADD'); RETURN NEW; END IF; END; $$; DROP TRIGGER IF EXISTS trg_billing_procedures_update_total ON billing_procedures; CREATE TRIGGER trg_billing_procedures_update_total AFTER INSERT OR DELETE ON billing_procedures FOR EACH ROW EXECUTE FUNCTION t1_billing_line_item_changed(); DROP TRIGGER IF EXISTS trg_billing_lab_tests_update_total ON billing_lab_tests; CREATE TRIGGER trg_billing_lab_tests_update_total AFTER INSERT OR DELETE ON billing_lab_tests FOR EACH ROW EXECUTE FUNCTION t1_billing_line_item_changed(); CREATE OR REPLACE FUNCTION t2_billing_status_transition() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN IF NOT is_valid_billing_transition(OLD.payment_status, NEW.payment_status) THEN RAISE EXCEPTION 'Cannot transition billing status from % to %', OLD.payment_status, NEW.payment_status; END IF; IF NEW.payment_status = 'PAID' AND NEW.payment_date IS NULL THEN NEW.payment_date := CURRENT_DATE; END IF; IF OLD.payment_status <> NEW.payment_status THEN INSERT INTO billing_audit_log (bill_id, old_status, new_status, change_type) VALUES (NEW.bill_id, OLD.payment_status, NEW.payment_status, 'UPDATE'); END IF; RETURN NEW; END; $$; DROP TRIGGER IF EXISTS trg_billing_status_transition ON billing; CREATE TRIGGER trg_billing_status_transition BEFORE UPDATE ON billing FOR EACH ROW EXECUTE FUNCTION t2_billing_status_transition(); }}} === Views === {{{ CREATE OR REPLACE VIEW v_overdue_billings AS SELECT b.bill_id, p.patient_id, p.first_name, p.last_name, b.total_cost, b.payment_status, b.created_at, CURRENT_DATE - b.created_at::DATE AS days_outstanding, CASE WHEN CURRENT_DATE - b.created_at::DATE > 60 THEN 'CRITICAL' ELSE 'OVERDUE' END AS urgency FROM billing b JOIN medical_records mr ON b.record_id = mr.record_id JOIN patients p ON mr.patient_id = p.patient_id WHERE b.payment_status = 'PENDING' AND CURRENT_DATE - b.created_at::DATE > 30 ORDER BY days_outstanding DESC; }}} ---- == Custom domains for EMBG and Phone number formats == Custom domains replace one-off column checks with reusable types. **non_negative_cost** consolidates the `cost >= 0` rule that was repeated on **procedures**, **lab_tests** and **billing**; since it matches an existing rule it is applied immediately and the three old CHECK constraints are dropped. It is created in the Custom domains block of the first section on this page, because the column types must be changed before any view that uses these columns is created (such as **v_overdue_billings** and **mv_revenue_monthly**), and PostgreSQL cannot change the type of a column that a view depends on. **embg_format** (the same rule as the existing `patients_embg_format_chk`), **email_format** and **phone_number_format** are defined for reuse but not yet applied to any column: email_format is stricter than the existing checks (for example, it requires a domain ending such as `.com`, which the admin and lab technician checks do not), so existing data has to be checked before it is applied. === Custom domains === {{{ CREATE DOMAIN embg_format AS TEXT CHECK (VALUE ~ '^\d{13}$'); CREATE DOMAIN email_format AS TEXT CHECK ( VALUE ~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$' AND LENGTH(VALUE) <= 254 ); CREATE DOMAIN phone_number_format AS TEXT CHECK (VALUE ~ '^\+?[\d\s\-().]{7,20}$'); }}} ---- == Revenue reporting (materialized view and background refresh job) == Revenue reports need expensive joins across **billing, procedures, and lab tests**, so computing them fresh on every request is wasteful. This materialized view precomputes monthly revenue from `PAID` bills, split by procedure vs lab test and grouped by the month of **payment_date**. Procedure revenue is attributed down to department, since **procedures** carries a **doctor_id** directly. Lab revenue can't be: **lab_tests** has no doctor or department reference, and **billing_lab_tests** links only to the catalog **test_id**, not a specific performed instance, so lab rows are reported for the clinic as a whole. The view is refreshed with `CALL medora_job_refresh_revenue_view();`, either manually or from a scheduler such as pg_cron; the refresh procedure was verified by manual invocation. === Views === {{{ DROP MATERIALIZED VIEW IF EXISTS mv_revenue_monthly CASCADE; CREATE MATERIALIZED VIEW mv_revenue_monthly AS SELECT DATE_TRUNC('month', b.payment_date)::DATE AS month, dept.department_id, dept.department_name, 'PROCEDURE' AS revenue_type, SUM(p.cost) AS revenue, COUNT(DISTINCT b.bill_id) AS transaction_count FROM billing b JOIN billing_procedures bp ON bp.bill_id = b.bill_id JOIN procedures p ON p.procedure_id = bp.procedure_id JOIN doctors doc ON doc.doctor_id = p.doctor_id JOIN departments dept ON dept.department_id = doc.department_id WHERE b.payment_status = 'PAID' AND b.payment_date IS NOT NULL GROUP BY DATE_TRUNC('month', b.payment_date), dept.department_id, dept.department_name UNION ALL SELECT DATE_TRUNC('month', b.payment_date)::DATE AS month, NULL AS department_id, NULL AS department_name, 'LAB' AS revenue_type, SUM(lt.cost) AS revenue, COUNT(DISTINCT b.bill_id) AS transaction_count FROM billing b JOIN billing_lab_tests blt ON blt.bill_id = b.bill_id JOIN lab_tests lt ON lt.test_id = blt.test_id WHERE b.payment_status = 'PAID' AND b.payment_date IS NOT NULL GROUP BY DATE_TRUNC('month', b.payment_date); CREATE INDEX idx_mv_revenue_monthly_month ON mv_revenue_monthly (month, revenue_type); CREATE OR REPLACE VIEW v_current_month_revenue AS SELECT month, department_id, department_name, revenue_type, revenue, transaction_count FROM mv_revenue_monthly WHERE month = DATE_TRUNC('month', CURRENT_DATE)::DATE ORDER BY revenue_type, revenue DESC; }}} === Stored procedures/functions === {{{ CREATE OR REPLACE PROCEDURE medora_job_refresh_revenue_view() LANGUAGE plpgsql AS $$ BEGIN REFRESH MATERIALIZED VIEW mv_revenue_monthly; END; $$; }}}