Changes between Initial Version and Version 1 of AdvancedDatabaseDevelopment


Ignore:
Timestamp:
05/24/26 17:35:04 (43 hours ago)
Author:
202033
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • AdvancedDatabaseDevelopment

    v1 v1  
     1== Валидација на термини (Appointment Validation) ==
     2
     3=== Опис на барањата за податочни ограничувања ===
     4
     5Имплементираните правила се:
     6
     7 * термин не смее да биде во минато,
     8 * `end_time` мора да е после `appointment_time`,
     9 * терминот мора да е во availability прозорец (`is_closed = false`),
     10 * не е дозволено преклопување со друг ''SCHEDULED'' термин,
     11 * при креирање мора да има барем една услуга,
     12 * `end_time` и `total_price` автоматски се пресметуваат од услуги,
     13 * валидацијата се извршува на INSERT/UPDATE.
     14
     15=== Имплементација ===
     16
     17==== `fn_validate_appointment` ====
     18
     19 * '''Име:''' `fn_validate_appointment`
     20 * '''Работи над:''' `appointment`, `availability`, `status`
     21 * '''Бизнис правило:''' временска валидност, availability валидација, anti-overlap.
     22
     23{{{
     24#!sql
     25CREATE OR REPLACE FUNCTION fn_validate_appointment(p_appointment_id int)
     26RETURNS void
     27LANGUAGE plpgsql
     28AS $$
     29DECLARE
     30    v_start timestamp;
     31    v_end timestamp;
     32    v_date date;
     33    v_overlap_count int;
     34    v_avail_count int;
     35BEGIN
     36
     37    SELECT appointment_time, end_time
     38    INTO v_start, v_end
     39    FROM appointment
     40    WHERE appointment_id = p_appointment_id;
     41
     42    IF v_start IS NULL OR v_end IS NULL THEN
     43        RAISE EXCEPTION 'Appointment must have a start and end time';
     44    END IF;
     45
     46    IF v_end <= v_start THEN
     47        RAISE EXCEPTION 'Appointment end time must be after start time';
     48    END IF;
     49
     50    IF v_start < now() THEN
     51        RAISE EXCEPTION 'Appointment time must be in the future';
     52    END IF;
     53
     54    v_date := v_start::date;
     55
     56    SELECT COUNT(*)
     57    INTO v_avail_count
     58    FROM availability a
     59    WHERE a.date = v_date
     60      AND a.is_closed = false
     61      AND v_start >= (a.date + a.start_time)
     62      AND v_end <= (a.date + a.end_time);
     63
     64    IF v_avail_count = 0 THEN
     65        RAISE EXCEPTION 'Appointment is outside availability';
     66    END IF;
     67
     68    SELECT COUNT(*)
     69    INTO v_overlap_count
     70    FROM appointment ap
     71    JOIN status st
     72      ON st.status_id = ap.status_id
     73    WHERE ap.appointment_id <> p_appointment_id
     74      AND UPPER(st.name) = 'SCHEDULED'
     75      AND v_start < ap.end_time
     76      AND v_end > ap.appointment_time;
     77
     78    IF v_overlap_count > 0 THEN
     79        RAISE EXCEPTION 'Appointment overlaps an existing booking';
     80    END IF;
     81
     82END;
     83$$;
     84}}}
     85
     86==== `fn_recalculate_appointment` ====
     87
     88 * '''Име:''' `fn_recalculate_appointment`
     89 * '''Работи над:''' `appointment`, `appointmentservice`, `service`
     90 * '''Бизнис правило:''' автоматско пресметување на `total_price` и `end_time`.
     91
     92{{{
     93#!sql
     94CREATE OR REPLACE FUNCTION fn_recalculate_appointment(p_appointment_id int)
     95RETURNS void
     96LANGUAGE plpgsql
     97AS $$
     98DECLARE
     99    v_total_minutes int;
     100    v_total_price numeric;
     101    v_start timestamp;
     102BEGIN
     103
     104    SELECT appointment_time
     105    INTO v_start
     106    FROM appointment
     107    WHERE appointment_id = p_appointment_id
     108    FOR UPDATE;
     109
     110    IF v_start IS NULL THEN
     111        RETURN;
     112    END IF;
     113
     114    SELECT
     115        COALESCE(SUM(s.duration_minutes), 0)::int,
     116        COALESCE(SUM(s.price), 0)::numeric
     117    INTO
     118        v_total_minutes,
     119        v_total_price
     120    FROM appointmentservice aps
     121    JOIN service s
     122      ON s.service_id = aps.service_id
     123    WHERE aps.appointment_id = p_appointment_id;
     124
     125    UPDATE appointment
     126    SET
     127        total_price = v_total_price,
     128        end_time = v_start + make_interval(mins => v_total_minutes)
     129    WHERE appointment_id = p_appointment_id;
     130
     131END;
     132$$;
     133}}}
     134
     135==== `sp_create_appointment` ====
     136
     137 * '''Име:''' `sp_create_appointment`
     138 * '''Работи над:''' `service`, `status`, `appointment`, `appointmentservice`
     139 * '''Бизнис правило:''' креирање термин со целосна DB валидација и пресметка.
     140
     141''извадок од функцијата''
     142
     143{{{
     144#!sql
     145CREATE OR REPLACE FUNCTION sp_create_appointment(
     146    p_user_id int,
     147    p_appointment_time timestamp,
     148    p_service_ids int[],
     149    p_notes text
     150)
     151RETURNS int
     152LANGUAGE plpgsql
     153AS $$
     154DECLARE
     155    v_appointment_id int;
     156    v_status_id int;
     157    v_service_count int;
     158    v_total_minutes int;
     159    v_total_price numeric;
     160    v_end_time timestamp;
     161    v_enum_type text;
     162    v_enum_label text;
     163BEGIN
     164
     165    IF p_service_ids IS NULL
     166       OR array_length(p_service_ids, 1) IS NULL THEN
     167        RAISE EXCEPTION 'At least one service is required';
     168    END IF;
     169
     170    SELECT COUNT(*)
     171    INTO v_service_count
     172    FROM service
     173    WHERE service_id = ANY(p_service_ids);
     174
     175    IF v_service_count <> array_length(p_service_ids, 1) THEN
     176        RAISE EXCEPTION 'One or more services not found';
     177    END IF;
     178
     179    v_total_minutes := fn_service_total_minutes(p_service_ids);
     180    v_total_price := fn_service_total_price(p_service_ids);
     181
     182    v_end_time :=
     183        p_appointment_time
     184        + make_interval(mins => v_total_minutes);
     185
     186    SELECT status_id
     187    INTO v_status_id
     188    FROM status
     189    WHERE UPPER(name) = 'SCHEDULED'
     190    LIMIT 1;
     191
     192    EXECUTE format(
     193        'INSERT INTO appointment
     194        (
     195            appointment_time,
     196            end_time,
     197            total_price,
     198            notes,
     199            status_id,
     200            user_id,
     201            "type"
     202        )
     203        VALUES ($1,$2,$3,$4,$5,$6,$7::%I)
     204        RETURNING appointment_id',
     205        v_enum_type
     206    )
     207    INTO v_appointment_id
     208    USING
     209        p_appointment_time,
     210        v_end_time,
     211        v_total_price,
     212        p_notes,
     213        v_status_id,
     214        p_user_id,
     215        v_enum_label;
     216
     217    INSERT INTO appointmentservice
     218    (
     219        appointment_id,
     220        service_id
     221    )
     222    SELECT
     223        v_appointment_id,
     224        unnest(p_service_ids);
     225
     226    PERFORM fn_validate_appointment(v_appointment_id);
     227
     228    RETURN v_appointment_id;
     229
     230END;
     231$$;
     232}}}
     233
     234==== `trg_validate_appointment` + `appointment_validate` ====
     235
     236 * '''Имиња:''' `trg_validate_appointment`, `appointment_validate`
     237 * '''Работи над:''' `appointment`
     238 * '''Бизнис правило:''' задолжителна валидација и на insert и на update.
     239
     240{{{
     241#!sql
     242CREATE OR REPLACE FUNCTION trg_validate_appointment()
     243RETURNS trigger
     244LANGUAGE plpgsql
     245AS $$
     246BEGIN
     247
     248    PERFORM fn_validate_appointment(NEW.appointment_id);
     249
     250    RETURN NEW;
     251
     252END;
     253$$;
     254
     255DROP TRIGGER IF EXISTS appointment_validate
     256ON appointment;
     257
     258CREATE CONSTRAINT TRIGGER appointment_validate
     259AFTER INSERT OR UPDATE
     260ON appointment
     261DEFERRABLE INITIALLY DEFERRED
     262FOR EACH ROW
     263EXECUTE FUNCTION trg_validate_appointment();
     264}}}
     265
     266==== `trg_appointmentservice_recalculate` + `appointmentservice_recalculate` ====
     267
     268 * '''Имиња:''' `trg_appointmentservice_recalculate`, `appointmentservice_recalculate`
     269 * '''Работи над:''' `appointmentservice`, `appointment`
     270 * '''Бизнис правило:''' секоја промена на услуги во термин автоматски ги рефрешира цена/траење.
     271
     272{{{
     273#!sql
     274CREATE OR REPLACE FUNCTION trg_appointmentservice_recalculate()
     275RETURNS trigger
     276LANGUAGE plpgsql
     277AS $$
     278BEGIN
     279
     280    IF TG_OP IN ('INSERT', 'UPDATE') THEN
     281        PERFORM fn_recalculate_appointment(NEW.appointment_id);
     282    END IF;
     283
     284    IF TG_OP IN ('DELETE', 'UPDATE') THEN
     285        PERFORM fn_recalculate_appointment(OLD.appointment_id);
     286    END IF;
     287
     288    RETURN NULL;
     289
     290END;
     291$$;
     292
     293DROP TRIGGER IF EXISTS appointmentservice_recalculate
     294ON appointmentservice;
     295
     296CREATE TRIGGER appointmentservice_recalculate
     297AFTER INSERT OR UPDATE OR DELETE
     298ON appointmentservice
     299FOR EACH ROW
     300EXECUTE FUNCTION trg_appointmentservice_recalculate();
     301}}}