feat(bc): sync 40hc pricing

This commit is contained in:
Christian Vidal Wolf
2026-05-26 10:34:11 +02:00
parent 74d4d37769
commit 4037f7924a
4 changed files with 169 additions and 34 deletions
+39 -25
View File
@@ -170,37 +170,30 @@ export function MatrixView({ data, headers, rowStatuses }: MatrixViewProps) {
setBcValidationError(null);
setBcValidationWarning(null);
try {
const hasWritableItems = bcValidationResult.items.changes.some((change: any) => change.changed);
const hasOnlyUomChanges = !hasWritableItems && bcValidationResult.itemUnitsOfMeasure.changes.some((change: any) => change.changed);
if (hasOnlyUomChanges) {
setBcValidationWarning(bcValidationResult.itemUnitsOfMeasure.supportReason || 'itemUnitsOfMeasure sync is preview only');
return;
}
const result = await applyBusinessCentralSync(headers, bcPreviewRow, bcValidationResult.previewToken);
if (!result.success) {
if (isPreviewTokenMismatchError(result.error)) {
const refreshedPreview = await previewBusinessCentralSync(headers, bcPreviewRow);
if (refreshedPreview.success) {
setBcValidationResult(refreshedPreview);
const retry = await applyBusinessCentralSync(headers, bcPreviewRow, refreshedPreview.previewToken);
if (retry.success) {
setBcValidationResult(prev => retry.preview ? { ...retry.preview, hasChanges: false } : prev);
if (retry.warning) {
setBcValidationWarning(retry.warning);
}
return;
if (refreshedPreview.success) {
setBcValidationResult(refreshedPreview);
const retry = await applyBusinessCentralSync(headers, bcPreviewRow, refreshedPreview.previewToken);
if (retry.success) {
setBcValidationResult(prev => retry.preview ? { ...retry.preview, hasChanges: false } : prev);
if (retry.warning) {
setBcValidationWarning(retry.warning);
}
setBcValidationError(retry.error || 'Apply failed');
return;
}
setBcValidationError(retry.error || 'Apply failed');
return;
}
setBcValidationError(result.error || 'Apply failed');
if (result.warning) {
setBcValidationWarning(result.warning);
}
return;
}
setBcValidationError(result.error || 'Apply failed');
if (result.warning) {
setBcValidationWarning(result.warning);
}
return;
}
setBcValidationResult(prev => result.preview ? { ...result.preview, hasChanges: false } : prev);
if (result.warning) {
setBcValidationWarning(result.warning);
@@ -417,8 +410,8 @@ export function MatrixView({ data, headers, rowStatuses }: MatrixViewProps) {
</div>
</div>
<div className="grid grid-cols-1 xl:grid-cols-2 gap-4">
{(['items', 'itemUnitsOfMeasure'] as const).map(sectionKey => {
<div className="grid grid-cols-1 xl:grid-cols-3 gap-4">
{(['items', 'itemUnitsOfMeasure', 'itemUnits40HC'] as const).map(sectionKey => {
const section = bcValidationResult[sectionKey];
const changed = section.changes.filter((change: any) => change.changed);
@@ -472,7 +465,7 @@ export function MatrixView({ data, headers, rowStatuses }: MatrixViewProps) {
</div>
)}
<div className="grid grid-cols-1 xl:grid-cols-2 gap-4">
<div className="grid grid-cols-1 xl:grid-cols-3 gap-4">
<div className="rounded-lg border border-slate-700 bg-slate-950/60 p-4">
<div className="flex items-center justify-between mb-3">
<div>
@@ -514,6 +507,27 @@ export function MatrixView({ data, headers, rowStatuses }: MatrixViewProps) {
{JSON.stringify(bcMappingPreview.itemUnitsOfMeasurePayload, null, 2)}
</pre>
</div>
<div className="rounded-lg border border-slate-700 bg-slate-950/60 p-4">
<div className="flex items-center justify-between mb-3">
<div>
<h3 className="text-sm font-semibold text-white">API `itemUnitsOfMeasure2` 40HC preview</h3>
<p className="text-xs text-slate-400">SKU {bcMappingPreview.articleNo}</p>
</div>
</div>
<div className="space-y-2">
{bcMappingPreview.itemUnits40HCFields.map(field => (
<div key={field.targetField} className="grid grid-cols-3 gap-3 text-xs">
<div className="text-slate-400">{field.sourceLabel}</div>
<div className="text-slate-500 truncate">{field.targetField}</div>
<div className="text-white truncate">{String(field.value ?? '—')}</div>
</div>
))}
</div>
<pre className="mt-4 text-[11px] leading-relaxed bg-slate-950 border border-slate-800 rounded p-3 overflow-auto text-slate-200">
{JSON.stringify(bcMappingPreview.itemUnits40HCPayload, null, 2)}
</pre>
</div>
</div>
</div>
)}
+3 -1
View File
@@ -27,7 +27,7 @@ export interface BCFieldChange {
}
export interface BCSyncPreviewSection {
type: 'items' | 'itemUnitsOfMeasure';
type: 'items' | 'itemUnitsOfMeasure' | 'itemUnits40HC';
desired: Record<string, any>;
current: Record<string, any> | null;
changes: BCFieldChange[];
@@ -46,6 +46,7 @@ export interface BCSyncPreviewResult {
articleNo: string;
items: BCSyncPreviewSection;
itemUnitsOfMeasure: BCSyncPreviewSection;
itemUnits40HC: BCSyncPreviewSection;
hasChanges: boolean;
previewToken: string;
error?: string;
@@ -59,6 +60,7 @@ export interface BCSyncApplyResult {
results?: {
items: { applied: boolean; reason?: string; url?: string };
itemUnitsOfMeasure: { applied: boolean; reason?: string; url?: string };
itemUnits40HC: { applied: boolean; reason?: string; url?: string };
};
error?: string;
warning?: string;
+18
View File
@@ -11,8 +11,10 @@ export interface BusinessCentralMappingPreview {
articleNo: string;
itemsPayload: Record<string, any>;
itemUnitsOfMeasurePayload: Record<string, any>;
itemUnits40HCPayload: Record<string, any>;
itemsFields: MappingFieldPreview[];
itemUnitsFields: MappingFieldPreview[];
itemUnits40HCFields: MappingFieldPreview[];
}
function findHeaderIndex(headers: string[], patterns: string[]): number {
@@ -81,6 +83,11 @@ export function buildBusinessCentralMappingPreview(headers: string[], row: Excel
const cpnpIdx = findHeaderIndex(headers, ['cpnp']);
const unitsOuterIdx = findHeaderIndex(headers, ['units', 'outer']);
const units40HqIdx = (() => {
const hqIdx = findHeaderIndex(headers, ['40', 'hq']);
if (hqIdx >= 0) return hqIdx;
return findHeaderIndex(headers, ['40', 'hc']);
})();
const outerWIdx = findHeaderIndex(headers, ['outer', 'w']);
const outerLIdx = findHeaderIndex(headers, ['outer', 'l']);
const outerHIdx = findHeaderIndex(headers, ['outer', 'h']);
@@ -108,10 +115,17 @@ export function buildBusinessCentralMappingPreview(headers: string[], row: Excel
height: toBcDecimal(getValue(row, outerHIdx)),
};
const itemUnits40HCPayload = {
itemNo: getValue(row, articleNoIdx),
code: '40HC',
qtyPerUnitOfMeasure: toBcDecimal(getValue(row, units40HqIdx)),
};
return {
articleNo,
itemsPayload,
itemUnitsOfMeasurePayload,
itemUnits40HCPayload,
itemsFields: [
makeFieldPreview('Article No.', articleNoIdx, 'no', itemsPayload.no),
makeFieldPreview('Article Details - English', articleDetailsEnIdx, 'articleDetailsEnglish', itemsPayload.articleDetailsEnglish),
@@ -130,5 +144,9 @@ export function buildBusinessCentralMappingPreview(headers: string[], row: Excel
makeFieldPreview('Outer L (cm)', outerLIdx, 'length', itemUnitsOfMeasurePayload.length),
makeFieldPreview('Outer H (cm)', outerHIdx, 'height', itemUnitsOfMeasurePayload.height),
],
itemUnits40HCFields: [
makeFieldPreview('Article No.', articleNoIdx, 'itemNo', itemUnits40HCPayload.itemNo),
makeFieldPreview('Units 40FT HQ', units40HqIdx, 'qtyPerUnitOfMeasure', itemUnits40HCPayload.qtyPerUnitOfMeasure),
],
};
}