Changeset 6c7cfa6 for database.js


Ignore:
Timestamp:
09/21/26 00:41:03 (9 days ago)
Author:
Klimentina Efremova <klimentina08642@…>
Branches:
main
Children:
2d1ec46
Parents:
6149556
git-author:
Klimentina Efremova <klimentina08642@…> (09/21/26 00:24:09)
git-committer:
Klimentina Efremova <klimentina08642@…> (09/21/26 00:41:03)
Message:

Implemented Advaced database reports

File:
1 edited

Legend:

Unmodified
Added
Removed
  • database.js

    r6149556 r6c7cfa6  
    13831383
    13841384    query(
    1385         `SELECT *
    1386          FROM report
    1387          WHERE store_id = $1
    1388          ORDER BY generated_at DESC`,
     1385        `SELECT
     1386            r.date,
     1387            r.store_id,
     1388            r.overall_profit,
     1389            r.sales_trend,
     1390            r.marketing_growth,
     1391            r.owner_signature
     1392         FROM report r
     1393         WHERE r.store_id = $1
     1394         ORDER BY r.date DESC`,
    13891395        [storeId],
    13901396        (err, result) => {
    1391 
    13921397            callback(
    13931398                err,
    … …  
    14041409
    14051410    query(
    1406         `SELECT COUNT(*) AS total_products
    1407          FROM product
    1408          WHERE store_id = $1`,
     1411        `SELECT COUNT(DISTINCT se.product_code) AS total_products
     1412         FROM sells se
     1413         WHERE se.store_id = $1`,
    14091414        [storeId],
    14101415        (err, result) => {
    … …  
    14151420            }
    14161421
    1417             stats.total_products =
    1418                 result.rows[0]
    1419                     ? Number(result.rows[0].total_products)
    1420                     : 0;
     1422            stats.total_products = Number(
     1423                result.rows[0]?.total_products || 0
     1424            );
    14211425
    14221426            query(
    1423                 `SELECT COUNT(*) AS total_orders
    1424                  FROM "order"
    1425                  WHERE store_id = $1`,
     1427                `SELECT COUNT(DISTINCT o.order_num) AS total_orders
     1428                 FROM sells se
     1429                 JOIN includes i
     1430                   ON i.product_code = se.product_code
     1431                 JOIN "order" o
     1432                   ON o.order_num = i.order_num
     1433                 WHERE se.store_id = $1`,
    14261434                [storeId],
    14271435                (err, result) => {
    … …  
    14321440                    }
    14331441
    1434                     stats.total_orders =
    1435                         result.rows[0]
    1436                             ? Number(result.rows[0].total_orders)
    1437                             : 0;
     1442                    stats.total_orders = Number(
     1443                        result.rows[0]?.total_orders || 0
     1444                    );
    14381445
    14391446                    query(
    … …  
    14411448                            COALESCE(
    14421449                                SUM(
    1443                                     oi.price * oi.quantity
     1450                                    p.price * i.quantity
     1451                                    * (1 - COALESCE(o.discount, 0) / 100.0)
    14441452                                ),
    14451453                                0
    14461454                            ) AS total_revenue
    1447                          FROM order_items oi
     1455                         FROM sells se
     1456                         JOIN product p
     1457                           ON p.code = se.product_code
     1458                         JOIN includes i
     1459                           ON i.product_code = se.product_code
    14481460                         JOIN "order" o
    1449                            ON oi.order_num =
    1450                               o.order_num
    1451                          WHERE o.store_id = $1`,
     1461                           ON o.order_num = i.order_num
     1462                         WHERE se.store_id = $1`,
    14521463                        [storeId],
    14531464                        (err, result) => {
    … …  
    14581469                            }
    14591470
    1460                             stats.total_revenue =
    1461                                 Number(
    1462                                     result.rows[0]
    1463                                         .total_revenue || 0
    1464                                 );
     1471                            stats.total_revenue = Number(
     1472                                result.rows[0]?.total_revenue || 0
     1473                            );
    14651474
    14661475                            query(
    1467                                 `SELECT
    1468                                     COALESCE(
    1469                                         AVG(r.rating),
    1470                                         0
    1471                                     ) AS avg_rating
    1472                                  FROM review r
    1473                                  JOIN product p
    1474                                    ON r.product_code =
    1475                                       p.code
    1476                                  WHERE p.store_id = $1`,
     1476                                `WITH store_reviews AS (
     1477                                    SELECT DISTINCT
     1478                                        r.order_num,
     1479                                        r.rating
     1480                                    FROM review r
     1481                                    JOIN includes i
     1482                                      ON i.order_num = r.order_num
     1483                                    JOIN sells se
     1484                                      ON se.product_code = i.product_code
     1485                                    WHERE se.store_id = $1
     1486                                )
     1487                                SELECT COALESCE(AVG(rating), 0) AS avg_rating
     1488                                FROM store_reviews`,
    14771489                                [storeId],
    14781490                                (err, result) => {
    … …  
    14831495                                    }
    14841496
    1485                                     stats.avg_rating =
    1486                                         Number(
    1487                                             result.rows[0]
    1488                                                 .avg_rating || 0
    1489                                         );
    1490 
    1491                                     callback(
    1492                                         null,
    1493                                         stats
     1497                                    stats.avg_rating = Number(
     1498                                        result.rows[0]?.avg_rating || 0
    14941499                                    );
     1500
     1501                                    callback(null, stats);
    14951502                                }
    14961503                            );
    … …  
    22932300
    22942301
     2302
     2303
     2304/*
     2305 * ============================================================
     2306 * ADVANCED REPORTS
     2307 * ============================================================
     2308 */
     2309
     2310
     2311const REPORT_FUNCTIONS_SQL = String.raw`
     2312-- ============================================================
     2313-- HANDCRAFT MARKETPLACE REPORT FUNCTIONS
     2314-- PostgreSQL / exact project schema
     2315-- ============================================================
     2316
     2317CREATE OR REPLACE FUNCTION get_orders_by_total()
     2318RETURNS TABLE (
     2319    order_num VARCHAR(11),
     2320    client_id INTEGER,
     2321    client_name TEXT,
     2322    order_quantity BIGINT,
     2323    order_status VARCHAR(20),
     2324    payment_method VARCHAR(250),
     2325    discount NUMERIC,
     2326    order_total NUMERIC
     2327)
     2328LANGUAGE sql
     2329AS $$
     2330    SELECT
     2331        o.order_num,
     2332        o.client_id,
     2333        CONCAT_WS(' ', c.first_name, c.last_name) AS client_name,
     2334        COALESCE(SUM(i.quantity), 0)::BIGINT AS order_quantity,
     2335        o.status,
     2336        o.payment_method,
     2337        COALESCE(o.discount, 0)::NUMERIC AS discount,
     2338        ROUND(
     2339            COALESCE(SUM(p.price * i.quantity), 0)
     2340            * (1 - COALESCE(o.discount, 0) / 100.0),
     2341            2
     2342        ) AS order_total
     2343    FROM "order" o
     2344    LEFT JOIN client c ON c.client_id = o.client_id
     2345    LEFT JOIN includes i ON i.order_num = o.order_num
     2346    LEFT JOIN product p ON p.code = i.product_code
     2347    GROUP BY
     2348        o.order_num, o.client_id, c.first_name, c.last_name,
     2349        o.status, o.payment_method, o.discount
     2350    ORDER BY order_total DESC, o.order_num;
     2351$$;
     2352
     2353CREATE OR REPLACE FUNCTION get_products_by_total_sales()
     2354RETURNS TABLE (
     2355    product_code VARCHAR(8),
     2356    product_description VARCHAR(500),
     2357    product_price NUMERIC,
     2358    number_of_orders BIGINT,
     2359    total_quantity_sold BIGINT,
     2360    total_revenue NUMERIC
     2361)
     2362LANGUAGE sql
     2363AS $$
     2364    SELECT
     2365        p.code,
     2366        p.description,
     2367        p.price::NUMERIC,
     2368        COUNT(DISTINCT i.order_num) AS number_of_orders,
     2369        COALESCE(SUM(i.quantity), 0)::BIGINT AS total_quantity_sold,
     2370        ROUND(
     2371            COALESCE(
     2372                SUM(
     2373                    p.price * i.quantity
     2374                    * (1 - COALESCE(o.discount, 0) / 100.0)
     2375                ),
     2376                0
     2377            ),
     2378            2
     2379        ) AS total_revenue
     2380    FROM product p
     2381    LEFT JOIN includes i ON i.product_code = p.code
     2382    LEFT JOIN "order" o ON o.order_num = i.order_num
     2383    GROUP BY p.code, p.description, p.price
     2384    ORDER BY number_of_orders DESC, total_quantity_sold DESC, total_revenue DESC;
     2385$$;
     2386
     2387CREATE OR REPLACE FUNCTION get_low_stock_high_demand_products(
     2388    p_stock_threshold INTEGER,
     2389    p_demand_threshold INTEGER
     2390)
     2391RETURNS TABLE (
     2392    product_code VARCHAR(8),
     2393    product_description VARCHAR(500),
     2394    current_stock INTEGER,
     2395    number_of_orders BIGINT,
     2396    total_quantity_sold BIGINT
     2397)
     2398LANGUAGE sql
     2399AS $$
     2400    SELECT
     2401        p.code,
     2402        p.description,
     2403        p.availability,
     2404        COUNT(DISTINCT i.order_num),
     2405        COALESCE(SUM(i.quantity), 0)::BIGINT
     2406    FROM product p
     2407    JOIN includes i ON i.product_code = p.code
     2408    GROUP BY p.code, p.description, p.availability
     2409    HAVING
     2410        p.availability < p_stock_threshold
     2411        AND COUNT(DISTINCT i.order_num) >= p_demand_threshold
     2412    ORDER BY number_of_orders DESC, total_quantity_sold DESC, current_stock ASC;
     2413$$;
     2414
     2415CREATE OR REPLACE FUNCTION get_products_monthly_sales()
     2416RETURNS TABLE (
     2417    product_code VARCHAR(8),
     2418    product_description VARCHAR(500),
     2419    year INTEGER,
     2420    month INTEGER,
     2421    number_of_orders BIGINT,
     2422    total_quantity_sold BIGINT,
     2423    total_revenue NUMERIC
     2424)
     2425LANGUAGE sql
     2426AS $$
     2427    SELECT
     2428        p.code,
     2429        p.description,
     2430        EXTRACT(YEAR FROM o.last_date_mod)::INTEGER,
     2431        EXTRACT(MONTH FROM o.last_date_mod)::INTEGER,
     2432        COUNT(DISTINCT o.order_num),
     2433        COALESCE(SUM(i.quantity), 0)::BIGINT,
     2434        ROUND(
     2435            COALESCE(
     2436                SUM(
     2437                    p.price * i.quantity
     2438                    * (1 - COALESCE(o.discount, 0) / 100.0)
     2439                ),
     2440                0
     2441            ),
     2442            2
     2443        )
     2444    FROM product p
     2445    JOIN includes i ON i.product_code = p.code
     2446    JOIN "order" o ON o.order_num = i.order_num
     2447    GROUP BY
     2448        p.code, p.description,
     2449        EXTRACT(YEAR FROM o.last_date_mod),
     2450        EXTRACT(MONTH FROM o.last_date_mod)
     2451    ORDER BY year DESC, month DESC, total_revenue DESC;
     2452$$;
     2453
     2454CREATE OR REPLACE FUNCTION get_stores_by_last_calendar_year_revenue()
     2455RETURNS TABLE (
     2456    store_id VARCHAR(3),
     2457    store_name VARCHAR(50),
     2458    number_of_orders BIGINT,
     2459    total_quantity_sold BIGINT,
     2460    total_revenue NUMERIC
     2461)
     2462LANGUAGE sql
     2463AS $$
     2464    SELECT
     2465        s.store_id,
     2466        s.name,
     2467        COUNT(DISTINCT o.order_num),
     2468        COALESCE(SUM(i.quantity), 0)::BIGINT,
     2469        ROUND(
     2470            COALESCE(
     2471                SUM(
     2472                    p.price * i.quantity
     2473                    * (1 - COALESCE(o.discount, 0) / 100.0)
     2474                ),
     2475                0
     2476            ),
     2477            2
     2478        )
     2479    FROM store s
     2480    LEFT JOIN sells se ON se.store_id = s.store_id
     2481    LEFT JOIN product p ON p.code = se.product_code
     2482    LEFT JOIN includes i ON i.product_code = p.code
     2483    LEFT JOIN "order" o
     2484        ON o.order_num = i.order_num
     2485       AND o.last_date_mod >= DATE_TRUNC('year', CURRENT_DATE) - INTERVAL '1 year'
     2486       AND o.last_date_mod < DATE_TRUNC('year', CURRENT_DATE)
     2487    GROUP BY s.store_id, s.name
     2488    ORDER BY total_revenue DESC, s.store_id;
     2489$$;
     2490
     2491CREATE OR REPLACE FUNCTION get_products_never_ordered()
     2492RETURNS TABLE (
     2493    product_code VARCHAR(8),
     2494    product_description VARCHAR(500),
     2495    product_price NUMERIC,
     2496    current_stock INTEGER
     2497)
     2498LANGUAGE sql
     2499AS $$
     2500    SELECT p.code, p.description, p.price::NUMERIC, p.availability
     2501    FROM product p
     2502    WHERE NOT EXISTS (
     2503        SELECT 1
     2504        FROM includes i
     2505        WHERE i.product_code = p.code
     2506    )
     2507    ORDER BY p.code;
     2508$$;
     2509
     2510CREATE OR REPLACE FUNCTION get_products_by_number_of_orders()
     2511RETURNS TABLE (
     2512    product_code VARCHAR(8),
     2513    product_description VARCHAR(500),
     2514    product_price NUMERIC,
     2515    number_of_orders BIGINT
     2516)
     2517LANGUAGE sql
     2518AS $$
     2519    SELECT
     2520        p.code,
     2521        p.description,
     2522        p.price::NUMERIC,
     2523        COUNT(DISTINCT i.order_num)
     2524    FROM product p
     2525    JOIN includes i ON i.product_code = p.code
     2526    GROUP BY p.code, p.description, p.price
     2527    ORDER BY number_of_orders DESC, p.code;
     2528$$;
     2529
     2530CREATE OR REPLACE FUNCTION get_stores_by_average_review()
     2531RETURNS TABLE (
     2532    store_id VARCHAR(3),
     2533    store_name VARCHAR(50),
     2534    average_review NUMERIC,
     2535    number_of_reviews BIGINT
     2536)
     2537LANGUAGE sql
     2538AS $$
     2539    WITH store_reviews AS (
     2540        SELECT DISTINCT
     2541            s.store_id,
     2542            s.name AS store_name,
     2543            r.order_num,
     2544            r.rating
     2545        FROM store s
     2546        JOIN sells se ON se.store_id = s.store_id
     2547        JOIN includes i ON i.product_code = se.product_code
     2548        JOIN review r ON r.order_num = i.order_num
     2549    )
     2550    SELECT
     2551        s.store_id,
     2552        s.name,
     2553        COALESCE(ROUND(AVG(sr.rating), 2), 0)::NUMERIC,
     2554        COUNT(sr.order_num)
     2555    FROM store s
     2556    LEFT JOIN store_reviews sr ON sr.store_id = s.store_id
     2557    GROUP BY s.store_id, s.name
     2558    ORDER BY average_review DESC, s.store_id;
     2559$$;
     2560
     2561CREATE OR REPLACE FUNCTION get_store_with_highest_revenue_growth()
     2562RETURNS TABLE (
     2563    store_id VARCHAR(3),
     2564    store_name VARCHAR(50),
     2565    previous_year_revenue NUMERIC,
     2566    last_year_revenue NUMERIC,
     2567    revenue_growth NUMERIC
     2568)
     2569LANGUAGE sql
     2570AS $$
     2571    WITH store_years AS (
     2572        SELECT
     2573            s.store_id,
     2574            s.name AS store_name,
     2575            EXTRACT(YEAR FROM o.last_date_mod)::INTEGER AS sales_year,
     2576            SUM(
     2577                p.price * i.quantity
     2578                * (1 - COALESCE(o.discount, 0) / 100.0)
     2579            ) AS revenue
     2580        FROM store s
     2581        JOIN sells se ON se.store_id = s.store_id
     2582        JOIN includes i ON i.product_code = se.product_code
     2583        JOIN product p ON p.code = i.product_code
     2584        JOIN "order" o ON o.order_num = i.order_num
     2585        WHERE o.last_date_mod >= DATE_TRUNC('year', CURRENT_DATE) - INTERVAL '2 years'
     2586          AND o.last_date_mod < DATE_TRUNC('year', CURRENT_DATE)
     2587        GROUP BY s.store_id, s.name, EXTRACT(YEAR FROM o.last_date_mod)
     2588    ),
     2589    comparison AS (
     2590        SELECT
     2591            s.store_id,
     2592            s.name AS store_name,
     2593            COALESCE(MAX(CASE
     2594                WHEN sy.sales_year = EXTRACT(YEAR FROM CURRENT_DATE)::INTEGER - 2
     2595                THEN sy.revenue ELSE 0 END), 0) AS previous_year_revenue,
     2596            COALESCE(MAX(CASE
     2597                WHEN sy.sales_year = EXTRACT(YEAR FROM CURRENT_DATE)::INTEGER - 1
     2598                THEN sy.revenue ELSE 0 END), 0) AS last_year_revenue
     2599        FROM store s
     2600        LEFT JOIN store_years sy ON sy.store_id = s.store_id
     2601        GROUP BY s.store_id, s.name
     2602    )
     2603    SELECT
     2604        store_id,
     2605        store_name,
     2606        ROUND(previous_year_revenue, 2),
     2607        ROUND(last_year_revenue, 2),
     2608        ROUND(last_year_revenue - previous_year_revenue, 2)
     2609    FROM comparison
     2610    ORDER BY revenue_growth DESC, store_id
     2611    LIMIT 1;
     2612$$;
     2613
     2614CREATE OR REPLACE FUNCTION get_clients_by_number_of_orders()
     2615RETURNS TABLE (
     2616    client_id INTEGER,
     2617    client_name TEXT,
     2618    number_of_orders BIGINT
     2619)
     2620LANGUAGE sql
     2621AS $$
     2622    SELECT
     2623        c.client_id,
     2624        CONCAT_WS(' ', c.first_name, c.last_name),
     2625        COUNT(o.order_num)
     2626    FROM client c
     2627    JOIN "order" o ON o.client_id = c.client_id
     2628    GROUP BY c.client_id, c.first_name, c.last_name
     2629    ORDER BY number_of_orders DESC, c.client_id;
     2630$$;
     2631
     2632CREATE OR REPLACE FUNCTION get_approximate_orders_per_client()
     2633RETURNS TABLE (
     2634    total_clients BIGINT,
     2635    total_orders BIGINT,
     2636    approximate_orders_per_client NUMERIC
     2637)
     2638LANGUAGE sql
     2639AS $$
     2640    SELECT
     2641        (SELECT COUNT(*) FROM client),
     2642        (SELECT COUNT(*) FROM "order"),
     2643        ROUND(
     2644            (SELECT COUNT(*)::NUMERIC FROM "order")
     2645            / NULLIF((SELECT COUNT(*) FROM client), 0),
     2646            2
     2647        );
     2648$$;
     2649
     2650CREATE OR REPLACE FUNCTION get_clients_without_orders()
     2651RETURNS TABLE (
     2652    client_id INTEGER,
     2653    client_name TEXT,
     2654    email VARCHAR(50)
     2655)
     2656LANGUAGE sql
     2657AS $$
     2658    SELECT
     2659        c.client_id,
     2660        CONCAT_WS(' ', c.first_name, c.last_name),
     2661        c.email
     2662    FROM client c
     2663    WHERE NOT EXISTS (
     2664        SELECT 1 FROM "order" o WHERE o.client_id = c.client_id
     2665    )
     2666    ORDER BY c.client_id;
     2667$$;
     2668
     2669CREATE OR REPLACE FUNCTION get_store_request_statistics()
     2670RETURNS TABLE (
     2671    store_id VARCHAR(3),
     2672    store_name VARCHAR(50),
     2673    total_requests BIGINT,
     2674    solved_requests BIGINT,
     2675    requests_in_progress BIGINT
     2676)
     2677LANGUAGE sql
     2678AS $$
     2679    SELECT
     2680        s.store_id,
     2681        s.name,
     2682        COUNT(r.request_num),
     2683        COUNT(r.request_num) FILTER (WHERE r.customer_satisfaction > 0),
     2684        COUNT(r.request_num) FILTER (WHERE r.customer_satisfaction <= 0)
     2685    FROM store s
     2686    LEFT JOIN for_store fs ON fs.store_id = s.store_id
     2687    LEFT JOIN request r ON r.request_num = fs.request_num
     2688    GROUP BY s.store_id, s.name
     2689    ORDER BY total_requests DESC, s.store_id;
     2690$$;
     2691
     2692CREATE OR REPLACE FUNCTION get_top_10_employees_by_requests_last_month()
     2693RETURNS TABLE (
     2694    employee_id VARCHAR(10),
     2695    employee_name TEXT,
     2696    number_of_requests BIGINT
     2697)
     2698LANGUAGE sql
     2699AS $$
     2700    SELECT
     2701        e.employee_id,
     2702        CONCAT_WS(' ', p.first_name, p.last_name),
     2703        COUNT(DISTINCT a.request_num)
     2704    FROM employees e
     2705    JOIN personal p ON p.id = e.employee_id
     2706    JOIN answers a ON a.personal_id = e.employee_id
     2707    JOIN request r ON r.request_num = a.request_num
     2708    WHERE r.date_and_time >= DATE_TRUNC('month', CURRENT_DATE) - INTERVAL '1 month'
     2709      AND r.date_and_time < DATE_TRUNC('month', CURRENT_DATE)
     2710    GROUP BY e.employee_id, p.first_name, p.last_name
     2711    ORDER BY number_of_requests DESC, e.employee_id
     2712    LIMIT 10;
     2713$$;
     2714
     2715CREATE OR REPLACE FUNCTION get_employees_by_hours_and_pay()
     2716RETURNS TABLE (
     2717    employee_id VARCHAR(10),
     2718    employee_name TEXT,
     2719    total_hours_worked NUMERIC,
     2720    total_pay NUMERIC
     2721)
     2722LANGUAGE sql
     2723AS $$
     2724    SELECT
     2725        e.employee_id,
     2726        CONCAT_WS(' ', p.first_name, p.last_name),
     2727        COALESCE(SUM(w.total_hours), 0),
     2728        COALESCE(SUM(w.wage * w.total_hours), 0)
     2729    FROM employees e
     2730    JOIN personal p ON p.id = e.employee_id
     2731    LEFT JOIN worked w ON w.personal_id = e.employee_id
     2732    GROUP BY e.employee_id, p.first_name, p.last_name
     2733    ORDER BY total_hours_worked DESC, total_pay DESC, e.employee_id;
     2734$$;
     2735
     2736CREATE OR REPLACE FUNCTION get_stores_average_pay()
     2737RETURNS TABLE (
     2738    store_id VARCHAR(3),
     2739    store_name VARCHAR(50),
     2740    average_pay NUMERIC
     2741)
     2742LANGUAGE sql
     2743AS $$
     2744    SELECT
     2745        s.store_id,
     2746        s.name,
     2747        COALESCE(ROUND(AVG(w.wage), 2), 0)
     2748    FROM store s
     2749    LEFT JOIN worked w ON w.store_id = s.store_id
     2750    GROUP BY s.store_id, s.name
     2751    ORDER BY average_pay DESC, s.store_id;
     2752$$;
     2753
     2754CREATE OR REPLACE FUNCTION get_employee_product_changes_last_month()
     2755RETURNS TABLE (
     2756    employee_id VARCHAR(10),
     2757    employee_name TEXT,
     2758    number_of_product_changes BIGINT
     2759)
     2760LANGUAGE sql
     2761AS $$
     2762    SELECT
     2763        e.employee_id,
     2764        CONCAT_WS(' ', p.first_name, p.last_name),
     2765        COUNT(mc.change_date_time)
     2766    FROM employees e
     2767    JOIN personal p ON p.id = e.employee_id
     2768    LEFT JOIN makes_change mc
     2769        ON mc.personal_id = e.employee_id
     2770       AND mc.change_date_time >= DATE_TRUNC('month', CURRENT_DATE) - INTERVAL '1 month'
     2771       AND mc.change_date_time < DATE_TRUNC('month', CURRENT_DATE)
     2772    GROUP BY e.employee_id, p.first_name, p.last_name
     2773    ORDER BY number_of_product_changes DESC, e.employee_id;
     2774$$;
     2775
     2776CREATE OR REPLACE FUNCTION get_stores_by_monthly_profit_and_revenue_growth()
     2777RETURNS TABLE (
     2778    store_id VARCHAR(3),
     2779    store_name VARCHAR(50),
     2780    month_and_year TEXT,
     2781    monthly_profit NUMERIC,
     2782    previous_month_revenue NUMERIC,
     2783    current_month_revenue NUMERIC,
     2784    revenue_growth NUMERIC
     2785)
     2786LANGUAGE sql
     2787AS $$
     2788    WITH monthly_revenue AS (
     2789        SELECT
     2790            s.store_id,
     2791            s.name AS store_name,
     2792            DATE_TRUNC('month', o.last_date_mod)::DATE AS month_date,
     2793            SUM(
     2794                p.price * i.quantity
     2795                * (1 - COALESCE(o.discount, 0) / 100.0)
     2796            ) AS revenue
     2797        FROM store s
     2798        JOIN sells se ON se.store_id = s.store_id
     2799        JOIN includes i ON i.product_code = se.product_code
     2800        JOIN product p ON p.code = i.product_code
     2801        JOIN "order" o ON o.order_num = i.order_num
     2802        GROUP BY s.store_id, s.name, DATE_TRUNC('month', o.last_date_mod)
     2803    ),
     2804    with_previous AS (
     2805        SELECT
     2806            store_id,
     2807            store_name,
     2808            month_date,
     2809            revenue,
     2810            LAG(revenue) OVER (
     2811                PARTITION BY store_id
     2812                ORDER BY month_date
     2813            ) AS previous_revenue
     2814        FROM monthly_revenue
     2815    )
     2816    SELECT
     2817        store_id,
     2818        store_name,
     2819        TO_CHAR(month_date, 'YYYY-MM'),
     2820        ROUND(revenue, 2),
     2821        ROUND(COALESCE(previous_revenue, 0), 2),
     2822        ROUND(revenue, 2),
     2823        ROUND(revenue - COALESCE(previous_revenue, 0), 2)
     2824    FROM with_previous
     2825    ORDER BY month_date DESC, monthly_profit DESC, store_id;
     2826$$;
     2827
     2828CREATE OR REPLACE FUNCTION get_unapproved_reports()
     2829RETURNS TABLE (
     2830    report_date TIMESTAMP,
     2831    store_id VARCHAR(3),
     2832    overall_profit NUMERIC,
     2833    sales_trend VARCHAR(100),
     2834    marketing_growth VARCHAR(100),
     2835    owner_signature VARCHAR(50)
     2836)
     2837LANGUAGE sql
     2838AS $$
     2839    SELECT
     2840        r.date,
     2841        r.store_id,
     2842        r.overall_profit,
     2843        r.sales_trend,
     2844        r.marketing_growth,
     2845        r.owner_signature
     2846    FROM report r
     2847    LEFT JOIN approves a
     2848        ON a.report_date = r.date
     2849       AND a.store_id = r.store_id
     2850    WHERE a.report_date IS NULL
     2851    ORDER BY r.date DESC, r.store_id;
     2852$$;
     2853`;
     2854
     2855function installReportFunctions(callback) {
     2856
     2857    pool.query(REPORT_FUNCTIONS_SQL)
     2858        .then(() => {
     2859            console.log('✅ PostgreSQL report functions installed');
     2860            if (callback) callback(null);
     2861        })
     2862        .catch(err => {
     2863            console.error('❌ Failed to install PostgreSQL report functions:', err);
     2864            if (callback) callback(err);
     2865        });
     2866}
     2867
     2868const REPORT_FUNCTION_NAMES = new Set([
     2869    'get_orders_by_total',
     2870    'get_products_by_total_sales',
     2871    'get_low_stock_high_demand_products',
     2872    'get_products_monthly_sales',
     2873    'get_stores_by_last_calendar_year_revenue',
     2874    'get_products_never_ordered',
     2875    'get_products_by_number_of_orders',
     2876    'get_stores_by_average_review',
     2877    'get_store_with_highest_revenue_growth',
     2878    'get_clients_by_number_of_orders',
     2879    'get_approximate_orders_per_client',
     2880    'get_clients_without_orders',
     2881    'get_store_request_statistics',
     2882    'get_top_10_employees_by_requests_last_month',
     2883    'get_employees_by_hours_and_pay',
     2884    'get_stores_average_pay',
     2885    'get_employee_product_changes_last_month',
     2886    'get_stores_by_monthly_profit_and_revenue_growth',
     2887    'get_unapproved_reports'
     2888]);
     2889
     2890function runReport(reportName, params, callback) {
     2891
     2892    if (!REPORT_FUNCTION_NAMES.has(reportName)) {
     2893        callback(new Error('Unknown report: ' + reportName), null);
     2894        return;
     2895    }
     2896
     2897    const values = Array.isArray(params) ? params : [];
     2898
     2899    const placeholders = values.map(
     2900        (_, index) => '$' + (index + 1)
     2901    ).join(', ');
     2902
     2903    query(
     2904        `SELECT * FROM ${reportName}(${placeholders})`,
     2905        values,
     2906        (err, result) => {
     2907            callback(
     2908                err,
     2909                result ? result.rows : []
     2910            );
     2911        }
     2912    );
     2913}
     2914
     2915function generateStoreReport(
     2916    storeId,
     2917    startDate,
     2918    endDate,
     2919    type,
     2920    period,
     2921    ownerSignature,
     2922    callback
     2923) {
     2924
     2925    query(
     2926        `WITH sales AS (
     2927            SELECT
     2928                COALESCE(
     2929                    SUM(
     2930                        p.price * i.quantity
     2931                        * (1 - COALESCE(o.discount, 0) / 100.0)
     2932                    ),
     2933                    0
     2934                ) AS revenue
     2935            FROM sells se
     2936            JOIN product p
     2937              ON p.code = se.product_code
     2938            JOIN includes i
     2939              ON i.product_code = se.product_code
     2940            JOIN "order" o
     2941              ON o.order_num = i.order_num
     2942            WHERE se.store_id = $1
     2943              AND o.last_date_mod >= $2::timestamp
     2944              AND o.last_date_mod < ($3::timestamp + INTERVAL '1 day')
     2945        ),
     2946        refunds AS (
     2947            SELECT
     2948                COALESCE(SUM(rf.amount), 0) AS refund_total
     2949            FROM refund rf
     2950            JOIN "order" o
     2951              ON o.order_num = rf.order_num
     2952            WHERE LEFT(o.order_num, 3) = $1
     2953              AND o.last_date_mod >= $2::timestamp
     2954              AND o.last_date_mod < ($3::timestamp + INTERVAL '1 day')
     2955              AND rf.status IN ('approved', 'processed')
     2956        )
     2957        SELECT
     2958            sales.revenue,
     2959            refunds.refund_total,
     2960            sales.revenue - refunds.refund_total AS net_profit
     2961        FROM sales CROSS JOIN refunds`,
     2962        [storeId, startDate, endDate],
     2963        (err, result) => {
     2964
     2965            if (err) {
     2966                callback(err, null);
     2967                return;
     2968            }
     2969
     2970            const row = result.rows[0] || {};
     2971            const revenue = Number(row.revenue || 0);
     2972            const refundTotal = Number(row.refund_total || 0);
     2973            const netProfit = Number(row.net_profit || 0);
     2974
     2975            const salesTrend =
     2976                `Revenue ${revenue.toFixed(2)}; Refunds ${refundTotal.toFixed(2)}`;
     2977
     2978            query(
     2979                `SELECT
     2980                    COALESCE(SUM(
     2981                        p.price * i.quantity
     2982                        * (1 - COALESCE(o.discount, 0) / 100.0)
     2983                    ), 0) AS revenue
     2984                 FROM sells se
     2985                 JOIN product p
     2986                   ON p.code = se.product_code
     2987                 JOIN includes i
     2988                   ON i.product_code = se.product_code
     2989                 JOIN "order" o
     2990                   ON o.order_num = i.order_num
     2991                 WHERE se.store_id = $1
     2992                   AND o.last_date_mod >= DATE_TRUNC('month', CURRENT_DATE) - INTERVAL '1 month'
     2993                   AND o.last_date_mod < DATE_TRUNC('month', CURRENT_DATE)`,
     2994                [storeId],
     2995                (previousErr, previousResult) => {
     2996
     2997                    if (previousErr) {
     2998                        callback(previousErr, null);
     2999                        return;
     3000                    }
     3001
     3002                    const previousRevenue =
     3003                        Number(previousResult.rows[0]?.revenue || 0);
     3004
     3005                    const growth =
     3006                        previousRevenue === 0
     3007                            ? (revenue > 0 ? 100 : 0)
     3008                            : ((revenue - previousRevenue) / previousRevenue) * 100;
     3009
     3010                    const marketingGrowth =
     3011                        `${growth.toFixed(2)}%`;
     3012
     3013                    query(
     3014                        `INSERT INTO report
     3015                            (
     3016                                date,
     3017                                store_id,
     3018                                overall_profit,
     3019                                sales_trend,
     3020                                marketing_growth,
     3021                                owner_signature
     3022                            )
     3023                         VALUES
     3024                            (
     3025                                CURRENT_TIMESTAMP,
     3026                                $1,
     3027                                $2,
     3028                                $3,
     3029                                $4,
     3030                                $5
     3031                            )
     3032                         RETURNING
     3033                            date,
     3034                            store_id,
     3035                            overall_profit,
     3036                            sales_trend,
     3037                            marketing_growth,
     3038                            owner_signature`,
     3039                        [
     3040                            storeId,
     3041                            Math.max(0, netProfit),
     3042                            salesTrend.slice(0, 100),
     3043                            marketingGrowth.slice(0, 100),
     3044                            ownerSignature || 'Not signed yet'
     3045                        ],
     3046                        (insertErr, insertResult) => {
     3047
     3048                            if (insertErr) {
     3049                                callback(insertErr, null);
     3050                                return;
     3051                            }
     3052
     3053                            const report = insertResult.rows[0];
     3054
     3055                            /*
     3056                             * monthly_profit has a composite primary key of
     3057                             * (report_date, store_id), so it can contain one
     3058                             * summary row per generated report without changing
     3059                             * the project database structure.
     3060                             */
     3061                            query(
     3062                                `INSERT INTO monthly_profit
     3063                                    (
     3064                                        report_date,
     3065                                        store_id,
     3066                                        month_and_year,
     3067                                        profit
     3068                                    )
     3069                                 VALUES
     3070                                    (
     3071                                        $1,
     3072                                        $2,
     3073                                        DATE_TRUNC('month', $3::timestamp)::DATE,
     3074                                        $4
     3075                                    )
     3076                                 ON CONFLICT (report_date, store_id)
     3077                                 DO UPDATE SET
     3078                                    month_and_year = EXCLUDED.month_and_year,
     3079                                    profit = EXCLUDED.profit`,
     3080                                [
     3081                                    report.date,
     3082                                    storeId,
     3083                                    endDate,
     3084                                    Math.max(0, netProfit)
     3085                                ],
     3086                                (monthlyErr) => {
     3087
     3088                                    if (monthlyErr) {
     3089                                        console.error(
     3090                                            'Warning inserting monthly profit:',
     3091                                            monthlyErr
     3092                                        );
     3093                                    }
     3094
     3095                                    query(
     3096                                        `INSERT INTO exchanges_data
     3097                                            (
     3098                                                report_date,
     3099                                                store_id,
     3100                                                monthly_profit,
     3101                                                date,
     3102                                                sales,
     3103                                                damages
     3104                                            )
     3105                                         VALUES
     3106                                            (
     3107                                                $1,
     3108                                                $2,
     3109                                                $3,
     3110                                                CURRENT_TIMESTAMP,
     3111                                                $4,
     3112                                                $5
     3113                                            )
     3114                                         ON CONFLICT (report_date, store_id)
     3115                                         DO UPDATE SET
     3116                                            monthly_profit = EXCLUDED.monthly_profit,
     3117                                            date = EXCLUDED.date,
     3118                                            sales = EXCLUDED.sales,
     3119                                            damages = EXCLUDED.damages`,
     3120                                        [
     3121                                            report.date,
     3122                                            storeId,
     3123                                            Math.max(0, netProfit),
     3124                                            revenue,
     3125                                            -refundTotal
     3126                                        ],
     3127                                        (exchangeErr) => {
     3128
     3129                                            if (exchangeErr) {
     3130                                                console.error(
     3131                                                    'Warning inserting exchange data:',
     3132                                                    exchangeErr
     3133                                                );
     3134                                            }
     3135
     3136                                            callback(
     3137                                                null,
     3138                                                report
     3139                                            );
     3140                                        }
     3141                                    );
     3142                                }
     3143                            );
     3144                        }
     3145                    );
     3146                }
     3147            );
     3148        }
     3149    );
     3150}
    22953151/*
    22963152 * ============================================================
    … …  
    23403196    getStoreStats,
    23413197
     3198    installReportFunctions,
     3199    runReport,
     3200    generateStoreReport,
     3201
    23423202    createOrderNew,
    23433203    getOrdersByClient,
Note: See TracChangeset for help on using the changeset viewer.