mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 14:15:24 +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}
|
||||
|
||||
@@ -185,7 +185,7 @@ export function EditPanel({ row, rowIndex, onSave, onClose, onCaptureState }: Ed
|
||||
outerH: row[COLUMNS.OUTER_H] !== undefined && row[COLUMNS.OUTER_H] !== null ? String(row[COLUMNS.OUTER_H]) : '',
|
||||
unitsOuter: row[COLUMNS.UNITS_OUTER] !== undefined && row[COLUMNS.UNITS_OUTER] !== null ? String(row[COLUMNS.UNITS_OUTER]) : '',
|
||||
moq: row[COLUMNS.MOQ] !== undefined && row[COLUMNS.MOQ] !== null ? String(row[COLUMNS.MOQ]) : '',
|
||||
productType: row[COLUMNS.PRODUCT_TYPE] !== undefined && row[COLUMNS.PRODUCT_TYPE] !== null ? String(row[COLUMNS.PRODUCT_TYPE]) : '',
|
||||
productType: row[COLUMNS.CATEGORIZATION_CODE] !== undefined && row[COLUMNS.CATEGORIZATION_CODE] !== null ? String(row[COLUMNS.CATEGORIZATION_CODE]) : '',
|
||||
itemToLogistic: row[COLUMNS.ITEM_TO_LOGISTIC] !== undefined && row[COLUMNS.ITEM_TO_LOGISTIC] !== null ? String(row[COLUMNS.ITEM_TO_LOGISTIC]) : '',
|
||||
});
|
||||
|
||||
@@ -222,7 +222,7 @@ export function EditPanel({ row, rowIndex, onSave, onClose, onCaptureState }: Ed
|
||||
moq: COLUMNS.MOQ,
|
||||
detailsDe: COLUMNS.DETAILS_DE,
|
||||
detailsEn: COLUMNS.DETAILS_EN,
|
||||
productType: COLUMNS.PRODUCT_TYPE,
|
||||
productType: COLUMNS.CATEGORIZATION_CODE,
|
||||
itemToLogistic: COLUMNS.ITEM_TO_LOGISTIC,
|
||||
};
|
||||
const colIndex = (colMap as Record<string, number>)[field as string];
|
||||
@@ -360,7 +360,7 @@ export function EditPanel({ row, rowIndex, onSave, onClose, onCaptureState }: Ed
|
||||
newRow[COLUMNS.MOQ] = formData.moq;
|
||||
newRow[COLUMNS.DETAILS_DE] = formData.detailsDe;
|
||||
newRow[COLUMNS.DETAILS_EN] = formData.detailsEn;
|
||||
newRow[COLUMNS.PRODUCT_TYPE] = formData.productType;
|
||||
newRow[COLUMNS.CATEGORIZATION_CODE] = formData.productType;
|
||||
newRow[COLUMNS.ITEM_TO_LOGISTIC] = formData.itemToLogistic;
|
||||
onSave(rowIndex, newRow);
|
||||
};
|
||||
@@ -450,7 +450,7 @@ export function EditPanel({ row, rowIndex, onSave, onClose, onCaptureState }: Ed
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3 pt-2 border-t border-slate-700/30">
|
||||
<DimensionInput label="Type" value={formData.productType} field="productType" isModified={isModified('productType')} onChange={(val) => setFormData(p => ({...p, productType: val}))} />
|
||||
<DimensionInput label="CategorizationCode" value={formData.productType} field="productType" isModified={isModified('productType')} onChange={(val) => setFormData(p => ({...p, productType: val}))} />
|
||||
<DimensionInput label="Item to Logistic" value={formData.itemToLogistic} field="itemToLogistic" isModified={isModified('itemToLogistic')} onChange={(val) => setFormData(p => ({...p, itemToLogistic: val}))} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -405,7 +405,7 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
||||
[data]);
|
||||
|
||||
const uniqueProductTypes = useMemo(() =>
|
||||
Array.from(new Set(data.map(r => String(r[COLUMNS.PRODUCT_TYPE] || '')).filter(Boolean))).sort(),
|
||||
Array.from(new Set(data.map(r => String(r[COLUMNS.CATEGORIZATION_CODE] || '')).filter(Boolean))).sort(),
|
||||
[data]);
|
||||
|
||||
const uniqueUnitsOuter = useMemo(() =>
|
||||
@@ -518,7 +518,7 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
||||
result = result.filter(r => (classificationFilter || []).includes(String(r.row[COLUMNS.CLASSIFICATION] || '')));
|
||||
}
|
||||
if ((productTypeFilter || []).length > 0) {
|
||||
result = result.filter(r => (productTypeFilter || []).includes(String(r.row[COLUMNS.PRODUCT_TYPE] || '')));
|
||||
result = result.filter(r => (productTypeFilter || []).includes(String(r.row[COLUMNS.CATEGORIZATION_CODE] || '')));
|
||||
}
|
||||
if ((unitsOuterFilter || []).length > 0) {
|
||||
result = result.filter(r => (unitsOuterFilter || []).includes(String(r.row[unitsOuterIdx] || '')));
|
||||
@@ -615,7 +615,7 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
||||
case 'articleName': colIndex = COLUMNS.ARTICLE_NAME; break;
|
||||
case 'line': colIndex = COLUMNS.LINE; break;
|
||||
case 'classification': colIndex = COLUMNS.CLASSIFICATION; break;
|
||||
case 'productType': colIndex = COLUMNS.PRODUCT_TYPE; break;
|
||||
case 'productType': colIndex = COLUMNS.CATEGORIZATION_CODE; break;
|
||||
case 'unitsOuter': colIndex = unitsOuterIdx; break;
|
||||
case 'outerW': colIndex = COLUMNS.OUTER_W; break;
|
||||
case 'outerL': colIndex = COLUMNS.OUTER_L; break;
|
||||
@@ -704,9 +704,9 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
||||
if (e.key === 'Escape') cancelEdit();
|
||||
};
|
||||
|
||||
// ── All unique product types (for autocomplete) ───────────────────────────
|
||||
// ── All unique categorization codes (for autocomplete) ────────────────────
|
||||
const allProductTypes = useMemo(() =>
|
||||
Array.from(new Set(data.map(r => String(r[COLUMNS.PRODUCT_TYPE] || '')).filter(Boolean))).sort()
|
||||
Array.from(new Set(data.map(r => String(r[COLUMNS.CATEGORIZATION_CODE] || '')).filter(Boolean))).sort()
|
||||
, [data]);
|
||||
|
||||
const startEditType = (rowIndex: number, currentValue: string) => {
|
||||
@@ -720,8 +720,8 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
||||
setTypeSuggestions([]);
|
||||
const original = data[rowIndex];
|
||||
const newRow = [...original];
|
||||
newRow[COLUMNS.PRODUCT_TYPE] = value;
|
||||
onCaptureState(`Updated type for ${original[COLUMNS.ARTICLE_NO]}`);
|
||||
newRow[COLUMNS.CATEGORIZATION_CODE] = value;
|
||||
onCaptureState(`Updated CategorizationCode for ${original[COLUMNS.ARTICLE_NO]}`);
|
||||
await onSaveRow(rowIndex, newRow);
|
||||
}, [data, onSaveRow, onCaptureState]);
|
||||
|
||||
@@ -760,7 +760,7 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
||||
{ key: 'articleName', label: 'Article Name', index: COLUMNS.ARTICLE_NAME },
|
||||
{ key: 'line', label: 'Line', index: COLUMNS.LINE },
|
||||
{ key: 'classification', label: 'Classification', index: COLUMNS.CLASSIFICATION },
|
||||
{ key: 'productType', label: 'Type', index: COLUMNS.PRODUCT_TYPE },
|
||||
{ key: 'productType', label: 'CategorizationCode', index: COLUMNS.CATEGORIZATION_CODE },
|
||||
{ key: 'itemToLogistic', label: 'Item to Logistic', index: COLUMNS.ITEM_TO_LOGISTIC },
|
||||
{ key: 'unitsOuter', label: 'Units/Outer', index: unitsOuterIdx },
|
||||
{ key: 'outerW', label: 'Outer W', index: COLUMNS.OUTER_W },
|
||||
@@ -1521,7 +1521,7 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
||||
)} onClick={() => handleSort('productType')}>
|
||||
<span className="flex items-center gap-1">
|
||||
{isPinned('productType') && <Pin className="w-3 h-3 text-blue-400 shrink-0" />}
|
||||
Type <SortIcon current={sortConfig.key === 'productType' ? sortConfig.direction : null} />
|
||||
CategorizationCode <SortIcon current={sortConfig.key === 'productType' ? sortConfig.direction : null} />
|
||||
<button
|
||||
id="filter-trigger-type"
|
||||
onClick={(e: React.MouseEvent) => { e.stopPropagation(); setOpenFilter(openFilter === 'productType' ? null : 'productType'); }}
|
||||
@@ -2065,7 +2065,7 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
||||
</span>
|
||||
</td>
|
||||
|
||||
{/* TYPE cell */}
|
||||
{/* CategorizationCode cell */}
|
||||
<td className={cn("px-3 py-2 overflow-visible relative", isPinned('productType') && "sticky bg-slate-800")} style={isPinned('productType') ? { left: getStickyLeft('productType') ?? 0, zIndex: getStickyRank('productType') ?? 0 } : {}}>
|
||||
{editingType?.rowIndex === dataIndex ? (
|
||||
<div className="relative">
|
||||
@@ -2097,15 +2097,15 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => startEditType(dataIndex, String(row[COLUMNS.PRODUCT_TYPE] || ''))}
|
||||
onClick={() => startEditType(dataIndex, String(row[COLUMNS.CATEGORIZATION_CODE] || ''))}
|
||||
className={cn(
|
||||
'group flex items-center gap-1.5 px-2 py-1 rounded text-xs transition-colors hover:bg-slate-700/60 w-full overflow-hidden',
|
||||
row[COLUMNS.PRODUCT_TYPE]
|
||||
row[COLUMNS.CATEGORIZATION_CODE]
|
||||
? 'text-purple-300 border border-transparent hover:border-slate-600'
|
||||
: 'text-slate-500 border border-dashed border-slate-600 hover:border-purple-500/50'
|
||||
)}
|
||||
>
|
||||
<span className="truncate">{row[COLUMNS.PRODUCT_TYPE] || 'Add type…'}</span>
|
||||
<span className="truncate">{row[COLUMNS.CATEGORIZATION_CODE] || 'Add CategorizationCode...'}</span>
|
||||
<Edit2 className="w-2.5 h-2.5 opacity-0 group-hover:opacity-50 shrink-0" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -18,7 +18,7 @@ interface TopBarProps {
|
||||
onRevertRow: (articleNo: string) => void;
|
||||
isSavingAll: boolean;
|
||||
bcQueueCount: number;
|
||||
bcQueueEntries: Record<string, { articleName: string; selected: boolean; status: string; error?: string; warning?: string }>;
|
||||
bcQueueEntries: Record<string, { articleName: string; selected: boolean; status: string; error?: string; warning?: string; categorizationCode?: string }>;
|
||||
onToggleBcQueueSelection: (articleNo: string) => void;
|
||||
onSelectAllBcQueue: (selected: boolean) => void;
|
||||
onPreviewSelectedBcSync: () => Promise<void>;
|
||||
@@ -274,6 +274,9 @@ export function TopBar({ stats, activeModule, onRefresh, userEmail, onSignOut, c
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-xs font-mono text-slate-400">{articleNo}</p>
|
||||
<p className="text-sm text-white truncate">{entry.articleName}</p>
|
||||
<p className="text-[10px] text-cyan-300 truncate">
|
||||
CategorizationCode: {entry.categorizationCode || 'empty - skipped'}
|
||||
</p>
|
||||
<p className="text-[10px] text-slate-500 uppercase">
|
||||
{formatQueueStatus(entry.status)}
|
||||
{entry.error ? ` · ${entry.error}` : ''}
|
||||
|
||||
@@ -24,6 +24,17 @@ function findHeaderIndex(headers: string[], patterns: string[]): number {
|
||||
);
|
||||
}
|
||||
|
||||
function findCategorizationCodeIndex(headers: string[]): number {
|
||||
const normalized = headers.map(h => String(h || '').toLowerCase().trim());
|
||||
const categorizationIdx = normalized.findIndex(header => header.replace(/[\s_-]+/g, '') === 'categorizationcode');
|
||||
if (categorizationIdx >= 0) return categorizationIdx;
|
||||
|
||||
const typeIdx = normalized.findIndex(header => header === 'type');
|
||||
if (typeIdx >= 0) return typeIdx;
|
||||
|
||||
return normalized.findIndex(header => header.includes('product') && header.includes('type'));
|
||||
}
|
||||
|
||||
function formatDateForBc(value: any): string | null {
|
||||
if (value === null || value === undefined || value === '') return null;
|
||||
|
||||
@@ -81,6 +92,7 @@ export function buildBusinessCentralMappingPreview(headers: string[], row: Excel
|
||||
const shortDeIdx = findHeaderIndex(headers, ['short', 'description', 'german']);
|
||||
const shortEnIdx = findHeaderIndex(headers, ['short', 'description', 'english']);
|
||||
const cpnpIdx = findHeaderIndex(headers, ['cpnp']);
|
||||
const categorizationCodeIdx = findCategorizationCodeIndex(headers);
|
||||
|
||||
const unitsOuterIdx = findHeaderIndex(headers, ['units', 'outer']);
|
||||
const units40HqIdx = (() => {
|
||||
@@ -94,7 +106,10 @@ export function buildBusinessCentralMappingPreview(headers: string[], row: Excel
|
||||
|
||||
const articleNo = String(getValue(row, articleNoIdx) ?? '');
|
||||
|
||||
const itemsPayload = {
|
||||
const categorizationCode = getValue(row, categorizationCodeIdx);
|
||||
const hasCategorizationCode = String(categorizationCode ?? '').trim() !== '';
|
||||
|
||||
const itemsPayload: Record<string, any> = {
|
||||
no: getValue(row, articleNoIdx),
|
||||
articleDetailsEnglish: getValue(row, articleDetailsEnIdx),
|
||||
articleDetailsGerman: getValue(row, articleDetailsDeIdx),
|
||||
@@ -106,6 +121,10 @@ export function buildBusinessCentralMappingPreview(headers: string[], row: Excel
|
||||
cpnpNo: getValue(row, cpnpIdx),
|
||||
};
|
||||
|
||||
if (hasCategorizationCode) {
|
||||
itemsPayload.categorizationCode = String(categorizationCode).trim();
|
||||
}
|
||||
|
||||
const itemUnitsOfMeasurePayload = {
|
||||
itemNo: getValue(row, articleNoIdx),
|
||||
code: 'OUTER',
|
||||
@@ -136,6 +155,9 @@ export function buildBusinessCentralMappingPreview(headers: string[], row: Excel
|
||||
makeFieldPreview('Short Description - German', shortDeIdx, 'shortDescriptionInGerman', itemsPayload.shortDescriptionInGerman),
|
||||
makeFieldPreview('Short Description - English', shortEnIdx, 'shortDescriptionInEnglish', itemsPayload.shortDescriptionInEnglish),
|
||||
makeFieldPreview('CPNP', cpnpIdx, 'cpnpNo', itemsPayload.cpnpNo),
|
||||
...(hasCategorizationCode
|
||||
? [makeFieldPreview('CategorizationCode', categorizationCodeIdx, 'categorizationCode', itemsPayload.categorizationCode)]
|
||||
: []),
|
||||
],
|
||||
itemUnitsFields: [
|
||||
makeFieldPreview('Article No.', articleNoIdx, 'itemNo', itemUnitsOfMeasurePayload.itemNo),
|
||||
|
||||
+15
-1
@@ -40,6 +40,7 @@ export const COLUMNS = {
|
||||
VERIFIED_DIMS: 100,
|
||||
VALIDATED_CHECK: 101,
|
||||
VALIDATED_NOTE: 102,
|
||||
CATEGORIZATION_CODE: 103,
|
||||
PRODUCT_TYPE: 103,
|
||||
ITEM_TO_LOGISTIC: 104,
|
||||
ANNA_CHECK: 105,
|
||||
@@ -78,6 +79,7 @@ export const COLUMN_PATTERNS: Record<keyof typeof COLUMNS, string[]> = {
|
||||
VERIFIED_DIMS: ['verified', 'dims'],
|
||||
VALIDATED_CHECK: ['validated', 'check'],
|
||||
VALIDATED_NOTE: ['validated', 'note'],
|
||||
CATEGORIZATION_CODE: ['categorization', 'code'],
|
||||
PRODUCT_TYPE: ['product', 'type'],
|
||||
ITEM_TO_LOGISTIC: ['item', 'logistic'],
|
||||
ANNA_CHECK: ['anna', 'check'],
|
||||
@@ -101,5 +103,17 @@ export function resolveColumnIndices(headers: string[]): typeof COLUMNS {
|
||||
}
|
||||
});
|
||||
|
||||
let categorizationIdx = h.findIndex(headerText => headerText.replace(/[\s_-]+/g, '') === 'categorizationcode');
|
||||
if (categorizationIdx < 0) {
|
||||
categorizationIdx = h.findIndex(headerText => headerText.trim() === 'type');
|
||||
}
|
||||
|
||||
if (categorizationIdx >= 0) {
|
||||
resolved.CATEGORIZATION_CODE = categorizationIdx;
|
||||
resolved.PRODUCT_TYPE = categorizationIdx;
|
||||
} else {
|
||||
resolved.CATEGORIZATION_CODE = resolved.PRODUCT_TYPE;
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user