mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 11:45:24 +02:00
feat(bc): sync 40hc pricing
This commit is contained in:
+109
-8
@@ -62,6 +62,11 @@ export function buildBusinessCentralMappingPreview(headers, row) {
|
|||||||
const cpnpIdx = findHeaderIndex(headers, ['cpnp']);
|
const cpnpIdx = findHeaderIndex(headers, ['cpnp']);
|
||||||
|
|
||||||
const unitsOuterIdx = findHeaderIndex(headers, ['units', 'outer']);
|
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 innerWIdx = findHeaderIndex(headers, ['inner', 'w']);
|
const innerWIdx = findHeaderIndex(headers, ['inner', 'w']);
|
||||||
const innerLIdx = findHeaderIndex(headers, ['inner', 'l']);
|
const innerLIdx = findHeaderIndex(headers, ['inner', 'l']);
|
||||||
const innerHIdx = findHeaderIndex(headers, ['inner', 'h']);
|
const innerHIdx = findHeaderIndex(headers, ['inner', 'h']);
|
||||||
@@ -92,10 +97,17 @@ export function buildBusinessCentralMappingPreview(headers, row) {
|
|||||||
height: toBcDecimal(getValue(row, outerHIdx)),
|
height: toBcDecimal(getValue(row, outerHIdx)),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const itemUnits40HCPayload = {
|
||||||
|
itemNo: getValue(row, articleNoIdx),
|
||||||
|
code: '40HC',
|
||||||
|
qtyPerUnitOfMeasure: toBcDecimal(getValue(row, units40HqIdx)),
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
articleNo,
|
articleNo,
|
||||||
itemsPayload,
|
itemsPayload,
|
||||||
itemUnitsOfMeasurePayload,
|
itemUnitsOfMeasurePayload,
|
||||||
|
itemUnits40HCPayload,
|
||||||
itemsFields: [
|
itemsFields: [
|
||||||
makeFieldPreview('Article No.', articleNoIdx, 'no', itemsPayload.no),
|
makeFieldPreview('Article No.', articleNoIdx, 'no', itemsPayload.no),
|
||||||
makeFieldPreview('Article Details - English', articleDetailsEnIdx, 'articleDetailsEnglish', itemsPayload.articleDetailsEnglish),
|
makeFieldPreview('Article Details - English', articleDetailsEnIdx, 'articleDetailsEnglish', itemsPayload.articleDetailsEnglish),
|
||||||
@@ -114,6 +126,10 @@ export function buildBusinessCentralMappingPreview(headers, row) {
|
|||||||
makeFieldPreview('Outer L (cm)', outerLIdx, 'length', itemUnitsOfMeasurePayload.length),
|
makeFieldPreview('Outer L (cm)', outerLIdx, 'length', itemUnitsOfMeasurePayload.length),
|
||||||
makeFieldPreview('Outer H (cm)', outerHIdx, 'height', itemUnitsOfMeasurePayload.height),
|
makeFieldPreview('Outer H (cm)', outerHIdx, 'height', itemUnitsOfMeasurePayload.height),
|
||||||
],
|
],
|
||||||
|
itemUnits40HCFields: [
|
||||||
|
makeFieldPreview('Article No.', articleNoIdx, 'itemNo', itemUnits40HCPayload.itemNo),
|
||||||
|
makeFieldPreview('Units 40FT HQ', units40HqIdx, 'qtyPerUnitOfMeasure', itemUnits40HCPayload.qtyPerUnitOfMeasure),
|
||||||
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,16 +285,26 @@ export async function fetchBusinessCentralSnapshot(config, token, articleNo) {
|
|||||||
const { json: itemsJson, etag: itemsEtag } = await fetchJsonOrThrow(itemsUrl, token, 'BC GET items');
|
const { json: itemsJson, etag: itemsEtag } = await fetchJsonOrThrow(itemsUrl, token, 'BC GET items');
|
||||||
const item = Array.isArray(itemsJson.value) && itemsJson.value.length > 0 ? itemsJson.value[0] : null;
|
const item = Array.isArray(itemsJson.value) && itemsJson.value.length > 0 ? itemsJson.value[0] : null;
|
||||||
|
|
||||||
const uomFilter = encodeURIComponent(`itemNo eq '${articleNo}' and code eq '${config.itemUnitsCode || 'OUTER'}'`);
|
const outerCode = config.itemUnitsCode || 'OUTER';
|
||||||
const uomUrl = `${getItemUnitsOfMeasureUrl(config)}?$filter=${uomFilter}&$select=${getItemUnitsSelect()}`;
|
const uomUrl = getItemUnitsOfMeasureUrl(config);
|
||||||
const { json: uomJson, etag: uomEtag } = await fetchJsonOrThrow(uomUrl, token, 'BC GET itemUnitsOfMeasure');
|
|
||||||
const itemUnitsOfMeasure = Array.isArray(uomJson.value) && uomJson.value.length > 0 ? uomJson.value[0] : null;
|
const outerFilter = encodeURIComponent(`itemNo eq '${articleNo}' and code eq '${outerCode}'`);
|
||||||
|
const outerUrl = `${uomUrl}?$filter=${outerFilter}&$select=${getItemUnitsSelect()}`;
|
||||||
|
const { json: outerJson, etag: outerEtag } = await fetchJsonOrThrow(outerUrl, token, 'BC GET itemUnitsOfMeasure OUTER');
|
||||||
|
const itemUnitsOfMeasure = Array.isArray(outerJson.value) && outerJson.value.length > 0 ? outerJson.value[0] : null;
|
||||||
|
|
||||||
|
const hcFilter = encodeURIComponent(`itemNo eq '${articleNo}' and code eq '40HC'`);
|
||||||
|
const hcUrl = `${uomUrl}?$filter=${hcFilter}&$select=${getItemUnitsSelect()}`;
|
||||||
|
const { json: hcJson, etag: hcEtag } = await fetchJsonOrThrow(hcUrl, token, 'BC GET itemUnitsOfMeasure 40HC');
|
||||||
|
const itemUnits40HC = Array.isArray(hcJson.value) && hcJson.value.length > 0 ? hcJson.value[0] : null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
item,
|
item,
|
||||||
itemUnitsOfMeasure,
|
itemUnitsOfMeasure,
|
||||||
|
itemUnits40HC,
|
||||||
itemEtag: item?.['@odata.etag'] || itemsEtag || '*',
|
itemEtag: item?.['@odata.etag'] || itemsEtag || '*',
|
||||||
itemUnitsEtag: itemUnitsOfMeasure?.['@odata.etag'] || uomEtag || '*',
|
itemUnitsEtag: itemUnitsOfMeasure?.['@odata.etag'] || outerEtag || '*',
|
||||||
|
itemUnits40HCEtag: itemUnits40HC?.['@odata.etag'] || hcEtag || '*',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,19 +357,34 @@ export function buildBusinessCentralSyncPreview(config, mapping, snapshot) {
|
|||||||
writeBodyTemplate: config.itemUnitsWriteBodyTemplate || null,
|
writeBodyTemplate: config.itemUnitsWriteBodyTemplate || null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const itemUnits40HCSection = makePreviewSection({
|
||||||
|
type: 'itemUnits40HC',
|
||||||
|
desiredPayload: mapping.itemUnits40HCPayload,
|
||||||
|
currentRecord: snapshot.itemUnits40HC,
|
||||||
|
fieldPreviews: mapping.itemUnits40HCFields,
|
||||||
|
writeMethod: config.itemUnits40HCWriteMethod || config.itemUnitsWriteMethod || null,
|
||||||
|
writeUrlTemplate: config.itemUnits40HCWriteUrlTemplate || config.itemUnitsWriteUrlTemplate || null,
|
||||||
|
writeBodyTemplate: config.itemUnits40HCWriteBodyTemplate || config.itemUnitsWriteBodyTemplate || null,
|
||||||
|
});
|
||||||
|
|
||||||
const previewPayload = {
|
const previewPayload = {
|
||||||
articleNo: mapping.articleNo,
|
articleNo: mapping.articleNo,
|
||||||
items: itemsSection,
|
items: itemsSection,
|
||||||
itemUnitsOfMeasure: itemUnitsSection,
|
itemUnitsOfMeasure: itemUnitsSection,
|
||||||
|
itemUnits40HC: itemUnits40HCSection,
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...previewPayload,
|
...previewPayload,
|
||||||
hasChanges: itemsSection.changes.some(change => change.changed) || itemUnitsSection.changes.some(change => change.changed),
|
hasChanges:
|
||||||
|
itemsSection.changes.some(change => change.changed) ||
|
||||||
|
itemUnitsSection.changes.some(change => change.changed) ||
|
||||||
|
itemUnits40HCSection.changes.some(change => change.changed),
|
||||||
previewToken: buildPreviewHash({
|
previewToken: buildPreviewHash({
|
||||||
articleNo: mapping.articleNo,
|
articleNo: mapping.articleNo,
|
||||||
items: itemsSection.changes,
|
items: itemsSection.changes,
|
||||||
itemUnitsOfMeasure: itemUnitsSection.changes,
|
itemUnitsOfMeasure: itemUnitsSection.changes,
|
||||||
|
itemUnits40HC: itemUnits40HCSection.changes,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -496,6 +537,38 @@ async function applyItemUnitsSection(config, token, snapshot, preview, context)
|
|||||||
return { applied: true, url };
|
return { applied: true, url };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function applyItemUnits40HCSection(config, token, snapshot, preview, context) {
|
||||||
|
const changedFields = preview.itemUnits40HC.changes.filter(change => change.changed);
|
||||||
|
if (changedFields.length === 0) {
|
||||||
|
return { applied: false, reason: 'No itemUnits40HC changes' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config.itemUnits40HCWriteUrlTemplate && !config.itemUnitsWriteUrlTemplate) {
|
||||||
|
return { applied: false, reason: 'itemUnits40HC write template not configured' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const urlTemplate = config.itemUnits40HCWriteUrlTemplate || config.itemUnitsWriteUrlTemplate;
|
||||||
|
const writeMethod = preview.itemUnits40HC.writeMethod || 'PATCH';
|
||||||
|
const hcContext = {
|
||||||
|
...context,
|
||||||
|
itemUnitsCode: '40HC',
|
||||||
|
itemUnits40HCCode: '40HC',
|
||||||
|
itemUnitsPayload: preview.itemUnits40HC.desired,
|
||||||
|
};
|
||||||
|
const url = renderTemplate(urlTemplate, hcContext);
|
||||||
|
const payload = renderJsonBody(preview.itemUnits40HC.writeBodyTemplate, hcContext, buildChangedPayload(preview.itemUnits40HC.changes));
|
||||||
|
|
||||||
|
await renderAndPatchRecord({
|
||||||
|
token,
|
||||||
|
url,
|
||||||
|
method: writeMethod,
|
||||||
|
body: payload,
|
||||||
|
etag: snapshot.itemUnits40HCEtag || snapshot.itemUnits40HC?.['@odata.etag'] || '*',
|
||||||
|
});
|
||||||
|
|
||||||
|
return { applied: true, url };
|
||||||
|
}
|
||||||
|
|
||||||
export async function applyBusinessCentralSync(config, token, headers, row, previewToken) {
|
export async function applyBusinessCentralSync(config, token, headers, row, previewToken) {
|
||||||
const mapping = buildBusinessCentralMappingPreview(headers, row);
|
const mapping = buildBusinessCentralMappingPreview(headers, row);
|
||||||
const snapshot = await fetchBusinessCentralSnapshot(config, token, mapping.articleNo);
|
const snapshot = await fetchBusinessCentralSnapshot(config, token, mapping.articleNo);
|
||||||
@@ -508,6 +581,13 @@ export async function applyBusinessCentralSync(config, token, headers, row, prev
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasItemUnits40HCChanges = preview.itemUnits40HC.changes.some(change => change.changed);
|
||||||
|
if (hasItemUnits40HCChanges && !snapshot.itemUnits40HC) {
|
||||||
|
const error = new Error(`BC itemUnits40HC row missing for ${mapping.articleNo}. This BC API cannot update these fields until the row exists or BC exposes an upsert action.`);
|
||||||
|
error.statusCode = 409;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
if (previewToken && previewToken !== preview.previewToken) {
|
if (previewToken && previewToken !== preview.previewToken) {
|
||||||
const error = new Error('Preview token mismatch. BC data changed or preview is stale.');
|
const error = new Error('Preview token mismatch. BC data changed or preview is stale.');
|
||||||
error.statusCode = 409;
|
error.statusCode = 409;
|
||||||
@@ -520,20 +600,24 @@ export async function applyBusinessCentralSync(config, token, headers, row, prev
|
|||||||
articleNo: mapping.articleNo,
|
articleNo: mapping.articleNo,
|
||||||
itemNo: mapping.articleNo,
|
itemNo: mapping.articleNo,
|
||||||
itemUnitsCode: config.itemUnitsCode || 'OUTER',
|
itemUnitsCode: config.itemUnitsCode || 'OUTER',
|
||||||
|
itemUnits40HCCode: '40HC',
|
||||||
systemId: snapshot.item?.systemId || '',
|
systemId: snapshot.item?.systemId || '',
|
||||||
cpnpNo: mapping.itemsPayload.cpnpNo,
|
cpnpNo: mapping.itemsPayload.cpnpNo,
|
||||||
itemsPayload: preview.items.desired,
|
itemsPayload: preview.items.desired,
|
||||||
itemUnitsPayload: preview.itemUnitsOfMeasure.desired,
|
itemUnitsPayload: preview.itemUnitsOfMeasure.desired,
|
||||||
|
itemUnits40HCPayload: preview.itemUnits40HC.desired,
|
||||||
...preview.items.desired,
|
...preview.items.desired,
|
||||||
...preview.itemUnitsOfMeasure.desired,
|
...preview.itemUnitsOfMeasure.desired,
|
||||||
|
...preview.itemUnits40HC.desired,
|
||||||
};
|
};
|
||||||
|
|
||||||
const results = {
|
const results = {
|
||||||
items: await applyItemsSection(config, token, snapshot, preview, context),
|
items: await applyItemsSection(config, token, snapshot, preview, context),
|
||||||
itemUnitsOfMeasure: await applyItemUnitsSection(config, token, snapshot, preview, context),
|
itemUnitsOfMeasure: await applyItemUnitsSection(config, token, snapshot, preview, context),
|
||||||
|
itemUnits40HC: await applyItemUnits40HCSection(config, token, snapshot, preview, context),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (results.items.applied || results.itemUnitsOfMeasure.applied) {
|
if (results.items.applied || results.itemUnitsOfMeasure.applied || results.itemUnits40HC.applied) {
|
||||||
const verificationSnapshot = await fetchBusinessCentralSnapshot(config, token, mapping.articleNo);
|
const verificationSnapshot = await fetchBusinessCentralSnapshot(config, token, mapping.articleNo);
|
||||||
|
|
||||||
if (results.items.applied) {
|
if (results.items.applied) {
|
||||||
@@ -565,6 +649,21 @@ export async function applyBusinessCentralSync(config, token, headers, row, prev
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (results.itemUnits40HC.applied) {
|
||||||
|
const hcMismatches = preview.itemUnits40HC.changes
|
||||||
|
.filter(change => change.changed)
|
||||||
|
.map(change => ({
|
||||||
|
...change,
|
||||||
|
before: verificationSnapshot.itemUnits40HC ? verificationSnapshot.itemUnits40HC[change.targetField] : undefined,
|
||||||
|
}))
|
||||||
|
.filter(change => normalizeForComparison(change.targetField, change.before) !== normalizeForComparison(change.targetField, change.after));
|
||||||
|
if (hcMismatches.length > 0) {
|
||||||
|
const error = new Error(`BC verification failed for itemUnits40HC: ${describeUnappliedChanges('itemUnits40HC', hcMismatches)}`);
|
||||||
|
error.statusCode = 409;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -573,8 +672,10 @@ export async function applyBusinessCentralSync(config, token, headers, row, prev
|
|||||||
previewToken: preview.previewToken,
|
previewToken: preview.previewToken,
|
||||||
results,
|
results,
|
||||||
preview,
|
preview,
|
||||||
warning: preview.itemUnitsOfMeasure.changes.some(change => change.changed) && !preview.itemUnitsOfMeasure.supported
|
warning: (preview.itemUnitsOfMeasure.changes.some(change => change.changed) && !preview.itemUnitsOfMeasure.supported)
|
||||||
? (preview.itemUnitsOfMeasure.supportReason || 'itemUnitsOfMeasure sync is not supported by this BC API yet; preview only')
|
? (preview.itemUnitsOfMeasure.supportReason || 'itemUnitsOfMeasure sync is not supported by this BC API yet; preview only')
|
||||||
|
: (preview.itemUnits40HC.changes.some(change => change.changed) && !preview.itemUnits40HC.supported)
|
||||||
|
? (preview.itemUnits40HC.supportReason || 'itemUnits40HC sync is not supported by this BC API yet; preview only')
|
||||||
: undefined,
|
: undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,37 +170,30 @@ export function MatrixView({ data, headers, rowStatuses }: MatrixViewProps) {
|
|||||||
setBcValidationError(null);
|
setBcValidationError(null);
|
||||||
setBcValidationWarning(null);
|
setBcValidationWarning(null);
|
||||||
try {
|
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);
|
const result = await applyBusinessCentralSync(headers, bcPreviewRow, bcValidationResult.previewToken);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
if (isPreviewTokenMismatchError(result.error)) {
|
if (isPreviewTokenMismatchError(result.error)) {
|
||||||
const refreshedPreview = await previewBusinessCentralSync(headers, bcPreviewRow);
|
const refreshedPreview = await previewBusinessCentralSync(headers, bcPreviewRow);
|
||||||
if (refreshedPreview.success) {
|
if (refreshedPreview.success) {
|
||||||
setBcValidationResult(refreshedPreview);
|
setBcValidationResult(refreshedPreview);
|
||||||
const retry = await applyBusinessCentralSync(headers, bcPreviewRow, refreshedPreview.previewToken);
|
const retry = await applyBusinessCentralSync(headers, bcPreviewRow, refreshedPreview.previewToken);
|
||||||
if (retry.success) {
|
if (retry.success) {
|
||||||
setBcValidationResult(prev => retry.preview ? { ...retry.preview, hasChanges: false } : prev);
|
setBcValidationResult(prev => retry.preview ? { ...retry.preview, hasChanges: false } : prev);
|
||||||
if (retry.warning) {
|
if (retry.warning) {
|
||||||
setBcValidationWarning(retry.warning);
|
setBcValidationWarning(retry.warning);
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
setBcValidationError(retry.error || 'Apply failed');
|
|
||||||
return;
|
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);
|
setBcValidationResult(prev => result.preview ? { ...result.preview, hasChanges: false } : prev);
|
||||||
if (result.warning) {
|
if (result.warning) {
|
||||||
setBcValidationWarning(result.warning);
|
setBcValidationWarning(result.warning);
|
||||||
@@ -417,8 +410,8 @@ export function MatrixView({ data, headers, rowStatuses }: MatrixViewProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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">
|
||||||
{(['items', 'itemUnitsOfMeasure'] as const).map(sectionKey => {
|
{(['items', 'itemUnitsOfMeasure', 'itemUnits40HC'] as const).map(sectionKey => {
|
||||||
const section = bcValidationResult[sectionKey];
|
const section = bcValidationResult[sectionKey];
|
||||||
const changed = section.changes.filter((change: any) => change.changed);
|
const changed = section.changes.filter((change: any) => change.changed);
|
||||||
|
|
||||||
@@ -472,7 +465,7 @@ export function MatrixView({ data, headers, rowStatuses }: MatrixViewProps) {
|
|||||||
</div>
|
</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="rounded-lg border border-slate-700 bg-slate-950/60 p-4">
|
||||||
<div className="flex items-center justify-between mb-3">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<div>
|
<div>
|
||||||
@@ -514,6 +507,27 @@ export function MatrixView({ data, headers, rowStatuses }: MatrixViewProps) {
|
|||||||
{JSON.stringify(bcMappingPreview.itemUnitsOfMeasurePayload, null, 2)}
|
{JSON.stringify(bcMappingPreview.itemUnitsOfMeasurePayload, null, 2)}
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export interface BCFieldChange {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface BCSyncPreviewSection {
|
export interface BCSyncPreviewSection {
|
||||||
type: 'items' | 'itemUnitsOfMeasure';
|
type: 'items' | 'itemUnitsOfMeasure' | 'itemUnits40HC';
|
||||||
desired: Record<string, any>;
|
desired: Record<string, any>;
|
||||||
current: Record<string, any> | null;
|
current: Record<string, any> | null;
|
||||||
changes: BCFieldChange[];
|
changes: BCFieldChange[];
|
||||||
@@ -46,6 +46,7 @@ export interface BCSyncPreviewResult {
|
|||||||
articleNo: string;
|
articleNo: string;
|
||||||
items: BCSyncPreviewSection;
|
items: BCSyncPreviewSection;
|
||||||
itemUnitsOfMeasure: BCSyncPreviewSection;
|
itemUnitsOfMeasure: BCSyncPreviewSection;
|
||||||
|
itemUnits40HC: BCSyncPreviewSection;
|
||||||
hasChanges: boolean;
|
hasChanges: boolean;
|
||||||
previewToken: string;
|
previewToken: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
@@ -59,6 +60,7 @@ export interface BCSyncApplyResult {
|
|||||||
results?: {
|
results?: {
|
||||||
items: { applied: boolean; reason?: string; url?: string };
|
items: { applied: boolean; reason?: string; url?: string };
|
||||||
itemUnitsOfMeasure: { applied: boolean; reason?: string; url?: string };
|
itemUnitsOfMeasure: { applied: boolean; reason?: string; url?: string };
|
||||||
|
itemUnits40HC: { applied: boolean; reason?: string; url?: string };
|
||||||
};
|
};
|
||||||
error?: string;
|
error?: string;
|
||||||
warning?: string;
|
warning?: string;
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ export interface BusinessCentralMappingPreview {
|
|||||||
articleNo: string;
|
articleNo: string;
|
||||||
itemsPayload: Record<string, any>;
|
itemsPayload: Record<string, any>;
|
||||||
itemUnitsOfMeasurePayload: Record<string, any>;
|
itemUnitsOfMeasurePayload: Record<string, any>;
|
||||||
|
itemUnits40HCPayload: Record<string, any>;
|
||||||
itemsFields: MappingFieldPreview[];
|
itemsFields: MappingFieldPreview[];
|
||||||
itemUnitsFields: MappingFieldPreview[];
|
itemUnitsFields: MappingFieldPreview[];
|
||||||
|
itemUnits40HCFields: MappingFieldPreview[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function findHeaderIndex(headers: string[], patterns: string[]): number {
|
function findHeaderIndex(headers: string[], patterns: string[]): number {
|
||||||
@@ -81,6 +83,11 @@ export function buildBusinessCentralMappingPreview(headers: string[], row: Excel
|
|||||||
const cpnpIdx = findHeaderIndex(headers, ['cpnp']);
|
const cpnpIdx = findHeaderIndex(headers, ['cpnp']);
|
||||||
|
|
||||||
const unitsOuterIdx = findHeaderIndex(headers, ['units', 'outer']);
|
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 outerWIdx = findHeaderIndex(headers, ['outer', 'w']);
|
||||||
const outerLIdx = findHeaderIndex(headers, ['outer', 'l']);
|
const outerLIdx = findHeaderIndex(headers, ['outer', 'l']);
|
||||||
const outerHIdx = findHeaderIndex(headers, ['outer', 'h']);
|
const outerHIdx = findHeaderIndex(headers, ['outer', 'h']);
|
||||||
@@ -108,10 +115,17 @@ export function buildBusinessCentralMappingPreview(headers: string[], row: Excel
|
|||||||
height: toBcDecimal(getValue(row, outerHIdx)),
|
height: toBcDecimal(getValue(row, outerHIdx)),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const itemUnits40HCPayload = {
|
||||||
|
itemNo: getValue(row, articleNoIdx),
|
||||||
|
code: '40HC',
|
||||||
|
qtyPerUnitOfMeasure: toBcDecimal(getValue(row, units40HqIdx)),
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
articleNo,
|
articleNo,
|
||||||
itemsPayload,
|
itemsPayload,
|
||||||
itemUnitsOfMeasurePayload,
|
itemUnitsOfMeasurePayload,
|
||||||
|
itemUnits40HCPayload,
|
||||||
itemsFields: [
|
itemsFields: [
|
||||||
makeFieldPreview('Article No.', articleNoIdx, 'no', itemsPayload.no),
|
makeFieldPreview('Article No.', articleNoIdx, 'no', itemsPayload.no),
|
||||||
makeFieldPreview('Article Details - English', articleDetailsEnIdx, 'articleDetailsEnglish', itemsPayload.articleDetailsEnglish),
|
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 L (cm)', outerLIdx, 'length', itemUnitsOfMeasurePayload.length),
|
||||||
makeFieldPreview('Outer H (cm)', outerHIdx, 'height', itemUnitsOfMeasurePayload.height),
|
makeFieldPreview('Outer H (cm)', outerHIdx, 'height', itemUnitsOfMeasurePayload.height),
|
||||||
],
|
],
|
||||||
|
itemUnits40HCFields: [
|
||||||
|
makeFieldPreview('Article No.', articleNoIdx, 'itemNo', itemUnits40HCPayload.itemNo),
|
||||||
|
makeFieldPreview('Units 40FT HQ', units40HqIdx, 'qtyPerUnitOfMeasure', itemUnits40HCPayload.qtyPerUnitOfMeasure),
|
||||||
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user