diff --git a/api/dropbox-sync.js b/api/dropbox-sync.js index bff3157..45cfcbe 100644 --- a/api/dropbox-sync.js +++ b/api/dropbox-sync.js @@ -10,86 +10,6 @@ function setCors(req, res) { applyCors(req, res, 'POST, OPTIONS'); } -const COLUMN_PATTERNS = { - CLASSIFICATION: ['classification'], - LONG_DE: ['long', 'description', 'german'], - LONG_EN: ['long', 'description', 'english'], - SHORT_DE: ['short', 'description', 'german'], - SHORT_EN: ['short', 'description', 'english'], - DETAILS_DE: ['details', 'german'], - DETAILS_EN: ['details', 'english'], - INNER_L: ['inner', 'l'], - INNER_W: ['inner', 'w'], - INNER_H: ['inner', 'h'], - OUTER_L: ['outer', 'l'], - OUTER_W: ['outer', 'w'], - OUTER_H: ['outer', 'h'], - UNITS_OUTER: ['units', 'outer'], - MOQ: ['moq'], - VERIFIED_DIMS: ['verified', 'dims'], - VALIDATED_CHECK: ['validated', 'check'], - VALIDATED_NOTE: ['validated', 'note'], - PRODUCT_TYPE: ['product', 'type'], - ITEM_TO_LOGISTIC: ['item', 'logistic'], - CPNP_NO: ['cpnp'], -}; - -function resolveEditableColumns(headers) { - if (!headers || !Array.isArray(headers)) { - // Fallback default editable column indices - return new Set([ - 9, 10, 11, 28, 33, 43, 44, 45, 48, 49, 50, 64, 65, 66, 67, - 100, 101, 102, 103, 104, 107, - 18, 19, 20, 21, 22, 23, 24, 25, 26, 27 - ]); - } - - const h = headers.map(val => String(val || '').toLowerCase()); - const resolved = {}; - - Object.entries(COLUMN_PATTERNS).forEach(([key, patterns]) => { - const idx = h.findIndex(headerText => - patterns.every(p => headerText.includes(p.toLowerCase())) - ); - if (idx >= 0) { - resolved[key] = idx; - } - }); - - const editableColumns = new Set([ - resolved.CLASSIFICATION, - resolved.LONG_DE, - resolved.LONG_EN, - resolved.SHORT_DE, - resolved.SHORT_EN, - resolved.DETAILS_DE, - resolved.DETAILS_EN, - resolved.INNER_L, - resolved.INNER_W, - resolved.INNER_H, - resolved.OUTER_L, - resolved.OUTER_W, - resolved.OUTER_H, - resolved.UNITS_OUTER, - resolved.MOQ, - resolved.VERIFIED_DIMS, - resolved.VALIDATED_CHECK, - resolved.VALIDATED_NOTE, - resolved.PRODUCT_TYPE, - resolved.ITEM_TO_LOGISTIC, - resolved.CPNP_NO - ].filter(val => val !== undefined)); - - headers.forEach((header, i) => { - const headerText = String(header || '').toLowerCase(); - if (headerText.includes('srp') || headerText.includes('uvp') || headerText.includes('price')) { - editableColumns.add(i); - } - }); - - return editableColumns; -} - export default async function handler(req, res) { setCors(req, res); @@ -107,66 +27,47 @@ export default async function handler(req, res) { return res.status(405).json({ error: 'Method not allowed' }); } - const { rows, headers } = req.body; + const { rows } = req.body; if (!rows || !Array.isArray(rows)) { return res.status(400).json({ error: 'Missing rows data' }); } const articleNoIdx = 0; - // Fetch data and status of ALL products so we can merge non-editable columns for manually edited ones + // Fetch current product rows so we can preserve internal-only columns + // that do not exist in the live Dropbox workbook. const { data: existingProducts } = await supabase .from('products') .select('product_id, data, status'); - - // Protect any product that has history entries - const { data: historyProducts } = await supabase - .from('products_history') - .select('product_id'); - const historyIds = new Set((historyProducts || []).map(h => h.product_id)); - - const manuallyEditedProducts = new Map(); - (existingProducts || []).forEach(p => { - const isProtected = (p.status && p.status !== 'excel') || historyIds.has(p.product_id); - if (isProtected) { - manuallyEditedProducts.set(p.product_id, p); - } - }); - - const editableColumns = resolveEditableColumns(headers); + const existingProductMap = new Map((existingProducts || []).map(p => [p.product_id, p])); const productsToUpsert = []; for (const row of rows) { const productId = String(row[articleNoIdx]); if (!productId || productId.trim() === '') continue; - const manuallyEdited = manuallyEditedProducts.get(productId); + const existing = existingProductMap.get(productId); + const dbRowData = existing?.data; - if (manuallyEdited) { - const dbRowData = manuallyEdited.data; - if (dbRowData && Array.isArray(dbRowData)) { - const mergedData = [...dbRowData]; - while (mergedData.length < row.length) { - mergedData.push(null); - } - let hasChange = false; - for (let i = 0; i < row.length; i++) { - if (i >= 100) continue; // Protect virtual columns - if (editableColumns.has(i)) continue; // Protect editable columns - if (mergedData[i] !== row[i]) { - mergedData[i] = row[i]; - hasChange = true; - } - } - if (hasChange) { - productsToUpsert.push({ - product_id: productId, - data: mergedData, - status: manuallyEdited.status || 'edited', - updated_at: new Date().toISOString() - }); + if (dbRowData && Array.isArray(dbRowData)) { + const mergedData = [...row]; + while (mergedData.length < dbRowData.length) { + mergedData.push(null); + } + + for (let i = 100; i < dbRowData.length; i++) { + const internalValue = dbRowData[i]; + if (internalValue !== undefined && internalValue !== null && internalValue !== '') { + mergedData[i] = internalValue; } } + + productsToUpsert.push({ + product_id: productId, + data: mergedData, + status: existing?.status || 'excel', + updated_at: new Date().toISOString() + }); } else { // Normal excel sync productsToUpsert.push({ diff --git a/src/App.tsx b/src/App.tsx index 5ecd87e..208cba1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -338,37 +338,6 @@ export default function App() { } const resolvedCols = resolveColumnIndices(extendedHeaders); - const editableColumns = new Set([ - resolvedCols.CLASSIFICATION, - resolvedCols.LONG_DE, - resolvedCols.LONG_EN, - resolvedCols.SHORT_DE, - resolvedCols.SHORT_EN, - resolvedCols.DETAILS_DE, - resolvedCols.DETAILS_EN, - resolvedCols.INNER_L, - resolvedCols.INNER_W, - resolvedCols.INNER_H, - resolvedCols.OUTER_L, - resolvedCols.OUTER_W, - resolvedCols.OUTER_H, - resolvedCols.UNITS_OUTER, - resolvedCols.MOQ, - resolvedCols.VERIFIED_DIMS, - resolvedCols.VALIDATED_CHECK, - resolvedCols.VALIDATED_NOTE, - resolvedCols.PRODUCT_TYPE, - resolvedCols.ITEM_TO_LOGISTIC, - resolvedCols.CPNP_NO - ]); - - headers.forEach((h: any, i: number) => { - const headerText = String(h || '').toLowerCase(); - if (headerText.includes('srp') || headerText.includes('uvp') || headerText.includes('price')) { - editableColumns.add(i); - } - }); - const articleNoIdx = resolvedCols.ARTICLE_NO; const processedRows = rows.map(row => { const articleNo = String(row[articleNoIdx]); @@ -425,16 +394,6 @@ export default function App() { finalRow[COLUMNS.CPNP_NO] = legacyCpnp; } - // When a row is still in an edited/pending state, restore editable business fields too. - // This includes historical CPNP edits that were persisted directly in products. - if (synced?.status === 'pending' || synced?.status === 'edited') { - editableColumns.forEach(idx => { - if (idx < 100 && sourceForInternal[idx] !== undefined && sourceForInternal[idx] !== null) { - finalRow[idx] = sourceForInternal[idx]; - } - }); - } - // Update row status in UI if it's not the default 'excel' if (synced?.status && synced.status !== 'excel') { setRowStatuses(prev => ({ ...prev, [articleNo]: synced.status! }));