| 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; |