Changeset 2d1ec46


Ignore:
Timestamp:
09/21/26 00:54:07 (9 days ago)
Author:
Klimentina Efremova <klimentina08642@…>
Branches:
main
Parents:
6c7cfa6
Message:

Implemented Triggers and Views

Files:
35 deleted
1 edited

Legend:

Unmodified
Added
Removed
  • server.js

    r6c7cfa6 r2d1ec46  
    911911`;
    912912
     913
     914const TRIGGERS_SQL = String.raw`
     915-- ============================================================
     916-- HANDCRAFT MARKETPLACE TRIGGERS
     917-- Compatible with the supplied project schema.
     918-- ============================================================
     919
     920CREATE OR REPLACE FUNCTION update_store_rating()
     921RETURNS TRIGGER
     922LANGUAGE plpgsql
     923AS $$
     924DECLARE
     925    v_order_num VARCHAR(11);
     926    v_store_id VARCHAR(3);
     927BEGIN
     928    IF TG_OP = 'DELETE' THEN
     929        v_order_num := OLD.order_num;
     930    ELSE
     931        v_order_num := NEW.order_num;
     932    END IF;
     933
     934    v_store_id := LEFT(v_order_num, 3);
     935
     936    UPDATE store s
     937    SET rating = COALESCE(
     938        (
     939            SELECT ROUND(AVG(r.rating), 1)
     940            FROM review r
     941            JOIN includes i
     942              ON i.order_num = r.order_num
     943            JOIN sells sl
     944              ON sl.product_code = i.product_code
     945             AND sl.store_ID = v_store_id
     946            JOIN "order" o
     947              ON o.order_num = r.order_num
     948            WHERE LEFT(o.order_num, 3) = v_store_id
     949        ),
     950        0
     951    )
     952    WHERE s.store_ID = v_store_id;
     953
     954    RETURN NULL;
     955END;
     956$$;
     957
     958DROP TRIGGER IF EXISTS trg_update_store_rating ON review;
     959
     960CREATE TRIGGER trg_update_store_rating
     961AFTER INSERT OR UPDATE OR DELETE
     962ON review
     963FOR EACH ROW
     964EXECUTE FUNCTION update_store_rating();
     965
     966
     967CREATE OR REPLACE FUNCTION check_product_availability()
     968RETURNS TRIGGER
     969LANGUAGE plpgsql
     970AS $$
     971DECLARE
     972    v_available_quantity INTEGER;
     973BEGIN
     974    SELECT p.availability
     975    INTO v_available_quantity
     976    FROM product p
     977    WHERE p.code = NEW.product_code
     978    FOR UPDATE;
     979
     980    IF v_available_quantity IS NULL THEN
     981        RAISE EXCEPTION
     982            'Product % does not exist or is not available.',
     983            NEW.product_code;
     984    END IF;
     985
     986    IF TG_OP = 'INSERT' THEN
     987        IF NEW.quantity > v_available_quantity THEN
     988            RAISE EXCEPTION
     989                'Insufficient stock for product %. Available: %, requested: %.',
     990                NEW.product_code,
     991                v_available_quantity,
     992                NEW.quantity;
     993        END IF;
     994
     995    ELSIF TG_OP = 'UPDATE' THEN
     996        IF NEW.product_code = OLD.product_code THEN
     997            IF NEW.quantity > OLD.quantity
     998               AND (NEW.quantity - OLD.quantity) > v_available_quantity THEN
     999                RAISE EXCEPTION
     1000                    'Insufficient stock for product %. Available: %, additional requested: %.',
     1001                    NEW.product_code,
     1002                    v_available_quantity,
     1003                    NEW.quantity - OLD.quantity;
     1004            END IF;
     1005        ELSE
     1006            IF NEW.quantity > v_available_quantity THEN
     1007                RAISE EXCEPTION
     1008                    'Insufficient stock for product %. Available: %, requested: %.',
     1009                    NEW.product_code,
     1010                    v_available_quantity,
     1011                    NEW.quantity;
     1012            END IF;
     1013        END IF;
     1014    END IF;
     1015
     1016    RETURN NEW;
     1017END;
     1018$$;
     1019
     1020DROP TRIGGER IF EXISTS trg_check_product_availability ON includes;
     1021
     1022CREATE TRIGGER trg_check_product_availability
     1023BEFORE INSERT OR UPDATE
     1024ON includes
     1025FOR EACH ROW
     1026EXECUTE FUNCTION check_product_availability();
     1027
     1028
     1029CREATE OR REPLACE FUNCTION update_product_availability()
     1030RETURNS TRIGGER
     1031LANGUAGE plpgsql
     1032AS $$
     1033BEGIN
     1034    IF TG_OP = 'INSERT' THEN
     1035
     1036        UPDATE product
     1037        SET availability = availability - NEW.quantity
     1038        WHERE code = NEW.product_code;
     1039
     1040    ELSIF TG_OP = 'UPDATE' THEN
     1041
     1042        IF NEW.product_code = OLD.product_code THEN
     1043
     1044            UPDATE product
     1045            SET availability = availability - (NEW.quantity - OLD.quantity)
     1046            WHERE code = NEW.product_code;
     1047
     1048        ELSE
     1049
     1050            UPDATE product
     1051            SET availability = availability + OLD.quantity
     1052            WHERE code = OLD.product_code;
     1053
     1054            UPDATE product
     1055            SET availability = availability - NEW.quantity
     1056            WHERE code = NEW.product_code;
     1057
     1058        END IF;
     1059
     1060    ELSIF TG_OP = 'DELETE' THEN
     1061
     1062        UPDATE product
     1063        SET availability = availability + OLD.quantity
     1064        WHERE code = OLD.product_code;
     1065
     1066    END IF;
     1067
     1068    RETURN NULL;
     1069END;
     1070$$;
     1071
     1072DROP TRIGGER IF EXISTS trg_update_product_availability ON includes;
     1073
     1074CREATE TRIGGER trg_update_product_availability
     1075AFTER INSERT OR UPDATE OR DELETE
     1076ON includes
     1077FOR EACH ROW
     1078EXECUTE FUNCTION update_product_availability();
     1079
     1080
     1081CREATE OR REPLACE FUNCTION delete_product_changes()
     1082RETURNS TRIGGER
     1083LANGUAGE plpgsql
     1084AS $$
     1085BEGIN
     1086    DELETE FROM "change"
     1087    WHERE product_code = OLD.code;
     1088
     1089    RETURN OLD;
     1090END;
     1091$$;
     1092
     1093DROP TRIGGER IF EXISTS trg_delete_product_changes ON product;
     1094
     1095CREATE TRIGGER trg_delete_product_changes
     1096BEFORE DELETE
     1097ON product
     1098FOR EACH ROW
     1099EXECUTE FUNCTION delete_product_changes();
     1100
     1101
     1102CREATE OR REPLACE FUNCTION prevent_store_deletion()
     1103RETURNS TRIGGER
     1104LANGUAGE plpgsql
     1105AS $$
     1106BEGIN
     1107    IF EXISTS (
     1108        SELECT 1
     1109        FROM report
     1110        WHERE store_ID = OLD.store_ID
     1111    ) THEN
     1112        RAISE EXCEPTION
     1113            'Store % cannot be deleted because it has existing reports.',
     1114            OLD.store_ID;
     1115    END IF;
     1116
     1117    IF EXISTS (
     1118        SELECT 1
     1119        FROM sells
     1120        WHERE store_ID = OLD.store_ID
     1121    ) THEN
     1122        RAISE EXCEPTION
     1123            'Store % cannot be deleted because it has existing product records.',
     1124            OLD.store_ID;
     1125    END IF;
     1126
     1127    IF EXISTS (
     1128        SELECT 1
     1129        FROM "order"
     1130        WHERE LEFT(order_num, 3) = OLD.store_ID
     1131    ) THEN
     1132        RAISE EXCEPTION
     1133            'Store % cannot be deleted because it has existing orders.',
     1134            OLD.store_ID;
     1135    END IF;
     1136
     1137    RETURN OLD;
     1138END;
     1139$$;
     1140
     1141DROP TRIGGER IF EXISTS trg_prevent_store_deletion ON store;
     1142
     1143CREATE TRIGGER trg_prevent_store_deletion
     1144BEFORE DELETE
     1145ON store
     1146FOR EACH ROW
     1147EXECUTE FUNCTION prevent_store_deletion();
     1148
     1149
     1150CREATE OR REPLACE FUNCTION update_order_modified_date()
     1151RETURNS TRIGGER
     1152LANGUAGE plpgsql
     1153AS $$
     1154BEGIN
     1155    NEW.last_date_mod := CURRENT_TIMESTAMP;
     1156    RETURN NEW;
     1157END;
     1158$$;
     1159
     1160DROP TRIGGER IF EXISTS trg_update_order_modified_date ON "order";
     1161
     1162CREATE TRIGGER trg_update_order_modified_date
     1163BEFORE UPDATE
     1164ON "order"
     1165FOR EACH ROW
     1166EXECUTE FUNCTION update_order_modified_date();
     1167
     1168
     1169CREATE OR REPLACE FUNCTION validate_employee_authorization()
     1170RETURNS TRIGGER
     1171LANGUAGE plpgsql
     1172AS $$
     1173DECLARE
     1174    v_authorisation TEXT;
     1175BEGIN
     1176    SELECT p.authorisation
     1177    INTO v_authorisation
     1178    FROM permissions p
     1179    WHERE p.personal_id = NEW.personal_id;
     1180
     1181    IF v_authorisation IS NULL THEN
     1182        RAISE EXCEPTION
     1183            'Employee % does not have valid authorization.',
     1184            NEW.personal_id;
     1185    END IF;
     1186
     1187    -- makes_change has no authorisation column in the supplied schema.
     1188    -- The employee's permission row is therefore the source of truth.
     1189    RETURN NEW;
     1190END;
     1191$$;
     1192
     1193DROP TRIGGER IF EXISTS trg_validate_employee_authorization ON makes_change;
     1194
     1195CREATE TRIGGER trg_validate_employee_authorization
     1196BEFORE INSERT OR UPDATE
     1197ON makes_change
     1198FOR EACH ROW
     1199EXECUTE FUNCTION validate_employee_authorization();
     1200
     1201
     1202CREATE OR REPLACE FUNCTION initialize_employee_statistics()
     1203RETURNS TRIGGER
     1204LANGUAGE plpgsql
     1205AS $$
     1206BEGIN
     1207    /*
     1208     * The schema creates an employee before store/report assignment in the
     1209     * normal application flow. If an assignment and a report already exist,
     1210     * create a zero-hours starting record; otherwise there is nothing to
     1211     * initialize yet.
     1212     */
     1213    INSERT INTO worked (
     1214        personal_id,
     1215        report_date,
     1216        store_ID,
     1217        wage,
     1218        pay_method,
     1219        total_hours,
     1220        week
     1221    )
     1222    SELECT
     1223        NEW.employee_id,
     1224        r.date,
     1225        wis.store_ID,
     1226        0,
     1227        'full_time',
     1228        0,
     1229        TO_CHAR(CURRENT_DATE - INTERVAL '6 days', 'DD.MM.YYYY')
     1230            || ' - ' ||
     1231        TO_CHAR(CURRENT_DATE, 'DD.MM.YYYY')
     1232    FROM works_in_store wis
     1233    JOIN LATERAL (
     1234        SELECT r2.date
     1235        FROM report r2
     1236        WHERE r2.store_ID = wis.store_ID
     1237        ORDER BY r2.date DESC
     1238        LIMIT 1
     1239    ) r ON TRUE
     1240    WHERE wis.personal_id = NEW.employee_id
     1241    ON CONFLICT (personal_id, report_date, store_ID) DO NOTHING;
     1242
     1243    RETURN NEW;
     1244END;
     1245$$;
     1246
     1247DROP TRIGGER IF EXISTS trg_initialize_employee_statistics ON employees;
     1248
     1249CREATE TRIGGER trg_initialize_employee_statistics
     1250AFTER INSERT
     1251ON employees
     1252FOR EACH ROW
     1253EXECUTE FUNCTION initialize_employee_statistics();
     1254`;
     1255
     1256
     1257const VIEWS_SQL = String.raw`
     1258-- ============================================================
     1259-- HANDCRAFT MARKETPLACE VIEWS
     1260-- Compatible with the supplied project schema.
     1261-- ============================================================
     1262
     1263DROP VIEW IF EXISTS vw_product_store_overview;
     1264CREATE VIEW vw_product_store_overview AS
     1265SELECT
     1266    p.code AS product_code,
     1267    p.description,
     1268    p.price,
     1269    p.availability,
     1270    p.weight,
     1271    p.width_x_length_x_depth,
     1272    p.aprox_production_time,
     1273    s.store_ID,
     1274    s.name AS store_name,
     1275    s.physical_address,
     1276    s.rating,
     1277    sl.discount
     1278FROM product p
     1279JOIN sells sl
     1280    ON sl.product_code = p.code
     1281JOIN store s
     1282    ON s.store_ID = sl.store_ID;
     1283
     1284
     1285DROP VIEW IF EXISTS vw_customer_order_overview;
     1286CREATE VIEW vw_customer_order_overview AS
     1287SELECT
     1288    o.order_num,
     1289    i.quantity,
     1290    o.status,
     1291    o.last_date_mod,
     1292    o.payment_method,
     1293    o.discount,
     1294    c.client_ID,
     1295    c.first_name,
     1296    c.last_name,
     1297    c.email,
     1298    p.code AS product_code,
     1299    p.description AS product_description,
     1300    p.price
     1301FROM "order" o
     1302JOIN makes_request mr
     1303    ON mr.order_num = o.order_num
     1304JOIN client c
     1305    ON c.client_ID = mr.client_ID
     1306JOIN includes i
     1307    ON i.order_num = o.order_num
     1308JOIN product p
     1309    ON p.code = i.product_code;
     1310
     1311
     1312DROP VIEW IF EXISTS vw_monthly_sales_profit;
     1313CREATE VIEW vw_monthly_sales_profit AS
     1314SELECT
     1315    s.store_ID,
     1316    s.name AS store_name,
     1317    r.date AS report_date,
     1318    mp.month_and_year,
     1319    mp.profit,
     1320    r.overall_profit,
     1321    ed.monthly_profit,
     1322    ed.sales,
     1323    ed.damages
     1324FROM store s
     1325JOIN report r
     1326    ON r.store_ID = s.store_ID
     1327LEFT JOIN monthly_profit mp
     1328    ON mp.report_date = r.date
     1329   AND mp.store_ID = r.store_ID
     1330LEFT JOIN exchanges_data ed
     1331    ON ed.report_date = r.date
     1332   AND ed.store_ID = r.store_ID;
     1333
     1334
     1335DROP VIEW IF EXISTS vw_employee_workload_salary;
     1336CREATE VIEW vw_employee_workload_salary AS
     1337SELECT
     1338    e.employee_id,
     1339    p.first_name,
     1340    p.last_name,
     1341    p.email,
     1342    e.date_of_hire,
     1343    s.store_ID,
     1344    s.name AS store_name,
     1345    w.week,
     1346    w.total_hours,
     1347    w.wage,
     1348    w.pay_method,
     1349    COALESCE(w.wage * w.total_hours, 0) AS total_pay
     1350FROM employees e
     1351JOIN personal p
     1352    ON p.id = e.employee_id
     1353JOIN works_in_store wis
     1354    ON wis.personal_id = e.employee_id
     1355JOIN store s
     1356    ON s.store_ID = wis.store_ID
     1357LEFT JOIN worked w
     1358    ON w.personal_id = e.employee_id
     1359   AND w.store_ID = wis.store_ID;
     1360
     1361
     1362DROP VIEW IF EXISTS vw_customer_request_response;
     1363CREATE VIEW vw_customer_request_response AS
     1364SELECT
     1365    r.request_num,
     1366    r.date_and_time,
     1367    r.problem,
     1368    r.notes_of_communication,
     1369    r.customer_satisfaction,
     1370    c.client_ID,
     1371    c.first_name AS client_first_name,
     1372    c.last_name AS client_last_name,
     1373    c.email AS client_email,
     1374    p.id AS employee_id,
     1375    p.first_name AS employee_first_name,
     1376    p.last_name AS employee_last_name
     1377FROM request r
     1378LEFT JOIN client c
     1379    ON c.client_ID = CASE
     1380        WHEN SUBSTRING(r.request_num FROM 9 FOR 4) ~ '^[0-9]{4}$'
     1381        THEN SUBSTRING(r.request_num FROM 9 FOR 4)::INTEGER
     1382        ELSE NULL
     1383    END
     1384LEFT JOIN answers a
     1385    ON a.request_num = r.request_num
     1386LEFT JOIN personal p
     1387    ON p.id = a.personal_id;
     1388
     1389
     1390DROP VIEW IF EXISTS vw_store_inventory;
     1391CREATE VIEW vw_store_inventory AS
     1392SELECT
     1393    s.store_ID,
     1394    s.name AS store_name,
     1395    p.code AS product_code,
     1396    p.description,
     1397    p.price,
     1398    p.availability,
     1399    p.aprox_production_time,
     1400    sl.discount
     1401FROM store s
     1402JOIN sells sl
     1403    ON sl.store_ID = s.store_ID
     1404JOIN product p
     1405    ON p.code = sl.product_code;
     1406
     1407
     1408DROP VIEW IF EXISTS vw_customer_order_history;
     1409CREATE VIEW vw_customer_order_history AS
     1410SELECT
     1411    c.client_ID,
     1412    c.first_name,
     1413    c.last_name,
     1414    c.email,
     1415    o.order_num,
     1416    i.quantity,
     1417    o.status,
     1418    o.last_date_mod,
     1419    o.payment_method,
     1420    o.discount,
     1421    p.code AS product_code,
     1422    p.description AS product_description,
     1423    p.price
     1424FROM client c
     1425JOIN makes_request mr
     1426    ON mr.client_ID = c.client_ID
     1427JOIN "order" o
     1428    ON o.order_num = mr.order_num
     1429JOIN includes i
     1430    ON i.order_num = o.order_num
     1431JOIN product p
     1432    ON p.code = i.product_code;
     1433
     1434
     1435DROP VIEW IF EXISTS vw_store_performance;
     1436CREATE VIEW vw_store_performance AS
     1437SELECT
     1438    s.store_ID,
     1439    s.name AS store_name,
     1440    s.date_of_founding,
     1441    s.rating,
     1442    COUNT(DISTINCT r.date) AS number_of_reports,
     1443    COALESCE(SUM(mp.profit), 0) AS total_reported_profit,
     1444    COALESCE(MAX(r.overall_profit), 0) AS overall_profit,
     1445    COALESCE(SUM(ed.sales), 0) AS total_sales,
     1446    COALESCE(SUM(ed.damages), 0) AS total_damages,
     1447    COALESCE(SUM(ed.monthly_profit), 0) AS total_monthly_profit
     1448FROM store s
     1449LEFT JOIN report r
     1450    ON r.store_ID = s.store_ID
     1451LEFT JOIN monthly_profit mp
     1452    ON mp.report_date = r.date
     1453   AND mp.store_ID = r.store_ID
     1454LEFT JOIN exchanges_data ed
     1455    ON ed.report_date = r.date
     1456   AND ed.store_ID = r.store_ID
     1457GROUP BY
     1458    s.store_ID,
     1459    s.name,
     1460    s.date_of_founding,
     1461    s.rating;
     1462`;
     1463
     1464
    9131465const database = {
    9141466    database: {
    … …  
    10331585        await pool.query(REPORT_FUNCTIONS_SQL);
    10341586        console.log('✅ PostgreSQL report functions installed');
     1587    },
     1588
     1589    async installTriggersAndViews() {
     1590        await pool.query(TRIGGERS_SQL);
     1591        console.log('✅ PostgreSQL triggers installed');
     1592
     1593        await pool.query(VIEWS_SQL);
     1594        console.log('✅ PostgreSQL views installed');
    10351595    },
    10361596
    … …  
    21622722        await database.initializeDatabase();
    21632723        await database.installReportFunctions();
     2724        await database.installTriggersAndViews();
    21642725        console.log('✅ Database initialization completed');
    21652726    } catch (err) {
Note: See TracChangeset for help on using the changeset viewer.