Changes between Version 1 and Version 2 of FunctionsProceduresTriggers


Ignore:
Timestamp:
07/01/26 16:55:17 (9 days ago)
Author:
231561
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • FunctionsProceduresTriggers

    v1 v2  
    33== Overview of implemented objects
    44
    5 || Type || Name || Description || Step ||
    6 || Trigger || trg_appointment_status_audit ||
    7 || Function || fn_get_active_diagnoses ||
    8 || Function || fn_prescribe_medicine ||
    9 || Function || fn_check_doctor_availability ||
    10 || Procedure || prc_create_referral ||
    11 || Procedure || prc_add_appointment_for_referral ||
    12 || Procedure || prc_complete_examination ||
    13 || Procedure || prc_add_new_diagnosis ||
    14 || Procedure || prc_create_prescription ||
    15 || Procedure || prc_buy_prescription ||
     5||= Type =||= Name =||= Description =||= Step =||
     6||=Trigger=|| trg_appointment_status_audit || Logs every change in appointment status || - ||
     7||=Function=|| fn_get_active_diagnoses || Returns active diagnoses for patient || before 5 ||
     8||=Function=|| fn_prescribe_medicine || Validates and returns data for prescription || in 5 ||
     9||=Function=|| fn_check_doctor_availability || Checks doctor availability for date || in 2 ||
     10||=Procedure=|| prc_create_referral || Creates a referral || 1 ||
     11||=Procedure=|| prc_add_appointment_for_referral || Schedules an appointment after a referral || 2 ||
     12||=Procedure=|| prc_complete_examination || Completes an appointment, creates an examination || 3 ||
     13||=Procedure=|| prc_add_new_diagnosis || Adds a diagnosis after an examination || 4 ||
     14||=Procedure=|| prc_create_prescription || Creates a prescription for a diagnosis || 5 ||
     15||=Procedure=|| prc_buy_prescription || Dispenses a medicine at a pharmacy || 6 ||
     16
     17== Application workflow
     18
     19||= Step =||= Description =||
     20||=1=|| Doctor refers patient to department ||
     21||=2=|| Schedule appointment by referral ||
     22||=3=|| Complete appointment + create examination ||
     23||=4=|| Doctor adds diagnosis by examination ||
     24||=5=|| Doctor issues prescription for diagnosis ||
     25||=6=|| Patient picks up medication at pharmacy ||
     26
     27=== Trigger 1 : trg_appointment_status_audit
     28
     29The trigger is automatically fired after every UPDATE on the Appointment table. When the old and new status values ​​differ, a new record is inserted into appointment_status_audit with the old status, the new status, the time of change, and the user. This provides a complete history of appointment changes without any additional logic in the application — the trigger is transparent and fires both on direct UPDATE operations and when calling prc_complete_examination.
     30
     31**Supporting table:**
     32
     33CREATE TABLE IF NOT EXISTS appointment_status_audit (
     34
     35    audit_id        SERIAL PRIMARY KEY,
     36
     37    appointment_id  INT NOT NULL,
     38
     39    old_status      VARCHAR(30),
     40
     41    new_status      VARCHAR(30),
     42
     43    changed_at      TIMESTAMP NOT NULL DEFAULT NOW(),
     44
     45    changed_by      TEXT DEFAULT CURRENT_USER
     46
     47);
     48
     49**Implementation:**
     50
     51CREATE OR REPLACE FUNCTION fn_audit_appointment_status()
     52
     53RETURNS TRIGGER AS $$
     54
     55BEGIN
     56
     57    IF OLD.status IS DISTINCT FROM NEW.status THEN
     58
     59        INSERT INTO appointment_status_audit (appointment_id, old_status, new_status, changed_at, changed_by)
     60
     61        VALUES (NEW.appointment_id, OLD.status, NEW.status, NOW(), CURRENT_USER);
     62
     63    END IF;
     64
     65    RETURN NEW;
     66
     67END;
     68
     69$$ LANGUAGE plpgsql;
     70
     71CREATE TRIGGER trg_appointment_status_audit
     72
     73    AFTER UPDATE ON Appointment
     74
     75    FOR EACH ROW
     76
     77    EXECUTE FUNCTION fn_audit_appointment_status();
     78
     79=== Function 1 : fn_get_active_diagnoses
     80
     81The function returns a tabular result with all active diagnoses for a given patient. A diagnosis whose date_to is NULL or is in the future is active. For each diagnosis, the ICD code, description, whether it is primary, and the name of the doctor who prescribed it are returned. Before prescribing a medication, the application calls this function to show the doctor for which diagnosis it is prescribing.
     82
     83**Implementation:**
     84
     85CREATE OR REPLACE FUNCTION fn_get_active_diagnoses(p_patient_id INT)
     86
     87RETURNS TABLE (patient_diagnosis_id INT, diagnosis_name VARCHAR(255),
     88
     89               icd_code VARCHAR(20), icd_description VARCHAR(255),
     90
     91               is_primary BOOLEAN, date_from DATE, date_to DATE,
     92
     93               doctor_name TEXT) AS $$
     94
     95BEGIN
     96
     97    IF NOT EXISTS (SELECT 1 FROM Patient WHERE patient_id = p_patient_id) THEN
     98
     99        RAISE EXCEPTION 'Patient with ID % does not exist.', p_patient_id;
     100
     101    END IF;
     102
     103    RETURN QUERY
     104
     105        SELECT pd.patient_diagnosis_id, pd.diagnosis_name,
     106
     107               i.code, i.description, pd.is_primary,
     108
     109               pd.date_from, pd.date_to,
     110
     111               (d.first_name | | ' ' | | d.last_name)::TEXT
     112
     113        FROM Patient_diagnosis pd
     114
     115        JOIN ICD    i ON i.icd_id    = pd.icd_id
     116
     117        JOIN Doctor d ON d.doctor_id = pd.doctor_id
     118
     119        WHERE pd.patient_id = p_patient_id AND (pd.date_to IS NULL OR pd.date_to >= CURRENT_DATE)
     120
     121        ORDER BY pd.is_primary DESC, pd.date_from DESC;
     122
     123END; $$ LANGUAGE plpgsql;
     124
     125=== Function 2 : fn_prescribe_medicine
     126
     127The function is the central validation logic for prescribing medications. It checks that the patient and doctor exist, that the diagnosis belongs to the patient and is active, that the ATC code exists, that the dosage and duration are positive, and that there is sufficient stock for that ATC code. If all hecks pass, it returns a single row with all the data needed to create a prescription. The prc_create_prescription procedure calls this function and directly uses the returned values.
     128
     129**Implementation:**
     130
     131CREATE OR REPLACE FUNCTION fn_prescribe_medicine(
     132
     133    p_patient_id INT, p_doctor_id INT,
     134
     135    p_patient_diagnosis_id INT, p_atc_id INT,
     136
     137    p_dosage INT, p_duration INT
     138
     139)
     140
     141RETURNS TABLE (patient_id INT, doctor_id INT,
     142
     143               patient_diagnosis_id INT, atc_id INT,
     144
     145               inventory_id INT, dosage INT, duration INT,
     146
     147               presc_date DATE, available_stock INT,
     148
     149               drug_name TEXT) AS $$
     150
     151DECLARE
     152
     153    v_inventory_id INT; v_stock INT; v_drug_name TEXT;
     154
     155BEGIN
     156
     157    IF NOT EXISTS (SELECT 1 FROM Patient WHERE patient_id = p_patient_id)
     158
     159        THEN RAISE EXCEPTION 'Patient % does not exist.', p_patient_id;
     160
     161    END IF;
     162
     163    IF NOT EXISTS (SELECT 1 FROM Doctor WHERE doctor_id = p_doctor_id)
     164
     165        THEN RAISE EXCEPTION 'Doctor % does not exist.', p_doctor_id;
     166
     167    END IF;
     168
     169    IF NOT EXISTS (
     170
     171        SELECT 1 FROM Patient_diagnosis
     172
     173        WHERE patient_diagnosis_id = p_patient_diagnosis_id
     174
     175          AND patient_id = p_patient_id
     176
     177          AND (date_to IS NULL OR date_to >= CURRENT_DATE)
     178
     179    ) THEN
     180
     181       RAISE EXCEPTION 'Diagnosis % not active for patient %.', p_patient_diagnosis_id, p_patient_id;
     182
     183    END IF;
     184
     185    SELECT i.inventory_id, i.quantity, (dp.producer_name | | ' - ' | | ac.atc_code)::TEXT
     186
     187    INTO v_inventory_id, v_stock, v_drug_name
     188
     189    FROM Inventory i
     190
     191    JOIN Drug d ON d.product_id = i.product_id
     192
     193    JOIN ATC_code ac ON ac.atc_id = d.atc_id
     194
     195    JOIN Drug_producers dp ON dp.drug_prod_id = d.drug_prod_id
     196
     197    WHERE d.atc_id = p_atc_id AND i.quantity >= p_dosage
     198
     199    ORDER BY i.quantity DESC LIMIT 1;
     200
     201    IF NOT FOUND THEN
     202
     203        RAISE EXCEPTION 'No stock for ATC ID %. Required: %.', p_atc_id, p_dosage;
     204
     205    END IF;
     206
     207    RETURN QUERY SELECT p_patient_id, p_doctor_id, p_patient_diagnosis_id, p_atc_id, v_inventory_id, p_dosage, p_duration, CURRENT_DATE, v_stock, v_drug_name;
     208
     209END; $$ LANGUAGE plpgsql;
     210
     211=== Function 3 : fn_check_doctor_availability
     212
     213The function counts the number of active appointments (excluding CANCELLED and NO_SHOW) that a doctor has on a given date. If the number is less than 30, it returns TRUE (available), otherwise FALSE. The prc_add_appointment_for_referral procedure calls this function and throws an exception if the doctor is not available, instead of directly inserting and then detecting a problem. It can also be used directly from the application to display available appointments.
     214
     215**Implementation:**
     216
     217CREATE OR REPLACE FUNCTION fn_check_doctor_availability(
     218
     219    p_doctor_id        INT,
     220
     221    p_appointment_date DATE
     222
     223)
     224
     225RETURNS BOOLEAN AS $$
     226
     227DECLARE v_count INT;
     228
     229BEGIN
     230
     231    IF NOT EXISTS (SELECT 1 FROM Doctor WHERE doctor_id = p_doctor_id) THEN
     232
     233        RAISE EXCEPTION 'Doctor with ID % does not exist.', p_doctor_id;
     234
     235    END IF;
     236
     237    SELECT COUNT(*) INTO v_count
     238
     239    FROM Appointment
     240
     241    WHERE doctor_id = p_doctor_id AND appointment_date = p_appointment_date AND status NOT IN ('CANCELLED', 'NO_SHOW');
     242
     243    RETURN v_count < 30;
     244
     245END;
     246
     247$$ LANGUAGE plpgsql;
     248
     249=== Procedure 1 : prc_create_referral [Step 1]
     250
     251The procedure does the following: (1) validates that the patient, department, and referring doctor exist, (2) if a specific doctor is given — validates that it exists and is not the same as the referring doctor, (3) inserts a new record into Referral. This is the entry point into the medical process — without a referral, there is no appointment.
     252
     253**Implementation:**
     254
     255CREATE OR REPLACE PROCEDURE prc_create_referral(
     256
     257    p_patient_id          INT,
     258
     259    p_department_id       INT,
     260
     261    p_referring_doctor_id INT,
     262
     263    p_referred_doctor_id  INT DEFAULT NULL
     264
     265)
     266
     267LANGUAGE plpgsql AS $$
     268
     269DECLARE v_new_referral_id INT;
     270
     271BEGIN
     272
     273    IF NOT EXISTS (SELECT 1 FROM Patient WHERE patient_id = p_patient_id) THEN
     274
     275        RAISE EXCEPTION 'Patient % does not exist.', p_patient_id;
     276
     277    END IF;
     278
     279    IF NOT EXISTS (SELECT 1 FROM Department WHERE department_id = p_department_id) THEN
     280
     281        RAISE EXCEPTION 'Department % does not exist.', p_department_id;
     282
     283    END IF;
     284
     285    IF NOT EXISTS (SELECT 1 FROM Doctor WHERE doctor_id = p_referring_doctor_id) THEN
     286
     287        RAISE EXCEPTION 'Referring doctor % does not exist.', p_referring_doctor_id;
     288
     289    END IF;
     290
     291    IF p_referred_doctor_id IS NOT NULL THEN
     292
     293        IF NOT EXISTS (SELECT 1 FROM Doctor WHERE doctor_id = p_referred_doctor_id) THEN
     294
     295            RAISE EXCEPTION 'Referred doctor % does not exist.', p_referred_doctor_id;
     296
     297        END IF;
     298
     299        IF p_referred_doctor_id = p_referring_doctor_id THEN
     300
     301            RAISE EXCEPTION 'Referring and referred doctor cannot be the same (ID: %).',
     302
     303                p_referring_doctor_id;
     304
     305        END IF;
     306
     307    END IF;
     308
     309    SELECT COALESCE(MAX(referral_id),0)+1 INTO v_new_referral_id FROM Referral;
     310
     311    INSERT INTO Referral(referral_id,patient_id,department_id, referred_doctor_id,referring_doctor_id)
     312
     313    VALUES(v_new_referral_id,p_patient_id,p_department_id, p_referred_doctor_id,p_referring_doctor_id);
     314
     315    RAISE NOTICE 'Referral % created. Patient % → Department % by Doctor %.', v_new_referral_id, p_patient_id, p_department_id, p_referring_doctor_id;
     316
     317END; $$;
     318
     319=== Procedure 2 : prc_add_appointment_for_referral [Step 2]
     320
     321The procedure does the following: (1) takes the patient_id from the referral, (2) validates the doctor, date (not in the past), type, and priority, (3) calls fn_check_doctor_availability — if the doctor is busy it throws an exception, (4) checks that the referral does not already have an active appointment on that date, (5) inserts a new Appointment with status='SCHEDULED'. This procedure is the second in the flow — a referral must exist first.
     322
     323**Implemenetation:**
     324
     325CREATE OR REPLACE PROCEDURE prc_add_appointment_for_referral(
     326
     327    p_referral_id INT, p_doctor_id INT, p_appointment_date DATE,
     328
     329    p_appointment_type VARCHAR(30) DEFAULT 'REGULAR',
     330
     331    p_priority_level   VARCHAR(30) DEFAULT 'MEDIUM'
     332
     333)
     334
     335LANGUAGE plpgsql AS $$
     336
     337DECLARE
     338
     339    v_patient_id INT; v_new_appt_id INT; v_available BOOLEAN;
     340
     341BEGIN
     342
     343    SELECT patient_id INTO v_patient_id FROM Referral
     344
     345    WHERE referral_id = p_referral_id;
     346
     347    IF NOT FOUND THEN
     348
     349        RAISE EXCEPTION 'Referral % does not exist.', p_referral_id;
     350 
     351    END IF;
     352
     353    IF p_appointment_date < CURRENT_DATE THEN
     354
     355        RAISE EXCEPTION 'Date % cannot be in the past.', p_appointment_date;
     356   
     357    END IF;
     358
     359    SELECT fn_check_doctor_availability(p_doctor_id, p_appointment_date)
     360
     361    INTO v_available;
     362
     363    IF NOT v_available THEN
     364
     365        RAISE EXCEPTION 'Doctor % has no availability on %.', p_doctor_id, p_appointment_date;
     366
     367    END IF;
     368
     369    IF EXISTS (SELECT 1 FROM Appointment
     370
     371               WHERE referral_id=p_referral_id
     372
     373                 AND appointment_date=p_appointment_date
     374
     375                 AND status NOT IN ('CANCELLED','NO_SHOW')) THEN
     376
     377        RAISE EXCEPTION 'Referral % already has an appointment on %.', p_referral_id, p_appointment_date;
     378
     379    END IF;
     380
     381    SELECT COALESCE(MAX(appointment_id),0)+1 INTO v_new_appt_id FROM Appointment;
     382
     383    INSERT INTO Appointment(appointment_id,referral_id,doctor_id,patient_id, prescription_value,appointment_date,status,appointment_type,priority_level)
     384
     385    VALUES(v_new_appt_id,p_referral_id,p_doctor_id,v_patient_id, FALSE,p_appointment_date,'SCHEDULED',p_appointment_type,p_priority_level);
     386
     387    RAISE NOTICE 'Appointment % for patient % (referral %) with doctor % on %.', v_new_appt_id,v_patient_id,p_referral_id,p_doctor_id,p_appointment_date;
     388
     389END; $$;
     390
     391=== Procedure 3 : prc_complete_examination [Step 3]
     392
     393The procedure: (1) takes the appointment details, (2) validates that it is not already completed, cancelled, or in the future, (3) checks that there is no other examination for that appointment, (4) sets status='COMPLETED' — this automatically triggers trg_appointment_status_audit, (5)
     394inserts a new record into Medical_examination. After completing this procedure, the doctor can
     395add a diagnosis.
     396
     397**Implementation:**
     398
     399CREATE OR REPLACE PROCEDURE prc_complete_examination(
     400
     401    p_appointment_id INT,
     402
     403    p_notes TEXT DEFAULT 'Examination completed.'
     404
     405)
     406
     407LANGUAGE plpgsql AS $$
     408
     409DECLARE
     410
     411    v_doctor_id INT; v_exam_date DATE;
     412
     413    v_new_exam_id INT; v_current_status VARCHAR(30);
     414
     415BEGIN
     416
     417    SELECT doctor_id, appointment_date, status
     418
     419    INTO v_doctor_id, v_exam_date, v_current_status
     420
     421    FROM Appointment WHERE appointment_id = p_appointment_id;
     422
     423    IF NOT FOUND THEN
     424
     425        RAISE EXCEPTION 'Appointment % does not exist.', p_appointment_id;
     426
     427    END IF;
     428
     429    IF v_current_status = 'COMPLETED' THEN
     430
     431        RAISE EXCEPTION 'Appointment % already completed.', p_appointment_id;
     432
     433    END IF;
     434
     435    IF v_current_status = 'CANCELLED' THEN
     436
     437        RAISE EXCEPTION 'Cannot complete CANCELLED appointment %.', p_appointment_id;
     438
     439    END IF;
     440
     441    IF v_exam_date > CURRENT_DATE THEN
     442
     443        RAISE EXCEPTION 'Cannot complete future appointment % on %.', p_appointment_id, v_exam_date;
     444
     445    END IF;
     446
     447    IF EXISTS (SELECT 1 FROM Medical_examination WHERE appointment_id=p_appointment_id) THEN
     448
     449        RAISE EXCEPTION 'Exam already exists for appointment %.', p_appointment_id;
     450
     451    END IF;
     452
     453    UPDATE Appointment SET status='COMPLETED' WHERE appointment_id=p_appointment_id;
     454
     455    SELECT COALESCE(MAX(exam_id),0)+1 INTO v_new_exam_id FROM Medical_examination;
     456
     457    INSERT INTO Medical_examination(exam_id,exam_date,notes,doctor_id,appointment_id)
     458
     459    VALUES(v_new_exam_id,v_exam_date,p_notes,v_doctor_id,p_appointment_id);
     460
     461    RAISE NOTICE 'Appointment % completed. Examination % created.', p_appointment_id, v_new_exam_id;
     462
     463END; $$;
     464
     465=== Procedure 4 : prc_add_new_diagnosis [Step 4]
     466
     467The procedure: (1) takes the patient_id and the exam date via exam_id, (2) validates that the doctor and ICD code exist, (3) validates that date_to (if specified) is not before the exam date, (4) if the diagnosis is set as primary — reports if the patient already has an active primary, (5) inserts a new record into Patient_diagnosis. This procedure is a prerequisite for prc_create_prescription.
     468
     469**Implementation:**
     470
     471CREATE OR REPLACE PROCEDURE prc_add_new_diagnosis(
     472
     473    p_exam_id        INT,
     474
     475    p_icd_id         INT,
     476
     477    p_doctor_id      INT,
     478
     479    p_diagnosis_name VARCHAR(255),
     480
     481    p_is_primary     BOOLEAN DEFAULT FALSE,
     482
     483    p_date_to        DATE    DEFAULT NULL
     484
     485)
     486
     487LANGUAGE plpgsql AS $$
     488
     489DECLARE
     490
     491    v_patient_id INT; v_exam_date DATE; v_new_diagnosis_id INT;
     492
     493BEGIN
     494
     495    SELECT a.patient_id, me.exam_date INTO v_patient_id, v_exam_date
     496
     497    FROM Medical_examination me
     498
     499    JOIN Appointment a ON a.appointment_id = me.appointment_id
     500
     501    WHERE me.exam_id = p_exam_id;
     502
     503    IF NOT FOUND THEN
     504
     505        RAISE EXCEPTION 'Exam % does not exist.', p_exam_id;
     506
     507    END IF;
     508
     509    IF NOT EXISTS (SELECT 1 FROM Doctor WHERE doctor_id = p_doctor_id) THEN
     510
     511        RAISE EXCEPTION 'Doctor % does not exist.', p_doctor_id;
     512
     513    END IF;
     514
     515    IF NOT EXISTS (SELECT 1 FROM ICD WHERE icd_id = p_icd_id) THEN
     516
     517        RAISE EXCEPTION 'ICD code % does not exist.', p_icd_id;
     518
     519    END IF;
     520
     521    IF p_date_to IS NOT NULL AND p_date_to < v_exam_date THEN
     522
     523        RAISE EXCEPTION 'date_to (%) cannot be before exam date (%).', p_date_to, v_exam_date;
     524
     525    END IF;
     526
     527    SELECT COALESCE(MAX(patient_diagnosis_id),0)+1
     528
     529    INTO v_new_diagnosis_id FROM Patient_diagnosis;
     530
     531    INSERT INTO Patient_diagnosis(patient_diagnosis_id,patient_id,doctor_id, exam_id,icd_id,diagnosis_name,is_primary,date_from,date_to)
     532
     533    VALUES(v_new_diagnosis_id,v_patient_id,p_doctor_id, p_exam_id,p_icd_id,p_diagnosis_name,p_is_primary,v_exam_date,p_date_to);
     534
     535    RAISE NOTICE 'Diagnosis % (ID: %) added for patient % from exam %.', p_diagnosis_name, v_new_diagnosis_id, v_patient_id, p_exam_id;
     536
     537END; $$;
     538
     539=== Procedure 5 : prc_create_prescription [Step 5]
     540
     541The procedure calls fn_prescribe_medicine which performs all the validation (patient, doctor, diagnosis, ATC, stock). If the function succeeds, the procedure additionally checks that there is no existing prescription for that diagnosis and ATC code, and inserts a new entry into Prescription. This procedure is a prerequisite for prc_buy_prescription.
     542
     543**Implementation:**
     544
     545CREATE OR REPLACE PROCEDURE prc_create_prescription(
     546
     547    p_patient_id           INT,
     548
     549    p_doctor_id            INT,
     550
     551    p_patient_diagnosis_id INT,
     552
     553    p_atc_id               INT,
     554
     555    p_dosage               INT,
     556
     557    p_duration             INT
     558
     559)
     560
     561LANGUAGE plpgsql AS $$
     562
     563DECLARE
     564
     565    v_new_presc_id INT; v_inventory_id INT;
     566
     567    v_drug_name TEXT; v_stock INT;
     568
     569BEGIN
     570
     571    SELECT r.inventory_id, r.available_stock, r.drug_name
     572
     573    INTO v_inventory_id, v_stock, v_drug_name
     574
     575    FROM fn_prescribe_medicine(p_patient_id, p_doctor_id, p_patient_diagnosis_id, p_atc_id, p_dosage, p_duration) r;
     576
     577    IF EXISTS (SELECT 1 FROM Prescription
     578
     579               WHERE patient_diagnosis_id = p_patient_diagnosis_id AND atc_id = p_atc_id AND patient_id = p_patient_id) THEN
     580
     581        RAISE EXCEPTION 'Prescription for ATC % already exists for diagnosis %.', p_atc_id, p_patient_diagnosis_id;
     582
     583    END IF;
     584
     585    SELECT COALESCE(MAX(presc_id),0)+1 INTO v_new_presc_id FROM Prescription;
     586
     587    INSERT INTO Prescription(presc_id,atc_id,doctor_id,patient_id, presc_date,duration,dosage,inventory_id,patient_diagnosis_id)
     588
     589    VALUES(v_new_presc_id,p_atc_id,p_doctor_id,p_patient_id, CURRENT_DATE,p_duration,p_dosage,v_inventory_id,p_patient_diagnosis_id);
     590
     591    RAISE NOTICE 'Prescription % created. Drug: %. Dosage: %. Duration: % days.', v_new_presc_id, v_drug_name, p_dosage, p_duration;
     592
     593END; $$;
     594
     595=== Procedure 6 : prc_buy_prescription [Step 6]
     596
     597The procedure: (1) takes the prescription details, (2) validates that the pharmacy exists, (3) checks that the prescription is not already filled at that pharmacy, (4) checks inventory, (5) takes the last sale price, (6) inserts Pharmacy_sale, (7) inserts Sale_item, and (8) decrements the quantity in Inventory. This is the final step in the entire medical process and closes the referral → medication cycle.
     598
     599**Implementation:**
     600
     601CREATE OR REPLACE PROCEDURE prc_buy_prescription(
     602
     603    p_presc_id INT, p_pharmacy_id INT
     604
     605)
     606
     607LANGUAGE plpgsql AS $$
     608
     609DECLARE
     610
     611    v_patient_id INT; v_inventory_id INT; v_dosage INT;
     612
     613    v_current_qty INT; v_sale_price NUMERIC;
     614
     615    v_total_amount NUMERIC; v_new_sale_id INT; v_new_item_id INT;
     616
     617BEGIN
     618
     619    SELECT patient_id, inventory_id, dosage INTO v_patient_id, v_inventory_id, v_dosage
     620
     621    FROM Prescription WHERE presc_id = p_presc_id;
     622
     623    IF NOT FOUND THEN
     624
     625        RAISE EXCEPTION 'Prescription % does not exist.', p_presc_id;
     626
     627    END IF;
     628
     629    IF NOT EXISTS (SELECT 1 FROM Pharmacy WHERE pharmacy_id = p_pharmacy_id) THEN
     630
     631        RAISE EXCEPTION 'Pharmacy % does not exist.', p_pharmacy_id;
     632
     633    END IF;
     634
     635    IF EXISTS (SELECT 1 FROM Pharmacy_sale WHERE presc_id=p_presc_id AND pharmacy_id=p_pharmacy_id) THEN
     636
     637        RAISE EXCEPTION 'Prescription % already dispensed at pharmacy %.', p_presc_id, p_pharmacy_id;
     638
     639    END IF;
     640
     641    SELECT quantity INTO v_current_qty FROM Inventory WHERE inventory_id=v_inventory_id;
     642
     643    IF v_current_qty < v_dosage THEN
     644
     645        RAISE EXCEPTION 'Insufficient stock. Required: %, Available: %.', v_dosage, v_current_qty;
     646
     647    END IF;
     648
     649    SELECT sale_price INTO v_sale_price FROM Inventory_price
     650
     651    WHERE inventory_id=v_inventory_id ORDER BY date DESC LIMIT 1;
     652
     653    v_total_amount := ROUND(v_sale_price * v_dosage, 2);
     654
     655    SELECT COALESCE(MAX(sale_id),0)+1 INTO v_new_sale_id FROM Pharmacy_sale;
     656
     657    INSERT INTO Pharmacy_sale(sale_id,patient_id,pharmacy_id,presc_id,sale_date,total_amount)
     658
     659    VALUES(v_new_sale_id,v_patient_id,p_pharmacy_id,p_presc_id,CURRENT_DATE,v_total_amount);
     660
     661    SELECT COALESCE(MAX(sale_item_id),0)+1 INTO v_new_item_id FROM Sale_item;
     662
     663    INSERT INTO Sale_item(sale_item_id,sale_id,presc_id,inventory_id,date)
     664
     665    VALUES(v_new_item_id,v_new_sale_id,p_presc_id,v_inventory_id,CURRENT_DATE);
     666
     667    UPDATE Inventory SET quantity=quantity-v_dosage WHERE inventory_id=v_inventory_id;
     668
     669    RAISE NOTICE 'Prescription % dispensed at pharmacy %. Sale: %. Total: %. Stock left: %.', p_presc_id,p_pharmacy_id,v_new_sale_id,v_total_amount,v_current_qtyv_dosage;
     670
     671END; $$;