Allow oriol.rodrigo@craze-group.com login and restrict to dashboard

This commit is contained in:
Christian Vidal Wolf
2026-07-14 15:59:11 +02:00
parent ea4592b695
commit 56593f942b
8 changed files with 210 additions and 44 deletions
+123 -14
View File
@@ -436,10 +436,32 @@ async function renderAndPatchRecord({ token, url, method, body, etag }) {
if (!res.ok) {
const txt = await res.text();
throw new Error(`BC write failed (${res.status}): ${txt}`);
const error = new Error(`BC write failed (${res.status}): ${txt}`);
error.statusCode = res.status;
error.responseText = txt;
throw error;
}
}
function isCategorizationCodeRelationError(error) {
const text = `${error?.message || ''} ${error?.responseText || ''}`.toLowerCase();
return (
text.includes('categorization') &&
(
text.includes('invalidtablerelation') ||
text.includes('invalid table relation') ||
text.includes('related table') ||
text.includes('cannot be found') ||
text.includes('could not be found')
)
);
}
function isAlreadyExistsError(error) {
const text = `${error?.message || ''} ${error?.responseText || ''}`.toLowerCase();
return text.includes('already exists') || text.includes('duplicate') || text.includes('conflict');
}
function renderTemplate(template, context) {
return String(template).replace(/\{\{\s*([^}]+?)\s*\}\}/g, (_match, key) => {
const value = context[key];
@@ -508,6 +530,51 @@ function renderJsonBody(template, context, fallbackPayload) {
}
}
async function ensureCategorizationCode(config, token, categorizationCode, context) {
const value = String(categorizationCode ?? '').trim();
if (!value) {
return { created: false, reason: 'Empty CategorizationCode' };
}
if (!config.categorizationWriteUrlTemplate) {
return {
created: false,
reason: 'Business Central categorization write endpoint is not configured',
};
}
const categorizationContext = {
...context,
categorizationCode: value,
categorizationPayload: {
code: value,
description: value,
},
};
const url = renderTemplate(config.categorizationWriteUrlTemplate, categorizationContext);
const body = renderJsonBody(
config.categorizationWriteBodyTemplate,
categorizationContext,
categorizationContext.categorizationPayload
);
try {
await renderAndPatchRecord({
token,
url,
method: config.categorizationWriteMethod || 'POST',
body,
etag: '*',
});
return { created: true, url };
} catch (error) {
if (isAlreadyExistsError(error)) {
return { created: false, alreadyExists: true, url };
}
throw error;
}
}
async function applyItemsSection(config, token, snapshot, preview, context) {
const changedFields = preview.items.changes.filter(change => change.changed);
if (changedFields.length === 0) {
@@ -521,13 +588,46 @@ async function applyItemsSection(config, token, snapshot, preview, context) {
const url = renderTemplate(config.itemsWriteUrlTemplate, context);
const payload = renderJsonBody(preview.items.writeBodyTemplate, context, buildChangedPayload(preview.items.changes));
await renderAndPatchRecord({
token,
url,
method: preview.items.writeMethod || 'PATCH',
body: payload,
etag: snapshot.itemEtag || snapshot.item?.['@odata.etag'] || '*',
});
try {
await renderAndPatchRecord({
token,
url,
method: preview.items.writeMethod || 'PATCH',
body: payload,
etag: snapshot.itemEtag || snapshot.item?.['@odata.etag'] || '*',
});
} catch (error) {
const categorizationChange = changedFields.find(change => change.targetField === 'categorizationCode');
if (!categorizationChange || !isCategorizationCodeRelationError(error)) {
throw error;
}
const categorizationResult = await ensureCategorizationCode(config, token, categorizationChange.after, context);
if (categorizationResult.created || categorizationResult.alreadyExists) {
await renderAndPatchRecord({
token,
url,
method: preview.items.writeMethod || 'PATCH',
body: payload,
etag: snapshot.itemEtag || snapshot.item?.['@odata.etag'] || '*',
});
return {
applied: true,
url,
createdRelatedRecords: categorizationResult.created ? ['categorizationCode'] : [],
warning: categorizationResult.created
? `CategorizationCode "${categorizationChange.after}" was created in Business Central and synced.`
: undefined,
};
}
const categorizationError = new Error(
`CategorizationCode "${categorizationChange.after}" was not synced. Business Central requires a related Categorization record, and no categorization write endpoint is configured.`
);
categorizationError.statusCode = 409;
throw categorizationError;
}
return { applied: true, url };
}
@@ -644,8 +744,9 @@ export async function applyBusinessCentralSync(config, token, headers, row, prev
const verificationSnapshot = await fetchBusinessCentralSnapshot(config, token, mapping.articleNo);
if (results.items.applied) {
const skippedItemFields = new Set(results.items.skippedFields || []);
const itemMismatches = preview.items.changes
.filter(change => change.changed)
.filter(change => change.changed && !skippedItemFields.has(change.targetField))
.map(change => ({
...change,
before: verificationSnapshot.item ? verificationSnapshot.item[change.targetField] : undefined,
@@ -689,17 +790,25 @@ export async function applyBusinessCentralSync(config, token, headers, row, prev
}
}
const warnings = [
results.items.warning,
results.itemUnitsOfMeasure.warning,
results.itemUnits40HC.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')
: undefined,
(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,
].filter(Boolean);
return {
success: true,
articleNo: mapping.articleNo,
previewToken: preview.previewToken,
results,
preview,
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,
warning: warnings.length > 0 ? warnings.join('\n') : undefined,
};
}