Small dashboard updates

This commit is contained in:
2026-03-24 09:56:51 -04:00
parent 884bcbad78
commit 76a8836769
13 changed files with 982 additions and 462 deletions

View File

@@ -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) => {