Changes between Version 2 and Version 3 of AdvancedDB


Ignore:
Timestamp:
03/28/26 12:56:12 (2 weeks ago)
Author:
221296
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • AdvancedDB

    v2 v3  
    175175   AND CURRENT_DATE BETWEEN us.start_date AND us.end_date;
    176176}}}
    177 == Data constraints requirements: Appointment owner consistency, no overlap scheduling
     177== Data constraints requirements: Certificate generation consistency + one certificate per completed enrollment
    178178=== Data requirements description
    179 Appointments must obey real scheduling constraints:
    180 * The responsible_owner_id for an appointment must match the owner of the animal.
    181 * The same owner cannot have overlapping appointments.
    182 * The same animal cannot have overlapping appointments.
    183 * Confirming appointments in the past is blocked.
     179Certificates must obey completion and consistency rules:
     180* A certificate can only be issued for an enrollment whose completion status is COMPLETED.
     181* The same enrollment cannot have more than one certificate.
     182* A certificate must always refer to a valid enrollment.
     183* Issuing a certificate before course completion is blocked.
    184184=== Implementation
    185185{{{Triggers}}}
    186186{{{
    187 CREATE OR REPLACE FUNCTION petify_trg_appointments_enforce()
     187CREATE OR REPLACE FUNCTION olpms_trg_certificate_enforce()
    188188RETURNS trigger
    189189LANGUAGE plpgsql
    190190AS $$
    191191DECLARE
    192   v_animal_owner bigint;
    193 BEGIN
    194   SELECT owner_id INTO v_animal_owner
    195   FROM animals
    196   WHERE animal_id = NEW.animal_id;
    197 
    198   IF v_animal_owner IS NULL THEN
    199     RAISE EXCEPTION 'Animal % not found for appointment', NEW.animal_id;
    200   END IF;
    201 
    202   IF NEW.responsible_owner_id <> v_animal_owner THEN
    203     RAISE EXCEPTION
    204       'Appointment responsible_owner_id (%) must match animals.owner_id (%) for animal %',
    205       NEW.responsible_owner_id, v_animal_owner, NEW.animal_id;
    206   END IF;
    207 
    208   IF NEW.status = 'CONFIRMED' AND NEW.date_time < now() THEN
    209     RAISE EXCEPTION 'Cannot CONFIRM an appointment in the past (date_time=%)', NEW.date_time;
    210   END IF;
    211 
    212   IF NEW.status = 'DONE' AND NEW.date_time > now() THEN
    213     RAISE EXCEPTION 'Cannot mark DONE for an appointment that is in the future (date_time=%)', NEW.date_time;
    214   END IF;
    215 
    216   RETURN NEW;
    217 END;
    218 $$;
    219 
    220 DO $$
    221 BEGIN
    222   IF NOT EXISTS (
    223     SELECT 1
    224     FROM information_schema.columns
    225     WHERE table_name = 'appointments'
    226       AND column_name = 'slot'
    227   ) THEN
    228     ALTER TABLE appointments
    229       ADD COLUMN slot tstzrange
    230       GENERATED ALWAYS AS (
    231         tstzrange(date_time, date_time + interval '30 minutes', '[)')
    232       ) STORED;
    233   END IF;
    234 END $$;
    235 
    236 CREATE OR REPLACE FUNCTION petify_trg_appointments_no_overlap()
    237 RETURNS trigger
    238 LANGUAGE plpgsql
    239 AS $$
    240 BEGIN
    241  
    242   IF NEW.status NOT IN ('CONFIRMED','DONE') THEN
     192    v_completion_status VARCHAR(30);
     193BEGIN
     194    -- find the status of the referenced enrollment
     195    SELECT e.completion_status
     196    INTO v_completion_status
     197    FROM enrollment e
     198    WHERE e.enrollment_id = NEW.enrollment_id;
     199
     200    IF v_completion_status IS NULL THEN
     201        RAISE EXCEPTION 'Enrollment % does not exist or has no completion status', NEW.enrollment_id;
     202    END IF;
     203
     204    -- certificate can only be issued for completed enrollments
     205    IF v_completion_status <> 'COMPLETED' THEN
     206        RAISE EXCEPTION
     207            'Certificate cannot be issued because enrollment % is not COMPLETED',
     208            NEW.enrollment_id;
     209    END IF;
     210
     211    -- prevent more than one certificate for the same enrollment
     212    IF EXISTS (
     213        SELECT 1
     214        FROM certificate c
     215        WHERE c.enrollment_id = NEW.enrollment_id
     216          AND (TG_OP <> 'UPDATE' OR c.certificate_id <> NEW.certificate_id)
     217    ) THEN
     218        RAISE EXCEPTION
     219            'Enrollment % already has a certificate',
     220            NEW.enrollment_id;
     221    END IF;
     222
    243223    RETURN NEW;
    244   END IF;
    245 
    246 
    247   IF EXISTS (
    248     SELECT 1
    249     FROM appointments a
    250     WHERE a.responsible_owner_id = NEW.responsible_owner_id
    251       AND a.status IN ('CONFIRMED','DONE')
    252       AND a.slot && NEW.slot
    253       AND (TG_OP <> 'UPDATE' OR a.appointment_id <> NEW.appointment_id)
    254   ) THEN
    255     RAISE EXCEPTION
    256       'Overlapping appointment for owner % at %',
    257       NEW.responsible_owner_id, NEW.date_time;
    258   END IF;
    259 
    260  
    261   IF EXISTS (
    262     SELECT 1
    263     FROM appointments a
    264     WHERE a.animal_id = NEW.animal_id
    265       AND a.status IN ('CONFIRMED','DONE')
    266       AND a.slot && NEW.slot
    267       AND (TG_OP <> 'UPDATE' OR a.appointment_id <> NEW.appointment_id)
    268   ) THEN
    269     RAISE EXCEPTION
    270       'Overlapping appointment for animal % at %',
    271       NEW.animal_id, NEW.date_time;
    272   END IF;
    273 
    274   RETURN NEW;
    275 END;
    276 $$;
    277 
    278 
    279 DROP TRIGGER IF EXISTS trg_appointments_enforce ON appointments;
    280 CREATE TRIGGER trg_appointments_enforce
     224END;
     225$$;
     226
     227DROP TRIGGER IF EXISTS trg_certificate_enforce ON certificate;
     228
     229CREATE TRIGGER trg_certificate_enforce
    281230BEFORE INSERT OR UPDATE
    282 ON appointments
    283 FOR EACH ROW
    284 EXECUTE FUNCTION petify_trg_appointments_enforce();
    285 
    286 DROP TRIGGER IF EXISTS trg_appointments_no_overlap ON appointments;
    287 CREATE TRIGGER trg_appointments_no_overlap
    288 BEFORE INSERT OR UPDATE
    289 ON appointments
    290 FOR EACH ROW
    291 EXECUTE FUNCTION petify_trg_appointments_no_overlap();
     231ON certificate
     232FOR EACH ROW
     233EXECUTE FUNCTION olpms_trg_certificate_enforce();
    292234
    293235}}}
     
    295237{{{Views}}}
    296238{{{
    297 CREATE OR REPLACE VIEW v_clinic_appointments_monthly AS
     239CREATE OR REPLACE VIEW v_certificates_enriched AS
    298240SELECT
    299   clinic_id,
    300   date_trunc('month', date_time) AS month,
    301   COUNT(*) FILTER (WHERE status='CONFIRMED') AS confirmed_cnt,
    302   COUNT(*) FILTER (WHERE status='DONE')      AS done_cnt,
    303   COUNT(*) FILTER (WHERE status='NO_SHOW')   AS no_show_cnt,
    304   COUNT(*) FILTER (WHERE status='CANCELLED') AS cancelled_cnt,
    305   COUNT(*) AS total_cnt
    306 FROM appointments
    307 GROUP BY clinic_id, date_trunc('month', date_time)
    308 ORDER BY month DESC;
     241    c.certificate_id,
     242    c.enrollment_id,
     243    c.issue_date,
     244    c.certificate_code,
     245    c.status AS certificate_status,
     246    e.user_id,
     247    ue.first_name,
     248    ue.last_name,
     249    ue.email,
     250    e.course_id,
     251    cr.name AS course_name,
     252    e.enroll_date,
     253    e.completion_status,
     254    e.progress_percentage
     255FROM certificate c
     256JOIN enrollment e
     257    ON e.enrollment_id = c.enrollment_id
     258JOIN users u
     259    ON u.id = e.user_id
     260JOIN user_entity ue
     261    ON ue.id = u.id
     262JOIN course cr
     263    ON cr.course_id = e.course_id;
    309264
    310265}}}