mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 11:35: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 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 innerLIdx = findHeaderIndex(headers, ['inner', 'l']);
|
||||
const innerHIdx = findHeaderIndex(headers, ['inner', 'h']);
|
||||
@@ -92,10 +97,17 @@ export function buildBusinessCentralMappingPreview(headers, row) {
|
||||
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),
|
||||
@@ -114,6 +126,10 @@ export function buildBusinessCentralMappingPreview(headers, row) {
|
||||
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),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -269,16 +285,26 @@ export async function fetchBusinessCentralSnapshot(config, token, articleNo) {
|
||||
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 uomFilter = encodeURIComponent(`itemNo eq '${articleNo}' and code eq '${config.itemUnitsCode || 'OUTER'}'`);
|
||||
const uomUrl = `${getItemUnitsOfMeasureUrl(config)}?$filter=${uomFilter}&$select=${getItemUnitsSelect()}`;
|
||||
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 outerCode = config.itemUnitsCode || 'OUTER';
|
||||
const uomUrl = getItemUnitsOfMeasureUrl(config);
|
||||
|
||||
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 {
|
||||
item,
|
||||
itemUnitsOfMeasure,
|
||||
itemUnits40HC,
|
||||
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,
|
||||
});
|
||||
|
||||
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 = {
|
||||
articleNo: mapping.articleNo,
|
||||
items: itemsSection,
|
||||
itemUnitsOfMeasure: itemUnitsSection,
|
||||
itemUnits40HC: itemUnits40HCSection,
|
||||
};
|
||||
|
||||
return {
|
||||
...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({
|
||||
articleNo: mapping.articleNo,
|
||||
items: itemsSection.changes,
|
||||
itemUnitsOfMeasure: itemUnitsSection.changes,
|
||||
itemUnits40HC: itemUnits40HCSection.changes,
|
||||
}),
|
||||
};
|
||||
}
|
||||
@@ -496,6 +537,38 @@ async function applyItemUnitsSection(config, token, snapshot, preview, context)
|
||||
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) {
|
||||
const mapping = buildBusinessCentralMappingPreview(headers, row);
|
||||
const snapshot = await fetchBusinessCentralSnapshot(config, token, mapping.articleNo);
|
||||
@@ -508,6 +581,13 @@ export async function applyBusinessCentralSync(config, token, headers, row, prev
|
||||
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) {
|
||||
const error = new Error('Preview token mismatch. BC data changed or preview is stale.');
|
||||
error.statusCode = 409;
|
||||
@@ -520,20 +600,24 @@ export async function applyBusinessCentralSync(config, token, headers, row, prev
|
||||
articleNo: mapping.articleNo,
|
||||
itemNo: mapping.articleNo,
|
||||
itemUnitsCode: config.itemUnitsCode || 'OUTER',
|
||||
itemUnits40HCCode: '40HC',
|
||||
systemId: snapshot.item?.systemId || '',
|
||||
cpnpNo: mapping.itemsPayload.cpnpNo,
|
||||
itemsPayload: preview.items.desired,
|
||||
itemUnitsPayload: preview.itemUnitsOfMeasure.desired,
|
||||
itemUnits40HCPayload: preview.itemUnits40HC.desired,
|
||||
...preview.items.desired,
|
||||
...preview.itemUnitsOfMeasure.desired,
|
||||
...preview.itemUnits40HC.desired,
|
||||
};
|
||||
|
||||
const results = {
|
||||
items: await applyItemsSection(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);
|
||||
|
||||
if (results.items.applied) {
|
||||
@@ -565,6 +649,21 @@ export async function applyBusinessCentralSync(config, token, headers, row, prev
|
||||
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 {
|
||||
@@ -573,8 +672,10 @@ export async function applyBusinessCentralSync(config, token, headers, row, prev
|
||||
previewToken: preview.previewToken,
|
||||
results,
|
||||
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.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,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user