feat(brands): Add margin estimation to stores/performance endpoint
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
- Add ?margin_pct query param (default 50% industry standard) - Returns margin_pct and margin_est per store - Includes margin_pct_assumed in response metadata 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -710,6 +710,8 @@ export function createBrandsRouter(pool: Pool): Router {
|
||||
const stateCode = req.query.state as string | undefined;
|
||||
const limit = parseLimit(req.query.limit as string, 100);
|
||||
const offset = parseOffset(req.query.offset as string);
|
||||
// Margin assumption - default 50% (industry standard)
|
||||
const marginPct = Math.min(Math.max(parseFloat(req.query.margin_pct as string) || 50, 0), 100);
|
||||
|
||||
const startDate = new Date();
|
||||
startDate.setDate(startDate.getDate() - days);
|
||||
@@ -879,11 +881,15 @@ export function createBrandsRouter(pool: Pool): Router {
|
||||
brand: brandName,
|
||||
period_days: days,
|
||||
state: stateCode || 'all',
|
||||
margin_pct_assumed: marginPct,
|
||||
summary: {
|
||||
total_stores: parseInt(totals.total_stores) || 0,
|
||||
total_oos: parseInt(totals.total_oos) || 0,
|
||||
},
|
||||
stores: result.rows.map(row => ({
|
||||
stores: result.rows.map(row => {
|
||||
const totalSalesEst = row.total_sales_est ? parseFloat(row.total_sales_est) : null;
|
||||
const marginEst = totalSalesEst ? totalSalesEst * (marginPct / 100) : null;
|
||||
return {
|
||||
store_id: row.store_id,
|
||||
store_name: row.store_name,
|
||||
state_code: row.state_code,
|
||||
@@ -896,7 +902,10 @@ export function createBrandsRouter(pool: Pool): Router {
|
||||
oos_pct: parseInt(row.oos_pct) || 0,
|
||||
// Velocity & Sales
|
||||
avg_daily_units: row.avg_daily_units ? parseFloat(row.avg_daily_units) : null,
|
||||
total_sales_est: row.total_sales_est ? parseFloat(row.total_sales_est) : null,
|
||||
total_sales_est: totalSalesEst,
|
||||
// Margin (estimated)
|
||||
margin_pct: marginPct,
|
||||
margin_est: marginEst ? parseFloat(marginEst.toFixed(2)) : null,
|
||||
// Inventory
|
||||
avg_days_on_hand: row.avg_days_on_hand ? parseFloat(row.avg_days_on_hand) : null,
|
||||
total_stock: row.total_stock ? parseInt(row.total_stock) : null,
|
||||
@@ -906,7 +915,7 @@ export function createBrandsRouter(pool: Pool): Router {
|
||||
lost_opportunity: row.lost_opportunity ? parseFloat(row.lost_opportunity) : null,
|
||||
// Categories breakdown
|
||||
categories: row.categories || {},
|
||||
})),
|
||||
};}),
|
||||
pagination: {
|
||||
limit,
|
||||
offset,
|
||||
|
||||
Reference in New Issue
Block a user