| | 1 | = Функции, процедури и тригери |
| | 2 | |
| | 3 | == Функции |
| | 4 | |
| | 5 | Функција 1: Проверка дали корисникот има доволно баланс за BUY |
| | 6 | |
| | 7 | {{{ |
| | 8 | CREATE OR REPLACE FUNCTION check_portfolio_balance( |
| | 9 | p_portfolio_id BIGINT, |
| | 10 | p_price FLOAT8, |
| | 11 | p_quantity INT |
| | 12 | ) |
| | 13 | RETURNS BOOLEAN AS $$ |
| | 14 | DECLARE |
| | 15 | v_balance NUMERIC(18,2); |
| | 16 | v_total FLOAT8; |
| | 17 | BEGIN |
| | 18 | SELECT balance INTO v_balance |
| | 19 | FROM portfolios |
| | 20 | WHERE id = p_portfolio_id; |
| | 21 | |
| | 22 | v_total := p_price * p_quantity; |
| | 23 | |
| | 24 | IF v_balance >= v_total THEN |
| | 25 | RETURN TRUE; |
| | 26 | ELSE |
| | 27 | RETURN FALSE; |
| | 28 | END IF; |
| | 29 | END; |
| | 30 | $$ LANGUAGE plpgsql; |
| | 31 | }}} |
| | 32 | |
| | 33 | Функција 2: Вредност на портфолио за конкретен корисник |
| | 34 | |
| | 35 | {{{ |
| | 36 | CREATE OR REPLACE FUNCTION get_portfolio_value( |
| | 37 | p_user_id BIGINT |
| | 38 | ) |
| | 39 | RETURNS FLOAT8 AS $$ |
| | 40 | DECLARE |
| | 41 | v_value FLOAT8; |
| | 42 | BEGIN |
| | 43 | SELECT SUM(ph.quantity * s.current_price) INTO v_value |
| | 44 | FROM portfolios p |
| | 45 | JOIN portfolio_holdings ph ON p.id = ph.portfolio_id |
| | 46 | JOIN stock s ON ph.stock_id = s.id |
| | 47 | WHERE p.user_id = p_user_id; |
| | 48 | |
| | 49 | RETURN COALESCE(v_value, 0); |
| | 50 | END; |
| | 51 | $$ LANGUAGE plpgsql; |
| | 52 | }}} |
| | 53 | |
| | 54 | |
| | 55 | == Процедури |
| | 56 | |
| | 57 | Процедура 1: Креирање на trade request од корисник |
| | 58 | |
| | 59 | {{{ |
| | 60 | CREATE OR REPLACE PROCEDURE create_trade_request( |
| | 61 | p_portfolio_id BIGINT, |
| | 62 | p_price FLOAT8, |
| | 63 | p_quantity INT, |
| | 64 | p_stock_symbol VARCHAR(20), |
| | 65 | p_type VARCHAR(10) |
| | 66 | ) |
| | 67 | AS $$ |
| | 68 | DECLARE |
| | 69 | v_user_id BIGINT; |
| | 70 | BEGIN |
| | 71 | SELECT user_id INTO v_user_id |
| | 72 | FROM portfolios |
| | 73 | WHERE id = p_portfolio_id; |
| | 74 | |
| | 75 | IF p_type = 'BUY' THEN |
| | 76 | IF NOT check_portfolio_balance(p_portfolio_id, p_price, p_quantity) THEN |
| | 77 | RAISE EXCEPTION 'Недоволен баланс за купување. Portfolio ID: %', p_portfolio_id; |
| | 78 | END IF; |
| | 79 | END IF; |
| | 80 | |
| | 81 | INSERT INTO trade_request ( |
| | 82 | portfolio_id, price_per_unit, quantity, |
| | 83 | status, stock_symbol, timestamp, type |
| | 84 | ) |
| | 85 | VALUES ( |
| | 86 | p_portfolio_id, p_price, p_quantity, |
| | 87 | 'OPEN', p_stock_symbol, NOW(), p_type |
| | 88 | ); |
| | 89 | |
| | 90 | RAISE NOTICE 'Trade request креиран за корисник % — % x % на %', |
| | 91 | v_user_id, p_type, p_quantity, p_stock_symbol; |
| | 92 | END; |
| | 93 | $$ LANGUAGE plpgsql; |
| | 94 | }}} |
| | 95 | |
| | 96 | |
| | 97 | Процедура 2: Одобрување на trade request од admin - креира broker order и settlement |
| | 98 | |
| | 99 | |
| | 100 | {{{ |
| | 101 | CREATE OR REPLACE PROCEDURE approve_trade_request( |
| | 102 | p_trade_request_id BIGINT |
| | 103 | ) |
| | 104 | AS $$ |
| | 105 | DECLARE |
| | 106 | v_tr trade_request%ROWTYPE; |
| | 107 | v_portfolio portfolios%ROWTYPE; |
| | 108 | v_broker_order_id BIGINT; |
| | 109 | v_stock_id BIGINT; |
| | 110 | BEGIN |
| | 111 | |
| | 112 | SELECT * INTO v_tr |
| | 113 | FROM trade_request |
| | 114 | WHERE id = p_trade_request_id AND status = 'OPEN'; |
| | 115 | |
| | 116 | IF NOT FOUND THEN |
| | 117 | RAISE EXCEPTION 'Trade request % не постои или не е OPEN', p_trade_request_id; |
| | 118 | END IF; |
| | 119 | |
| | 120 | SELECT * INTO v_portfolio |
| | 121 | FROM portfolios |
| | 122 | WHERE id = v_tr.portfolio_id; |
| | 123 | |
| | 124 | |
| | 125 | SELECT id INTO v_stock_id |
| | 126 | FROM stock |
| | 127 | WHERE symbol = v_tr.stock_symbol; |
| | 128 | |
| | 129 | |
| | 130 | UPDATE trade_request |
| | 131 | SET status = 'CLOSED' |
| | 132 | WHERE id = p_trade_request_id; |
| | 133 | |
| | 134 | |
| | 135 | INSERT INTO broker_order ( |
| | 136 | order_type, limit_price, quantity, status, |
| | 137 | executed_price, broker_fee, mse_reference_number, |
| | 138 | submitted_at, executed_at, trade_request_id |
| | 139 | ) |
| | 140 | VALUES ( |
| | 141 | v_tr.type, v_tr.price_per_unit, v_tr.quantity, 'EXECUTED', |
| | 142 | v_tr.price_per_unit, (random() * 50)::FLOAT8, |
| | 143 | 'MSE-' || p_trade_request_id, |
| | 144 | v_tr.timestamp, NOW(), p_trade_request_id |
| | 145 | ) |
| | 146 | RETURNING id INTO v_broker_order_id; |
| | 147 | |
| | 148 | |
| | 149 | INSERT INTO settlement_record ( |
| | 150 | broker_order_id, user_id, stock_symbol, quantity, |
| | 151 | settled_price, total_cost, settlement_status, settled_at |
| | 152 | ) |
| | 153 | VALUES ( |
| | 154 | v_broker_order_id, v_portfolio.user_id, v_tr.stock_symbol, |
| | 155 | v_tr.quantity, v_tr.price_per_unit, |
| | 156 | v_tr.price_per_unit * v_tr.quantity, |
| | 157 | 'SETTLED', NOW() |
| | 158 | ); |
| | 159 | |
| | 160 | |
| | 161 | INSERT INTO trade_transaction ( |
| | 162 | price, quantity, timestamp, type, origin, user_id, stock_id |
| | 163 | ) |
| | 164 | VALUES ( |
| | 165 | v_tr.price_per_unit, v_tr.quantity, NOW(), |
| | 166 | v_tr.type, 'INTERNAL', v_portfolio.user_id, v_stock_id |
| | 167 | ); |
| | 168 | |
| | 169 | RAISE NOTICE 'Trade request % одобрен — Broker Order % креиран', |
| | 170 | p_trade_request_id, v_broker_order_id; |
| | 171 | END; |
| | 172 | $$ LANGUAGE plpgsql; |
| | 173 | }}} |
| | 174 | |
| | 175 | |
| | 176 | |
| | 177 | == Тригери |
| | 178 | |
| | 179 | Тригер 1: При SELL - провери дали корисникот има доволно акции во holdings |
| | 180 | |
| | 181 | |
| | 182 | {{{ |
| | 183 | CREATE OR REPLACE FUNCTION check_holdings_before_sell() |
| | 184 | RETURNS TRIGGER AS $$ |
| | 185 | DECLARE |
| | 186 | v_stock_id BIGINT; |
| | 187 | v_available_qty INT; |
| | 188 | BEGIN |
| | 189 | IF NEW.type = 'SELL' THEN |
| | 190 | SELECT id INTO v_stock_id |
| | 191 | FROM stock WHERE symbol = NEW.stock_symbol; |
| | 192 | |
| | 193 | SELECT quantity INTO v_available_qty |
| | 194 | FROM portfolio_holdings |
| | 195 | WHERE portfolio_id = NEW.portfolio_id |
| | 196 | AND stock_id = v_stock_id; |
| | 197 | |
| | 198 | IF NOT FOUND OR v_available_qty < NEW.quantity THEN |
| | 199 | RAISE EXCEPTION 'Недоволно акции за продажба. Достапно: %, Побарано: %', |
| | 200 | COALESCE(v_available_qty, 0), NEW.quantity; |
| | 201 | END IF; |
| | 202 | END IF; |
| | 203 | |
| | 204 | RETURN NEW; |
| | 205 | END; |
| | 206 | $$ LANGUAGE plpgsql; |
| | 207 | |
| | 208 | CREATE TRIGGER trg_check_holdings_before_sell |
| | 209 | BEFORE INSERT ON trade_request |
| | 210 | FOR EACH ROW |
| | 211 | EXECUTE FUNCTION check_holdings_before_sell(); |
| | 212 | }}} |