n8n-nodes-sevdesk-v2
Version:
n8n community node for SevDesk API v2 integration with 24 production-ready workflow templates. Direct API access without external dependencies - simplified, secure, and optimized for German accounting automation (n8n 1.101.1 compatible).
294 lines (293 loc) • 10.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ResourceDependencyResolver = void 0;
class ResourceDependencyResolver {
static initialize() {
if (this.initialized)
return;
this.registerDependencies();
this.initialized = true;
}
static registerDependencies() {
this.registerDependency({
resource: 'invoice',
operation: 'create',
dependsOn: [
{
resource: 'contact',
operation: 'create',
field: 'contact',
required: true
}
],
provides: [
{
field: 'id',
type: 'id'
}
]
});
this.registerDependency({
resource: 'order',
operation: 'create',
dependsOn: [
{
resource: 'contact',
operation: 'create',
field: 'contact',
required: true
}
],
provides: [
{
field: 'id',
type: 'id'
}
]
});
this.registerDependency({
resource: 'voucher',
operation: 'create',
dependsOn: [
{
resource: 'contact',
operation: 'create',
field: 'supplier',
required: false
}
],
provides: [
{
field: 'id',
type: 'id'
}
]
});
this.registerDependency({
resource: 'creditNote',
operation: 'create',
dependsOn: [
{
resource: 'contact',
operation: 'create',
field: 'contact',
required: true
},
{
resource: 'invoice',
operation: 'create',
field: 'invoice',
required: false
}
],
provides: [
{
field: 'id',
type: 'id'
}
]
});
this.registerDependency({
resource: 'contactAddress',
operation: 'create',
dependsOn: [
{
resource: 'contact',
operation: 'create',
field: 'contact',
required: true
}
]
});
this.registerDependency({
resource: 'communicationWay',
operation: 'create',
dependsOn: [
{
resource: 'contact',
operation: 'create',
field: 'contact',
required: true
}
]
});
}
static registerDependency(dependency) {
const key = `${dependency.resource}:${dependency.operation}`;
this.dependencies.set(key, dependency);
}
static getDependency(resource, operation) {
const key = `${resource}:${operation}`;
return this.dependencies.get(key) || null;
}
static resolveDependencies(operations) {
this.initialize();
const result = {
executionOrder: [],
dependencyMap: new Map(),
isValid: true,
errors: []
};
const dependencyGraph = this.buildDependencyGraph(operations);
const circularDeps = this.detectCircularDependencies(dependencyGraph);
if (circularDeps.length > 0) {
result.isValid = false;
result.errors.push(`Circular dependencies detected: ${circularDeps.join(', ')}`);
return result;
}
const sortedOperations = this.topologicalSort(operations, dependencyGraph);
if (sortedOperations.length !== operations.length) {
result.isValid = false;
result.errors.push('Unable to resolve all dependencies');
return result;
}
result.executionOrder = sortedOperations.map((op, index) => ({
resource: op.resource,
operation: op.operation,
index: op.index,
dependencies: dependencyGraph.get(`${op.resource}:${op.operation}:${op.index}`) || []
}));
result.dependencyMap = dependencyGraph;
return result;
}
static buildDependencyGraph(operations) {
const graph = new Map();
operations.forEach((operation, index) => {
const opKey = `${operation.resource}:${operation.operation}:${index}`;
const dependencies = [];
const dependency = this.getDependency(operation.resource, operation.operation);
if (dependency) {
dependency.dependsOn.forEach(dep => {
const satisfyingOps = operations.filter((op, opIndex) => op.resource === dep.resource &&
op.operation === dep.operation &&
opIndex !== index);
satisfyingOps.forEach(satisfyingOp => {
const depKey = `${satisfyingOp.resource}:${satisfyingOp.operation}:${satisfyingOp.index}`;
dependencies.push(depKey);
});
if (dep.required && satisfyingOps.length === 0) {
const existingResource = this.findExistingResource(operation, dep);
if (!existingResource) {
dependencies.push(`MISSING:${dep.resource}:${dep.operation}`);
}
}
});
}
graph.set(opKey, dependencies);
});
return graph;
}
static findExistingResource(operation, dependency) {
if (dependency.field && operation.data[dependency.field]) {
return true;
}
return false;
}
static detectCircularDependencies(graph) {
const visited = new Set();
const recursionStack = new Set();
const circularDeps = [];
const dfs = (node) => {
if (recursionStack.has(node)) {
circularDeps.push(node);
return true;
}
if (visited.has(node)) {
return false;
}
visited.add(node);
recursionStack.add(node);
const dependencies = graph.get(node) || [];
for (const dep of dependencies) {
if (!dep.startsWith('MISSING:') && dfs(dep)) {
return true;
}
}
recursionStack.delete(node);
return false;
};
for (const node of graph.keys()) {
if (!visited.has(node)) {
dfs(node);
}
}
return circularDeps;
}
static topologicalSort(operations, graph) {
const inDegree = new Map();
const result = [];
const queue = [];
operations.forEach((op, index) => {
const key = `${op.resource}:${op.operation}:${index}`;
inDegree.set(key, 0);
});
graph.forEach((dependencies, node) => {
dependencies.forEach(dep => {
if (!dep.startsWith('MISSING:')) {
inDegree.set(dep, (inDegree.get(dep) || 0) + 1);
}
});
});
inDegree.forEach((degree, node) => {
if (degree === 0) {
queue.push(node);
}
});
while (queue.length > 0) {
const current = queue.shift();
const [resource, operation, indexStr] = current.split(':');
const index = parseInt(indexStr);
const currentOp = operations.find((op, i) => op.resource === resource &&
op.operation === operation &&
i === index);
if (currentOp) {
result.push(currentOp);
}
const dependencies = graph.get(current) || [];
dependencies.forEach(dep => {
if (!dep.startsWith('MISSING:')) {
const newDegree = (inDegree.get(dep) || 0) - 1;
inDegree.set(dep, newDegree);
if (newDegree === 0) {
queue.push(dep);
}
}
});
}
return result;
}
static validateDependencies(operations) {
this.initialize();
const errors = [];
operations.forEach((operation, index) => {
const dependency = this.getDependency(operation.resource, operation.operation);
if (dependency) {
dependency.dependsOn.forEach(dep => {
if (dep.required) {
const isSatisfied = operations.some((op, opIndex) => op.resource === dep.resource &&
op.operation === dep.operation &&
opIndex !== index) || this.findExistingResource(operation, dep);
if (!isSatisfied) {
errors.push(`Operation ${operation.resource}:${operation.operation} requires ${dep.resource}:${dep.operation} but it's not provided`);
}
}
});
}
});
return {
isValid: errors.length === 0,
errors
};
}
static getAllDependencies() {
this.initialize();
return new Map(this.dependencies);
}
static clearDependencies() {
this.dependencies.clear();
this.initialized = false;
}
}
exports.ResourceDependencyResolver = ResourceDependencyResolver;
ResourceDependencyResolver.dependencies = new Map();
ResourceDependencyResolver.initialized = false;