Changes between Initial Version and Version 1 of DatabaseProgramming


Ignore:
Timestamp:
05/27/26 00:27:39 (18 hours ago)
Author:
231108
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • DatabaseProgramming

    v1 v1  
     1= Фаза 4 - Функции, процедури, тригери =
     2
     3== Функции ==
     4
     5=== get_avg_gpa_by_university ===
     6Пресметува просечна GPA на сите студенти запишани на факултети кои припаѓаат на даден универзитет. Се користи при генерирање на академски извештаи и споредба на успешноста на студентите по универзитет.
     7{{{
     8CREATE OR REPLACE FUNCTION get_avg_gpa_by_university(p_university_id INT)
     9RETURNS FLOAT AS $func$
     10DECLARE
     11    avg_gpa FLOAT;
     12BEGIN
     13    SELECT AVG(u.gpa) INTO avg_gpa
     14    FROM "User" u
     15    JOIN Studies s ON s.user_id = u.user_id
     16    JOIN StudyProgram sp ON sp.study_program_id = s.study_program_id
     17    JOIN Faculty f ON f.faculty_id = sp.faculty_id
     18    WHERE f.university_id = p_university_id;
     19    RETURN avg_gpa;
     20END;
     21$func$ LANGUAGE plpgsql;
     22}}}
     23
     24=== get_employment_count_by_company ===
     25Враќа вкупниот број на вработувања (минати и тековни) за одредена компанија. Се користи за аналитика на компании и рангирање според бројот на вработени alumni.
     26{{{
     27CREATE OR REPLACE FUNCTION get_employment_count_by_company(p_company_id INT)
     28RETURNS INT AS $func$
     29DECLARE
     30    emp_count INT;
     31BEGIN
     32    SELECT COUNT(*) INTO emp_count FROM Employment WHERE company_id = p_company_id;
     33    RETURN emp_count;
     34END;
     35$func$ LANGUAGE plpgsql;
     36}}}
     37
     38=== get_user_skills ===
     39Враќа табела со сите вештини (ime и тип) кои ги поседува одреден корисник. Се користи при приказ на профилот на корисникот и при пребарување на кандидати по вештини.
     40{{{
     41CREATE OR REPLACE FUNCTION get_user_skills(p_user_id INT)
     42RETURNS TABLE(skill_name VARCHAR, skill_type VARCHAR) AS $func$
     43BEGIN
     44    RETURN QUERY
     45    SELECT s.skill_name, s.type
     46    FROM Skill s
     47    JOIN User_Skill us ON us.skill_id = s.skill_id
     48    WHERE us.user_id = p_user_id;
     49END;
     50$func$ LANGUAGE plpgsql;
     51}}}
     52
     53----
     54
     55== Процедури ==
     56
     57=== add_user_with_studies ===
     58Додава нов корисник во системот и автоматски го запишува на студиска програма со статус 'active', сè во рамки на една трансакција. Ја имплементира бизнис логиката за регистрација на нов student во системот — корисникот не може да постои без студиска програма.
     59{{{
     60CREATE OR REPLACE PROCEDURE add_user_with_studies(
     61    p_first_name VARCHAR, p_last_name VARCHAR, p_email VARCHAR,
     62    p_study_program_id INT, p_start_date DATE
     63)
     64LANGUAGE plpgsql AS $func$
     65DECLARE v_user_id INT;
     66BEGIN
     67    IF EXISTS (SELECT 1 FROM "User" WHERE email = p_email) THEN
     68        RAISE EXCEPTION 'Корисник со е-пошта % веќе постои!', p_email;
     69    END IF;
     70    INSERT INTO "User"(first_name, last_name, email)
     71    VALUES(p_first_name, p_last_name, p_email) RETURNING user_id INTO v_user_id;
     72    INSERT INTO Studies(study_program_id, user_id, start_date, status)
     73    VALUES(p_study_program_id, v_user_id, p_start_date, 'active');
     74    RAISE NOTICE 'Корисник % % успешно додаден! ID: %', p_first_name, p_last_name, v_user_id;
     75END;
     76$func$;
     77}}}
     78
     79=== add_employment_with_salary ===
     80Креира ново вработување и почетна плата во историјата. Ако корисникот веќе има тековно вработување (is_current = 1), само додава нов запис за плата без да креира дупликат вработување. Ја имплементира логиката за управување со кариерата на alumni.
     81{{{
     82CREATE OR REPLACE PROCEDURE add_employment_with_salary(
     83    p_user_id INT, p_company_id INT, p_start_date DATE,
     84    p_employment_type VARCHAR, p_salary INT, p_currency VARCHAR
     85)
     86LANGUAGE plpgsql AS $func$
     87DECLARE v_employment_id INT; v_existing_emp_id INT;
     88BEGIN
     89    SELECT employment_id INTO v_existing_emp_id
     90    FROM Employment WHERE user_id = p_user_id AND is_current = 1 LIMIT 1;
     91    IF v_existing_emp_id IS NOT NULL THEN
     92        INSERT INTO SalaryHistory(effective_from, currency, salary_amount, employment_id)
     93        VALUES(p_start_date, p_currency, p_salary, v_existing_emp_id);
     94        RAISE NOTICE 'Платата ажурирана на % %!', p_salary, p_currency;
     95    ELSE
     96        INSERT INTO Employment(start_date, employment_type, currency,
     97            is_current, promotion_flag, user_id, company_id)
     98        VALUES(p_start_date, p_employment_type, p_currency, 1, 0, p_user_id, p_company_id)
     99        RETURNING employment_id INTO v_employment_id;
     100        INSERT INTO SalaryHistory(effective_from, currency, salary_amount, employment_id)
     101        VALUES(p_start_date, p_currency, p_salary, v_employment_id);
     102        RAISE NOTICE 'Ново вработување додадено со плата % %!', p_salary, p_currency;
     103    END IF;
     104END;
     105$func$;
     106}}}
     107
     108=== add_startup_with_founder ===
     109Регистрира нов стартап и автоматски го поврзува со основачот. Проверува дали стартапот веќе постои и дали статусот е валиден. Ја имплементира логиката за евидентирање на претприемачката активност на alumni.
     110{{{
     111CREATE OR REPLACE PROCEDURE add_startup_with_founder(
     112    p_name VARCHAR, p_status VARCHAR, p_description VARCHAR,
     113    p_company_id INT, p_user_id INT
     114)
     115LANGUAGE plpgsql AS $func$
     116DECLARE v_startup_id INT;
     117BEGIN
     118    IF EXISTS (SELECT 1 FROM Startup WHERE name = p_name) THEN
     119        RAISE EXCEPTION 'Стартап со именото % веќе постои!', p_name;
     120    END IF;
     121    IF p_status NOT IN ('active', 'closed', 'acquired') THEN
     122        RAISE EXCEPTION 'Невалиден статус! Дозволени: active, closed, acquired.';
     123    END IF;
     124    INSERT INTO Startup(name, status, description, company_id)
     125    VALUES(p_name, p_status, p_description, p_company_id) RETURNING startup_id INTO v_startup_id;
     126    INSERT INTO Founder(user_id, startup_id) VALUES(p_user_id, v_startup_id);
     127    RAISE NOTICE 'Стартапот % успешно креиран! ID: %', p_name, v_startup_id;
     128END;
     129$func$;
     130}}}
     131
     132=== add_promotion ===
     133Додава унапредување за постоечко вработување со валидација дека нивоата се различни и зголемувањето е позитивно. Ја имплементира бизнис логиката за следење на кариерниот напредок на alumni.
     134{{{
     135CREATE OR REPLACE PROCEDURE add_promotion(
     136    p_employment_id INT, p_promotion_date DATE,
     137    p_old_job_level VARCHAR, p_new_job_level VARCHAR, p_salary_increase INT
     138)
     139LANGUAGE plpgsql AS $func$
     140BEGIN
     141    IF p_old_job_level = p_new_job_level THEN
     142        RAISE EXCEPTION 'Старото и новото ниво не смеат да бидат исти!';
     143    END IF;
     144    INSERT INTO Promotion(promotion_date, old_job_level, new_job_level,
     145        salary_increase, employment_id)
     146    VALUES(p_promotion_date, p_old_job_level, p_new_job_level,
     147        p_salary_increase, p_employment_id);
     148    RAISE NOTICE 'Унапредување: % -> % за вработување ID %',
     149        p_old_job_level, p_new_job_level, p_employment_id;
     150END;
     151$func$;
     152}}}
     153
     154=== add_event_with_participants ===
     155Додава нов настан и автоматски запишува листа на учесници. Ако некој корисник не постои, се прескокнува со предупредување. Ја имплементира логиката за управување со настани и мрежата на alumni.
     156{{{
     157CREATE OR REPLACE PROCEDURE add_event_with_participants(
     158    p_name VARCHAR, p_date DATE, p_location VARCHAR,
     159    p_company_id INT, p_user_ids INT[]
     160)
     161LANGUAGE plpgsql AS $func$
     162DECLARE v_event_id INT; v_user_id INT;
     163BEGIN
     164    INSERT INTO Event(name, "date", location, company_id)
     165    VALUES(p_name, p_date, p_location, p_company_id) RETURNING event_id INTO v_event_id;
     166    FOREACH v_user_id IN ARRAY p_user_ids LOOP
     167        IF NOT EXISTS (SELECT 1 FROM "User" WHERE user_id = v_user_id) THEN
     168            RAISE WARNING 'Корисник % не постои, прескокнат.', v_user_id;
     169            CONTINUE;
     170        END IF;
     171        INSERT INTO Event_User(event_id, user_id, role)
     172        VALUES(v_event_id, v_user_id, 'attendee') ON CONFLICT DO NOTHING;
     173    END LOOP;
     174    RAISE NOTICE 'Настанот % успешно креиран! ID: %', p_name, v_event_id;
     175END;
     176$func$;
     177}}}
     178
     179=== add_position ===
     180Додава нова позиција за постоечко вработување и автоматски го превзема company_id. Ја имплементира логиката за следење на работните позиции на alumni низ нивната кариера.
     181{{{
     182CREATE OR REPLACE PROCEDURE add_position(
     183    p_employment_id INT, p_title VARCHAR,
     184    p_job_level VARCHAR, p_job_category VARCHAR
     185)
     186LANGUAGE plpgsql AS $func$
     187DECLARE v_company_id INT;
     188BEGIN
     189    SELECT company_id INTO v_company_id FROM Employment WHERE employment_id = p_employment_id;
     190    IF NOT FOUND THEN
     191        RAISE EXCEPTION 'Вработување со ID % не постои!', p_employment_id;
     192    END IF;
     193    INSERT INTO position(title, job_level, job_category, employment_id, company_id)
     194    VALUES(p_title, p_job_level, p_job_category, p_employment_id, v_company_id);
     195    RAISE NOTICE 'Позиција % успешно додадена за вработување ID %', p_title, p_employment_id;
     196END;
     197$func$;
     198}}}
     199
     200=== add_internship ===
     201Додава пракса за корисник во компанија со валидација на датуми. Ја имплементира бизнис логиката за евидентирање на студентски пракси пред или за време на студирањето.
     202{{{
     203CREATE OR REPLACE PROCEDURE add_internship(
     204    p_user_id INT, p_company_id INT, p_start_date DATE, p_end_date DATE
     205)
     206LANGUAGE plpgsql AS $func$
     207BEGIN
     208    IF p_end_date IS NOT NULL AND p_end_date <= p_start_date THEN
     209        RAISE EXCEPTION 'Датумот на завршување мора да биде после почетокот!';
     210    END IF;
     211    INSERT INTO Internship(start_date, end_date, user_id, company_id)
     212    VALUES(p_start_date, p_end_date, p_user_id, p_company_id);
     213    RAISE NOTICE 'Пракса успешно додадена за корисник % во компанија %', p_user_id, p_company_id;
     214END;
     215$func$;
     216}}}
     217
     218=== add_award ===
     219Додава награда за корисник поврзана со компанија. Ја имплементира логиката за следење на достигнувањата и признанијата на alumni во нивната кариера.
     220{{{
     221CREATE OR REPLACE PROCEDURE add_award(
     222    p_user_id INT, p_company_id INT,
     223    p_title VARCHAR, p_organization VARCHAR, p_date DATE
     224)
     225LANGUAGE plpgsql AS $func$
     226BEGIN
     227    INSERT INTO Award(title, organization, "date", company_id, user_id)
     228    VALUES(p_title, p_organization, p_date, p_company_id, p_user_id);
     229    RAISE NOTICE 'Награда "%" успешно додадена за корисник %', p_title, p_user_id;
     230END;
     231$func$;
     232}}}
     233
     234=== add_course_completion ===
     235Запишува корисник на дополнителен курс или ажурира статус ако веќе е запишан. Ја имплементира логиката за следење на континуираното образование и професионален развој на alumni.
     236{{{
     237CREATE OR REPLACE PROCEDURE add_course_completion(
     238    p_user_id INT, p_course_id INT,
     239    p_status VARCHAR, p_completion_date DATE DEFAULT NULL
     240)
     241LANGUAGE plpgsql AS $func$
     242BEGIN
     243    IF p_status NOT IN ('in-progress', 'completed', 'failed') THEN
     244        RAISE EXCEPTION 'Невалиден статус!';
     245    END IF;
     246    IF EXISTS (SELECT 1 FROM AdditionalCourse_User
     247               WHERE user_id = p_user_id AND additional_course_id = p_course_id) THEN
     248        UPDATE AdditionalCourse_User SET status = p_status, completion_date = p_completion_date
     249        WHERE user_id = p_user_id AND additional_course_id = p_course_id;
     250    ELSE
     251        INSERT INTO AdditionalCourse_User(user_id, additional_course_id, completion_date, status)
     252        VALUES(p_user_id, p_course_id, p_completion_date, p_status);
     253    END IF;
     254    RAISE NOTICE 'Статус % за курс % на корисник % ажуриран.', p_status, p_course_id, p_user_id;
     255END;
     256$func$;
     257}}}
     258
     259=== update_study_status ===
     260Ажурира статусот на студии (active → completed/dropped). Ако статусот е 'completed', автоматски генерира диплома. Ја имплементира бизнис логиката за завршување на студиите.
     261{{{
     262CREATE OR REPLACE PROCEDURE update_study_status(
     263    p_studies_id INT, p_new_status VARCHAR, p_end_date DATE DEFAULT NULL
     264)
     265LANGUAGE plpgsql AS $func$
     266DECLARE v_start_date DATE;
     267BEGIN
     268    SELECT start_date INTO v_start_date FROM Studies WHERE studies_id = p_studies_id;
     269    IF NOT FOUND THEN RAISE EXCEPTION 'Студии со ID % не постојат!', p_studies_id; END IF;
     270    UPDATE Studies SET status = p_new_status, end_date = p_end_date WHERE studies_id = p_studies_id;
     271    IF p_new_status = 'completed' AND p_end_date IS NOT NULL THEN
     272        INSERT INTO Diploma(graduation_date, thesis_title, studies_id)
     273        VALUES(p_end_date, 'Дипломски труд ' || p_studies_id, p_studies_id)
     274        ON CONFLICT DO NOTHING;
     275        RAISE NOTICE 'Студиите завршени и диплома додадена!';
     276    END IF;
     277END;
     278$func$;
     279}}}
     280
     281=== add_skill_to_user ===
     282Додава вештина на корисник со проверка за дупликат. Ја имплементира логиката за управување со портфолиото на вештини на alumni.
     283{{{
     284CREATE OR REPLACE PROCEDURE add_skill_to_user(p_user_id INT, p_skill_id INT)
     285LANGUAGE plpgsql AS $func$
     286BEGIN
     287    IF EXISTS (SELECT 1 FROM User_Skill WHERE user_id = p_user_id AND skill_id = p_skill_id) THEN
     288        RAISE EXCEPTION 'Корисникот веќе ја поседува оваа вештина!';
     289    END IF;
     290    INSERT INTO User_Skill(user_id, skill_id) VALUES(p_user_id, p_skill_id);
     291    RAISE NOTICE 'Вештина % успешно додадена на корисник %', p_skill_id, p_user_id;
     292END;
     293$func$;
     294}}}
     295
     296=== remove_skill_from_user ===
     297Брише вештина од корисник. Се користи кога корисникот сака да го ажурира своето профилно портфолио на вештини.
     298{{{
     299CREATE OR REPLACE PROCEDURE remove_skill_from_user(p_user_id INT, p_skill_id INT)
     300LANGUAGE plpgsql AS $func$
     301BEGIN
     302    IF NOT EXISTS (SELECT 1 FROM User_Skill WHERE user_id = p_user_id AND skill_id = p_skill_id) THEN
     303        RAISE EXCEPTION 'Корисник % нема вештина со ID %!', p_user_id, p_skill_id;
     304    END IF;
     305    DELETE FROM User_Skill WHERE user_id = p_user_id AND skill_id = p_skill_id;
     306    RAISE NOTICE 'Вештина % отстранета од корисник %', p_skill_id, p_user_id;
     307END;
     308$func$;
     309}}}
     310
     311=== end_employment ===
     312Завршува тековно вработување — поставува is_current = 0 и end_date, и го затвора последниот запис во SalaryHistory. Ја имплементира логиката за евидентирање на напуштање на работното место.
     313{{{
     314CREATE OR REPLACE PROCEDURE end_employment(p_employment_id INT, p_end_date DATE)
     315LANGUAGE plpgsql AS $func$
     316BEGIN
     317    IF NOT EXISTS (SELECT 1 FROM Employment WHERE employment_id = p_employment_id AND is_current = 1) THEN
     318        RAISE EXCEPTION 'Вработувањето не постои или веќе е завршено!';
     319    END IF;
     320    UPDATE Employment SET is_current = 0, end_date = p_end_date WHERE employment_id = p_employment_id;
     321    UPDATE SalaryHistory SET effective_to = p_end_date
     322    WHERE employment_id = p_employment_id AND effective_to IS NULL;
     323    RAISE NOTICE 'Вработување % завршено на %', p_employment_id, p_end_date;
     324END;
     325$func$;
     326}}}
     327
     328=== update_salary ===
     329Додава нов запис во SalaryHistory и го затвора претходниот. Ја имплементира бизнис логиката за следење на историјата на плати на alumni низ времето.
     330{{{
     331CREATE OR REPLACE PROCEDURE update_salary(
     332    p_employment_id INT, p_new_salary INT,
     333    p_currency VARCHAR, p_effective_from DATE
     334)
     335LANGUAGE plpgsql AS $func$
     336BEGIN
     337    IF p_new_salary <= 0 THEN RAISE EXCEPTION 'Платата мора да биде позитивна!'; END IF;
     338    UPDATE SalaryHistory SET effective_to = p_effective_from - 1
     339    WHERE employment_id = p_employment_id AND effective_to IS NULL;
     340    INSERT INTO SalaryHistory(effective_from, currency, salary_amount, employment_id)
     341    VALUES(p_effective_from, p_currency, p_new_salary, p_employment_id);
     342    RAISE NOTICE 'Платата ажурирана на % % за вработување %', p_new_salary, p_currency, p_employment_id;
     343END;
     344$func$;
     345}}}
     346
     347=== enroll_existing_user ===
     348Запишува постоечки корисник на нова студиска програма. Проверува дали веќе е активно запишан. Се користи кога alumni продолжува со образование (втор циклус, PhD).
     349{{{
     350CREATE OR REPLACE PROCEDURE enroll_existing_user(
     351    p_user_id INT, p_study_program_id INT, p_start_date DATE
     352)
     353LANGUAGE plpgsql AS $func$
     354BEGIN
     355    IF EXISTS (SELECT 1 FROM Studies WHERE user_id = p_user_id
     356               AND study_program_id = p_study_program_id AND status = 'active') THEN
     357        RAISE EXCEPTION 'Корисникот веќе е активно запишан на оваа програма!';
     358    END IF;
     359    INSERT INTO Studies(study_program_id, user_id, start_date, status)
     360    VALUES(p_study_program_id, p_user_id, p_start_date, 'active');
     361    RAISE NOTICE 'Корисник % успешно запишан на програма %', p_user_id, p_study_program_id;
     362END;
     363$func$;
     364}}}
     365
     366=== add_diploma ===
     367Директно додава диплома за завршени студии. Дозволено само ако студиите имаат статус 'completed'. Ја имплементира логиката за потврда на академската квалификација на alumni.
     368{{{
     369CREATE OR REPLACE PROCEDURE add_diploma(
     370    p_studies_id INT, p_graduation_date DATE, p_thesis_title VARCHAR
     371)
     372LANGUAGE plpgsql AS $func$
     373DECLARE v_status VARCHAR;
     374BEGIN
     375    SELECT status INTO v_status FROM Studies WHERE studies_id = p_studies_id;
     376    IF v_status != 'completed' THEN
     377        RAISE EXCEPTION 'Не може диплома за студии со статус "%"!', v_status;
     378    END IF;
     379    IF EXISTS (SELECT 1 FROM Diploma WHERE studies_id = p_studies_id) THEN
     380        RAISE EXCEPTION 'Диплома за овие студии веќе постои!';
     381    END IF;
     382    INSERT INTO Diploma(graduation_date, thesis_title, studies_id)
     383    VALUES(p_graduation_date, p_thesis_title, p_studies_id);
     384    RAISE NOTICE 'Диплома успешно додадена за studies_id %', p_studies_id;
     385END;
     386$func$;
     387}}}
     388
     389=== add_company ===
     390Додава нова компанија со валидација на индустрија и број на вработени. Се користи при проширување на базата на компании со кои alumni се поврзани.
     391{{{
     392CREATE OR REPLACE PROCEDURE add_company(
     393    p_name VARCHAR, p_number_employees INT, p_industry_id INT
     394)
     395LANGUAGE plpgsql AS $func$
     396BEGIN
     397    IF EXISTS (SELECT 1 FROM Company WHERE name = p_name) THEN
     398        RAISE EXCEPTION 'Компанија со именото % веќе постои!', p_name;
     399    END IF;
     400    IF p_number_employees < 0 THEN
     401        RAISE EXCEPTION 'Бројот на вработени не може да биде негативен!';
     402    END IF;
     403    INSERT INTO Company(name, number_employees, industry_id)
     404    VALUES(p_name, p_number_employees, p_industry_id);
     405    RAISE NOTICE 'Компанија % успешно додадена!', p_name;
     406END;
     407$func$;
     408}}}
     409
     410=== add_industry ===
     411Додава нова индустрија со проверка за уникатност. Се користи при проширување на категоризацијата на компании во системот.
     412{{{
     413CREATE OR REPLACE PROCEDURE add_industry(p_industry_name VARCHAR, p_sector_category VARCHAR)
     414LANGUAGE plpgsql AS $func$
     415BEGIN
     416    IF EXISTS (SELECT 1 FROM Industry WHERE industry_name = p_industry_name) THEN
     417        RAISE EXCEPTION 'Индустрија со именото % веќе постои!', p_industry_name;
     418    END IF;
     419    INSERT INTO Industry(industry_name, sector_category) VALUES(p_industry_name, p_sector_category);
     420    RAISE NOTICE 'Индустрија % успешно додадена!', p_industry_name;
     421END;
     422$func$;
     423}}}
     424
     425=== update_startup_status ===
     426Менува статусот на стартап (active → closed/acquired). Ја имплементира логиката за следење на животниот циклус на стартапите основани од alumni.
     427{{{
     428CREATE OR REPLACE PROCEDURE update_startup_status(p_startup_id INT, p_new_status VARCHAR)
     429LANGUAGE plpgsql AS $func$
     430DECLARE v_current_status VARCHAR;
     431BEGIN
     432    SELECT status INTO v_current_status FROM Startup WHERE startup_id = p_startup_id;
     433    IF NOT FOUND THEN RAISE EXCEPTION 'Стартап со ID % не постои!', p_startup_id; END IF;
     434    IF v_current_status = p_new_status THEN
     435        RAISE EXCEPTION 'Стартапот веќе има статус "%"!', p_new_status;
     436    END IF;
     437    UPDATE Startup SET status = p_new_status WHERE startup_id = p_startup_id;
     438    RAISE NOTICE 'Стартап %: статус сменет од % на %', p_startup_id, v_current_status, p_new_status;
     439END;
     440$func$;
     441}}}
     442
     443=== add_founder_to_startup ===
     444Додава уште еден основач на постоечки стартап со проверка за дупликат. Се користи кога стартап има повеќе соосновачи меѓу alumni.
     445{{{
     446CREATE OR REPLACE PROCEDURE add_founder_to_startup(p_user_id INT, p_startup_id INT)
     447LANGUAGE plpgsql AS $func$
     448BEGIN
     449    IF EXISTS (SELECT 1 FROM Founder WHERE user_id = p_user_id AND startup_id = p_startup_id) THEN
     450        RAISE EXCEPTION 'Корисникот веќе е основач на овој стартап!';
     451    END IF;
     452    INSERT INTO Founder(user_id, startup_id) VALUES(p_user_id, p_startup_id);
     453    RAISE NOTICE 'Корисник % додаден како основач на стартап %', p_user_id, p_startup_id;
     454END;
     455$func$;
     456}}}
     457
     458=== remove_user ===
     459Безбедно брише корисник од системот. Тригерот trg_prevent_delete_active_student автоматски го спречува бришењето ако корисникот има активни студии, обезбедувајќи интегритет на податоците.
     460{{{
     461CREATE OR REPLACE PROCEDURE remove_user(p_user_id INT)
     462LANGUAGE plpgsql AS $func$
     463BEGIN
     464    IF NOT EXISTS (SELECT 1 FROM "User" WHERE user_id = p_user_id) THEN
     465        RAISE EXCEPTION 'Корисник со ID % не постои!', p_user_id;
     466    END IF;
     467    DELETE FROM "User" WHERE user_id = p_user_id;
     468    RAISE NOTICE 'Корисник % успешно избришан!', p_user_id;
     469END;
     470$func$;
     471}}}
     472
     473=== cancel_internship ===
     474Предвремено завршува пракса со поставување на end_date. Се користи при предвремено прекинување на пракса поради различни околности.
     475{{{
     476CREATE OR REPLACE PROCEDURE cancel_internship(p_internship_id INT, p_end_date DATE)
     477LANGUAGE plpgsql AS $func$
     478DECLARE v_start_date DATE;
     479BEGIN
     480    SELECT start_date INTO v_start_date FROM Internship WHERE internship_id = p_internship_id;
     481    IF NOT FOUND THEN RAISE EXCEPTION 'Пракса со ID % не постои!', p_internship_id; END IF;
     482    IF p_end_date <= v_start_date THEN
     483        RAISE EXCEPTION 'Датумот на завршување мора да биде после почетокот!';
     484    END IF;
     485    UPDATE Internship SET end_date = p_end_date WHERE internship_id = p_internship_id;
     486    RAISE NOTICE 'Пракса % завршена на датум %', p_internship_id, p_end_date;
     487END;
     488$func$;
     489}}}
     490
     491----
     492
     493== Тригери ==
     494
     495=== trg_prevent_delete_active_student ===
     496Спречува бришење на корисник кој моментално има активни студии, фрлајќи исклучок пред да се изврши DELETE операцијата. Ја имплементира бизнис логиката за заштита на академските записи — активен студент не може да биде избришан од системот.
     497{{{
     498CREATE OR REPLACE FUNCTION prevent_delete_active_student()
     499RETURNS TRIGGER AS $func$
     500BEGIN
     501    IF EXISTS (SELECT 1 FROM Studies WHERE user_id = OLD.user_id AND status = 'active') THEN
     502        RAISE EXCEPTION 'Не може да се избрише корисник со активни студии!';
     503    END IF;
     504    RETURN OLD;
     505END;
     506$func$ LANGUAGE plpgsql;
     507
     508CREATE OR REPLACE TRIGGER trg_prevent_delete_active_student
     509BEFORE DELETE ON "User"
     510FOR EACH ROW EXECUTE FUNCTION prevent_delete_active_student();
     511}}}
     512
     513=== trg_update_current_employment ===
     514При додавање на ново тековно вработување (is_current = 1), автоматски ги поставува сите претходни вработувања на истиот корисник на is_current = 0. Ја имплементира бизнис логиката дека корисникот може да има само едно тековно вработување истовремено.
     515{{{
     516CREATE OR REPLACE FUNCTION update_current_employment()
     517RETURNS TRIGGER AS $func$
     518BEGIN
     519    IF NEW.is_current = 1 THEN
     520        UPDATE Employment SET is_current = 0
     521        WHERE user_id = NEW.user_id
     522          AND employment_id != NEW.employment_id
     523          AND is_current = 1;
     524    END IF;
     525    RETURN NEW;
     526END;
     527$func$ LANGUAGE plpgsql;
     528
     529CREATE OR REPLACE TRIGGER trg_update_current_employment
     530AFTER INSERT ON Employment
     531FOR EACH ROW EXECUTE FUNCTION update_current_employment();
     532}}}
     533
     534=== trg_check_promotion_flag ===
     535При додавање на нова промоција, автоматски го поставува promotion_flag на 1 во табелата Employment доколку не е веќе поставен. Ја имплементира логиката за автоматско означување на вработувања со историја на унапредување, без потреба од рачно ажурирање.
     536{{{
     537CREATE OR REPLACE FUNCTION check_promotion_flag()
     538RETURNS TRIGGER AS $func$
     539DECLARE v_flag INT;
     540BEGIN
     541    SELECT promotion_flag INTO v_flag FROM Employment WHERE employment_id = NEW.employment_id;
     542    IF v_flag = 0 THEN
     543        UPDATE Employment SET promotion_flag = 1 WHERE employment_id = NEW.employment_id;
     544        RAISE NOTICE 'promotion_flag автоматски поставен на 1!';
     545    END IF;
     546    RETURN NEW;
     547END;
     548$func$ LANGUAGE plpgsql;
     549
     550CREATE OR REPLACE TRIGGER trg_check_promotion_flag
     551AFTER INSERT ON Promotion
     552FOR EACH ROW EXECUTE FUNCTION check_promotion_flag();
     553}}}