mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 14:15:24 +02:00
feat(matrix): support new matrix layout with 4 added columns
- Update default column indices to July 2026 layout (EmpCO_Compliant, Comments, Categorisation Code, Ingredients added; PM Classification removed; downstream columns shifted) - Seed internal Categorisation Code from the new Excel column while keeping app-side edits as the source of truth - Guard legacy CPNP index-77 fallbacks: only trusted when the current layout still holds CPNP there (new layout stores Item To Root Units) - Use resolved column index for forced zero stock instead of static one Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
633f523fc8
commit
0d50fce790
+48
-6
@@ -276,10 +276,26 @@ export default function App() {
|
||||
console.warn('Supabase sync failed:', syncRes.status, errorText);
|
||||
}
|
||||
|
||||
// Layout detection: older matrix files carried CPNP No. at index 77
|
||||
// (LEGACY_CPNP_INDEX); the July 2026 layout stores "Item To Root Units"
|
||||
// there, so legacy CPNP fallbacks must be disabled for the new layout.
|
||||
const legacyCpnpIndexIsCpnp = String(headers[LEGACY_CPNP_INDEX] ?? '')
|
||||
.toLowerCase()
|
||||
.includes('cpnp');
|
||||
// The new layout also ships Categorisation Code as a real Excel column
|
||||
// (British spelling). It seeds the internal column below; app-side edits
|
||||
// stored in the internal column keep precedence.
|
||||
const normalizeHeaderKey = (h: unknown) =>
|
||||
String(h ?? '').toLowerCase().replace(/[\s_-]+/g, '');
|
||||
const excelCategorisationIdx = headers.findIndex(h => {
|
||||
const key = normalizeHeaderKey(h);
|
||||
return key === 'categorisationcode' || key === 'categorizationcode';
|
||||
});
|
||||
|
||||
console.log('Fetching synced data from Supabase...');
|
||||
const syncedData = await getAllSyncedRows();
|
||||
console.log('Fetching history data from Supabase for merge...');
|
||||
const historyData = await getHistoryDataForMerge();
|
||||
const historyData = await getHistoryDataForMerge(legacyCpnpIndexIsCpnp);
|
||||
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.
|
||||
@@ -354,12 +370,15 @@ export default function App() {
|
||||
});
|
||||
|
||||
// Older cosmetic edits stored CPNP in index 77 before the dedicated
|
||||
// internal CPNP column was stabilized at index 107.
|
||||
// internal CPNP column was stabilized at index 107. Only trust that
|
||||
// index when the current layout still holds CPNP there — in the new
|
||||
// layout index 77 is "Item To Root Units".
|
||||
const legacyCpnp =
|
||||
hist?.[COLUMNS.CPNP_NO] ??
|
||||
synced?.data?.[COLUMNS.CPNP_NO] ??
|
||||
hist?.[LEGACY_CPNP_INDEX] ??
|
||||
synced?.data?.[LEGACY_CPNP_INDEX];
|
||||
(legacyCpnpIndexIsCpnp
|
||||
? hist?.[LEGACY_CPNP_INDEX] ?? synced?.data?.[LEGACY_CPNP_INDEX]
|
||||
: undefined);
|
||||
if (legacyCpnp !== undefined && legacyCpnp !== null && String(legacyCpnp).trim() !== '') {
|
||||
finalRow[COLUMNS.CPNP_NO] = legacyCpnp;
|
||||
}
|
||||
@@ -370,9 +389,17 @@ export default function App() {
|
||||
}
|
||||
}
|
||||
|
||||
// Categorisation Code: Excel value is the base, internal app edits win
|
||||
if (excelCategorisationIdx >= 0 && excelCategorisationIdx !== COLUMNS.CATEGORIZATION_CODE) {
|
||||
const internalCat = finalRow[COLUMNS.CATEGORIZATION_CODE];
|
||||
if (internalCat === undefined || internalCat === null || String(internalCat).trim() === '') {
|
||||
finalRow[COLUMNS.CATEGORIZATION_CODE] = row[excelCategorisationIdx];
|
||||
}
|
||||
}
|
||||
|
||||
// Force stock 0 for specific SKUs (User request)
|
||||
if (FORCED_ZERO_STOCK_SKUS.has(articleNo)) {
|
||||
finalRow[COLUMNS.ITEM_AVAILABLE] = 0;
|
||||
finalRow[resolvedCols.ITEM_AVAILABLE] = 0;
|
||||
}
|
||||
|
||||
return finalRow.map((val: any, idx: number) => {
|
||||
@@ -820,6 +847,7 @@ export default function App() {
|
||||
synced: [] as string[],
|
||||
failed: [] as string[],
|
||||
skippedEmptyCategorization: [] as string[],
|
||||
warnings: [] as string[],
|
||||
};
|
||||
try {
|
||||
const syncColumns = resolveColumnIndices(appState.headers);
|
||||
@@ -896,6 +924,9 @@ export default function App() {
|
||||
});
|
||||
setRowStatuses(prev => ({ ...prev, [articleNo]: 'synced' }));
|
||||
summary.synced.push(articleNo);
|
||||
if (retryResult.warning) {
|
||||
summary.warnings.push(`${articleNo}: ${retryResult.warning}`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -932,15 +963,26 @@ export default function App() {
|
||||
});
|
||||
setRowStatuses(prev => ({ ...prev, [articleNo]: 'synced' }));
|
||||
summary.synced.push(articleNo);
|
||||
if (result.warning) {
|
||||
summary.warnings.push(`${articleNo}: ${result.warning}`);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
setIsSyncingBC(false);
|
||||
const summaryTitle = summary.failed.length > 0
|
||||
? 'Business Central sync finished with errors.'
|
||||
: summary.warnings.length > 0
|
||||
? 'Business Central sync finished with warnings.'
|
||||
: 'Business Central sync finished.';
|
||||
const lines = [
|
||||
`Business Central sync finished.`,
|
||||
summaryTitle,
|
||||
`Synced: ${summary.synced.length}`,
|
||||
`Failed: ${summary.failed.length}`,
|
||||
`Empty CategorizationCode skipped: ${summary.skippedEmptyCategorization.length}`,
|
||||
];
|
||||
if (summary.warnings.length > 0) {
|
||||
lines.push('', 'Warnings (sync completed):', ...summary.warnings.slice(0, 10));
|
||||
}
|
||||
if (summary.failed.length > 0) {
|
||||
lines.push('', 'Failures:', ...summary.failed.slice(0, 10));
|
||||
}
|
||||
|
||||
+8
-4
@@ -299,7 +299,7 @@ export async function getHistory(): Promise<HistoryEntry[]> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getHistoryDataForMerge(): Promise<Record<string, ExcelRow>> {
|
||||
export async function getHistoryDataForMerge(includeLegacyCpnpIndex = true): Promise<Record<string, ExcelRow>> {
|
||||
try {
|
||||
const PAGE_SIZE = 1000;
|
||||
const entries: HistoryEntry[] = [];
|
||||
@@ -348,15 +348,19 @@ export async function getHistoryDataForMerge(): Promise<Record<string, ExcelRow>
|
||||
|
||||
// CPNP values were saved through multiple code paths over time.
|
||||
// Preserve the latest non-empty value even when a given history row
|
||||
// does not surface it as a changed index.
|
||||
// does not surface it as a changed index. Index 77 is only a CPNP
|
||||
// location in the pre-July-2026 layout (includeLegacyCpnpIndex);
|
||||
// in the new layout it holds "Item To Root Units".
|
||||
const latestCpnp =
|
||||
entry.new_data?.[COLUMNS.CPNP_NO] ??
|
||||
entry.new_data?.[LEGACY_CPNP_INDEX] ??
|
||||
(includeLegacyCpnpIndex ? entry.new_data?.[LEGACY_CPNP_INDEX] : undefined) ??
|
||||
entry.old_data?.[COLUMNS.CPNP_NO];
|
||||
const fallbackCpnp =
|
||||
latestCpnp !== undefined && latestCpnp !== null && latestCpnp !== ''
|
||||
? latestCpnp
|
||||
: entry.old_data?.[LEGACY_CPNP_INDEX];
|
||||
: includeLegacyCpnpIndex
|
||||
? entry.old_data?.[LEGACY_CPNP_INDEX]
|
||||
: undefined;
|
||||
|
||||
if (fallbackCpnp !== undefined && fallbackCpnp !== null && fallbackCpnp !== '') {
|
||||
target[COLUMNS.CPNP_NO] = fallbackCpnp;
|
||||
|
||||
+25
-22
@@ -10,33 +10,36 @@ export interface AppState {
|
||||
}
|
||||
|
||||
// Canonical column indices (defaults)
|
||||
// Aligned to the matrix layout introduced in July 2026, which added
|
||||
// EmpCO_Compliant (9), Comments (10), Categorisation Code (11) and
|
||||
// Ingredients (80), and removed PM Classification.
|
||||
export const COLUMNS = {
|
||||
ARTICLE_NO: 0,
|
||||
ARTICLE_NAME: 2,
|
||||
LINE: 7,
|
||||
LICENSE: 8,
|
||||
DETAILS_EN: 9,
|
||||
DETAILS_DE: 10,
|
||||
BARCODE: 29,
|
||||
TARIFF_CODE: 60,
|
||||
COUNTRY_ORIGIN: 62,
|
||||
LONG_DE: 64,
|
||||
LONG_EN: 65,
|
||||
SHORT_DE: 66,
|
||||
SHORT_EN: 67,
|
||||
RECOMMENDED_AGE: 70,
|
||||
CLASSIFICATION: 11,
|
||||
ITEM_AVAILABLE: 16,
|
||||
MOQ: 28,
|
||||
UNITS_INNER: 32,
|
||||
UNITS_OUTER: 33,
|
||||
ASIN: 76,
|
||||
INNER_W: 43,
|
||||
INNER_L: 44,
|
||||
INNER_H: 45,
|
||||
OUTER_W: 48,
|
||||
OUTER_L: 49,
|
||||
OUTER_H: 50,
|
||||
DETAILS_EN: 12,
|
||||
DETAILS_DE: 13,
|
||||
BARCODE: 31,
|
||||
TARIFF_CODE: 62,
|
||||
COUNTRY_ORIGIN: 64,
|
||||
LONG_DE: 65,
|
||||
LONG_EN: 66,
|
||||
SHORT_DE: 67,
|
||||
SHORT_EN: 68,
|
||||
RECOMMENDED_AGE: 71,
|
||||
CLASSIFICATION: 14,
|
||||
ITEM_AVAILABLE: 18,
|
||||
MOQ: 30,
|
||||
UNITS_INNER: 34,
|
||||
UNITS_OUTER: 35,
|
||||
ASIN: 78,
|
||||
INNER_W: 45,
|
||||
INNER_L: 46,
|
||||
INNER_H: 47,
|
||||
OUTER_W: 50,
|
||||
OUTER_L: 51,
|
||||
OUTER_H: 52,
|
||||
VERIFIED_DIMS: 100,
|
||||
VALIDATED_CHECK: 101,
|
||||
VALIDATED_NOTE: 102,
|
||||
|
||||
Reference in New Issue
Block a user