Files
Craze-Data-check/bc-runtime.js
T

191 lines
6.3 KiB
JavaScript
Raw Normal View History

import * as XLSX from 'xlsx';
export function getBcConfig(env = process.env) {
const legacyWriteMethod = String(env.BC_WRITE_METHOD || 'PATCH').toUpperCase();
const legacyWriteUrlTemplate = env.BC_WRITE_URL_TEMPLATE || `{{itemsUrl}}('{{itemNo}}')`;
const legacyWriteBodyTemplate = env.BC_WRITE_BODY_TEMPLATE || JSON.stringify({ cpnpNo: '{{cpnpNo}}' });
return {
tenantId: env.BC_TENANT_ID || 'fab724f7-6b6d-4e3b-86e3-8c1e05e36b2a',
clientId: env.BC_CLIENT_ID || '6f832138-cb48-43e7-8601-efca120b45dc',
clientSecret: env.BC_CLIENT_SECRET,
companyId: env.BC_COMPANY_ID || '2acec35c-7d06-ed11-82f8-0022485ceea3',
writeMethod: legacyWriteMethod,
writeUrlTemplate: legacyWriteUrlTemplate,
writeBodyTemplate: legacyWriteBodyTemplate,
itemsWriteMethod: String(env.BC_ITEMS_WRITE_METHOD || legacyWriteMethod).toUpperCase(),
itemsWriteUrlTemplate: env.BC_ITEMS_WRITE_URL_TEMPLATE || legacyWriteUrlTemplate,
itemsWriteBodyTemplate: env.BC_ITEMS_WRITE_BODY_TEMPLATE || null,
2026-05-26 10:25:37 +02:00
itemUnitsCode: env.BC_UOM_CODE || 'OUTER',
itemUnitsWriteMethod: env.BC_UOM_WRITE_METHOD ? String(env.BC_UOM_WRITE_METHOD).toUpperCase() : 'PATCH',
2026-05-26 10:25:37 +02:00
itemUnitsWriteUrlTemplate: env.BC_UOM_WRITE_URL_TEMPLATE || `{{itemUnitsOfMeasureUrl}}(itemNo='{{itemNo}}',code='{{itemUnitsCode}}')`,
itemUnitsWriteBodyTemplate: env.BC_UOM_WRITE_BODY_TEMPLATE || null,
};
}
export function getTokenUrl(config) {
return `https://login.microsoftonline.com/${config.tenantId}/oauth2/v2.0/token`;
}
export function getItemsUrl(config) {
return `https://api.businesscentral.dynamics.com/v2.0/${config.tenantId}/production/api/craze/integrations/v1.0/companies(${config.companyId})/items`;
}
2026-05-26 10:25:37 +02:00
export function getItemUnitsOfMeasureUrl(config) {
return `https://api.businesscentral.dynamics.com/v2.0/${config.tenantId}/production/api/craze/integrations/v1.0/companies(${config.companyId})/itemUnitsOfMeasure2`;
}
let tokenCache = { token: null, expiresAt: 0 };
export async function getBCToken(config) {
const now = Date.now();
if (tokenCache.token && now < tokenCache.expiresAt) {
return tokenCache.token;
}
if (!config.clientSecret) {
throw new Error('BC_CLIENT_SECRET env var not set');
}
const body = new URLSearchParams({
grant_type: 'client_credentials',
client_id: config.clientId,
client_secret: config.clientSecret,
scope: 'https://api.businesscentral.dynamics.com/.default',
});
const res = await fetch(getTokenUrl(config), {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body,
});
const data = await res.json();
if (!data.access_token) {
throw new Error('BC token error: ' + JSON.stringify(data));
}
tokenCache = { token: data.access_token, expiresAt: now + 55 * 60 * 1000 };
return data.access_token;
}
export async function fetchAllItems(config, token) {
const items = [];
let url = `${getItemsUrl(config)}?$top=1000`;
while (url) {
const res = await fetch(url, {
headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
});
if (!res.ok) {
const txt = await res.text();
throw new Error(`BC GET items failed (${res.status}): ${txt}`);
}
const json = await res.json();
if (Array.isArray(json.value)) {
items.push(...json.value);
}
url = json['@odata.nextLink'] || null;
}
return items;
}
export async function findItem(config, token, articleNo) {
const filter = encodeURIComponent(`no eq '${articleNo}'`);
const url = `${getItemsUrl(config)}?$filter=${filter}&$select=systemId,no,cpnpNo`;
const res = await fetch(url, {
headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
});
if (!res.ok) {
const txt = await res.text();
throw new Error(`BC GET items failed (${res.status}): ${txt}`);
}
const json = await res.json();
const items = json.value || [];
console.log('[bc-proxy] GET items result:', JSON.stringify(items));
if (items.length === 0) throw new Error(`Item not found in BC: ${articleNo}`);
return items[0];
}
export async function patchItemCpnpNo(config, token, item, cpnpNo) {
const etag = item['@odata.etag'] || '*';
const itemsUrl = getItemsUrl(config);
const url = config.writeUrlTemplate
.replaceAll('{{itemsUrl}}', itemsUrl)
.replaceAll('{{tenantId}}', config.tenantId)
.replaceAll('{{companyId}}', config.companyId)
.replaceAll('{{itemNo}}', encodeURIComponent(String(item.no)))
.replaceAll('{{systemId}}', encodeURIComponent(String(item.systemId || '')))
.replaceAll('{{cpnpNo}}', String(cpnpNo));
const bodyText = config.writeBodyTemplate
.replaceAll('{{itemsUrl}}', itemsUrl)
.replaceAll('{{tenantId}}', config.tenantId)
.replaceAll('{{companyId}}', config.companyId)
.replaceAll('{{itemNo}}', String(item.no))
.replaceAll('{{systemId}}', String(item.systemId || ''))
.replaceAll('{{cpnpNo}}', String(cpnpNo));
console.log('[bc-proxy] WRITE url:', url);
const headers = {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
};
if ((config.writeMethod || 'PATCH') === 'PATCH') {
headers['If-Match'] = etag;
}
const res = await fetch(url, {
method: config.writeMethod || 'PATCH',
headers,
body: bodyText,
});
if (!res.ok) {
const txt = await res.text();
throw new Error(`BC write failed (${res.status}): ${txt}`);
}
return true;
}
export function normalizeValue(value) {
if (value === null || value === undefined) return '';
if (value instanceof Date) return value.toISOString();
if (typeof value === 'object') return JSON.stringify(value);
return value;
}
export function buildWorkbook(items) {
const headers = [];
const seen = new Set();
items.forEach(item => {
Object.keys(item || {}).forEach(key => {
if (key.startsWith('@odata.')) return;
if (seen.has(key)) return;
seen.add(key);
headers.push(key);
});
});
const rows = items.map(item => {
const row = {};
headers.forEach(key => {
row[key] = normalizeValue(item?.[key]);
});
return row;
});
const ws = XLSX.utils.json_to_sheet(rows, { header: headers });
ws['!autofilter'] = {
ref: XLSX.utils.encode_range({
s: { c: 0, r: 0 },
e: { c: Math.max(headers.length - 1, 0), r: Math.max(rows.length, 0) },
}),
};
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'Items');
return wb;
}