3 | | **Daily Revenue** |
4 | | {{{ |
5 | | CREATE OR REPLACE FUNCTION get_daily_revenue(p_start_date DATE, p_end_date DATE) |
6 | | RETURNS TABLE(payment_date DATE, total_revenue NUMERIC) AS $$ |
| 3 | **Monthly operations summary** |
| 4 | {{{ |
| 5 | SELECT |
| 6 | dates.operation_date, |
| 7 | COUNT(DISTINCT r.id) as total_reservations, |
| 8 | COUNT(DISTINCT o.id) as total_orders, |
| 9 | COUNT(DISTINCT r.user_id) as unique_customers, |
| 10 | COUNT(DISTINCT fs.employee_id) as active_employees, |
| 11 | COALESCE(SUM(oi.quantity * oi.price), 0) as daily_revenue |
| 12 | FROM generate_series( |
| 13 | CURRENT_DATE - INTERVAL '30 days', |
| 14 | CURRENT_DATE, |
| 15 | '1 day'::interval |
| 16 | ) dates(operation_date) |
| 17 | LEFT JOIN reservations r |
| 18 | ON DATE(r.datetime) = dates.operation_date |
| 19 | LEFT JOIN orders o |
| 20 | ON DATE(o.datetime) = dates.operation_date |
| 21 | LEFT JOIN order_items oi |
| 22 | ON o.id = oi.order_id |
| 23 | LEFT JOIN tab_orders to2 |
| 24 | ON o.id = to2.order_id |
| 25 | LEFT JOIN front_staff fs |
| 26 | ON to2.front_staff_id = fs.employee_id |
| 27 | GROUP BY dates.operation_date |
| 28 | ORDER BY dates.operation_date DESC; |
| 29 | }}} |
| 30 | |
| 31 | ** Revenue Split: Online vs. Tab Orders ** |
| 32 | {{{ |
| 33 | CREATE OR REPLACE FUNCTION get_revenue_split(p_start_date DATE, p_end_date DATE) |
| 34 | RETURNS TABLE(order_type TEXT, total_revenue NUMERIC(14,2)) AS $$ |
8 | | RETURN QUERY |
9 | | SELECT |
10 | | DATE(created_at) AS payment_date, |
11 | | SUM(amount) AS total_revenue |
12 | | FROM payments |
13 | | WHERE DATE(created_at) BETWEEN p_start_date AND p_end_date |
14 | | GROUP BY DATE(created_at) |
15 | | ORDER BY payment_date; |
| 36 | RETURN QUERY |
| 37 | SELECT 'Online Orders'::text AS order_type, |
| 38 | COALESCE(SUM(p.amount), 0)::numeric(14,2) AS total_revenue |
| 39 | FROM orders o |
| 40 | JOIN payments p ON o.id = p.order_id |
| 41 | JOIN online_orders oo ON o.id = oo.order_id |
| 42 | WHERE o.datetime::date BETWEEN p_start_date AND p_end_date |
| 43 | |
| 44 | UNION ALL |
| 45 | |
| 46 | SELECT 'Tab Orders'::text AS order_type, |
| 47 | COALESCE(SUM(p.amount), 0)::numeric(14,2) AS total_revenue |
| 48 | FROM orders o |
| 49 | JOIN payments p ON o.id = p.order_id |
| 50 | JOIN tab_orders tord ON o.id = tord.order_id |
| 51 | WHERE o.datetime::date BETWEEN p_start_date AND p_end_date; |
18 | | |
19 | | SELECT * FROM get_daily_revenue('2025-01-01', '2025-01-31'); |
20 | | }}} |
21 | | |
22 | | **Daily Order Count and Average Order Value Report** |
23 | | {{{ |
24 | | CREATE OR REPLACE FUNCTION get_daily_order_stats(p_start_date DATE, p_end_date DATE) |
25 | | RETURNS TABLE(order_date DATE, total_orders BIGINT, avg_order_value NUMERIC) AS $$ |
26 | | BEGIN |
27 | | RETURN QUERY |
28 | | SELECT |
29 | | DATE(o.datetime) AS order_date, |
30 | | COUNT(o.id) AS total_orders, |
31 | | AVG(p.amount) AS avg_order_value |
| 54 | }}} |
| 55 | |
| 56 | **Top 10 products by revenue ** |
| 57 | {{{ |
| 58 | SELECT |
| 59 | p.id as product_id, |
| 60 | p.name as product_name, |
| 61 | c.name as category_name, |
| 62 | SUM(oi.quantity) as total_quantity_sold, |
| 63 | SUM(oi.quantity * oi.price) as total_revenue, |
| 64 | ROUND(100.0 * SUM(oi.quantity * oi.price) / SUM(SUM(oi.quantity * oi.price)) OVER (), 2) as revenue_share_percent |
| 65 | FROM products p |
| 66 | JOIN categories c ON p.category_id = c.id |
| 67 | JOIN order_items oi ON p.id = oi.product_id |
| 68 | JOIN orders o ON o.id = oi.order_id |
| 69 | WHERE o.datetime >= CURRENT_DATE - INTERVAL '90 days' |
| 70 | GROUP BY p.id, p.name, c.name |
| 71 | ORDER BY total_revenue DESC |
| 72 | LIMIT 10; |
| 73 | }}} |
| 74 | |
| 75 | **Revenue by shift period** |
| 76 | {{{ |
| 77 | CREATE OR REPLACE VIEW v_revenue_by_shift_period AS |
| 78 | WITH distinct_shift_periods AS ( |
| 79 | SELECT DISTINCT |
| 80 | start_time::time AS start_t, |
| 81 | end_time::time AS end_t |
| 82 | FROM |
| 83 | shifts |
| 84 | ) |
| 85 | SELECT |
| 86 | TO_CHAR(o.datetime, 'YYYY-MM') AS period, |
| 87 | dsp.start_t::text || '-' || dsp.end_t::text AS shift_period, |
| 88 | SUM(oi.price * oi.quantity) AS total_revenue |
| 89 | FROM |
| 90 | orders o |
| 91 | JOIN |
| 92 | order_items oi ON o.id = oi.order_id |
| 93 | JOIN |
| 94 | distinct_shift_periods dsp ON o.datetime::time >= dsp.start_t AND o.datetime::time < dsp.end_t |
| 95 | GROUP BY |
| 96 | period, |
| 97 | shift_period |
| 98 | ORDER BY |
| 99 | period DESC, |
| 100 | shift_period ASC; |
| 101 | }}} |
| 102 | |
| 103 | ** Managers' shifts above monthly average revenue ** |
| 104 | {{{ |
| 105 | WITH manager_worked_shifts AS ( |
| 106 | SELECT DISTINCT |
| 107 | s.id AS shift_id, |
| 108 | s.date, |
| 109 | s.start_time, |
| 110 | s.end_time, |
| 111 | a.employee_id AS manager_id |
| 112 | FROM assignments a |
| 113 | JOIN shifts s ON s.id = a.shift_id |
| 114 | JOIN managers m ON m.employee_id = a.employee_id |
| 115 | WHERE s.date >= date_trunc('year', CURRENT_DATE)::date |
| 116 | AND s.date < (date_trunc('year', CURRENT_DATE) + INTERVAL '1 year')::date |
| 117 | ), |
| 118 | shift_revenue AS ( |
| 119 | SELECT |
| 120 | mws.shift_id, |
| 121 | date_trunc('month', mws.date)::date AS month_start, |
| 122 | mws.date AS shift_date, |
| 123 | mws.start_time, |
| 124 | mws.end_time, |
| 125 | mws.manager_id, |
| 126 | COALESCE(SUM(oi.quantity * oi.price), 0)::numeric(14,2) AS shift_revenue |
| 127 | FROM manager_worked_shifts mws |
| 128 | LEFT JOIN orders o |
| 129 | ON o.datetime::date = mws.date |
| 130 | AND o.datetime::time >= mws.start_time |
| 131 | AND o.datetime::time < mws.end_time |
| 132 | LEFT JOIN order_items oi ON oi.order_id = o.id |
| 133 | GROUP BY |
| 134 | mws.shift_id, month_start, mws.date, mws.start_time, mws.end_time, mws.manager_id |
| 135 | ), |
| 136 | monthly_avg AS ( |
| 137 | SELECT month_start, AVG(shift_revenue)::numeric(14,2) AS avg_revenue_per_shift |
| 138 | FROM shift_revenue |
| 139 | GROUP BY month_start |
| 140 | ) |
| 141 | SELECT |
| 142 | to_char(sr.month_start, 'YYYY-MM') AS period, |
| 143 | sr.shift_id, |
| 144 | sr.shift_date, |
| 145 | sr.start_time AS shift_start_time, |
| 146 | sr.end_time AS shift_end_time, |
| 147 | u.email AS manager_email, |
| 148 | sr.shift_revenue, |
| 149 | ma.avg_revenue_per_shift, |
| 150 | (sr.shift_revenue - ma.avg_revenue_per_shift)::numeric(14,2) AS above_by |
| 151 | FROM shift_revenue sr |
| 152 | JOIN monthly_avg ma ON ma.month_start = sr.month_start |
| 153 | JOIN managers m ON m.employee_id = sr.manager_id |
| 154 | JOIN employees e ON e.user_id = m.employee_id |
| 155 | JOIN users u ON u.id = e.user_id |
| 156 | WHERE sr.shift_revenue > ma.avg_revenue_per_shift |
| 157 | ORDER BY period DESC, sr.shift_revenue DESC, sr.shift_date DESC; |
| 158 | }}} |
| 159 | |
| 160 | ** Monthly revenue vs labor cost ** |
| 161 | {{{ |
| 162 | WITH monthly_revenue AS ( |
| 163 | SELECT |
| 164 | DATE_TRUNC('month', o.datetime) as operation_month, |
| 165 | SUM(oi.quantity * oi.price) as revenue |
33 | | JOIN payments p ON o.id = p.order_id |
34 | | WHERE DATE(o.datetime) BETWEEN p_start_date AND p_end_date |
35 | | GROUP BY DATE(o.datetime) |
36 | | ORDER BY order_date; |
37 | | END; |
38 | | $$ LANGUAGE plpgsql; |
39 | | |
40 | | SELECT * FROM get_daily_order_stats('2025-01-01', '2025-01-31'); |
41 | | |
42 | | }}} |
43 | | |
44 | | ** Revenue Split: Online vs. Tab Orders ** |
45 | | {{{ |
46 | | CREATE OR REPLACE FUNCTION get_revenue_split(p_start_date DATE, p_end_date DATE) |
47 | | RETURNS TABLE(order_type TEXT, total_revenue NUMERIC) AS $$ |
48 | | BEGIN |
49 | | RETURN QUERY |
50 | | SELECT 'Online Orders' AS order_type, SUM(p.amount) AS total_revenue |
51 | | FROM orders o |
52 | | JOIN payments p ON o.id = p.order_id |
53 | | JOIN online_orders oo ON o.id = oo.order_id |
54 | | WHERE DATE(o.datetime) BETWEEN p_start_date AND p_end_date |
55 | | |
56 | | UNION ALL |
57 | | |
58 | | SELECT 'Tab Orders' AS order_type, SUM(p.amount) AS total_revenue |
59 | | FROM orders o |
60 | | JOIN payments p ON o.id = p.order_id |
61 | | JOIN tab_orders tord ON o.id = tord.order_id |
62 | | WHERE DATE(o.datetime) BETWEEN p_start_date AND p_end_date; |
63 | | END; |
64 | | $$ LANGUAGE plpgsql; |
65 | | |
66 | | SELECT * FROM get_revenue_split('2025-01-01', '2025-01-28'); |
67 | | }}} |
68 | | |
69 | | **Top 10 Best-Selling Products** |
70 | | {{{ |
71 | | SELECT |
72 | | p.name AS product, |
73 | | SUM(oi.quantity) AS total_quantity_sold |
74 | | FROM order_items oi |
75 | | JOIN products p ON oi.product_id = p.id |
76 | | GROUP BY p.name |
77 | | ORDER BY total_quantity_sold DESC |
78 | | LIMIT 10; |
| 167 | JOIN order_items oi ON o.id = oi.order_id |
| 168 | GROUP BY DATE_TRUNC('month', o.datetime) |
| 169 | ), |
| 170 | monthly_labor_cost AS ( |
| 171 | SELECT |
| 172 | monthly_assignments.operation_month, |
| 173 | SUM(e.gross_salary) as labor_cost |
| 174 | FROM ( |
| 175 | SELECT DISTINCT |
| 176 | DATE_TRUNC('month', s.date) as operation_month, |
| 177 | a.employee_id |
| 178 | FROM shifts s |
| 179 | JOIN assignments a ON s.id = a.shift_id |
| 180 | ) as monthly_assignments |
| 181 | JOIN employees e ON monthly_assignments.employee_id = e.user_id |
| 182 | GROUP BY monthly_assignments.operation_month |
| 183 | ) |
| 184 | SELECT |
| 185 | TO_CHAR(COALESCE(mr.operation_month, mlc.operation_month), 'YYYY-MM') as period, |
| 186 | ROUND(COALESCE(mr.revenue, 0)::numeric, 2) as total_revenue, |
| 187 | ROUND(COALESCE(mlc.labor_cost, 0)::numeric, 2) as total_labor_cost, |
| 188 | ROUND( |
| 189 | CASE |
| 190 | WHEN COALESCE(mr.revenue, 0) > 0 |
| 191 | THEN (COALESCE(mlc.labor_cost, 0) / mr.revenue * 100) |
| 192 | ELSE 0 |
| 193 | END::numeric, 2 |
| 194 | ) as labor_as_percent_of_revenue |
| 195 | FROM monthly_revenue mr |
| 196 | FULL OUTER JOIN monthly_labor_cost mlc ON mr.operation_month = mlc.operation_month |
| 197 | ORDER BY period DESC; |
| 198 | }}} |
| 199 | |
| 200 | ** Server performance & revenue ranking ** |
| 201 | {{{ |
| 202 | WITH server_metrics AS ( |
| 203 | SELECT |
| 204 | fs.employee_id, |
| 205 | u.email as server_email, |
| 206 | COUNT(DISTINCT a.id) as total_assignments, |
| 207 | COUNT(DISTINCT o.id) as orders_processed, |
| 208 | COALESCE(SUM(oi.quantity * oi.price), 0) as total_revenue_generated |
| 209 | FROM front_staff fs |
| 210 | JOIN employees e ON fs.employee_id = e.user_id |
| 211 | JOIN users u ON e.user_id = u.id |
| 212 | JOIN staff_roles sr ON fs.staff_role_id = sr.id |
| 213 | LEFT JOIN assignments a ON fs.employee_id = a.employee_id |
| 214 | LEFT JOIN shifts s ON a.shift_id = s.id |
| 215 | LEFT JOIN tab_orders to2 ON to2.front_staff_id = fs.employee_id |
| 216 | LEFT JOIN orders o ON o.id = to2.order_id |
| 217 | AND o.datetime >= CURRENT_DATE - INTERVAL '3 months' |
| 218 | LEFT JOIN order_items oi ON o.id = oi.order_id |
| 219 | WHERE LOWER(sr.name) = 'server' |
| 220 | GROUP BY fs.employee_id, u.email, u.phone_number, |
| 221 | e.net_salary, e.gross_salary, fs.tip_percent, sr.name |
| 222 | ), |
| 223 | performance_ranking AS ( |
| 224 | SELECT *, |
| 225 | RANK() OVER (ORDER BY total_revenue_generated DESC) as revenue_rank, |
| 226 | RANK() OVER (ORDER BY orders_processed DESC) as orders_rank, |
| 227 | CASE |
| 228 | WHEN total_assignments > 0 |
| 229 | THEN (orders_processed::float / total_assignments) |
| 230 | ELSE 0 |
| 231 | END as orders_per_assignment, |
| 232 | CASE |
| 233 | WHEN orders_processed > 0 |
| 234 | THEN total_revenue_generated / orders_processed |
| 235 | ELSE 0 |
| 236 | END as avg_revenue_per_order |
| 237 | FROM server_metrics |
| 238 | ) |
| 239 | SELECT |
| 240 | server_email, |
| 241 | total_assignments, |
| 242 | orders_processed, |
| 243 | total_revenue_generated, |
| 244 | revenue_rank, |
| 245 | orders_rank, |
| 246 | ROUND(orders_per_assignment::numeric, 2) as avg_orders_per_shift, |
| 247 | ROUND(avg_revenue_per_order::numeric, 2) as avg_order_value |
| 248 | FROM performance_ranking |
| 249 | ORDER BY total_revenue_generated DESC, orders_processed DESC; |