feat: Automate Buy Box tracker sync from Dropbox and improve parsing robustness

This commit is contained in:
Christian Vidal Wolf
2026-02-05 08:39:39 +01:00
parent c4ad999619
commit 09e06bad16
3 changed files with 93 additions and 18 deletions
+52 -15
View File
@@ -2142,13 +2142,12 @@ export const processStockExcel = async (fileOrBuffer: File | ArrayBuffer): Promi
}
};
// Sheet configuration for Buy Box tracking
const BB_SHEET_CONFIG: { sheet: string; country: string; reasonCol: number }[] = [
{ sheet: 'BB_FR', country: 'FR', reasonCol: 18 }, // Col S = index 18
{ sheet: 'BB_UK', country: 'UK', reasonCol: 9 }, // Col J = index 9
{ sheet: 'BB_DE', country: 'DE', reasonCol: 13 }, // Col N = index 13
{ sheet: 'BB_IT', country: 'IT', reasonCol: 12 }, // Col M = index 12
{ sheet: 'BB_ES', country: 'ES', reasonCol: 13 }, // Col N = index 13
const BB_SHEET_CONFIG: { sheet: string; country: string }[] = [
{ sheet: 'BB_FR', country: 'FR' },
{ sheet: 'BB_UK', country: 'UK' },
{ sheet: 'BB_DE', country: 'DE' },
{ sheet: 'BB_IT', country: 'IT' },
{ sheet: 'BB_ES', country: 'ES' },
];
export const processBuyBoxExcel = async (fileOrBuffer: File | ArrayBuffer): Promise<Map<string, { countries: string[]; reasons: Record<string, string> }>> => {
@@ -2161,32 +2160,70 @@ export const processBuyBoxExcel = async (fileOrBuffer: File | ArrayBuffer): Prom
// Map: ASIN -> { countries: [], reasons: {} }
const buyBoxMap = new Map<string, { countries: string[]; reasons: Record<string, string> }>();
// Log available sheets for debugging
console.log('[BuyBox] Available sheets:', workbook.SheetNames.join(', '));
for (const config of BB_SHEET_CONFIG) {
const worksheet = workbook.Sheets[config.sheet];
if (!worksheet) {
console.warn(`[BuyBox] Sheet ${config.sheet} not found, skipping...`);
// Find sheet case-insensitively and with flexible separators (BB_ES, BB-ES, BB ES)
const sheetName = workbook.SheetNames.find(name => {
const n = name.toUpperCase().replace(/[-_ ]/g, '');
const target = config.sheet.toUpperCase().replace(/[-_ ]/g, '');
return n === target;
});
if (!sheetName) {
console.warn(`[BuyBox] Sheet matching ${config.sheet} not found, skipping...`);
continue;
}
const worksheet = workbook.Sheets[sheetName];
const jsonData: any[][] = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
if (jsonData.length === 0) continue;
const headers: any[] = jsonData[0] || [];
const asinIdx = headers.findIndex(h => String(h || '').toUpperCase() === 'ASIN');
const asinIdx = headers.findIndex(h => {
const s = String(h || '').toUpperCase().trim();
return s === 'ASIN' || s.includes('AMAZON ASIN') || s.includes('IDENTIFIER') || s.includes('PRODUCT ID') || s.includes('SKU');
});
const finalAsinIdx = asinIdx !== -1 ? asinIdx : 1; // Default to Col B if not found
// Dynamically find the "Issue Type" or "Reason" column
let reasonIdx = headers.findIndex(h => {
const s = String(h || '').toUpperCase();
return s.includes('ISSUE TYPE') || s.includes('REASON') || s.includes('BUY BOX STATUS') || s.includes('LBB REASON') || s.includes('ESTADO BB') || s.includes('COMENTARIO');
});
// Fallback to previous hardcoded indices if header search fails
if (reasonIdx === -1) {
if (config.country === 'FR') reasonIdx = 18;
else if (config.country === 'UK') reasonIdx = 9;
else if (config.country === 'DE') reasonIdx = 13;
else if (config.country === 'IT') reasonIdx = 12;
else if (config.country === 'ES') reasonIdx = 13;
else reasonIdx = 13;
}
console.log(`[BuyBox] Sheet "${sheetName}" (Country: ${config.country}): ASIN Col=${finalAsinIdx} ("${headers[finalAsinIdx]}"), Reason Col=${reasonIdx} ("${headers[reasonIdx] || 'N/A'}")`);
// Skip header row (index 0)
for (let i = 1; i < jsonData.length; i++) {
const row = jsonData[i];
if (!row || row.length <= Math.max(finalAsinIdx, config.reasonCol)) continue;
if (!row || row.length <= Math.max(finalAsinIdx, reasonIdx)) continue;
const rawAsin = String(row[finalAsinIdx] || '').trim().toUpperCase();
if (!rawAsin || rawAsin.length < 5) continue;
const rawReason = String(row[config.reasonCol] || '').trim();
const rawReason = String(row[reasonIdx] || '').trim();
// Debug specific ASIN reported by user
if (rawAsin === 'B0D3874WSD' && config.country === 'ES') {
console.log(`[BuyBox Debug] ASIN B0D3874WSD found in ES. Raw Reason: "${rawReason}" (Length: ${rawReason.length})`);
}
// Only consider as BB lost if there is an Issue Type / Reason specified
// Empty reason or "Fixed" status means no issue = they have the Buy Box
if (!rawReason || rawReason.toLowerCase() === 'fixed') continue;
const lowReason = rawReason.toLowerCase();
if (!rawReason || lowReason === 'fixed' || lowReason === 'ok' || lowReason === 'hecho' || lowReason === 'solucionado') continue;
let entry = buyBoxMap.get(rawAsin);
if (!entry) {
@@ -2203,7 +2240,7 @@ export const processBuyBoxExcel = async (fileOrBuffer: File | ArrayBuffer): Prom
}
}
console.log(`[BuyBox] Processed ${buyBoxMap.size} ASINs with BB lost`);
console.log(`[BuyBox] Processed ${buyBoxMap.size} total ASINs with BB lost across all countries`);
return buyBoxMap;
} catch (error) {
console.error("Error processing Buy Box Excel:", error);