Overview tweaks
This commit is contained in:
@@ -21,8 +21,8 @@ router.get('/stock/metrics', async (req, res) => {
|
||||
COALESCE(COUNT(*), 0)::integer as total_products,
|
||||
COALESCE(COUNT(CASE WHEN current_stock > 0 THEN 1 END), 0)::integer as products_in_stock,
|
||||
COALESCE(SUM(CASE WHEN current_stock > 0 THEN current_stock END), 0)::integer as total_units,
|
||||
ROUND(COALESCE(SUM(CASE WHEN current_stock > 0 THEN current_stock_cost END), 0)::numeric, 3) as total_cost,
|
||||
ROUND(COALESCE(SUM(CASE WHEN current_stock > 0 THEN current_stock_retail END), 0)::numeric, 3) as total_retail
|
||||
ROUND(COALESCE(SUM(CASE WHEN current_stock > 0 THEN current_stock_cost END), 0)::numeric, 2) as total_cost,
|
||||
ROUND(COALESCE(SUM(CASE WHEN current_stock > 0 THEN current_stock_retail END), 0)::numeric, 2) as total_retail
|
||||
FROM product_metrics
|
||||
WHERE is_visible = true
|
||||
`);
|
||||
@@ -34,21 +34,21 @@ router.get('/stock/metrics', async (req, res) => {
|
||||
COALESCE(brand, 'Unbranded') as brand,
|
||||
COUNT(DISTINCT pid)::integer as variant_count,
|
||||
COALESCE(SUM(current_stock), 0)::integer as stock_units,
|
||||
ROUND(COALESCE(SUM(current_stock_cost), 0)::numeric, 3) as stock_cost,
|
||||
ROUND(COALESCE(SUM(current_stock_retail), 0)::numeric, 3) as stock_retail
|
||||
ROUND(COALESCE(SUM(current_stock_cost), 0)::numeric, 2) as stock_cost,
|
||||
ROUND(COALESCE(SUM(current_stock_retail), 0)::numeric, 2) as stock_retail
|
||||
FROM product_metrics
|
||||
WHERE current_stock > 0
|
||||
AND is_visible = true
|
||||
GROUP BY COALESCE(brand, 'Unbranded')
|
||||
HAVING ROUND(COALESCE(SUM(current_stock_cost), 0)::numeric, 3) > 0
|
||||
HAVING ROUND(COALESCE(SUM(current_stock_cost), 0)::numeric, 2) > 0
|
||||
),
|
||||
other_brands AS (
|
||||
SELECT
|
||||
'Other' as brand,
|
||||
SUM(variant_count)::integer as variant_count,
|
||||
SUM(stock_units)::integer as stock_units,
|
||||
ROUND(SUM(stock_cost)::numeric, 3) as stock_cost,
|
||||
ROUND(SUM(stock_retail)::numeric, 3) as stock_retail
|
||||
ROUND(SUM(stock_cost)::numeric, 2) as stock_cost,
|
||||
ROUND(SUM(stock_retail)::numeric, 2) as stock_retail
|
||||
FROM brand_totals
|
||||
WHERE stock_cost <= 5000
|
||||
),
|
||||
@@ -154,7 +154,10 @@ router.get('/purchase/metrics', async (req, res) => {
|
||||
vendor,
|
||||
SUM(on_order_qty)::integer AS units,
|
||||
ROUND(SUM(on_order_cost)::numeric, 2) AS cost,
|
||||
ROUND(SUM(on_order_retail)::numeric, 2) AS retail
|
||||
ROUND(SUM(on_order_retail)::numeric, 2) AS retail,
|
||||
SUM(SUM(on_order_qty)::integer) OVER () AS total_units,
|
||||
ROUND(SUM(SUM(on_order_cost)) OVER ()::numeric, 2) AS total_cost,
|
||||
ROUND(SUM(SUM(on_order_retail)) OVER ()::numeric, 2) AS total_retail
|
||||
FROM product_metrics
|
||||
WHERE is_visible = true AND on_order_qty > 0
|
||||
GROUP BY vendor
|
||||
@@ -169,9 +172,10 @@ router.get('/purchase/metrics', async (req, res) => {
|
||||
retail: parseFloat(v.retail) || 0
|
||||
}));
|
||||
|
||||
const onOrderUnits = vendorOrders.reduce((sum, v) => sum + v.units, 0);
|
||||
const onOrderCost = vendorOrders.reduce((sum, v) => sum + v.cost, 0);
|
||||
const onOrderRetail = vendorOrders.reduce((sum, v) => sum + v.retail, 0);
|
||||
const firstRow = vendorRows[0];
|
||||
const onOrderUnits = firstRow ? parseInt(firstRow.total_units) || 0 : 0;
|
||||
const onOrderCost = firstRow ? parseFloat(firstRow.total_cost) || 0 : 0;
|
||||
const onOrderRetail = firstRow ? parseFloat(firstRow.total_retail) || 0 : 0;
|
||||
|
||||
// Format response to match PurchaseMetricsData interface
|
||||
const response = {
|
||||
@@ -199,8 +203,8 @@ router.get('/replenishment/metrics', async (req, res) => {
|
||||
SELECT
|
||||
COUNT(DISTINCT pm.pid)::integer as products_to_replenish,
|
||||
COALESCE(SUM(pm.replenishment_units), 0)::integer as total_units_needed,
|
||||
ROUND(COALESCE(SUM(pm.replenishment_cost), 0)::numeric, 3) as total_cost,
|
||||
ROUND(COALESCE(SUM(pm.replenishment_retail), 0)::numeric, 3) as total_retail
|
||||
ROUND(COALESCE(SUM(pm.replenishment_cost), 0)::numeric, 2) as total_cost,
|
||||
ROUND(COALESCE(SUM(pm.replenishment_retail), 0)::numeric, 2) as total_retail
|
||||
FROM product_metrics pm
|
||||
WHERE pm.is_visible = true
|
||||
AND pm.is_replenishable = true
|
||||
@@ -216,8 +220,8 @@ router.get('/replenishment/metrics', async (req, res) => {
|
||||
pm.title,
|
||||
pm.current_stock::integer as current_stock,
|
||||
pm.replenishment_units::integer as replenish_qty,
|
||||
ROUND(pm.replenishment_cost::numeric, 3) as replenish_cost,
|
||||
ROUND(pm.replenishment_retail::numeric, 3) as replenish_retail,
|
||||
ROUND(pm.replenishment_cost::numeric, 2) as replenish_cost,
|
||||
ROUND(pm.replenishment_retail::numeric, 2) as replenish_retail,
|
||||
pm.status,
|
||||
pm.planning_period_days::text as planning_period
|
||||
FROM product_metrics pm
|
||||
@@ -552,7 +556,7 @@ router.get('/forecast/metrics', async (req, res) => {
|
||||
|
||||
return res.json({
|
||||
forecastSales: Math.round(totalUnits),
|
||||
forecastRevenue: totalRevenue.toFixed(2),
|
||||
forecastRevenue: parseFloat(totalRevenue.toFixed(2)),
|
||||
confidenceLevel,
|
||||
dailyForecasts,
|
||||
dailyForecastsByPhase,
|
||||
@@ -611,7 +615,7 @@ router.get('/forecast/metrics', async (req, res) => {
|
||||
|
||||
res.json({
|
||||
forecastSales: Math.round(dailyUnits * days),
|
||||
forecastRevenue: (dailyRevenue * days).toFixed(2),
|
||||
forecastRevenue: parseFloat((dailyRevenue * days).toFixed(2)),
|
||||
confidenceLevel: 0,
|
||||
dailyForecasts,
|
||||
categoryForecasts: categoryRows.map(c => ({
|
||||
@@ -794,10 +798,10 @@ router.get('/overstock/metrics', async (req, res) => {
|
||||
if (parseInt(countCheck.overstock_count) === 0) {
|
||||
return res.json({
|
||||
overstockedProducts: 0,
|
||||
total_excess_units: 0,
|
||||
total_excess_cost: 0,
|
||||
total_excess_retail: 0,
|
||||
category_data: []
|
||||
totalExcessUnits: 0,
|
||||
totalExcessCost: 0,
|
||||
totalExcessRetail: 0,
|
||||
categoryData: []
|
||||
});
|
||||
}
|
||||
|
||||
@@ -806,8 +810,8 @@ router.get('/overstock/metrics', async (req, res) => {
|
||||
SELECT
|
||||
COUNT(DISTINCT pid)::integer as total_overstocked,
|
||||
SUM(overstocked_units)::integer as total_excess_units,
|
||||
ROUND(SUM(overstocked_cost)::numeric, 3) as total_excess_cost,
|
||||
ROUND(SUM(overstocked_retail)::numeric, 3) as total_excess_retail
|
||||
ROUND(SUM(overstocked_cost)::numeric, 2) as total_excess_cost,
|
||||
ROUND(SUM(overstocked_retail)::numeric, 2) as total_excess_retail
|
||||
FROM product_metrics
|
||||
WHERE status = 'Overstock'
|
||||
AND is_visible = true
|
||||
@@ -819,8 +823,8 @@ router.get('/overstock/metrics', async (req, res) => {
|
||||
c.name as category_name,
|
||||
COUNT(DISTINCT pm.pid)::integer as overstocked_products,
|
||||
SUM(pm.overstocked_units)::integer as total_excess_units,
|
||||
ROUND(SUM(pm.overstocked_cost)::numeric, 3) as total_excess_cost,
|
||||
ROUND(SUM(pm.overstocked_retail)::numeric, 3) as total_excess_retail
|
||||
ROUND(SUM(pm.overstocked_cost)::numeric, 2) as total_excess_cost,
|
||||
ROUND(SUM(pm.overstocked_retail)::numeric, 2) as total_excess_retail
|
||||
FROM categories c
|
||||
JOIN product_categories pc ON c.cat_id = pc.cat_id
|
||||
JOIN product_metrics pm ON pc.pid = pm.pid
|
||||
@@ -850,10 +854,10 @@ router.get('/overstock/metrics', async (req, res) => {
|
||||
// Format response with explicit type conversion
|
||||
const response = {
|
||||
overstockedProducts: parseInt(summaryMetrics.total_overstocked) || 0,
|
||||
total_excess_units: parseInt(summaryMetrics.total_excess_units) || 0,
|
||||
total_excess_cost: parseFloat(summaryMetrics.total_excess_cost) || 0,
|
||||
total_excess_retail: parseFloat(summaryMetrics.total_excess_retail) || 0,
|
||||
category_data: categoryData.map(cat => ({
|
||||
totalExcessUnits: parseInt(summaryMetrics.total_excess_units) || 0,
|
||||
totalExcessCost: parseFloat(summaryMetrics.total_excess_cost) || 0,
|
||||
totalExcessRetail: parseFloat(summaryMetrics.total_excess_retail) || 0,
|
||||
categoryData: categoryData.map(cat => ({
|
||||
category: cat.category_name,
|
||||
products: parseInt(cat.overstocked_products) || 0,
|
||||
units: parseInt(cat.total_excess_units) || 0,
|
||||
|
||||
@@ -44,7 +44,7 @@ interface DailyPhaseData {
|
||||
|
||||
interface ForecastData {
|
||||
forecastSales: number
|
||||
forecastRevenue: string
|
||||
forecastRevenue: number
|
||||
confidenceLevel: number
|
||||
dailyForecasts: {
|
||||
date: string
|
||||
@@ -129,7 +129,7 @@ export function ForecastMetrics() {
|
||||
<p className="text-sm font-medium text-muted-foreground">Forecast Revenue</p>
|
||||
</div>
|
||||
{isLoading || !data ? <MetricSkeleton /> : (
|
||||
<p className="text-lg font-bold">{formatCurrency(Number(data.forecastRevenue) || 0)}</p>
|
||||
<p className="text-lg font-bold">{formatCurrency(data.forecastRevenue)}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -17,10 +17,10 @@ interface PhaseBreakdown {
|
||||
|
||||
interface OverstockMetricsData {
|
||||
overstockedProducts: number
|
||||
total_excess_units: number
|
||||
total_excess_cost: number
|
||||
total_excess_retail: number
|
||||
category_data: {
|
||||
totalExcessUnits: number
|
||||
totalExcessCost: number
|
||||
totalExcessRetail: number
|
||||
categoryData: {
|
||||
category: string
|
||||
products: number
|
||||
units: number
|
||||
@@ -69,7 +69,7 @@ export function OverstockMetrics() {
|
||||
<p className="text-sm font-medium text-muted-foreground">Overstocked Units</p>
|
||||
</div>
|
||||
{isLoading || !data ? <MetricSkeleton /> : (
|
||||
<p className="text-lg font-bold">{data.total_excess_units.toLocaleString()}</p>
|
||||
<p className="text-lg font-bold">{data.totalExcessUnits.toLocaleString()}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-baseline justify-between">
|
||||
@@ -78,7 +78,7 @@ export function OverstockMetrics() {
|
||||
<p className="text-sm font-medium text-muted-foreground">Overstocked Cost</p>
|
||||
</div>
|
||||
{isLoading || !data ? <MetricSkeleton /> : (
|
||||
<p className="text-lg font-bold">{formatCurrency(data.total_excess_cost)}</p>
|
||||
<p className="text-lg font-bold">{formatCurrency(data.totalExcessCost)}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-baseline justify-between">
|
||||
@@ -87,7 +87,7 @@ export function OverstockMetrics() {
|
||||
<p className="text-sm font-medium text-muted-foreground">Overstocked Retail</p>
|
||||
</div>
|
||||
{isLoading || !data ? <MetricSkeleton /> : (
|
||||
<p className="text-lg font-bold">{formatCurrency(data.total_excess_retail)}</p>
|
||||
<p className="text-lg font-bold">{formatCurrency(data.totalExcessRetail)}</p>
|
||||
)}
|
||||
</div>
|
||||
{data?.phaseBreakdown && data.phaseBreakdown.length > 0 && (
|
||||
|
||||
@@ -175,7 +175,7 @@ function SortableImageCell({
|
||||
src={src}
|
||||
alt={`Image ${image.iid}`}
|
||||
className={cn(
|
||||
"w-full h-full object-cover pointer-events-none select-none",
|
||||
"w-full h-full object-contain pointer-events-none select-none",
|
||||
isMain ? "rounded-lg" : "rounded-md"
|
||||
)}
|
||||
draggable={false}
|
||||
|
||||
Reference in New Issue
Block a user