mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 13:35:25 +02:00
feat(bc): sync categorization code
This commit is contained in:
+64
-5
@@ -59,6 +59,16 @@ const LEGACY_CPNP_INDEX = 77;
|
||||
const DROPBOX_PROXY_URL = '/api/dropbox-proxy';
|
||||
const DROPBOX_FILE_URL = '/dropbox-file/scl/fi/usa8me7ywgylrij2bt6hj/Data-Matrix.xlsx?rlkey=tsec8csrhye54u1fdvk15ped1&st=qbxxs4cn&dl=0';
|
||||
|
||||
function normalizeImportedHeaders(headers: any[]): string[] {
|
||||
const hasCategorizationCode = headers.some(header =>
|
||||
String(header ?? '').toLowerCase().trim().replace(/[\s_-]+/g, '') === 'categorizationcode'
|
||||
);
|
||||
return headers.map(header => {
|
||||
const label = String(header ?? '');
|
||||
return !hasCategorizationCode && label.trim().toUpperCase() === 'TYPE' ? 'CategorizationCode' : label;
|
||||
});
|
||||
}
|
||||
|
||||
type BcSyncStatus = 'queued' | 'previewed' | 'preview_only' | 'syncing' | 'synced' | 'failed';
|
||||
|
||||
interface BcSyncQueueEntry {
|
||||
@@ -249,7 +259,7 @@ export default function App() {
|
||||
}
|
||||
|
||||
if (rows.length > 0) {
|
||||
const headers = allData.slice(0, 1)[0];
|
||||
const headers = normalizeImportedHeaders(allData.slice(0, 1)[0]);
|
||||
|
||||
console.log('Syncing Excel data to Supabase...');
|
||||
const syncRes = await fetch('/api/dropbox-sync', {
|
||||
@@ -273,14 +283,14 @@ export default function App() {
|
||||
console.log('Supabase synced rows:', Object.keys(syncedData).length, '| History rows:', Object.keys(historyData).length);
|
||||
|
||||
// Extend headers with virtual columns if the Excel is shorter than the saved data.
|
||||
// COLUMNS hardcoded indices (PRODUCT_TYPE=103, ITEM_TO_LOGISTIC=104, etc.) are used
|
||||
// COLUMNS hardcoded indices (CATEGORIZATION_CODE=103, ITEM_TO_LOGISTIC=104, etc.) are used
|
||||
// by older saved rows — ensure headers covers them so the merge and render work.
|
||||
const extendedHeaders = [...headers];
|
||||
const VIRTUAL_COLS: Record<number, string> = {
|
||||
[COLUMNS.VERIFIED_DIMS]: 'Verified Dims',
|
||||
[COLUMNS.VALIDATED_CHECK]: 'Validated',
|
||||
[COLUMNS.VALIDATED_NOTE]: 'Note',
|
||||
[COLUMNS.PRODUCT_TYPE]: 'TYPE',
|
||||
[COLUMNS.CATEGORIZATION_CODE]: 'CategorizationCode',
|
||||
[COLUMNS.ITEM_TO_LOGISTIC]: 'Item to Logistic',
|
||||
[COLUMNS.ANNA_CHECK]: 'Anna Check',
|
||||
[COLUMNS.ANNA_NOTE]: 'Anna Note',
|
||||
@@ -448,7 +458,7 @@ export default function App() {
|
||||
const data = XLSX.utils.sheet_to_json<any[]>(ws, { header: 1 });
|
||||
|
||||
if (data.length > 0) {
|
||||
const headers = data[0];
|
||||
const headers = normalizeImportedHeaders(data[0]);
|
||||
const rawRows = data.slice(1);
|
||||
|
||||
const resolvedCols = resolveColumnIndices(headers);
|
||||
@@ -733,6 +743,18 @@ export default function App() {
|
||||
[bcQueueEntries]
|
||||
);
|
||||
|
||||
const bcQueueDisplayEntries = useMemo(() => {
|
||||
const queueColumns = resolveColumnIndices(appState.headers);
|
||||
return Object.fromEntries((Object.entries(bcSyncQueue) as Array<[string, BcSyncQueueEntry]>).map(([articleNo, entry]) => [articleNo, {
|
||||
articleName: entry.articleName,
|
||||
selected: entry.selected,
|
||||
status: entry.status,
|
||||
error: entry.error,
|
||||
warning: entry.warning,
|
||||
categorizationCode: String(entry.newData[queueColumns.CATEGORIZATION_CODE] ?? '').trim(),
|
||||
}]));
|
||||
}, [bcSyncQueue, appState.headers]);
|
||||
|
||||
useEffect(() => {
|
||||
if (appState.data.length === 0) return;
|
||||
const validArticles = new Set(appState.data.map(row => String(row[COLUMNS.ARTICLE_NO] ?? '').trim()).filter(Boolean));
|
||||
@@ -794,12 +816,26 @@ export default function App() {
|
||||
const handleSyncSelectedBcSync = async () => {
|
||||
if (bcQueueEntries.length === 0) return;
|
||||
setIsSyncingBC(true);
|
||||
const summary = {
|
||||
synced: [] as string[],
|
||||
failed: [] as string[],
|
||||
skippedEmptyCategorization: [] as string[],
|
||||
};
|
||||
try {
|
||||
const syncColumns = resolveColumnIndices(appState.headers);
|
||||
const entriesToSync = [...bcQueueEntries].sort((a, b) => {
|
||||
const rowDelta = a[1].rowIndex - b[1].rowIndex;
|
||||
if (rowDelta !== 0) return rowDelta;
|
||||
return a[0].localeCompare(b[0]);
|
||||
});
|
||||
const plannedCategorizationUpdates = entriesToSync
|
||||
.map(([articleNo, item]) => ({
|
||||
articleNo,
|
||||
categorizationCode: String(item.newData[syncColumns.CATEGORIZATION_CODE] ?? '').trim(),
|
||||
}))
|
||||
.filter(item => item.categorizationCode);
|
||||
|
||||
console.table(plannedCategorizationUpdates);
|
||||
|
||||
setBcSyncQueue(prev => {
|
||||
const next: BcSyncQueue = { ...prev };
|
||||
@@ -812,6 +848,14 @@ export default function App() {
|
||||
});
|
||||
|
||||
for (const [articleNo, item] of entriesToSync) {
|
||||
const categorizationCode = String(item.newData[syncColumns.CATEGORIZATION_CODE] ?? '').trim();
|
||||
if (!categorizationCode) {
|
||||
console.info(`[BC sync] ${articleNo}: CategorizationCode is empty, so categorizationCode will not be sent.`);
|
||||
summary.skippedEmptyCategorization.push(articleNo);
|
||||
} else {
|
||||
console.info(`[BC sync] ${articleNo}: will send categorizationCode=${categorizationCode}`);
|
||||
}
|
||||
|
||||
const preview = await previewBusinessCentralSync(appState.headers, item.newData);
|
||||
if (!preview.success) {
|
||||
setBcSyncQueue(prev => ({
|
||||
@@ -823,6 +867,7 @@ export default function App() {
|
||||
warning: undefined,
|
||||
},
|
||||
}));
|
||||
summary.failed.push(`${articleNo}: ${preview.error || 'Preview failed'}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -850,6 +895,7 @@ export default function App() {
|
||||
return next;
|
||||
});
|
||||
setRowStatuses(prev => ({ ...prev, [articleNo]: 'synced' }));
|
||||
summary.synced.push(articleNo);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -862,6 +908,7 @@ export default function App() {
|
||||
warning: retryResult.warning,
|
||||
},
|
||||
}));
|
||||
summary.failed.push(`${articleNo}: ${retryResult.error || 'BC sync failed'}`);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -874,6 +921,7 @@ export default function App() {
|
||||
warning: result.warning,
|
||||
},
|
||||
}));
|
||||
summary.failed.push(`${articleNo}: ${result.error || 'BC sync failed'}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -883,9 +931,20 @@ export default function App() {
|
||||
return next;
|
||||
});
|
||||
setRowStatuses(prev => ({ ...prev, [articleNo]: 'synced' }));
|
||||
summary.synced.push(articleNo);
|
||||
}
|
||||
} finally {
|
||||
setIsSyncingBC(false);
|
||||
const lines = [
|
||||
`Business Central sync finished.`,
|
||||
`Synced: ${summary.synced.length}`,
|
||||
`Failed: ${summary.failed.length}`,
|
||||
`Empty CategorizationCode skipped: ${summary.skippedEmptyCategorization.length}`,
|
||||
];
|
||||
if (summary.failed.length > 0) {
|
||||
lines.push('', 'Failures:', ...summary.failed.slice(0, 10));
|
||||
}
|
||||
alert(lines.join('\n'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -987,7 +1046,7 @@ export default function App() {
|
||||
onRevertRow={handleRevertRow}
|
||||
isSavingAll={isSavingAll}
|
||||
bcQueueCount={Object.keys(bcSyncQueue).length}
|
||||
bcQueueEntries={Object.fromEntries((Object.entries(bcSyncQueue) as Array<[string, BcSyncQueueEntry]>).map(([k, v]) => [k, { articleName: v.articleName, selected: v.selected, status: v.status, error: v.error, warning: v.warning }]))}
|
||||
bcQueueEntries={bcQueueDisplayEntries}
|
||||
onToggleBcQueueSelection={handleToggleBcQueueSelection}
|
||||
onSelectAllBcQueue={handleSelectAllBcQueue}
|
||||
onPreviewSelectedBcSync={handlePreviewSelectedBcSync}
|
||||
|
||||
Reference in New Issue
Block a user