fix(mkt): ensure all ad spend is aggregated correctly in MKT tab including unassigned spend

This commit is contained in:
Christian Vidal Wolf
2026-03-12 17:07:13 +01:00
parent eb20fe2878
commit 753b312412
+49 -39
View File
@@ -168,63 +168,73 @@ export default function MktDataView({ rawData, adsData }: MktDataViewProps) {
unitsMap.set(asin, (unitsMap.get(asin) || 0) + r.units); unitsMap.set(asin, (unitsMap.get(asin) || 0) + r.units);
}); });
// ASIN → global ad spend (all filtered data, no hardcoded year) // ASIN → global ad spend
// Include ALL records (even empty ASIN) so total matches ADs Weekly tab
const adsSpendMap = new Map<string, number>(); const adsSpendMap = new Map<string, number>();
let totalAdsPassed = 0;
adsData.forEach(r => { adsData.forEach(r => {
const asin = r.asin.trim().toUpperCase(); const asin = r.asin.trim().toUpperCase();
adsSpendMap.set(asin, (adsSpendMap.get(asin) || 0) + r.cost); adsSpendMap.set(asin, (adsSpendMap.get(asin) || 0) + r.cost);
totalAdsPassed += r.cost;
}); });
// Deals/Promos/Chargebacks are 2025-only data — only apply when 2025 is in scope // Deals/Promos/Chargebacks
const has2025 = rawData.some(r => r.year === 2025) || adsData.some(r => r.year === 2025); const has2025 = rawData.some(r => r.year === 2025) || adsData.some(r => r.year === 2025);
const getDeals = (asin: string) => has2025 ? (dealsMap.get(asin) || 0) : 0; const getDeals = (asin: string) => has2025 ? (dealsMap.get(asin) || 0) : 0;
const getPromos = (asin: string) => has2025 ? (promosMap.get(asin) || 0) : 0; const getPromos = (asin: string) => has2025 ? (promosMap.get(asin) || 0) : 0;
const getChargebacks = (asin: string) => has2025 ? (chargebacksMap.get(asin) || 0) : 0; const getChargebacks = (asin: string) => has2025 ? (chargebacksMap.get(asin) || 0) : 0;
// Build products from sales ASINs // 1. Start with all sales-based ASINs
const products = Array.from(metaMap.entries()) const products: Product[] = Array.from(metaMap.entries())
.map(([asin, meta]): Product => ({ .map(([asin, meta]): Product => {
id: asin, const spend = adsSpendMap.get(asin) || 0;
asin, adsSpendMap.delete(asin); // Mark as processed
sku: meta.sku, return {
name: meta.title || asin, id: asin,
image: '', asin,
category: meta.line, sku: meta.sku,
brand: '', name: meta.title || asin,
grossSales: sellOutMap.get(asin) || 0,
unitsSold: unitsMap.get(asin) || 0,
ppcSpend: adsSpendMap.get(asin) || 0,
deals: getDeals(asin),
promos: getPromos(asin),
chargebacks: getChargebacks(asin),
chargebacksPrevMonth: 0,
cogs: 0,
}));
// Add ASINs (or unassigned spend) that have ad spend but no sales records
adsSpendMap.forEach((spend, asin) => {
if (!metaMap.has(asin) && spend > 0) {
products.push({
id: asin || '__unassigned__',
asin: asin || '—',
sku: '',
name: asin ? asin : 'Unassigned Ad Spend',
image: '', image: '',
category: 'Other', category: meta.line,
brand: '', brand: '',
grossSales: 0, grossSales: sellOutMap.get(asin) || 0,
unitsSold: 0, unitsSold: unitsMap.get(asin) || 0,
ppcSpend: spend, ppcSpend: spend,
deals: asin ? getDeals(asin) : 0, deals: getDeals(asin),
promos: asin ? getPromos(asin) : 0, promos: getPromos(asin),
chargebacks: asin ? getChargebacks(asin) : 0, chargebacks: getChargebacks(asin),
chargebacksPrevMonth: 0, chargebacksPrevMonth: 0,
cogs: 0, cogs: 0,
}); };
} });
// 2. Add remaining ad spend (including "" ASIN and ASINs without sales)
adsSpendMap.forEach((spend, asin) => {
if (spend === 0) return;
products.push({
id: asin || '__unassigned__',
asin: asin || '—',
sku: '',
name: asin ? asin : 'Unassigned Ad Spend (incl. Sponsored Brands)',
image: '',
category: 'Other',
brand: '',
grossSales: 0,
unitsSold: 0,
ppcSpend: spend,
deals: asin ? getDeals(asin) : 0,
promos: asin ? getPromos(asin) : 0,
chargebacks: asin ? getChargebacks(asin) : 0,
chargebacksPrevMonth: 0,
cogs: 0,
});
}); });
// Debug check
const finalTotal = products.reduce((sum, p) => sum + p.ppcSpend, 0);
if (Math.abs(finalTotal - totalAdsPassed) > 0.01) {
console.warn(`[MktDataView] PPC Spend Mismatch: Total Ads=${totalAdsPassed.toFixed(2)}, Table Sum=${finalTotal.toFixed(2)}`);
}
return products; return products;
}, [rawData, adsData, dealsMap, promosMap, chargebacksMap]); }, [rawData, adsData, dealsMap, promosMap, chargebacksMap]);