== Додавање на продукт Оваа функционалност овозможува додавање на нов продукт во системот. = Функција: fn_validate_seller_package Проверува дали продавачот има право да објавува продукти. * Проверува дали корисникот постои и е активен. * Проверува дали корисникот е верифициран. * Проверува дали корисникот има активен пакет чиј период на важност не е истечен. {{{ CREATE OR REPLACE FUNCTION fn_validate_seller_package(p_seller_id INT) RETURNS VOID LANGUAGE plpgsql AS $$ begin IF NOT EXISTS ( SELECT 1 FROM appuser WHERE user_id = p_seller_id AND is_active = 1 ) THEN RAISE EXCEPTION 'Sellerot ne postoi ili ne e aktiven'; END IF; IF NOT EXISTS ( SELECT 1 FROM appuser WHERE user_id = p_seller_id AND is_verified = 1 ) THEN RAISE EXCEPTION 'Sellerot ne e verificiran'; END IF; IF NOT EXISTS ( SELECT 1 FROM userpackages WHERE seller_id = p_seller_id AND CURRENT_TIMESTAMP BETWEEN start_date AND end_date ) THEN RAISE EXCEPTION 'Sellerot nema aktiven paket (istecen ili ne postoi)'; END IF; END; $$; }}} = Функција fn_validate_product_attribute Валидира парови атрибут:вредност * Проверува дали атрибутот е дефиниран за категоријата во која припаѓа продуктот. * Проверува дали внесената вредност е една од дозволените вредности за атрибутот. {{{ CREATE OR REPLACE FUNCTION fn_validate_product_attribute( p_category_id INT, p_category_attribute_id INT, p_attribute_value VARCHAR ) RETURNS VOID LANGUAGE plpgsql AS $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM categoryattributes WHERE category_attribute_id = p_category_attribute_id AND category_id = p_category_id ) THEN RAISE EXCEPTION 'Atributot % ne pripagja na kategorijata', p_category_attribute_id; END IF; IF NOT EXISTS ( SELECT 1 FROM categoryattributesvalues WHERE category_attribute_id = p_category_attribute_id AND allowed_value = p_attribute_value ) THEN RAISE EXCEPTION 'Vrednosta % ne pripagja na atributot', p_attribute_value; END IF; END; $$; }}} = Процедура add_product Додава нов продукт во системот заедно со неговите атрибути, статистики и слики. {{{ CREATE OR REPLACE PROCEDURE add_product( p_title VARCHAR, p_description VARCHAR, p_price NUMERIC, p_location VARCHAR, p_currency VARCHAR, p_category_id INT, p_seller_id INT, p_quantity INT, p_attributes JSONB, -- ex. '[{"category_attribute_id": 11, "attribute_value": "M"}, ...]' p_images TEXT[] -- ex. ARRAY['url1.jpg', 'url2.jpg'] ) LANGUAGE plpgsql AS $$ DECLARE v_product_id INT; v_attr JSONB; v_image TEXT; begin -- Validacii IF p_price <= 0 THEN RAISE EXCEPTION 'Cenata mora da e pogolema od 0'; END IF; IF p_quantity < 0 THEN RAISE EXCEPTION 'Kolichinata ne moze da e negativna'; END IF; IF EXISTS ( SELECT 1 FROM category WHERE parent_category = p_category_id ) THEN RAISE EXCEPTION 'Kategorijata mora da e leaf kategorija'; END IF; --FUNKCIJA KORISNIK perform fn_validate_seller_package(p_seller_id); -- Insert produkt INSERT INTO product (title, description, price, "location" , category_id, seller_id, created_at, is_active, currency, quantity) VALUES ( p_title, p_description, p_price, p_location, p_category_id, p_seller_id, CURRENT_TIMESTAMP, CASE WHEN p_quantity > 0 THEN TRUE ELSE FALSE END, p_currency, p_quantity ) RETURNING product_id INTO v_product_id; -- Insert productstats (pochetni vrednosti) INSERT INTO productstats (product_id, views_count, favorites_count, sells_count) VALUES (v_product_id, 0, 0, 0); -- Insert atributi funkcija IF p_attributes IS NOT NULL then FOR v_attr IN SELECT value FROM jsonb_array_elements(p_attributes) LOOP PERFORM fn_validate_product_attribute( p_category_id, (v_attr->>'category_attribute_id')::INT, v_attr->>'attribute_value' ); INSERT INTO productattributes (product_id, category_attribute_id, attribute_value) VALUES (v_product_id, (v_attr->>'category_attribute_id')::INT, v_attr->>'attribute_value'); END LOOP; END IF; -- Insert sliki IF p_images IS NOT NULL THEN FOREACH v_image IN ARRAY p_images LOOP INSERT INTO productimages (product_id, image_url) VALUES (v_product_id, v_image); END LOOP; END IF; RAISE NOTICE 'Produktot e dodaden so ID: %', v_product_id; end; $$; }}} == Управување со кошничка Оваа функционалност овозможува додавање, бришење и ажурирање на продукти во кошничката. = Функција fn_validate_product_stock Проверува дали постои доволна количина од избраниот продукт за извршување на бараната операција. {{{ CREATE OR REPLACE FUNCTION fn_validate_product_stock( p_product_id INT, p_quantity INT ) RETURNS VOID LANGUAGE plpgsql AS $$ DECLARE all_stock INT; BEGIN SELECT quantity INTO all_stock FROM product WHERE product_id = p_product_id AND is_active = TRUE; IF NOT FOUND THEN RAISE EXCEPTION 'Produktot ne postoi ili ne e aktiven'; END IF; IF p_quantity > all_stock THEN RAISE EXCEPTION 'Nema dovolno zaliha. Barate: %, dostapno: %', p_quantity, all_stock; END IF; END; $$; }}} = Функција fn_calc_cart_total Ја пресметува вкупната вредност на кошничката. {{{ CREATE OR REPLACE FUNCTION fn_calc_cart_total(p_cart_id INT) RETURNS INT LANGUAGE plpgsql AS $$ DECLARE v_total INT; BEGIN SELECT COALESCE(SUM(quantity * price_at_time), 0) INTO v_total FROM cartitems WHERE cart_id = p_cart_id; RETURN v_total; END; $$; }}} = Процедура add_to_cart Додава продукт во кошничката. {{{ CREATE OR REPLACE PROCEDURE add_to_cart( p_cart_id INT, p_product_id INT, p_quantity INT ) LANGUAGE plpgsql AS $$ DECLARE v_price NUMERIC; BEGIN IF p_quantity <= 0 THEN RAISE EXCEPTION 'Kolicinata mora da e pogolema od 0'; END IF; SELECT price INTO v_price FROM product WHERE product_id = p_product_id AND is_active = 1; IF NOT FOUND THEN RAISE EXCEPTION 'Produktot ne postoi ili ne e aktiven'; END IF; PERFORM fn_validate_product_stock(p_product_id, p_quantity); INSERT INTO cartitems (cart_id, product_id, quantity, price_at_time) VALUES (p_cart_id, p_product_id, p_quantity, v_price); --azhurirame total price UPDATE cart SET total_price = fn_calc_cart_total(p_cart_id) WHERE cart_id = p_cart_id; END; $$; }}} = Процедура remove_from_cart Отстранува продукт од кошничката. {{{ CREATE OR REPLACE PROCEDURE remove_from_cart( p_cart_id INT, p_product_id INT ) LANGUAGE plpgsql AS $$ BEGIN DELETE FROM cartitems WHERE cart_id = p_cart_id AND product_id = p_product_id; IF NOT FOUND THEN RAISE EXCEPTION 'Produktot ne postoi vo kosnickata'; END IF; --azhurirame total price UPDATE cart SET total_price = fn_calc_cart_total(p_cart_id) WHERE cart_id = p_cart_id; END; $$; }}} = Процедура update_cart_quantity Ја менува количината на постоечки продукт во кошничката. {{{ CREATE OR REPLACE PROCEDURE update_cart_quantity( p_cart_id INT, p_product_id INT, p_quantity INT ) LANGUAGE plpgsql AS $$ DECLARE v_stock INT; BEGIN IF p_quantity <= 0 THEN RAISE EXCEPTION 'Kolicinata mora da e pogolema od 0'; END IF; PERFORM fn_validate_product_stock(p_product_id, p_quantity); UPDATE cartitems SET quantity = p_quantity WHERE cart_id = p_cart_id AND product_id = p_product_id; --azhurirame total price UPDATE cart SET total_price = fn_calc_cart_total(p_cart_id) WHERE cart_id = p_cart_id; END; $$; }}} == Купување на производи Оваа функционалност овозможува корисникот да ја претвори содржината на кошничката во нарачка. = Функција fn_reduce_product_stock Ја намалува количината на залиха на продукт по успешно купување. {{{ CREATE OR REPLACE FUNCTION fn_reduce_product_stock( p_product_id INT, p_quantity INT ) RETURNS VOID LANGUAGE plpgsql AS $$ BEGIN UPDATE product SET quantity = quantity - p_quantity WHERE product_id = p_product_id; UPDATE product SET is_active = 0 WHERE product_id = p_product_id AND quantity <= 0; END; $$; }}} = Процедура buy_from_cart Го реализира процесот на купување на сите производи кои се наоѓаат во кошничката на корисникот. * Намалување на залиха - за секој производ во кошничката се повикува: fn_reduce_product_stock(...) * Креирање на нарачка - во табелата Order се внесува нов запис * Креирање на ставки на нарачка - сите производи од кошничката(CartItems) се префрлаат во табелата OrderItems. * Ажурирање на статистика - во табелата ProductStats се зголемува полето: sells_count * Евидентирање на плаќање - се креира запис во табелата Payment * Креирање трансакции * За секој продавач кој има продадени производи во нарачката се креира запис во табелата Transactions. * Reset на кошничка {{{ CREATE OR REPLACE PROCEDURE buy_from_cart( p_buyer_id INT, p_payment_method VARCHAR, OUT p_order_id INT, OUT p_payment_id INT ) LANGUAGE plpgsql AS $$ DECLARE v_cart_id INT; v_total DECIMAL(10,2); v_item_count INT; v_updated_rows INT; BEGIN -- Najdi kosnicka SELECT cart_id INTO v_cart_id FROM cart WHERE user_id = p_buyer_id; IF NOT FOUND THEN RAISE EXCEPTION 'Kosnickata ne postoi'; END IF; -- Dali ima stavki? SELECT COUNT(*) INTO v_item_count FROM cartitems WHERE cart_id = v_cart_id; IF v_item_count = 0 THEN RAISE EXCEPTION 'Kosnickata e prazna'; END IF; --ja prezemame total price select total_price INTO v_total from cart c where c.cart_id = v_cart_id; --1. Namali kolichina PERFORM fn_reduce_product_stock( ci.product_id, ci.quantity ) FROM cartitems ci WHERE ci.cart_id = v_cart_id; -- 2. Kreiraj narachka INSERT INTO "Order" (buyer_id, cart_id, total_price, status, created_at) VALUES (p_buyer_id, v_cart_id, v_total, 'CONFIRMED', CURRENT_TIMESTAMP) RETURNING order_id INTO p_order_id; -- 3. Prefrli stavki vo OrderItems INSERT INTO orderitems (order_id, product_id, seller_id, price_at_time, quantity) SELECT p_order_id, ci.product_id, p.seller_id, ci.price_at_time, ci.quantity FROM cartitems ci JOIN product p ON p.product_id = ci.product_id WHERE ci.cart_id = v_cart_id; -- 4. Azuriraj sells_count UPDATE productstats ps SET sells_count = ps.sells_count + ci.quantity FROM cartitems ci WHERE ci.cart_id = v_cart_id AND ps.product_id = ci.product_id; -- 5. Plakjanje INSERT INTO payment (user_id, order_id, package_id, amount, payment_method, transaction_date) VALUES (p_buyer_id, p_order_id, NULL, v_total, p_payment_method, CURRENT_DATE) RETURNING payment_id INTO p_payment_id; -- 6. Transactions po prodavach INSERT INTO transactions (payment_id, seller_id, amount, status, transaction_date) SELECT p_payment_id, oi.seller_id, SUM(oi.price_at_time * oi.quantity), 'COMPLETED', CURRENT_DATE FROM orderitems oi WHERE oi.order_id = p_order_id GROUP BY oi.seller_id; -- 7. Isprazni kosnicka DELETE FROM cartitems WHERE cart_id = v_cart_id; UPDATE cart SET total_price = fn_calc_cart_total(p_cart_id) WHERE cart_id = v_cart_id; END; $$; }}} == Оставање рецензија за купен производ Оваа секција овозможува корисниците да остават рецензија за прозивод што го купиле. = Функција: fn_can_user_review_product Функцијата проверува дали корисникот има право да остави рецензија за одреден производ. Корисник може да остави рецензија на производ ако ги исполнува следните услови: * Нема веќе има оставено рецензија за истиот производ. * Има успешна нарачка што содржи го производот. * Статусот на нарачката е DELIVERED, односно производот е испорачан. {{{ CREATE OR REPLACE FUNCTION fn_can_user_review_product( p_user_id INT, p_product_id INT ) RETURNS INT LANGUAGE plpgsql AS $$ BEGIN IF EXISTS ( SELECT 1 FROM review WHERE buyer_id = p_user_id AND product_id = p_product_id ) THEN RAISE EXCEPTION 'You have already reviewed this product.'; END IF; IF NOT EXISTS ( SELECT 1 FROM orderitems oi JOIN "order" o ON o.order_id = oi.order_id WHERE o.buyer_id = p_user_id AND oi.product_id = p_product_id AND o.status = 'DELIVERED' ) THEN RAISE EXCEPTION 'Only users who purchased and received the product can leave a review.'; END IF; RETURN 1; END; $$; }}} = Процедура: sp_add_review Процедурата се користи за додавање нова рецензија за производ. {{{ CREATE OR REPLACE PROCEDURE sp_add_review( p_user_id INT, p_product_id INT, p_rating INT, p_comment TEXT ) LANGUAGE plpgsql AS $$ DECLARE v_seller_id INT; BEGIN PERFORM fn_can_user_review_product(p_user_id, p_product_id); IF p_rating NOT BETWEEN 1 AND 5 THEN RAISE EXCEPTION 'Rating must be between 1 and 5.'; END IF; SELECT seller_id INTO v_seller_id FROM product WHERE product_id = p_product_id; IF NOT FOUND THEN RAISE EXCEPTION 'Product with id % does not exist.', p_product_id; END IF; INSERT INTO review (product_id, buyer_id, seller_id, rating, comment, created_at) VALUES (p_product_id, p_user_id,v_seller_id, p_rating, p_comment, NOW()); END; $$; }}} == Тригери = trg_notify_payment_success Цел: автоматски после успешно плаќање, генерира нотификација до корисникот дека плаќањето е успешно завршено. {{{CREATE OR REPLACE FUNCTION fn_notify_payment_success() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN INSERT INTO notifications (user_id, type, title, message, order_id) VALUES ( NEW.user_id, 'PAYMENT', 'Uspeshno plakjanje', 'Vashata narachka #' || NEW.order_id || ' e uspeshno platena.', NEW.order_id ); RETURN NEW; END; $$; CREATE TRIGGER trg_notify_payment_success AFTER INSERT ON payment FOR EACH ROW EXECUTE FUNCTION fn_notify_payment_success(); }}} = trg_notify_order_status Цел: известување на купувачот кога ќе се промени статусот на нарачката. {{{ CREATE OR REPLACE FUNCTION fn_notify_order_status_change() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN IF OLD.status IS NOT DISTINCT FROM NEW.status THEN RETURN NEW; END IF; INSERT INTO notifications (user_id, type, title, message, order_id) VALUES ( NEW.buyer_id, 'ORDER_STATUS', 'Promena na status', 'Narachkata #' || NEW.order_id || ' e sega: ' || NEW.status || '.', NEW.order_id ); RETURN NEW; END; $$; CREATE TRIGGER trg_notify_order_status AFTER UPDATE OF status ON "order" FOR EACH ROW EXECUTE FUNCTION fn_notify_order_status_change(); }}} = trg_prevent_over_selling Цел: спречување на продажба на производи за кои нема доволно залиха(двојна нарачка). {{{ CREATE OR REPLACE FUNCTION trg_prevent_over_selling() RETURNS TRIGGER LANGUAGE plpgsql AS $$ DECLARE v_stock INT; BEGIN SELECT quantity INTO v_stock FROM product WHERE product_id = NEW.product_id; IF NOT FOUND THEN RAISE EXCEPTION 'Product % does not exist', NEW.product_id; END IF; IF v_stock < NEW.quantity THEN RAISE EXCEPTION 'Not enough stock for product %. Available: %, Requested: %', NEW.product_id, v_stock, NEW.quantity; END IF; RETURN NEW; END; $$; }}}