| 1 | -- 1
|
|---|
| 2 | create or replace function fn_insert_premium_worker()
|
|---|
| 3 | returns trigger
|
|---|
| 4 | language plpgsql
|
|---|
| 5 | as $$
|
|---|
| 6 | declare completed_status_id int;
|
|---|
| 7 | begin
|
|---|
| 8 | select status_id
|
|---|
| 9 | into completed_status_id
|
|---|
| 10 | from Payment_Status
|
|---|
| 11 | where status_name = 'Completed';
|
|---|
| 12 |
|
|---|
| 13 | if new.payment_status = completed_status_id then
|
|---|
| 14 | insert into Premium_Worker (start_date,end_date,plan_id,worker_id,payment_id)
|
|---|
| 15 | select
|
|---|
| 16 | new.payment_date,
|
|---|
| 17 | new.payment_date + interval '1 day' * pp.duration_days,
|
|---|
| 18 | new.plan_id,
|
|---|
| 19 | wm.worker_id,
|
|---|
| 20 | new.payment_id
|
|---|
| 21 | from Worker_Method wm
|
|---|
| 22 | join Premium_Plan pp
|
|---|
| 23 | on pp.plan_id = new.plan_id
|
|---|
| 24 | where wm.worker_method_id = new.worker_method_id;
|
|---|
| 25 | end if;
|
|---|
| 26 | return new;
|
|---|
| 27 | end;
|
|---|
| 28 | $$;
|
|---|
| 29 |
|
|---|
| 30 | create trigger trg_insert_premium_worker
|
|---|
| 31 | after insert or update on Payment
|
|---|
| 32 | for each row
|
|---|
| 33 | execute function fn_insert_premium_worker();
|
|---|
| 34 |
|
|---|
| 35 | --test
|
|---|
| 36 | insert into payment(amount, payment_date, worker_method_id, payment_status, plan_id)
|
|---|
| 37 | values (100, now(), 1, 2, 1);
|
|---|
| 38 |
|
|---|
| 39 | select * from premium_worker order by premium_id desc limit 3;
|
|---|
| 40 |
|
|---|
| 41 |
|
|---|
| 42 |
|
|---|
| 43 |
|
|---|
| 44 |
|
|---|
| 45 |
|
|---|
| 46 | -- 2
|
|---|
| 47 | create or replace function fn_is_worker_premium(p_worker_id int)
|
|---|
| 48 | returns boolean
|
|---|
| 49 | language plpgsql
|
|---|
| 50 | as $$
|
|---|
| 51 | declare v_exists boolean;
|
|---|
| 52 | begin
|
|---|
| 53 | select exists (
|
|---|
| 54 | select 1
|
|---|
| 55 | from premium_worker pw
|
|---|
| 56 | where pw.worker_id = p_worker_id
|
|---|
| 57 | and now() between pw.start_date and pw.end_date
|
|---|
| 58 | )
|
|---|
| 59 | into v_exists;
|
|---|
| 60 |
|
|---|
| 61 | return v_exists;
|
|---|
| 62 | end;
|
|---|
| 63 | $$;
|
|---|
| 64 |
|
|---|
| 65 | -- test: true
|
|---|
| 66 | insert into premium_worker(start_date,end_date,plan_id,worker_id,payment_id)
|
|---|
| 67 | values (now() - interval '1 day',now() + interval '30 days',1,6630361,1);
|
|---|
| 68 |
|
|---|
| 69 | select fn_is_worker_premium(6630361);
|
|---|
| 70 |
|
|---|
| 71 | -- test: false
|
|---|
| 72 | insert into premium_worker(start_date,end_date,plan_id,worker_id,payment_id)
|
|---|
| 73 | values (now() - interval '30 days',now() - interval '1 day',1,6630362,1);
|
|---|
| 74 |
|
|---|
| 75 | select fn_is_worker_premium(6630362);
|
|---|
| 76 |
|
|---|
| 77 |
|
|---|
| 78 |
|
|---|
| 79 |
|
|---|
| 80 |
|
|---|
| 81 |
|
|---|
| 82 | -- 3
|
|---|
| 83 | create or replace function fn_notify_worker_premium_start()
|
|---|
| 84 | returns trigger
|
|---|
| 85 | language plpgsql
|
|---|
| 86 | as $$
|
|---|
| 87 | declare v_user_id int; v_plan_name text;
|
|---|
| 88 | begin
|
|---|
| 89 | select w.user_id
|
|---|
| 90 | into v_user_id
|
|---|
| 91 | from worker w
|
|---|
| 92 | where w.worker_id = new.worker_id;
|
|---|
| 93 |
|
|---|
| 94 | select pp.name
|
|---|
| 95 | into v_plan_name
|
|---|
| 96 | from premium_plan pp
|
|---|
| 97 | where pp.plan_id = new.plan_id;
|
|---|
| 98 |
|
|---|
| 99 | insert into notification(message, is_read, created_at, user_id)
|
|---|
| 100 | values ('Your Premium subscription (' || v_plan_name || ') is now active until ' || to_char(new.end_date, 'DD Mon YYYY'),false,now(),v_user_id);
|
|---|
| 101 |
|
|---|
| 102 | return new;
|
|---|
| 103 | end;
|
|---|
| 104 | $$;
|
|---|
| 105 |
|
|---|
| 106 | create trigger trg_notify_worker_premium_start
|
|---|
| 107 | after insert on premium_worker
|
|---|
| 108 | for each row
|
|---|
| 109 | execute function fn_notify_worker_premium_start();
|
|---|
| 110 |
|
|---|
| 111 |
|
|---|
| 112 | --test
|
|---|
| 113 | insert into premium_worker(start_date, end_date, plan_id, worker_id, payment_id)
|
|---|
| 114 | values (now(), now() + interval '30 days', 1, 6630361, 1);
|
|---|
| 115 |
|
|---|
| 116 | select * from notification
|
|---|
| 117 | where user_id = (select user_id from worker where worker_id = 6630361)
|
|---|
| 118 | order by created_at desc
|
|---|
| 119 | limit 5;
|
|---|
| 120 |
|
|---|
| 121 |
|
|---|
| 122 |
|
|---|
| 123 |
|
|---|
| 124 |
|
|---|
| 125 |
|
|---|
| 126 | -- 4
|
|---|
| 127 | create or replace function fn_prevent_review_without_hire()
|
|---|
| 128 | returns trigger
|
|---|
| 129 | language plpgsql
|
|---|
| 130 | as $$
|
|---|
| 131 | declare v_worker_id int;
|
|---|
| 132 | begin
|
|---|
| 133 | select ws.worker_id
|
|---|
| 134 | into v_worker_id
|
|---|
| 135 | from worker_specialty ws
|
|---|
| 136 | where ws.worker_specialty_id = new.worker_specialty_id;
|
|---|
| 137 |
|
|---|
| 138 | if v_worker_id is null then
|
|---|
| 139 | raise exception 'Worker_Specialty % not found.', new.worker_specialty_id;
|
|---|
| 140 | end if;
|
|---|
| 141 |
|
|---|
| 142 | if not exists (
|
|---|
| 143 | select 1
|
|---|
| 144 | from application a
|
|---|
| 145 | join application_status s on a.status_id = s.status_id
|
|---|
| 146 | join post p on a.post_id = p.post_id
|
|---|
| 147 | join post_specialty ps on ps.post_id = p.post_id
|
|---|
| 148 | join worker_specialty ws on ws.worker_specialty_id = new.worker_specialty_id
|
|---|
| 149 | where a.worker_id = v_worker_id
|
|---|
| 150 | and p.user_id = new.reviewer_id
|
|---|
| 151 | and s.status_name = 'Accepted'
|
|---|
| 152 | and ps.specialty_id = ws.specialty_id
|
|---|
| 153 | ) then
|
|---|
| 154 | raise exception 'User % has not hired worker % for this specialty, and cannot review them.', new.reviewer_id, v_worker_id;
|
|---|
| 155 | end if;
|
|---|
| 156 |
|
|---|
| 157 | return new;
|
|---|
| 158 | end;
|
|---|
| 159 | $$;
|
|---|
| 160 |
|
|---|
| 161 | create trigger trg_prevent_review_without_hire
|
|---|
| 162 | before insert on Review
|
|---|
| 163 | for each row
|
|---|
| 164 | execute function fn_prevent_review_without_hire();
|
|---|
| 165 |
|
|---|
| 166 | --test should fail
|
|---|
| 167 | insert into review(grade, created_at, reviewer_id, worker_specialty_id)
|
|---|
| 168 | values (5, '2026-05-14 12:00:00.000', 218486, 1);
|
|---|
| 169 |
|
|---|
| 170 | --test should pass
|
|---|
| 171 | insert into review(grade, created_at, reviewer_id, worker_specialty_id)
|
|---|
| 172 | values (5, '2026-05-14 12:00:00.000', 366799, 2);
|
|---|
| 173 |
|
|---|
| 174 |
|
|---|
| 175 |
|
|---|
| 176 |
|
|---|
| 177 |
|
|---|
| 178 |
|
|---|
| 179 |
|
|---|
| 180 |
|
|---|
| 181 | -- 5
|
|---|
| 182 | create or replace function block_application_to_closed_post()
|
|---|
| 183 | returns trigger
|
|---|
| 184 | language plpgsql
|
|---|
| 185 | as $$
|
|---|
| 186 | declare v_status_name text;
|
|---|
| 187 | begin
|
|---|
| 188 | select ps.status_name
|
|---|
| 189 | into v_status_name
|
|---|
| 190 | from post p
|
|---|
| 191 | join post_status ps on p.status_id = ps.status_id
|
|---|
| 192 | where p.post_id = new.post_id;
|
|---|
| 193 |
|
|---|
| 194 | if v_status_name is null
|
|---|
| 195 | then raise exception 'Post % does not exist.', new.post_id;
|
|---|
| 196 | end if;
|
|---|
| 197 |
|
|---|
| 198 | if v_status_name <> 'Active' then
|
|---|
| 199 | raise exception 'Cannot apply to post %: status is "%", only "Active" posts accept applications.', new.post_id, v_status_name;
|
|---|
| 200 | end if;
|
|---|
| 201 |
|
|---|
| 202 | return new;
|
|---|
| 203 | end;
|
|---|
| 204 | $$;
|
|---|
| 205 |
|
|---|
| 206 | create trigger trg_block_application_to_closed_post
|
|---|
| 207 | before insert on application
|
|---|
| 208 | for each row execute function block_application_to_closed_post();
|
|---|
| 209 |
|
|---|
| 210 | -- test
|
|---|
| 211 | insert into Application(message,needed_time,expected_price,created_at,worker_id,post_id,status_id)
|
|---|
| 212 | values('I need work','09:00:00',200,'2025-05-06 17:36:15.015',6630361,11,1);
|
|---|
| 213 |
|
|---|
| 214 |
|
|---|
| 215 |
|
|---|
| 216 |
|
|---|
| 217 |
|
|---|
| 218 |
|
|---|
| 219 | -- 6
|
|---|
| 220 | create or replace function prevent_duplicate_application()
|
|---|
| 221 | returns trigger
|
|---|
| 222 | language plpgsql
|
|---|
| 223 | as $$
|
|---|
| 224 | begin
|
|---|
| 225 | if exists (
|
|---|
| 226 | select 1
|
|---|
| 227 | from application a
|
|---|
| 228 | join application_status s on a.status_id = s.status_id
|
|---|
| 229 | where a.worker_id = new.worker_id
|
|---|
| 230 | and a.post_id = new.post_id
|
|---|
| 231 | and s.status_name not in ('Cancelled')
|
|---|
| 232 | ) then
|
|---|
| 233 | raise exception 'Worker % already has an application for post %.', new.worker_id, new.post_id;
|
|---|
| 234 | end if;
|
|---|
| 235 |
|
|---|
| 236 | return new;
|
|---|
| 237 | end;
|
|---|
| 238 | $$;
|
|---|
| 239 |
|
|---|
| 240 | create trigger trg_prevent_duplicate_application
|
|---|
| 241 | before insert on application
|
|---|
| 242 | for each row execute function prevent_duplicate_application();
|
|---|
| 243 |
|
|---|
| 244 | -- test
|
|---|
| 245 | insert into Application(message,needed_time,expected_price,created_at,worker_id,post_id,status_id)
|
|---|
| 246 | values('I need work','09:00:00',200,'2025-05-06 17:36:15.015',6630361,9,1);
|
|---|
| 247 |
|
|---|
| 248 |
|
|---|
| 249 |
|
|---|
| 250 |
|
|---|
| 251 |
|
|---|
| 252 |
|
|---|
| 253 | -- 7
|
|---|
| 254 | create or replace function fn_auto_create_calendar()
|
|---|
| 255 | returns trigger
|
|---|
| 256 | language plpgsql
|
|---|
| 257 | as $$
|
|---|
| 258 | declare v_worker_id int;
|
|---|
| 259 | begin
|
|---|
| 260 | v_worker_id := new.worker_id;
|
|---|
| 261 |
|
|---|
| 262 | insert into calendar(worker_id)
|
|---|
| 263 | values (v_worker_id);
|
|---|
| 264 |
|
|---|
| 265 | return new;
|
|---|
| 266 | end;
|
|---|
| 267 | $$;
|
|---|
| 268 |
|
|---|
| 269 | create trigger trg_auto_create_calendar
|
|---|
| 270 | after insert on worker
|
|---|
| 271 | for each row execute function fn_auto_create_calendar();
|
|---|
| 272 |
|
|---|
| 273 | --test
|
|---|
| 274 | insert into worker(bio, works_remote, user_id, location_id)
|
|---|
| 275 | values ('Experienced electrician', true, 70728, 1);
|
|---|
| 276 |
|
|---|
| 277 | select * from calendar where worker_id = (select max(worker_id) from worker);
|
|---|
| 278 | select * from worker where worker_id = 6772445;
|
|---|
| 279 |
|
|---|
| 280 |
|
|---|
| 281 |
|
|---|
| 282 |
|
|---|
| 283 |
|
|---|
| 284 |
|
|---|
| 285 | -- 8
|
|---|
| 286 | create or replace function fn_auto_create_worker_notification()
|
|---|
| 287 | returns trigger
|
|---|
| 288 | language plpgsql
|
|---|
| 289 | as $$
|
|---|
| 290 | declare
|
|---|
| 291 | v_worker_user_id int;
|
|---|
| 292 | v_status_name text;
|
|---|
| 293 | begin
|
|---|
| 294 | select s.status_name
|
|---|
| 295 | into v_status_name
|
|---|
| 296 | from application_status s
|
|---|
| 297 | where s.status_id = new.status_id;
|
|---|
| 298 |
|
|---|
| 299 | if v_status_name in ('Accepted', 'Rejected', 'Deleted') then
|
|---|
| 300 | select w.user_id
|
|---|
| 301 | into v_worker_user_id
|
|---|
| 302 | from worker w
|
|---|
| 303 | where w.worker_id = new.worker_id;
|
|---|
| 304 |
|
|---|
| 305 | insert into notification(message, is_read, created_at, user_id)
|
|---|
| 306 | values (
|
|---|
| 307 | 'Your application has been ' || v_status_name,
|
|---|
| 308 | false,
|
|---|
| 309 | now(),
|
|---|
| 310 | v_worker_user_id
|
|---|
| 311 | );
|
|---|
| 312 | end if;
|
|---|
| 313 |
|
|---|
| 314 | return new;
|
|---|
| 315 | end;
|
|---|
| 316 | $$;
|
|---|
| 317 |
|
|---|
| 318 | create trigger trg_auto_create_worker_notification
|
|---|
| 319 | after update on application
|
|---|
| 320 | for each row execute function fn_auto_create_worker_notification()
|
|---|
| 321 |
|
|---|
| 322 |
|
|---|
| 323 | --test
|
|---|
| 324 | update application
|
|---|
| 325 | set status_id = 2
|
|---|
| 326 | where application_id = 22279;
|
|---|
| 327 |
|
|---|
| 328 | select * from notification
|
|---|
| 329 | where user_id = (select w.user_id from worker w where w.worker_id = 6746074)
|
|---|
| 330 | order by created_at desc
|
|---|
| 331 | limit 5;
|
|---|
| 332 |
|
|---|
| 333 |
|
|---|
| 334 |
|
|---|
| 335 |
|
|---|
| 336 |
|
|---|
| 337 |
|
|---|
| 338 | -- 9
|
|---|
| 339 | create or replace procedure sp_reject_pending_applications(p_post_id int)
|
|---|
| 340 | language plpgsql
|
|---|
| 341 | as $$
|
|---|
| 342 | declare
|
|---|
| 343 | v_rejected_status_id int;
|
|---|
| 344 | v_completed_post_status_id int;
|
|---|
| 345 | v_current_post_status text;
|
|---|
| 346 | begin
|
|---|
| 347 | select ps.status_name
|
|---|
| 348 | into v_current_post_status
|
|---|
| 349 | from post p join post_status ps on ps.status_id = p.status_id
|
|---|
| 350 | where p.post_id = p_post_id;
|
|---|
| 351 |
|
|---|
| 352 | if v_current_post_status is null then
|
|---|
| 353 | raise exception 'Post % does not exist.', p_post_id;
|
|---|
| 354 | end if;
|
|---|
| 355 |
|
|---|
| 356 | if v_current_post_status <> 'Active' then
|
|---|
| 357 | raise exception 'Post % is not active, current status is "%".', p_post_id, v_current_post_status;
|
|---|
| 358 | end if;
|
|---|
| 359 |
|
|---|
| 360 | select status_id
|
|---|
| 361 | into v_completed_post_status_id
|
|---|
| 362 | from post_status
|
|---|
| 363 | where status_name = 'Completed';
|
|---|
| 364 |
|
|---|
| 365 | update post
|
|---|
| 366 | set status_id = v_completed_post_status_id
|
|---|
| 367 | where post_id = p_post_id;
|
|---|
| 368 |
|
|---|
| 369 | select status_id
|
|---|
| 370 | into v_rejected_status_id
|
|---|
| 371 | from application_status
|
|---|
| 372 | where status_name = 'Rejected';
|
|---|
| 373 |
|
|---|
| 374 | update application
|
|---|
| 375 | set status_id = v_rejected_status_id
|
|---|
| 376 | where post_id = p_post_id
|
|---|
| 377 | and status_id not in (
|
|---|
| 378 | select status_id from application_status
|
|---|
| 379 | where status_name in ('Accepted', 'Deleted')
|
|---|
| 380 | );
|
|---|
| 381 | end;
|
|---|
| 382 | $$;
|
|---|
| 383 |
|
|---|
| 384 | -- test
|
|---|
| 385 | call sp_reject_pending_applications(19);
|
|---|
| 386 |
|
|---|
| 387 | select ps.status_name from post p
|
|---|
| 388 | join post_status ps on ps.status_id = p.status_id
|
|---|
| 389 | where p.post_id = 19;
|
|---|
| 390 |
|
|---|
| 391 | select a.application_id, s.status_name
|
|---|
| 392 | from application a
|
|---|
| 393 | join application_status s on s.status_id = a.status_id
|
|---|
| 394 | where a.post_id = 19;
|
|---|
| 395 |
|
|---|
| 396 |
|
|---|
| 397 |
|
|---|
| 398 |
|
|---|
| 399 |
|
|---|
| 400 |
|
|---|
| 401 |
|
|---|
| 402 | -- 10
|
|---|
| 403 | create or replace function fn_notify_client_new_application()
|
|---|
| 404 | returns trigger
|
|---|
| 405 | language plpgsql
|
|---|
| 406 | as $$
|
|---|
| 407 | declare
|
|---|
| 408 | v_client_user_id int;
|
|---|
| 409 | v_post_title text;
|
|---|
| 410 | v_worker_name text;
|
|---|
| 411 | begin
|
|---|
| 412 | select p.user_id, p.title
|
|---|
| 413 | into v_client_user_id, v_post_title
|
|---|
| 414 | from post p
|
|---|
| 415 | where p.post_id = new.post_id;
|
|---|
| 416 |
|
|---|
| 417 | select u.first_name || ' ' || u.last_name
|
|---|
| 418 | into v_worker_name
|
|---|
| 419 | from worker w
|
|---|
| 420 | join "User" u on u.user_id = w.user_id
|
|---|
| 421 | where w.worker_id = new.worker_id;
|
|---|
| 422 |
|
|---|
| 423 | insert into notification(message, is_read, created_at, user_id)
|
|---|
| 424 | values (
|
|---|
| 425 | 'Worker ' || v_worker_name || ' has applied to your post "' || v_post_title || '"',
|
|---|
| 426 | false,
|
|---|
| 427 | now(),
|
|---|
| 428 | v_client_user_id
|
|---|
| 429 | );
|
|---|
| 430 |
|
|---|
| 431 | return new;
|
|---|
| 432 | end;
|
|---|
| 433 | $$;
|
|---|
| 434 |
|
|---|
| 435 | create trigger trg_notify_client_new_application
|
|---|
| 436 | after insert on application
|
|---|
| 437 | for each row execute function fn_notify_client_new_application();
|
|---|
| 438 |
|
|---|
| 439 | -- test
|
|---|
| 440 | insert into application(message, needed_time, expected_price, created_at, worker_id, post_id, status_id)
|
|---|
| 441 | values ('I can do the job', '02:00:00', 150.00, '2026-05-14 12:00:00.000', 6630372, 166254, 1);
|
|---|
| 442 |
|
|---|
| 443 | select * from notification
|
|---|
| 444 | where user_id = (select user_id from post where post_id = 166254)
|
|---|
| 445 | order by created_at desc
|
|---|
| 446 | limit 5;
|
|---|
| 447 |
|
|---|
| 448 |
|
|---|
| 449 |
|
|---|
| 450 |
|
|---|
| 451 |
|
|---|
| 452 |
|
|---|
| 453 |
|
|---|
| 454 |
|
|---|
| 455 |
|
|---|
| 456 | -- 11
|
|---|
| 457 | create or replace function fn_check_worker_specialty_for_post()
|
|---|
| 458 | returns trigger
|
|---|
| 459 | language plpgsql
|
|---|
| 460 | as $$
|
|---|
| 461 | declare v_has_specialty bool;
|
|---|
| 462 | begin
|
|---|
| 463 | select exists (
|
|---|
| 464 | select 1
|
|---|
| 465 | from worker_specialty ws
|
|---|
| 466 | join post_specialty ps on ps.specialty_id = ws.specialty_id
|
|---|
| 467 | where ws.worker_id = new.worker_id
|
|---|
| 468 | and ps.post_id = new.post_id
|
|---|
| 469 | )
|
|---|
| 470 | into v_has_specialty;
|
|---|
| 471 |
|
|---|
| 472 | if not v_has_specialty then
|
|---|
| 473 | raise exception
|
|---|
| 474 | 'Worker % does not have any of the required specialties for post %.', new.worker_id, new.post_id;
|
|---|
| 475 | end if;
|
|---|
| 476 |
|
|---|
| 477 | return new;
|
|---|
| 478 | end;
|
|---|
| 479 | $$;
|
|---|
| 480 |
|
|---|
| 481 | create trigger trg_check_worker_specialty_for_post
|
|---|
| 482 | before insert on application
|
|---|
| 483 | for each row execute function fn_check_worker_specialty_for_post();
|
|---|
| 484 |
|
|---|
| 485 | -- test
|
|---|
| 486 | insert into application(message, needed_time, expected_price, created_at, worker_id, post_id, status_id)
|
|---|
| 487 | values ('I can do the job', '02:00:00', 150.00, '2026-05-14 12:00:00.000', 6630357, 166252, 1); |
|---|