DatabaseProgramming: FAZA4.sql

File FAZA4.sql, 23.6 KB (added by 231219, 8 days ago)
Line 
1
2
3
4
5-- TRIGGER 1 FUNCTION: fn_guard_closed_order
6
7--Prevents adding or modifying OrderItem rows on orders
8--that are already Completed or Cancelled.
9
10CREATE OR REPLACE FUNCTION fn_guard_closed_order()
11RETURNS TRIGGER
12LANGUAGE plpgsql AS
13$$
14DECLARE
15 v_status TEXT;
16BEGIN
17 SELECT os.Status
18 INTO v_status
19 FROM "Order" o
20 JOIN OrderStatus os ON os.Id = o.StatusId
21 WHERE o.Id = NEW.OrderId;
22
23 IF v_status IN ('Completed', 'Cancelled') THEN
24 RAISE EXCEPTION
25 '[OrderItem] Order #% is "%". Cannot be modified.',
26 NEW.OrderId, v_status;
27 END IF;
28
29 RETURN NEW;
30END;
31$$;
32
33-- TRIGGER 1: trg_prevent_closed_order_edit
34
35CREATE OR REPLACE TRIGGER trg_prevent_closed_order_edit
36BEFORE INSERT OR UPDATE ON OrderItem
37FOR EACH ROW
38EXECUTE FUNCTION fn_guard_closed_order();
39
40
41-- TRIGGER 2 FUNCTION: fn_deduct_inventory_on_finish
42--When the kitchen marks an OrderItem as Finished = TRUE,
43--automatically deducts the ingredients (preku consistOF) from StoredProduct inventory.
44
45CREATE OR REPLACE FUNCTION fn_deduct_inventory_on_finish()
46RETURNS TRIGGER
47LANGUAGE plpgsql AS
48$$
49DECLARE
50 v_product_id INT4;
51 v_ingredient RECORD;
52 v_needed INT4;
53 v_available INT4;
54BEGIN
55 IF OLD.Finished IS DISTINCT FROM NEW.Finished
56 AND NEW.Finished = TRUE
57 THEN
58 v_product_id := NEW.MenuItemId;
59
60 FOR v_ingredient IN
61 SELECT co.Component AS IngId,
62 co.Amount AS AmtPerUnit
63 FROM ConsistsOf co
64 WHERE co.Parent = v_product_id
65 LOOP
66 v_needed := NEW.Quantity * v_ingredient.AmtPerUnit;
67
68 SELECT COALESCE(SUM(sp.Quantity), 0)
69 INTO v_available
70 FROM StoredProduct sp
71 WHERE sp.ProductId = v_ingredient.IngId;
72
73 IF v_available < v_needed THEN
74 RAISE WARNING
75 '[Inventory] ProductId % - needed: %, available: %. Stock will be set to 0.',
76 v_ingredient.IngId, v_needed, v_available;
77 END IF;
78
79 UPDATE StoredProduct
80 SET Quantity = GREATEST(0, Quantity - v_needed)
81 WHERE ProductId = v_ingredient.IngId
82 AND Id = (
83 SELECT Id FROM StoredProduct
84 WHERE ProductId = v_ingredient.IngId
85 ORDER BY Id
86 LIMIT 1
87 );
88 END LOOP;
89 END IF;
90
91 RETURN NEW;
92END;
93$$;
94
95-- TRIGGER 2: trg_deduct_inventory_on_item_finish
96
97CREATE OR REPLACE TRIGGER trg_deduct_inventory_on_item_finish
98AFTER UPDATE OF Finished ON OrderItem
99FOR EACH ROW
100EXECUTE FUNCTION fn_deduct_inventory_on_finish();
101
102
103-- TRIGGER 3 FUNCTION: fn_log_stock_change
104--On every change to StoredProduct.Quantity,
105-- automatically writes a row into ProductUsageLog.
106
107CREATE OR REPLACE FUNCTION fn_log_stock_change()
108RETURNS TRIGGER
109LANGUAGE plpgsql AS
110$$
111DECLARE
112 v_diff INT4;
113 v_change_type_id INT4;
114 v_base_unit_id INT4;
115BEGIN
116 v_diff := NEW.Quantity - OLD.Quantity;
117
118 IF v_diff = 0 THEN
119 RETURN NEW;
120 END IF;
121
122 SELECT Id INTO v_change_type_id
123 FROM ChangeType
124 WHERE Sign = (v_diff > 0)
125 LIMIT 1;
126
127 SELECT p.BaseUnitId INTO v_base_unit_id
128 FROM Product p
129 WHERE p.Id = NEW.ProductId;
130
131 INSERT INTO ProductUsageLog
132 (ProductId, ChangeAmount, InputAmount,
133 Timestamp, ChangeTypeId, BaseUnitId, InputUnitId)
134 VALUES
135 (NEW.ProductId,
136 ABS(v_diff),
137 ABS(v_diff),
138 CURRENT_TIMESTAMP,
139 v_change_type_id,
140 v_base_unit_id,
141 v_base_unit_id);
142
143 RETURN NEW;
144END;
145$$;
146
147-- TRIGGER 3: trg_log_stock_change
148
149CREATE OR REPLACE TRIGGER trg_log_stock_change
150AFTER UPDATE OF Quantity ON StoredProduct
151FOR EACH ROW
152EXECUTE FUNCTION fn_log_stock_change();
153
154
155
156
157
158-- FUNCTION 4: fn_calculate_order_total
159--Calculates the total amount of an order.
160-- Automatically applies active discounts (DiscountItem)
161-- for the current date if they exist.
162
163CREATE OR REPLACE FUNCTION fn_calculate_order_total(p_order_id INT4)
164RETURNS NUMERIC(10,2)
165LANGUAGE plpgsql AS
166$$
167DECLARE
168 v_total NUMERIC(10,2) := 0;
169 v_item RECORD;
170 v_disc_price NUMERIC(10,2);
171 v_unit_price NUMERIC(10,2);
172BEGIN
173 FOR v_item IN
174 SELECT oi.Id AS OiId,
175 oi.Quantity,
176 oi.MenuMemberId,
177 oi.MenuItemId,
178 mm.Price AS BasePrice
179 FROM OrderItem oi
180 JOIN MenuMember mm
181 ON mm.Id = oi.MenuMemberId
182 AND mm.MenuItemId = oi.MenuItemId
183 WHERE oi.OrderId = p_order_id
184 LOOP
185 SELECT di.NewPrice INTO v_disc_price
186 FROM DiscountItem di
187 JOIN Discount d ON d.Id = di.DiscountId
188 WHERE di.MenuMemberId = v_item.MenuMemberId
189 AND di.MenuMemberMenuItemId = v_item.MenuItemId
190 AND d.Status = TRUE
191 AND CURRENT_DATE BETWEEN d."From" AND d."To"
192 ORDER BY di.NewPrice ASC
193 LIMIT 1;
194
195 v_unit_price := COALESCE(v_disc_price, v_item.BasePrice);
196 v_total := v_total + (v_unit_price * v_item.Quantity);
197
198 v_disc_price := NULL;
199 END LOOP;
200
201 RETURN v_total;
202END;
203$$;
204
205
206-- FUNCTION 5: fn_get_available_tables
207--Returns all tables that are free for a given date,time range and minimum capacity.
208-- Also checks the Reservation table for overlaps.
209
210CREATE OR REPLACE FUNCTION fn_get_available_tables(
211 p_date DATE,
212 p_start TIME,
213 p_end TIME,
214 p_guests INT4 DEFAULT 1
215)
216RETURNS TABLE(
217 TableNumber INT4,
218 Capacity INT4,
219 TableType TEXT
220)
221LANGUAGE plpgsql AS
222$$
223BEGIN
224 RETURN QUERY
225 SELECT rt.TableNumber,
226 rt.Capacity,
227 tt.Type::TEXT AS TableType
228 FROM RestaurantTable rt
229 JOIN TableType tt ON tt.Id = rt.TableTypeId
230 WHERE rt.Status = TRUE
231 AND rt.Capacity >= p_guests
232 AND rt.TableNumber NOT IN (
233 SELECT r.TableNumber
234 FROM Reservation r
235 WHERE r."Date" = p_date
236 AND r.TableNumber IS NOT NULL
237 AND tsrange(
238 (p_date + p_start)::TIMESTAMP,
239 (p_date + p_end )::TIMESTAMP
240 ) &&
241 tsrange(
242 (r."Date" + r.StartTime)::TIMESTAMP,
243 (r."Date" + r.EndTime )::TIMESTAMP
244 )
245 )
246 ORDER BY rt.Capacity ASC;
247END;
248$$;
249
250
251-- FUNCTION 6: fn_low_stock_report
252--Returns all products whose current stock is below a given threshold.
253-- Used by the owner to know when a restock is needed.
254
255CREATE OR REPLACE FUNCTION fn_low_stock_report(p_threshold INT4 DEFAULT 10)
256RETURNS TABLE(
257 Product TEXT,
258 ProductType TEXT,
259 Quantity INT4,
260 BaseUnit TEXT
261)
262LANGUAGE plpgsql AS
263$$
264BEGIN
265 RETURN QUERY
266 SELECT p.Name::TEXT AS Product,
267 pt.Type::TEXT AS ProductType,
268 sp.Quantity,
269 u.Name::TEXT AS BaseUnit
270 FROM StoredProduct sp
271 JOIN Product p ON p.Id = sp.ProductId
272 JOIN ProductType pt ON pt.Id = p.TypeId
273 JOIN Unit u ON u.Id = p.BaseUnitId
274 WHERE sp.Quantity < p_threshold
275 ORDER BY sp.Quantity ASC;
276END;
277$$;
278
279-- PROCEDURE 7: sp_add_order_item
280-- Adds a menu item to an existing open order.
281-- Validates that the order is open, the MenuMember exists
282-- in an active menu, and the quantity is positive.
283-- If the same item already exists on the order, it increases
284-- the quantity instead of inserting a duplicate row.
285-- Usage:
286-- CALL sp_add_order_item(5, 3, 3, 2, 1);
287
288CREATE OR REPLACE PROCEDURE sp_add_order_item(
289 p_order_id INT4,
290 p_menu_member_id INT4,
291 p_menu_item_id INT4,
292 p_quantity INT4,
293 p_created_by INT4 DEFAULT NULL
294)
295LANGUAGE plpgsql AS
296$$
297DECLARE
298 v_order_status TEXT;
299 v_existing_id INT4;
300BEGIN
301 IF p_quantity <= 0 THEN
302 RAISE EXCEPTION '[AddItem] Quantity must be > 0. Provided: %', p_quantity;
303 END IF;
304
305 -- Check order exists and is open
306 SELECT os.Status INTO v_order_status
307 FROM "Order" o
308 JOIN OrderStatus os ON os.Id = o.StatusId
309 WHERE o.Id = p_order_id;
310
311 IF NOT FOUND THEN
312 RAISE EXCEPTION '[AddItem] Order #% does not exist.', p_order_id;
313 END IF;
314
315 IF v_order_status IN ('Completed', 'Cancelled') THEN
316 RAISE EXCEPTION '[AddItem] Order #% is "%". Cannot add items.', p_order_id, v_order_status;
317 END IF;
318
319 -- Check the MenuMember exists
320 IF NOT EXISTS (
321 SELECT 1 FROM MenuMember
322 WHERE Id = p_menu_member_id
323 AND MenuItemId = p_menu_item_id
324 ) THEN
325 RAISE EXCEPTION '[AddItem] MenuMember (%, %) does not exist.',
326 p_menu_member_id, p_menu_item_id;
327 END IF;
328
329 -- If the same item is already on the order, just increase quantity
330 SELECT Id INTO v_existing_id
331 FROM OrderItem
332 WHERE OrderId = p_order_id
333 AND MenuMemberId = p_menu_member_id
334 AND MenuItemId = p_menu_item_id
335 AND Finished = FALSE
336 LIMIT 1;
337
338 IF FOUND THEN
339 UPDATE OrderItem
340 SET Quantity = Quantity + p_quantity
341 WHERE Id = v_existing_id;
342
343 RAISE NOTICE '[AddItem] Item (MenuMember %) quantity increased by % on Order #%.',
344 p_menu_member_id, p_quantity, p_order_id;
345 ELSE
346 INSERT INTO OrderItem
347 (Quantity, OrderId, CreatedBy, Finished, MenuMemberId, MenuItemId)
348 VALUES
349 (p_quantity, p_order_id, p_created_by, FALSE, p_menu_member_id, p_menu_item_id);
350
351 RAISE NOTICE '[AddItem] Item (MenuMember %) x% added to Order #%.',
352 p_menu_member_id, p_quantity, p_order_id;
353 END IF;
354END;
355$$;
356
357 -- PROCEDURE 8: sp_create_order
358-- Creates a new order for a waiter, order type and optionally
359-- assigns a table. Sets the initial status to the first
360-- non-terminal status found (e.g. 'Pending' or 'Open').
361-- If a table is given it marks it as occupied (Status = FALSE).
362-- Usage:
363-- CALL sp_create_order(1, 1, 3); -- dine-in table 3
364-- CALL sp_create_order(1, 2, NULL); -- takeaway, no table
365
366CREATE OR REPLACE PROCEDURE sp_create_order(
367 p_waiter_id INT4,
368 p_order_type_id INT4,
369 p_table_number INT4 DEFAULT NULL
370)
371LANGUAGE plpgsql AS
372$$
373DECLARE
374 v_status_id INT4;
375 v_order_id INT4;
376BEGIN
377 -- Validate waiter exists and is still active
378 IF NOT EXISTS (
379 SELECT 1 FROM Employee
380 WHERE Id = p_waiter_id
381 AND (DateResignation IS NULL OR DateResignation > CURRENT_DATE)
382 ) THEN
383 RAISE EXCEPTION '[Order] Employee #% does not exist or has resigned.', p_waiter_id;
384 END IF;
385
386 -- Validate order type
387 IF NOT EXISTS (SELECT 1 FROM OrderType WHERE Id = p_order_type_id) THEN
388 RAISE EXCEPTION '[Order] OrderType #% does not exist.', p_order_type_id;
389 END IF;
390
391 -- Validate table if provided
392 IF p_table_number IS NOT NULL THEN
393 IF NOT EXISTS (
394 SELECT 1 FROM RestaurantTable
395 WHERE TableNumber = p_table_number
396 AND Status = TRUE
397 ) THEN
398 RAISE EXCEPTION '[Order] Table #% does not exist or is already occupied.', p_table_number;
399 END IF;
400 END IF;
401
402 -- Get the initial order status (Pending or first available)
403 SELECT Id INTO v_status_id
404 FROM OrderStatus
405 WHERE Status NOT IN ('Completed', 'Cancelled')
406 ORDER BY Id
407 LIMIT 1;
408
409 IF NOT FOUND THEN
410 RAISE EXCEPTION '[Order] No valid initial OrderStatus found. Check the OrderStatus table.';
411 END IF;
412
413 -- Create the order
414 INSERT INTO "Order" (WaiterId, DateCreated, TypeId, StatusId, TableNumber)
415 VALUES (p_waiter_id, CURRENT_TIMESTAMP, p_order_type_id, v_status_id, p_table_number)
416 RETURNING Id INTO v_order_id;
417
418 -- Mark the table as occupied
419 IF p_table_number IS NOT NULL THEN
420 UPDATE RestaurantTable
421 SET Status = FALSE
422 WHERE TableNumber = p_table_number;
423 END IF;
424
425 RAISE NOTICE '[Order] Order #% created. Waiter: %, Table: %, Status: %.',
426 v_order_id, p_waiter_id, COALESCE(p_table_number::TEXT, 'N/A'), v_status_id;
427END;
428$$;
429
430
431
432
433-- PROCEDURE 9: sp_create_reservation
434-- Creates a reservation for a guest after validating that
435-- the requested table is free for that date and time slot.
436-- Uses fn_get_available_tables to check availability.
437
438-- CALL sp_create_reservation('John Smith', '071234567',
439-- '2024-12-31', '19:00', '21:00', 3, 2);
440
441CREATE OR REPLACE PROCEDURE sp_create_reservation(
442 p_guest_name VARCHAR(50),
443 p_guest_phone VARCHAR(50),
444 p_date DATE,
445 p_start TIME,
446 p_end TIME,
447 p_table_number INT4,
448 p_employee_id INT4 DEFAULT NULL
449)
450LANGUAGE plpgsql AS
451$$
452DECLARE
453 v_reservation_id INT4;
454BEGIN
455 -- Basic time validation
456 IF p_start >= p_end THEN
457 RAISE EXCEPTION '[Reservation] Start time must be before end time.';
458 END IF;
459
460 IF p_date < CURRENT_DATE THEN
461 RAISE EXCEPTION '[Reservation] Cannot make a reservation in the past.';
462 END IF;
463
464 -- Check the table exists
465 IF NOT EXISTS (
466 SELECT 1 FROM RestaurantTable WHERE TableNumber = p_table_number
467 ) THEN
468 RAISE EXCEPTION '[Reservation] Table #% does not exist.', p_table_number;
469 END IF;
470
471 -- Check the table is available for this slot using our function
472 IF NOT EXISTS (
473 SELECT 1 FROM fn_get_available_tables(p_date, p_start, p_end, 1)
474 WHERE TableNumber = p_table_number
475 ) THEN
476 RAISE EXCEPTION
477 '[Reservation] Table #% is not available on % from % to %.',
478 p_table_number, p_date, p_start, p_end;
479 END IF;
480
481 -- Validate employee if provided
482 IF p_employee_id IS NOT NULL AND NOT EXISTS (
483 SELECT 1 FROM Employee WHERE Id = p_employee_id
484 ) THEN
485 RAISE EXCEPTION '[Reservation] Employee #% does not exist.', p_employee_id;
486 END IF;
487
488 -- Create the reservation
489 INSERT INTO Reservation
490 (GuestName, GuestPhone, CreatedAt, StartTime, EndTime,
491 "Date", EmployeeId, TableNumber)
492 VALUES
493 (p_guest_name, p_guest_phone, CURRENT_TIMESTAMP,
494 p_start, p_end, p_date, p_employee_id, p_table_number)
495 RETURNING Id INTO v_reservation_id;
496
497 RAISE NOTICE '[Reservation] #% created for "%" on % % - %, Table #%.',
498 v_reservation_id, p_guest_name, p_date, p_start, p_end, p_table_number;
499END;
500$$;
501
502
503
504-- PROCEDURE 10: sp_cancel_order
505-- Safely cancels an open order.
506-- Sets status to Cancelled, sets DateFinished, and frees
507-- the table if no other active order is using it.
508-- Does NOT restock inventory (items already consumed by kitchen
509-- are not reversed - sp_restock_product for manual correction).
510-- Usage:
511-- CALL sp_cancel_order(7, 'Guest left before food was ready');
512
513CREATE OR REPLACE PROCEDURE sp_cancel_order(
514 p_order_id INT4,
515 p_reason TEXT DEFAULT 'No reason provided'
516)
517LANGUAGE plpgsql AS
518$$
519DECLARE
520 v_cancelled_id INT4;
521 v_cur_status TEXT;
522 v_table_number INT4;
523 v_other_open INT4;
524BEGIN
525 -- Get current order state
526 SELECT os.Status, o.TableNumber
527 INTO v_cur_status, v_table_number
528 FROM "Order" o
529 JOIN OrderStatus os ON os.Id = o.StatusId
530 WHERE o.Id = p_order_id;
531
532 IF NOT FOUND THEN
533 RAISE EXCEPTION '[Cancel] Order #% does not exist.', p_order_id;
534 END IF;
535
536 IF v_cur_status = 'Cancelled' THEN
537 RAISE EXCEPTION '[Cancel] Order #% is already cancelled.', p_order_id;
538 END IF;
539
540 IF v_cur_status = 'Completed' THEN
541 RAISE EXCEPTION '[Cancel] Cannot cancel a completed order (#%).', p_order_id;
542 END IF;
543
544 -- Get Cancelled status Id
545 SELECT Id INTO v_cancelled_id
546 FROM OrderStatus
547 WHERE Status = 'Cancelled';
548
549 IF NOT FOUND THEN
550 RAISE EXCEPTION '[Cancel] OrderStatus "Cancelled" missing from lookup table.';
551 END IF;
552
553 -- Cancel the order
554 UPDATE "Order"
555 SET StatusId = v_cancelled_id,
556 DateFinished = CURRENT_TIMESTAMP
557 WHERE Id = p_order_id;
558
559 -- Free the table if no other active order is using it
560 IF v_table_number IS NOT NULL THEN
561 SELECT COUNT(*) INTO v_other_open
562 FROM "Order" o
563 JOIN OrderStatus os ON os.Id = o.StatusId
564 WHERE o.TableNumber = v_table_number
565 AND o.Id <> p_order_id
566 AND os.Status NOT IN ('Completed', 'Cancelled');
567
568 IF v_other_open = 0 THEN
569 UPDATE RestaurantTable
570 SET Status = TRUE
571 WHERE TableNumber = v_table_number;
572
573 RAISE NOTICE '[Cancel] Table #% has been freed.', v_table_number;
574 END IF;
575 END IF;
576
577 RAISE NOTICE '[Cancel] Order #% cancelled. Reason: %', p_order_id, p_reason;
578END;
579$$;
580
581
582-- PROCEDURE 11: sp_close_order_and_invoice
583--Central operation - closing an order and automatically generating a full invoice:
584-- - Invoice - total amount and tax
585-- - InvoiceItem - per line item, with original and discounted price
586
587CREATE OR REPLACE PROCEDURE sp_close_order_and_invoice(
588 p_order_id INT4,
589 p_tax_rate NUMERIC(5,4) DEFAULT 0.18
590)
591LANGUAGE plpgsql AS
592$$
593DECLARE
594 v_status_id INT4;
595 v_cur_status TEXT;
596 v_total NUMERIC(10,2);
597 v_tax NUMERIC(10,2);
598 v_invoice_id INT4;
599 v_disc_price NUMERIC(10,2);
600 v_item RECORD;
601BEGIN
602 SELECT os.Status, os.Id
603 INTO v_cur_status, v_status_id
604 FROM "Order" o
605 JOIN OrderStatus os ON os.Id = o.StatusId
606 WHERE o.Id = p_order_id;
607
608 IF NOT FOUND THEN
609 RAISE EXCEPTION '[Invoice] Order #% does not exist.', p_order_id;
610 END IF;
611
612 IF v_cur_status IN ('Completed', 'Cancelled') THEN
613 RAISE EXCEPTION
614 '[Invoice] Order #% is already "%". Cannot close again.',
615 p_order_id, v_cur_status;
616 END IF;
617
618 SELECT Id INTO v_status_id
619 FROM OrderStatus
620 WHERE Status = 'Completed';
621
622 IF NOT FOUND THEN
623 RAISE EXCEPTION
624 '[Invoice] OrderStatus "Completed" does not exist. Add it to the lookup table.';
625 END IF;
626
627 v_total := fn_calculate_order_total(p_order_id);
628 v_tax := ROUND(v_total * p_tax_rate, 2);
629
630 INSERT INTO Invoice (OrderId, InvoiceDate, TotalAmount, TaxAmount)
631 VALUES (p_order_id, CURRENT_TIMESTAMP, v_total, v_tax)
632 RETURNING Id INTO v_invoice_id;
633
634 FOR v_item IN
635 SELECT oi.Id AS OiId,
636 oi.Quantity,
637 oi.MenuMemberId,
638 oi.MenuItemId,
639 mm.Price AS OrigPrice
640 FROM OrderItem oi
641 JOIN MenuMember mm
642 ON mm.Id = oi.MenuMemberId
643 AND mm.MenuItemId = oi.MenuItemId
644 WHERE oi.OrderId = p_order_id
645 LOOP
646 SELECT di.NewPrice INTO v_disc_price
647 FROM DiscountItem di
648 JOIN Discount d ON d.Id = di.DiscountId
649 WHERE di.MenuMemberId = v_item.MenuMemberId
650 AND di.MenuMemberMenuItemId = v_item.MenuItemId
651 AND d.Status = TRUE
652 AND CURRENT_DATE BETWEEN d."From" AND d."To"
653 ORDER BY di.NewPrice ASC
654 LIMIT 1;
655
656 INSERT INTO InvoiceItem
657 (InvoiceId, OrderItemId, OriginalPrice, DiscountedPrice, Quantity)
658 VALUES
659 (v_invoice_id,
660 v_item.OiId,
661 v_item.OrigPrice,
662 v_disc_price,
663 v_item.Quantity);
664
665 v_disc_price := NULL;
666 END LOOP;
667
668 UPDATE "Order"
669 SET StatusId = v_status_id,
670 DateFinished = CURRENT_TIMESTAMP
671 WHERE Id = p_order_id;
672
673 RAISE NOTICE
674 '[Invoice] Order #% closed. Invoice #% generated. Total: % (tax: %).',
675 p_order_id, v_invoice_id, v_total, v_tax;
676END;
677$$;
678
679
680-- PROCEDURE 12: sp_restock_product
681--Restock inventory for a product.
682-- If no row exists in StoredProduct it creates one.
683-- The log is written automatically via trg_log_stock_change.
684
685CREATE OR REPLACE PROCEDURE sp_restock_product(
686 p_product_id INT4,
687 p_quantity INT4
688)
689LANGUAGE plpgsql AS
690$$
691DECLARE
692 v_name TEXT;
693 v_old_qty INT4;
694BEGIN
695 IF p_quantity <= 0 THEN
696 RAISE EXCEPTION
697 '[Restock] Quantity must be > 0. Provided: %', p_quantity;
698 END IF;
699
700 SELECT Name INTO v_name
701 FROM Product
702 WHERE Id = p_product_id;
703
704 IF NOT FOUND THEN
705 RAISE EXCEPTION '[Restock] Product #% does not exist.', p_product_id;
706 END IF;
707
708 SELECT Quantity INTO v_old_qty
709 FROM StoredProduct
710 WHERE ProductId = p_product_id
711 ORDER BY Id
712 LIMIT 1;
713
714 IF NOT FOUND THEN
715 INSERT INTO StoredProduct (Quantity, ProductId)
716 VALUES (p_quantity, p_product_id);
717 RAISE NOTICE '[Restock] "%" - new record: 0 -> % units.', v_name, p_quantity;
718 ELSE
719 UPDATE StoredProduct
720 SET Quantity = Quantity + p_quantity
721 WHERE ProductId = p_product_id
722 AND Id = (SELECT Id FROM StoredProduct
723 WHERE ProductId = p_product_id ORDER BY Id LIMIT 1);
724
725 RAISE NOTICE '[Restock] "%" - % -> % units.',
726 v_name, v_old_qty, v_old_qty + p_quantity;
727 END IF;
728END;
729$$;
730
731
732-- PROCEDURE 13: sp_apply_discount_to_menu
733--The owner can apply a percentage discount to all items in a given menu for a set period.
734-- Automatically calculates the new prices and inserts rows into the Discount and DiscountItem tables.
735--
736
737CREATE OR REPLACE PROCEDURE sp_apply_discount_to_menu(
738 p_menu_id INT4,
739 p_name VARCHAR(30),
740 p_pct NUMERIC(5,2),
741 p_from DATE,
742 p_to DATE
743)
744LANGUAGE plpgsql AS
745$$
746DECLARE
747 v_disc_id INT4;
748 v_item RECORD;
749 v_new_prc NUMERIC(10,2);
750 v_count INT4 := 0;
751BEGIN
752 IF p_pct <= 0 OR p_pct >= 100 THEN
753 RAISE EXCEPTION
754 '[Discount] Percentage must be 0 < p_pct < 100. Provided: %', p_pct;
755 END IF;
756
757 IF p_from > p_to THEN
758 RAISE EXCEPTION '[Discount] "From" date must be before "To" date.';
759 END IF;
760
761 IF NOT EXISTS (SELECT 1 FROM Menu WHERE Id = p_menu_id) THEN
762 RAISE EXCEPTION '[Discount] Menu #% does not exist.', p_menu_id;
763 END IF;
764
765 INSERT INTO Discount ("Name", "From", "To", MenuId, Status)
766 VALUES (p_name, p_from, p_to, p_menu_id, TRUE)
767 RETURNING Id INTO v_disc_id;
768
769 FOR v_item IN
770 SELECT mm.Id AS MemberId, mm.MenuItemId, mm.Price
771 FROM MenuMember mm
772 WHERE mm.MenuId = p_menu_id
773 LOOP
774 v_new_prc := ROUND(v_item.Price * (1.0 - p_pct / 100.0), 2);
775
776 INSERT INTO DiscountItem
777 (NewPrice, MenuMemberId, MenuMemberMenuItemId, DiscountId)
778 VALUES
779 (v_new_prc, v_item.MemberId, v_item.MenuItemId, v_disc_id);
780
781 v_count := v_count + 1;
782 END LOOP;
783
784 IF v_count = 0 THEN
785 RAISE WARNING
786 '[Discount] Menu #% has no items. Discount created but is empty.',
787 p_menu_id;
788 ELSE
789 RAISE NOTICE
790 '[Discount] "%" (% %%) applied to % items. Valid: % to %.',
791 p_name, p_pct, v_count, p_from, p_to;
792 END IF;
793END;
794$$;
795
796
797
798SELECT routine_name, routine_type
799 FROM information_schema.routines
800 WHERE routine_schema = 'public'
801 AND routine_name IN (
802 'fn_calculate_order_total',
803 'fn_get_available_tables',
804 'fn_low_stock_report',
805 'sp_create_order',
806 'sp_add_order_item',
807 'sp_create_reservation',
808 'sp_cancel_order',
809 'sp_close_order_and_invoice',
810 'sp_restock_product',
811 'sp_apply_discount_to_menu'
812 )
813 ORDER BY routine_type, routine_name;
814
815
816
817SELECT trigger_name, event_manipulation, event_object_table, action_timing
818 FROM information_schema.triggers
819 WHERE trigger_schema = 'public'
820 AND trigger_name IN (
821 'trg_prevent_closed_order_edit',
822 'trg_deduct_inventory_on_item_finish',
823 'trg_log_stock_change'
824 )
825 ORDER BY trigger_name;