n8n-nodes-exact-online-v1
Version:
Exact Online Community node for n8n
296 lines • 13.4 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createReconciliationXml = exports.exactOnlineXmlRequest = exports.toOptionsFromStringArray = exports.toFieldFilterOptions = exports.toFieldSelectOptions = exports.toOptions = exports.toDivisionOptions = exports.getEndpointConfig = exports.getEndpointFieldConfig = exports.getResourceOptions = exports.getFieldType = exports.getServiceOptions = exports.getMandatoryFields = exports.getFields = exports.getAllData = exports.getData = exports.getCurrentDivision = exports.exactOnlineApiRequest = void 0;
const ExactOnlineRestApi_json_1 = __importDefault(require("./ExactOnlineRestApi.json"));
const ExactOnlineXmlApi_json_1 = __importDefault(require("./ExactOnlineXmlApi.json"));
const n8n_workflow_1 = require("n8n-workflow");
let mergedConfigs = null;
function getAllConfigs() {
if (mergedConfigs) {
return mergedConfigs;
}
const restEndpoints = ExactOnlineRestApi_json_1.default;
const xmlEndpoints = ExactOnlineXmlApi_json_1.default;
const typedRestEndpoints = restEndpoints.map(config => ({
...config,
apiType: 'rest',
}));
const typedXmlEndpoints = xmlEndpoints.map(config => ({
...config,
apiType: 'xml',
}));
mergedConfigs = [...typedRestEndpoints, ...typedXmlEndpoints];
return mergedConfigs;
}
async function exactOnlineApiRequest(method, uri, body = {}, qs = {}, option = {}, nextPageUrl = '') {
var _a;
let options = {
headers: {
'Content-Type': 'application/json',
},
method,
body,
qs,
uri: ``,
json: true,
};
options.resolveWithFullResponse = true;
const authenticationMethod = this.getNodeParameter('authentication', 0, 'oAuth2');
let credentialType;
let baseUrl;
if (authenticationMethod === 'accessToken') {
const credentials = await this.getCredentials('exactOnlineApi');
credentialType = 'exactOnlineApi';
baseUrl = String(credentials.url) || 'https://start.exactonline.nl';
}
else {
const credentials = await this.getCredentials('exactOnlineApiOAuth2Api');
credentialType = 'exactOnlineApiOAuth2Api';
baseUrl = String(credentials.url) || 'https://start.exactonline.nl';
}
options.uri = `${baseUrl}${uri}`;
if (nextPageUrl !== '') {
options.uri = nextPageUrl;
}
options = Object.assign({}, options, option);
try {
if (Object.keys(body).length === 0) {
delete options.body;
}
const oAuth2Options = {
includeCredentialsOnRefreshOnBody: true,
};
let response;
try {
response = await this.helpers.requestWithAuthentication.call(this, credentialType, options);
}
catch (error) {
console.warn('[ExactNode] Error: ' + error.httpCode + ' | ' + ((_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.status));
if (error.httpCode && parseInt(error.httpCode, 10) === 429) {
console.warn('[ExactNode] Detected 429: waiting 60 seconds.');
await new Promise((resolve) => setTimeout(resolve, 61000));
console.warn('[ExactNode] Waiting done.');
response = await this.helpers.requestWithAuthentication.call(this, credentialType, options);
}
else {
console.warn('[ExactNode] Unknown error', error);
throw error;
}
}
return response;
}
catch (error) {
throw new n8n_workflow_1.NodeApiError(this.getNode(), error);
}
}
exports.exactOnlineApiRequest = exactOnlineApiRequest;
async function getCurrentDivision() {
const responseData = await exactOnlineApiRequest.call(this, 'GET', `/api/v1/current/Me?$select=CurrentDivision`);
return responseData.body.d.results[0].CurrentDivision;
}
exports.getCurrentDivision = getCurrentDivision;
async function getData(uri, body = {}, qs = {}, option = {}) {
const responseData = await exactOnlineApiRequest.call(this, 'GET', uri, body, qs, option);
if (responseData.body.d.results) {
return [].concat(responseData.body.d.results);
}
else {
return [].concat(responseData.body.d);
}
}
exports.getData = getData;
async function getAllData(uri, limit = 60, body = {}, qs = {}, option = {}, ignoreRateLimit = false) {
let returnData = [];
let responseData;
let nextPageUrl = '';
do {
if (nextPageUrl === '') {
responseData = await exactOnlineApiRequest.call(this, 'GET', uri, body, qs, option);
}
else {
responseData = await exactOnlineApiRequest.call(this, 'GET', uri, body, {}, option, nextPageUrl);
}
if (responseData.body.d.results) {
returnData = returnData.concat(responseData.body.d.results);
}
else {
returnData = returnData.concat(responseData.body.d);
}
nextPageUrl = responseData.body.d.__next;
if (!ignoreRateLimit && responseData.headers && responseData.headers['x-ratelimit-minutely-remaining'] === "0") {
const resetTimestamp = responseData.headers['x-ratelimit-minutely-reset'];
if (resetTimestamp && typeof resetTimestamp === 'number') {
const waitTime = resetTimestamp - Date.now();
if (waitTime >= 0) {
await new Promise((resolve) => setTimeout(resolve, Math.min(waitTime, 60000)));
}
}
else if (resetTimestamp) {
console.warn(`[ExactNode Rate Limit] Invalid x-ratelimit-minutely-reset header type: ${typeof resetTimestamp}. Value: ${resetTimestamp}`);
}
}
} while ((limit === 0 || returnData.length < limit) && responseData.body.d.__next);
if (limit !== 0) {
return returnData.slice(0, limit);
}
return returnData;
}
exports.getAllData = getAllData;
async function getFields(endpointConfig) {
return endpointConfig.fields.map((a) => a.name);
}
exports.getFields = getFields;
async function getMandatoryFields(endpointConfig) {
return endpointConfig.fields.filter((x) => x.mandatory === true).map((a) => a.name);
}
exports.getMandatoryFields = getMandatoryFields;
async function getServiceOptions() {
const allConfigs = getAllConfigs();
return allConfigs.map((x) => x.service.toLocaleLowerCase());
}
exports.getServiceOptions = getServiceOptions;
async function getFieldType(endpointConfig, fieldName) {
var _a;
return (_a = endpointConfig.fields.filter((a) => a.name === fieldName)[0].type) !== null && _a !== void 0 ? _a : 'Edm.String';
}
exports.getFieldType = getFieldType;
async function getResourceOptions(service) {
const allConfigs = getAllConfigs();
return allConfigs.filter((x) => x.service.toLocaleLowerCase() === service).map((x) => x.endpoint);
}
exports.getResourceOptions = getResourceOptions;
async function getEndpointFieldConfig(service, endpoint) {
const endpointConfig = await getEndpointConfig.call(this, service, endpoint);
return (endpointConfig === null || endpointConfig === void 0 ? void 0 : endpointConfig.fields) || [];
}
exports.getEndpointFieldConfig = getEndpointFieldConfig;
async function getEndpointConfig(service, endpoint) {
const allConfigs = getAllConfigs();
return allConfigs.find((x) => x.service.toLocaleLowerCase() === service.toLocaleLowerCase() && x.endpoint === endpoint);
}
exports.getEndpointConfig = getEndpointConfig;
const toDivisionOptions = (items) => items.map(({ Code, CustomerName, Description }) => ({
name: `${CustomerName} : ${Description}`,
value: Code,
}));
exports.toDivisionOptions = toDivisionOptions;
const toOptions = (items) => items.map(({ value, name }) => ({ name, value }));
exports.toOptions = toOptions;
const toFieldSelectOptions = (items) => items.map(({ name }) => ({ name, value: name }));
exports.toFieldSelectOptions = toFieldSelectOptions;
const toFieldFilterOptions = (items) => items.map(({ name }) => ({ name, value: name }));
exports.toFieldFilterOptions = toFieldFilterOptions;
const toOptionsFromStringArray = (items) => items.map((x) => ({ name: x.charAt(0).toUpperCase() + x.slice(1), value: x }));
exports.toOptionsFromStringArray = toOptionsFromStringArray;
async function exactOnlineXmlRequest(division, topic, xmlBody) {
var _a;
const options = {
headers: {
'Content-Type': 'application/xml',
},
method: 'POST',
body: xmlBody,
uri: '',
resolveWithFullResponse: true,
};
const authenticationMethod = this.getNodeParameter('authentication', 0, 'oAuth2');
let credentialType;
let baseUrl;
if (authenticationMethod === 'accessToken') {
const credentials = await this.getCredentials('exactOnlineApi');
credentialType = 'exactOnlineApi';
baseUrl = String(credentials.url) || 'https://start.exactonline.nl';
}
else {
const credentials = await this.getCredentials('exactOnlineApiOAuth2Api');
credentialType = 'exactOnlineApiOAuth2Api';
baseUrl = String(credentials.url) || 'https://start.exactonline.nl';
}
const cleanBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
options.uri = `${cleanBaseUrl}/docs/XMLUpload.aspx?Topic=${topic}&_Division_=${division}`;
try {
const oAuth2Options = {
includeCredentialsOnRefreshOnBody: true,
};
let response;
try {
response = await this.helpers.requestWithAuthentication.call(this, credentialType, options);
}
catch (error) {
console.warn('[ExactNode] Error: ' + error.httpCode + ' | ' + ((_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.status));
if (error.httpCode && parseInt(error.httpCode, 10) === 429) {
console.warn('[ExactNode] Detected 429: waiting 60 seconds.');
await new Promise((resolve) => setTimeout(resolve, 61000));
console.warn('[ExactNode] Waiting done.');
response = await this.helpers.requestWithAuthentication.call(this, credentialType, options);
}
else {
console.warn('[ExactNode] Unknown error', error);
throw error;
}
}
return response;
}
catch (error) {
throw new n8n_workflow_1.NodeApiError(this.getNode(), error);
}
}
exports.exactOnlineXmlRequest = exactOnlineXmlRequest;
function createReconciliationXml(matchSets) {
let xml = '<?xml version="1.0" encoding="utf-8"?>\n';
xml += '<eExact xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">\n';
xml += ' <MatchSets>\n';
for (const matchSet of matchSets) {
xml += ' <MatchSet>\n';
xml += ` <GLAccount code="${matchSet.GLAccount}"/>\n`;
if (matchSet.Account) {
xml += ` <Account code="${matchSet.Account}"/>\n`;
}
xml += ' <MatchLines>\n';
for (const matchLine of matchSet.MatchLines) {
const finYearInt = parseInt(String(matchLine.finYear), 10);
const finPeriodInt = parseInt(String(matchLine.finPeriod), 10);
const entryInt = parseInt(String(matchLine.entry), 10);
const amountFloat = parseFloat(String(matchLine.amountDC));
xml += ' <MatchLine ';
xml += `finyear="${finYearInt}" `;
xml += `finperiod="${finPeriodInt}" `;
xml += `journal="${matchLine.journal}" `;
xml += `entry="${entryInt}" `;
xml += `amountdc="${amountFloat}" `;
xml += '/>\n';
}
xml += ' </MatchLines>\n';
if (matchSet.WriteOff) {
xml += ` <WriteOff type="${matchSet.WriteOff.type}">\n`;
if (matchSet.WriteOff.GLAccount) {
xml += ` <GLAccount code="${matchSet.WriteOff.GLAccount}"/>\n`;
}
if (matchSet.WriteOff.Description) {
xml += ` <Description>${matchSet.WriteOff.Description}</Description>\n`;
}
if (matchSet.WriteOff.FinYear) {
xml += ` <FinYear>${parseInt(String(matchSet.WriteOff.FinYear), 10)}</FinYear>\n`;
}
if (matchSet.WriteOff.FinPeriod) {
xml += ` <FinPeriod>${parseInt(String(matchSet.WriteOff.FinPeriod), 10)}</FinPeriod>\n`;
}
if (matchSet.WriteOff.Date) {
xml += ` <Date>${matchSet.WriteOff.Date}</Date>\n`;
}
if (matchSet.WriteOff.VATCorrection !== undefined) {
xml += ` <VATCorrection>${matchSet.WriteOff.VATCorrection}</VATCorrection>\n`;
}
xml += ' </WriteOff>\n';
}
xml += ' </MatchSet>\n';
}
xml += ' </MatchSets>\n';
xml += '</eExact>';
return xml;
}
exports.createReconciliationXml = createReconciliationXml;
//# sourceMappingURL=GenericFunctions.js.map