Small dashboard updates
This commit is contained in:
@@ -989,6 +989,79 @@ router.get('/best-sellers', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// GET /dashboard/year-revenue-estimate
|
||||
// Returns YTD actual revenue + rest-of-year forecast revenue for a full-year estimate
|
||||
router.get('/year-revenue-estimate', async (req, res) => {
|
||||
const now = new Date();
|
||||
const yearStart = `${now.getFullYear()}-01-01`;
|
||||
const todayISO = now.toISOString().split('T')[0];
|
||||
const yearEndISO = `${now.getFullYear()}-12-31`;
|
||||
|
||||
try {
|
||||
// YTD actual revenue from orders
|
||||
const { rows: [ytd] } = await executeQuery(`
|
||||
SELECT COALESCE(SUM(price * quantity), 0) AS revenue
|
||||
FROM orders
|
||||
WHERE date >= $1 AND date <= $2 AND canceled = false
|
||||
`, [yearStart, todayISO]);
|
||||
|
||||
// Forecast horizon
|
||||
const { rows: [horizonRow] } = await executeQuery(
|
||||
`SELECT MAX(forecast_date) AS max_date FROM product_forecasts`
|
||||
);
|
||||
const forecastHorizonISO = horizonRow?.max_date
|
||||
? (horizonRow.max_date instanceof Date
|
||||
? horizonRow.max_date.toISOString().split('T')[0]
|
||||
: horizonRow.max_date)
|
||||
: todayISO;
|
||||
|
||||
const clampedEnd = yearEndISO <= forecastHorizonISO ? yearEndISO : forecastHorizonISO;
|
||||
|
||||
// Forecast revenue from tomorrow to clamped end
|
||||
const { rows: [forecast] } = await executeQuery(`
|
||||
SELECT COALESCE(SUM(pf.forecast_revenue), 0) AS revenue
|
||||
FROM product_forecasts pf
|
||||
JOIN product_metrics pm ON pm.pid = pf.pid
|
||||
WHERE pm.is_visible = true
|
||||
AND pf.forecast_date > $1 AND pf.forecast_date <= $2
|
||||
`, [todayISO, clampedEnd]);
|
||||
|
||||
let eoyForecastRevenue = parseFloat(forecast.revenue) || 0;
|
||||
|
||||
// If forecast doesn't cover full year, extrapolate remaining days
|
||||
if (yearEndISO > forecastHorizonISO) {
|
||||
const { rows: [tailRow] } = await executeQuery(`
|
||||
SELECT AVG(daily_rev) AS avg_daily FROM (
|
||||
SELECT forecast_date, SUM(pf.forecast_revenue) AS daily_rev
|
||||
FROM product_forecasts pf
|
||||
JOIN product_metrics pm ON pm.pid = pf.pid
|
||||
WHERE pm.is_visible = true
|
||||
AND pf.forecast_date > ($1::date - INTERVAL '7 days')
|
||||
AND pf.forecast_date <= $1
|
||||
GROUP BY forecast_date
|
||||
) sub
|
||||
`, [forecastHorizonISO]);
|
||||
|
||||
const baselineDaily = parseFloat(tailRow?.avg_daily) || 0;
|
||||
const horizonDate = new Date(forecastHorizonISO + 'T00:00:00');
|
||||
const yearEnd = new Date(yearEndISO + 'T00:00:00');
|
||||
const extraDays = Math.round((yearEnd - horizonDate) / (1000 * 60 * 60 * 24));
|
||||
eoyForecastRevenue += baselineDaily * extraDays;
|
||||
}
|
||||
|
||||
const ytdRevenue = parseFloat(ytd.revenue) || 0;
|
||||
|
||||
res.json({
|
||||
ytdRevenue,
|
||||
eoyForecastRevenue,
|
||||
yearTotal: ytdRevenue + eoyForecastRevenue,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Error fetching year revenue estimate:', err);
|
||||
res.status(500).json({ error: 'Failed to fetch year revenue estimate' });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /dashboard/sales/metrics
|
||||
// Returns sales metrics for specified period
|
||||
router.get('/sales/metrics', async (req, res) => {
|
||||
|
||||
Reference in New Issue
Block a user