UNPKG

n8n-nodes-sap-service-layer

Version:

n8n custom node for SAP Service Layer integration

660 lines (659 loc) 29 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SapServiceLayer = void 0; const n8n_workflow_1 = require("n8n-workflow"); const axios_1 = __importStar(require("axios")); const https_1 = __importDefault(require("https")); class SapServiceLayer { constructor() { this.description = { displayName: "PlantScanner - SAP Service Layer", name: "sapServiceLayer", icon: "file:sap_1.svg", group: ["transform"], version: 1, subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', description: "Interact with SAP Service Layer API", defaults: { name: "SAP Service Layer", }, inputs: ["main"], outputs: ["main"], credentials: [ { name: "sapServiceLayerApi", required: true, }, ], properties: [ { displayName: "Resource", name: "resource", type: "options", noDataExpression: true, options: [ { name: "Item", value: "item", description: "Get materials and items", }, { name: "Production Order", value: "productionOrder", description: "Manage production orders", }, { name: "Warehouse", value: "warehouse", description: "Get warehouse information", }, { name: "Product Tree", value: "productTree", description: "Get production trees", }, { name: "Inventory Gen Entry", value: "inventoryGenEntry", description: "Create and manage inventory entries", }, { name: "Inventory Gen Exit", value: "inventoryGenExit", description: "Create and manage inventory exits", }, { name: "SQL Query", value: "sqlQuery", description: "Execute custom SQL queries", }, { name: "Custom API Call", value: "customApiCall", description: "Make custom API calls with custom endpoint, method, headers, and body", }, ], default: "item", }, { displayName: "Operation", name: "operation", type: "options", noDataExpression: true, displayOptions: { show: { resource: [ "item", "productionOrder", "warehouse", "productTree", "inventoryGenEntry", "inventoryGenExit", "sqlQuery", "customApiCall", ], }, }, options: [ { name: "Get", value: "get", description: "Get a single record", action: "Get a record", }, { name: "Get All", value: "getAll", description: "Get all records with filters", action: "Get all records", }, { name: "Update", value: "update", description: "Update a record", action: "Update a record", }, { name: "Create", value: "create", description: "Create a new record", action: "Create a new record", }, { name: "Execute Query", value: "executeQuery", description: "Execute a SQL query", action: "Execute a SQL query", }, { name: "Verify Exists", value: "verifyExists", description: "Check if a record exists", action: "Verify if a record exists", }, { name: "Custom Request", value: "customRequest", description: "Make a custom API request with specified method, endpoint, headers, and body", action: "Make a custom API request", }, ], default: "getAll", }, { displayName: "ID", name: "id", type: "string", displayOptions: { show: { operation: ["get", "update"], resource: ["productionOrder", "warehouse", "productTree"], }, }, default: "", required: true, description: "The ID of the record", }, { displayName: "Journal Memo", name: "journalMemo", type: "string", displayOptions: { show: { operation: ["verifyExists"], resource: ["inventoryGenEntry", "inventoryGenExit"], }, }, default: "", required: true, description: "The journal memo to check", }, { displayName: "Query Name", name: "queryName", type: "string", displayOptions: { show: { operation: ["executeQuery"], resource: ["sqlQuery"], }, }, default: "", required: true, description: "Name of the SQL query to execute (e.g., PlantScannerGetFirstBatch, PlantScannerStockUpdateOnly)", placeholder: "PlantScannerStockUpdateOnly", }, { displayName: "Query Parameters", name: "queryParams", type: "string", displayOptions: { show: { operation: ["executeQuery"], resource: ["sqlQuery"], }, }, default: "", required: false, description: "Parameters for the SQL query body (e.g., updateDate='20240616'). Leave empty if no body parameters needed.", placeholder: "updateDate='20240616'", }, { displayName: "URL Parameters", name: "urlParams", type: "string", displayOptions: { show: { operation: ["executeQuery"], resource: ["sqlQuery"], }, }, default: "", required: false, description: "URL parameters to append to the query (e.g., updateDate=20240614&$skip=400). Leave empty if no URL parameters needed.", placeholder: "updateDate=20240614&$skip=400", }, { displayName: "Filters", name: "filters", type: "string", displayOptions: { show: { operation: ["getAll"], resource: [ "item", "productionOrder", "warehouse", "productTree", "inventoryGenEntry", "inventoryGenExit", ], }, }, default: "", required: false, description: "OData filter string (e.g., WarehouseCode eq 'WH001' or CreationDate ge '2024-03-20')", }, { displayName: "Skip", name: "skip", type: "number", displayOptions: { show: { operation: ["getAll"], resource: [ "item", "productionOrder", "warehouse", "productTree", "inventoryGenEntry", "inventoryGenExit", ], }, }, default: 0, required: false, description: "Number of records to skip", }, { displayName: "Select", name: "select", type: "string", displayOptions: { show: { operation: ["get", "getAll"], resource: [ "item", "productionOrder", "warehouse", "productTree", "inventoryGenEntry", "inventoryGenExit", "sqlQuery", ], }, }, default: "", required: false, description: "Fields to select in the response (comma-separated). Leave empty to get all fields.", hint: "Example: ItemCode, ItemName, PurchaseItem, SalesItem", }, { displayName: "Expand", name: "expand", type: "string", displayOptions: { show: { operation: ["get", "getAll"], resource: [ "item", "productionOrder", "warehouse", "productTree", "inventoryGenEntry", "inventoryGenExit", ], }, }, default: "", required: false, description: "Related entities to expand (comma-separated).", hint: "Example: DocumentLines, Attachments2", }, { displayName: "Data", name: "data", type: "json", displayOptions: { show: { operation: ["create", "update"], resource: [ "productionOrder", "inventoryGenEntry", "inventoryGenExit", ], }, }, default: "{}", required: true, description: "The data to send", }, { displayName: "Custom Endpoint", name: "customEndpoint", type: "string", displayOptions: { show: { operation: ["customRequest"], resource: ["customApiCall"], }, }, default: "", required: true, description: "The custom endpoint to call (e.g., 'Items', 'BusinessPartners', 'Orders')", placeholder: "Items", }, { displayName: "HTTP Method", name: "customMethod", type: "options", displayOptions: { show: { operation: ["customRequest"], resource: ["customApiCall"], }, }, options: [ { name: "GET", value: "GET" }, { name: "POST", value: "POST" }, { name: "PUT", value: "PUT" }, { name: "PATCH", value: "PATCH" }, { name: "DELETE", value: "DELETE" }, ], default: "GET", required: true, description: "The HTTP method to use for the request", }, { displayName: "Filter", name: "customFilter", type: "string", displayOptions: { show: { operation: ["customRequest"], resource: ["customApiCall"], customMethod: ["GET"], }, }, default: "", required: false, description: "OData filter string (e.g., ItemCode eq 'ITEM001' or CreationDate ge '2024-03-20')", placeholder: "ItemCode eq 'ITEM001'", }, { displayName: "Select", name: "customSelect", type: "string", displayOptions: { show: { operation: ["customRequest"], resource: ["customApiCall"], customMethod: ["GET"], }, }, default: "", required: false, description: "Fields to select in the response (comma-separated). Leave empty to get all fields.", placeholder: "ItemCode, ItemName, PurchaseItem", }, { displayName: "Order By", name: "customOrderBy", type: "string", displayOptions: { show: { operation: ["customRequest"], resource: ["customApiCall"], customMethod: ["GET"], }, }, default: "", required: false, description: "Fields to order by (comma-separated). Use 'desc' for descending order.", placeholder: "ItemCode asc, CreationDate desc", }, { displayName: "Custom Headers", name: "customHeaders", type: "json", displayOptions: { show: { operation: ["customRequest"], resource: ["customApiCall"], }, }, default: "{}", required: false, description: "Additional headers to send with the request (JSON format)", hint: 'Example: {"Accept": "application/json", "Custom-Header": "value"}', }, { displayName: "Custom Body", name: "customBody", type: "json", displayOptions: { show: { operation: ["customRequest"], resource: ["customApiCall"], customMethod: ["POST", "PUT", "PATCH"], }, }, default: "{}", required: false, description: "The request body to send (JSON format). Only available for POST, PUT, and PATCH methods.", hint: 'Example: {"ItemCode": "ITEM001", "ItemName": "Test Item"}', }, ], }; } async execute() { var _a, _b, _c; const items = this.getInputData(); const returnData = []; const resource = this.getNodeParameter("resource", 0); const operation = this.getNodeParameter("operation", 0); // Get credentials const credentials = await this.getCredentials("sapServiceLayerApi"); const baseUrl = credentials.url; // Create axios instance with SSL verification disabled const axiosInstance = axios_1.default.create({ httpsAgent: new https_1.default.Agent({ rejectUnauthorized: false, }), }); // Login to get session cookie const loginResponse = await axiosInstance.post(`${baseUrl}/Login`, { CompanyDB: credentials.companyDB, UserName: credentials.username, Password: credentials.password, Language: credentials.language || 1, }); const cookies = loginResponse.headers["set-cookie"]; if (!cookies) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), "Failed to get session cookie"); } const sessionCookie = cookies[0].split(";")[0]; const headers = { Cookie: sessionCookie, }; // Process each item for (let i = 0; i < items.length; i++) { try { let response; const endpoint = SapServiceLayer.getEndpoint(resource); switch (operation) { case "create": const createData = this.getNodeParameter("data", i); response = await axiosInstance.post(`${baseUrl}/${endpoint}`, createData, { headers, }); break; case "get": const id = this.getNodeParameter("id", i); const selectGet = this.getNodeParameter("select", i, ""); const expandGet = this.getNodeParameter("expand", i, ""); const paramsGet = {}; if (selectGet) paramsGet.$select = selectGet; if (expandGet) paramsGet.$expand = expandGet; response = await axiosInstance.get(`${baseUrl}/${endpoint}(${id})`, { headers, params: paramsGet, }); break; case "getAll": const skip = this.getNodeParameter("skip", i, 0); const filters = this.getNodeParameter("filters", i, ""); const select = this.getNodeParameter("select", i, ""); const expand = this.getNodeParameter("expand", i, ""); const params = { $skip: skip, }; if (filters) { params.$filter = filters; } if (select) { params.$select = select; } if (expand) { params.$expand = expand; } response = await axiosInstance.get(`${baseUrl}/${endpoint}`, { headers, params, }); break; case "update": const updateId = this.getNodeParameter("id", i); const updateData = this.getNodeParameter("data", i); response = await axiosInstance.patch(`${baseUrl}/${endpoint}(${updateId})`, updateData, { headers }); break; case "executeQuery": const queryName = this.getNodeParameter("queryName", i); const queryParams = this.getNodeParameter("queryParams", i, ""); const urlParams = this.getNodeParameter("urlParams", i, ""); // Build the URL with query parameters let queryUrl = `${baseUrl}/SQLQueries('${queryName}')/List`; if (urlParams) { queryUrl += `?${urlParams}`; } // Build the request body - only include ParamList if queryParams is provided const requestBody = {}; if (queryParams && queryParams.trim() !== '') { requestBody.ParamList = queryParams; } response = await axiosInstance.post(queryUrl, requestBody, { headers }); break; case "verifyExists": const journalMemo = this.getNodeParameter("journalMemo", i); response = await axiosInstance.get(`${baseUrl}/${endpoint}`, { headers, params: { $filter: `JournalMemo eq '${journalMemo}'`, }, }); returnData.push({ json: { exists: response.data.value.length > 0, count: response.data.value.length, }, }); continue; case "customRequest": const customEndpoint = this.getNodeParameter("customEndpoint", i); const customMethod = this.getNodeParameter("customMethod", i); const customFilter = this.getNodeParameter("customFilter", i, ""); const customSelect = this.getNodeParameter("customSelect", i, ""); const customOrderBy = this.getNodeParameter("customOrderBy", i, ""); const customHeaders = this.getNodeParameter("customHeaders", i, "{}"); const customBody = this.getNodeParameter("customBody", i, "{}"); const parsedCustomHeaders = JSON.parse(customHeaders); const parsedCustomBody = JSON.parse(customBody); let customParams = {}; if (customFilter) { customParams.$filter = customFilter; } if (customSelect) { customParams.$select = customSelect; } if (customOrderBy) { customParams.$orderby = customOrderBy; } response = await axiosInstance.request({ url: `${baseUrl}/${customEndpoint}`, method: customMethod, headers: { ...headers, ...parsedCustomHeaders, }, params: customParams, data: parsedCustomBody, }); break; default: throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Operation ${operation} not supported`); } returnData.push({ json: response.data, }); } catch (error) { if (this.continueOnFail()) { const errorMessage = error instanceof axios_1.AxiosError ? ((_c = (_b = (_a = error.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.error) === null || _c === void 0 ? void 0 : _c.message) || error.message : error instanceof Error ? error.message : "Unknown error occurred"; returnData.push({ json: { error: errorMessage, }, }); continue; } throw error; } } return [returnData]; } static getEndpoint(resource) { switch (resource) { case "item": return "Items"; case "productionOrder": return "ProductionOrders"; case "warehouse": return "Warehouses"; case "productTree": return "ProductTrees"; case "inventoryGenEntry": return "InventoryGenEntries"; case "inventoryGenExit": return "InventoryGenExits"; case "sqlQuery": return "SQLQueries"; case "customApiCall": return ""; // Empty string since we'll use the custom endpoint directly default: throw new Error(`Resource ${resource} not supported`); } } } exports.SapServiceLayer = SapServiceLayer;