Changeset 6c7cfa6 for database.js
- Timestamp:
- 09/21/26 00:41:03 (9 days ago)
- 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)
- File:
-
- 1 edited
-
database.js (modified) (9 diffs)
Legend:
- Unmodified
- Added
- Removed
-
database.js
r6149556 r6c7cfa6 1383 1383 1384 1384 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`, 1389 1395 [storeId], 1390 1396 (err, result) => { 1391 1392 1397 callback( 1393 1398 err, … … 1404 1409 1405 1410 query( 1406 `SELECT COUNT( *) AS total_products1407 FROM product1408 WHERE s tore_id = $1`,1411 `SELECT COUNT(DISTINCT se.product_code) AS total_products 1412 FROM sells se 1413 WHERE se.store_id = $1`, 1409 1414 [storeId], 1410 1415 (err, result) => { … … 1415 1420 } 1416 1421 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 ); 1421 1425 1422 1426 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`, 1426 1434 [storeId], 1427 1435 (err, result) => { … … 1432 1440 } 1433 1441 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 ); 1438 1445 1439 1446 query( … … 1441 1448 COALESCE( 1442 1449 SUM( 1443 oi.price * oi.quantity 1450 p.price * i.quantity 1451 * (1 - COALESCE(o.discount, 0) / 100.0) 1444 1452 ), 1445 1453 0 1446 1454 ) 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 1448 1460 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`, 1452 1463 [storeId], 1453 1464 (err, result) => { … … 1458 1469 } 1459 1470 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 ); 1465 1474 1466 1475 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`, 1477 1489 [storeId], 1478 1490 (err, result) => { … … 1483 1495 } 1484 1496 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 1494 1499 ); 1500 1501 callback(null, stats); 1495 1502 } 1496 1503 ); … … 2293 2300 2294 2301 2302 2303 2304 /* 2305 * ============================================================ 2306 * ADVANCED REPORTS 2307 * ============================================================ 2308 */ 2309 2310 2311 const REPORT_FUNCTIONS_SQL = String.raw` 2312 -- ============================================================ 2313 -- HANDCRAFT MARKETPLACE REPORT FUNCTIONS 2314 -- PostgreSQL / exact project schema 2315 -- ============================================================ 2316 2317 CREATE OR REPLACE FUNCTION get_orders_by_total() 2318 RETURNS 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 ) 2328 LANGUAGE sql 2329 AS $$ 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 2353 CREATE OR REPLACE FUNCTION get_products_by_total_sales() 2354 RETURNS 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 ) 2362 LANGUAGE sql 2363 AS $$ 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 2387 CREATE OR REPLACE FUNCTION get_low_stock_high_demand_products( 2388 p_stock_threshold INTEGER, 2389 p_demand_threshold INTEGER 2390 ) 2391 RETURNS 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 ) 2398 LANGUAGE sql 2399 AS $$ 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 2415 CREATE OR REPLACE FUNCTION get_products_monthly_sales() 2416 RETURNS 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 ) 2425 LANGUAGE sql 2426 AS $$ 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 2454 CREATE OR REPLACE FUNCTION get_stores_by_last_calendar_year_revenue() 2455 RETURNS 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 ) 2462 LANGUAGE sql 2463 AS $$ 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 2491 CREATE OR REPLACE FUNCTION get_products_never_ordered() 2492 RETURNS TABLE ( 2493 product_code VARCHAR(8), 2494 product_description VARCHAR(500), 2495 product_price NUMERIC, 2496 current_stock INTEGER 2497 ) 2498 LANGUAGE sql 2499 AS $$ 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 2510 CREATE OR REPLACE FUNCTION get_products_by_number_of_orders() 2511 RETURNS TABLE ( 2512 product_code VARCHAR(8), 2513 product_description VARCHAR(500), 2514 product_price NUMERIC, 2515 number_of_orders BIGINT 2516 ) 2517 LANGUAGE sql 2518 AS $$ 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 2530 CREATE OR REPLACE FUNCTION get_stores_by_average_review() 2531 RETURNS TABLE ( 2532 store_id VARCHAR(3), 2533 store_name VARCHAR(50), 2534 average_review NUMERIC, 2535 number_of_reviews BIGINT 2536 ) 2537 LANGUAGE sql 2538 AS $$ 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 2561 CREATE OR REPLACE FUNCTION get_store_with_highest_revenue_growth() 2562 RETURNS 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 ) 2569 LANGUAGE sql 2570 AS $$ 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 2614 CREATE OR REPLACE FUNCTION get_clients_by_number_of_orders() 2615 RETURNS TABLE ( 2616 client_id INTEGER, 2617 client_name TEXT, 2618 number_of_orders BIGINT 2619 ) 2620 LANGUAGE sql 2621 AS $$ 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 2632 CREATE OR REPLACE FUNCTION get_approximate_orders_per_client() 2633 RETURNS TABLE ( 2634 total_clients BIGINT, 2635 total_orders BIGINT, 2636 approximate_orders_per_client NUMERIC 2637 ) 2638 LANGUAGE sql 2639 AS $$ 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 2650 CREATE OR REPLACE FUNCTION get_clients_without_orders() 2651 RETURNS TABLE ( 2652 client_id INTEGER, 2653 client_name TEXT, 2654 email VARCHAR(50) 2655 ) 2656 LANGUAGE sql 2657 AS $$ 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 2669 CREATE OR REPLACE FUNCTION get_store_request_statistics() 2670 RETURNS 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 ) 2677 LANGUAGE sql 2678 AS $$ 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 2692 CREATE OR REPLACE FUNCTION get_top_10_employees_by_requests_last_month() 2693 RETURNS TABLE ( 2694 employee_id VARCHAR(10), 2695 employee_name TEXT, 2696 number_of_requests BIGINT 2697 ) 2698 LANGUAGE sql 2699 AS $$ 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 2715 CREATE OR REPLACE FUNCTION get_employees_by_hours_and_pay() 2716 RETURNS TABLE ( 2717 employee_id VARCHAR(10), 2718 employee_name TEXT, 2719 total_hours_worked NUMERIC, 2720 total_pay NUMERIC 2721 ) 2722 LANGUAGE sql 2723 AS $$ 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 2736 CREATE OR REPLACE FUNCTION get_stores_average_pay() 2737 RETURNS TABLE ( 2738 store_id VARCHAR(3), 2739 store_name VARCHAR(50), 2740 average_pay NUMERIC 2741 ) 2742 LANGUAGE sql 2743 AS $$ 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 2754 CREATE OR REPLACE FUNCTION get_employee_product_changes_last_month() 2755 RETURNS TABLE ( 2756 employee_id VARCHAR(10), 2757 employee_name TEXT, 2758 number_of_product_changes BIGINT 2759 ) 2760 LANGUAGE sql 2761 AS $$ 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 2776 CREATE OR REPLACE FUNCTION get_stores_by_monthly_profit_and_revenue_growth() 2777 RETURNS 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 ) 2786 LANGUAGE sql 2787 AS $$ 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 2828 CREATE OR REPLACE FUNCTION get_unapproved_reports() 2829 RETURNS 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 ) 2837 LANGUAGE sql 2838 AS $$ 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 2855 function 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 2868 const 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 2890 function 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 2915 function 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 } 2295 3151 /* 2296 3152 * ============================================================ … … 2340 3196 getStoreStats, 2341 3197 3198 installReportFunctions, 3199 runReport, 3200 generateStoreReport, 3201 2342 3202 createOrderNew, 2343 3203 getOrdersByClient,
Note:
See TracChangeset
for help on using the changeset viewer.
