n8n-nodes-sap-service-layer
Version:
n8n custom node for SAP Service Layer integration
660 lines (659 loc) • 29 kB
JavaScript
;
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;