@pokash/n8n-nodes-optima-rest-api 1.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +152 -0
- package/dist/credentials/OptimaRestApiCredentials.credentials.d.ts +9 -0
- package/dist/credentials/OptimaRestApiCredentials.credentials.js +80 -0
- package/dist/nodes/OptimaRestApi/OptimaRestApi.node.d.ts +5 -0
- package/dist/nodes/OptimaRestApi/OptimaRestApi.node.js +690 -0
- package/dist/nodes/OptimaRestApi/optima.svg +4 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 POKASH.PL Sp. z o. o.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# n8n-nodes-optima-rest-api
|
|
2
|
+
|
|
3
|
+
Node n8n do integracji z Comarch Optima ERP poprzez REST API Gateway.
|
|
4
|
+
|
|
5
|
+
## Instalacja
|
|
6
|
+
|
|
7
|
+
### Community Node (n8n Cloud lub Self-hosted)
|
|
8
|
+
|
|
9
|
+
W n8n przejdź do **Settings > Community Nodes** i zainstaluj:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
n8n-nodes-optima-rest-api
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Manualna instalacja (Development)
|
|
16
|
+
|
|
17
|
+
1. Przejdź do folderu z custom nodes:
|
|
18
|
+
```bash
|
|
19
|
+
cd ~/.n8n/custom
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
2. Sklonuj lub skopiuj ten folder
|
|
23
|
+
|
|
24
|
+
3. Zainstaluj zależności:
|
|
25
|
+
```bash
|
|
26
|
+
npm install
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
4. Zbuduj projekt:
|
|
30
|
+
```bash
|
|
31
|
+
npm run build
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
5. Zrestartuj n8n
|
|
35
|
+
|
|
36
|
+
## Konfiguracja
|
|
37
|
+
|
|
38
|
+
### Credentials
|
|
39
|
+
|
|
40
|
+
Utwórz credentials typu **Optima REST API**:
|
|
41
|
+
|
|
42
|
+
- **Gateway URL**: URL adres serwera Gateway (np. `http://localhost:5000`)
|
|
43
|
+
- **Username**: Nazwa użytkownika Optima
|
|
44
|
+
- **Password**: Hasło użytkownika
|
|
45
|
+
- **Company**: Kod firmy w Optima (np. `FIRMA01`)
|
|
46
|
+
- **Modules**: Moduły Optima (np. `KP` dla księgowości, `CDN` dla handlu)
|
|
47
|
+
|
|
48
|
+
## Dostępne operacje
|
|
49
|
+
|
|
50
|
+
### Customer (Kontrahent)
|
|
51
|
+
|
|
52
|
+
- **Get**: Pobierz kontrahenta po ID
|
|
53
|
+
- **Get All**: Pobierz wszystkich kontrahentów
|
|
54
|
+
- **Create**: Utwórz nowego kontrahenta
|
|
55
|
+
- **Update**: Zaktualizuj kontrahenta
|
|
56
|
+
- **Delete**: Usuń kontrahenta
|
|
57
|
+
|
|
58
|
+
### Document (Dokument)
|
|
59
|
+
|
|
60
|
+
- **Create Invoice**: Utwórz fakturę sprzedaży lub zakupu
|
|
61
|
+
|
|
62
|
+
### Product (Towar)
|
|
63
|
+
|
|
64
|
+
- **Get**: Pobierz towar po ID
|
|
65
|
+
- **Get All**: Pobierz wszystkie towary
|
|
66
|
+
|
|
67
|
+
### Print (Wydruk)
|
|
68
|
+
|
|
69
|
+
- **Print Document**: Wygeneruj PDF dokumentu
|
|
70
|
+
|
|
71
|
+
## Przykłady użycia
|
|
72
|
+
|
|
73
|
+
### Utworzenie kontrahenta
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"akronim": "KLIENT01",
|
|
78
|
+
"nazwa": "Firma Example Sp. z o.o.",
|
|
79
|
+
"nip": "1234567890",
|
|
80
|
+
"ulica": "Główna 1",
|
|
81
|
+
"kod": "00-001",
|
|
82
|
+
"miasto": "Warszawa"
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Utworzenie faktury sprzedaży
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"platnik": {
|
|
91
|
+
"akronim": "KLIENT01"
|
|
92
|
+
},
|
|
93
|
+
"pozycje": [
|
|
94
|
+
{
|
|
95
|
+
"kod": "PROD01",
|
|
96
|
+
"ilosc": 2,
|
|
97
|
+
"cena": 100.00
|
|
98
|
+
}
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Wydruk dokumentu do PDF
|
|
104
|
+
|
|
105
|
+
Parametry:
|
|
106
|
+
- **Document ID**: 123 (ID dokumentu w Optima)
|
|
107
|
+
- **Format ID**: 1 (ID formatu wydruku z Optima)
|
|
108
|
+
|
|
109
|
+
Alternatywnie można użyć **SQL Filter**:
|
|
110
|
+
```
|
|
111
|
+
TrN_TrnId = 123
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Development
|
|
115
|
+
|
|
116
|
+
### Build
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
npm run build
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Watch mode
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
npm run dev
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Linting
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
npm run lint
|
|
132
|
+
npm run lintfix
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Format code
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
npm run format
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Licencja
|
|
142
|
+
|
|
143
|
+
MIT
|
|
144
|
+
|
|
145
|
+
## Autor
|
|
146
|
+
|
|
147
|
+
POKASH.PL Sp. z o. o. (hello@pokash.cloud)
|
|
148
|
+
|
|
149
|
+
## Links
|
|
150
|
+
|
|
151
|
+
- [GitHub Repository](https://github.com/pokash-pl/n8n-nodes-optima-rest-api)
|
|
152
|
+
- [npm Package](https://www.npmjs.com/package/@pokash/n8n-nodes-optima-rest-api)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { IAuthenticateGeneric, ICredentialTestRequest, ICredentialType, INodeProperties } from 'n8n-workflow';
|
|
2
|
+
export declare class OptimaRestApiCredentials implements ICredentialType {
|
|
3
|
+
name: string;
|
|
4
|
+
displayName: string;
|
|
5
|
+
documentationUrl: string;
|
|
6
|
+
properties: INodeProperties[];
|
|
7
|
+
authenticate: IAuthenticateGeneric;
|
|
8
|
+
test: ICredentialTestRequest;
|
|
9
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OptimaRestApiCredentials = void 0;
|
|
4
|
+
class OptimaRestApiCredentials {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.name = 'optimaRestApiCredentials';
|
|
7
|
+
this.displayName = 'Optima REST API';
|
|
8
|
+
this.documentationUrl = 'https://github.com/yourusername/n8n-nodes-optima-rest-api';
|
|
9
|
+
this.properties = [
|
|
10
|
+
{
|
|
11
|
+
displayName: 'Gateway URL',
|
|
12
|
+
name: 'gatewayUrl',
|
|
13
|
+
type: 'string',
|
|
14
|
+
default: 'http://localhost:5000',
|
|
15
|
+
placeholder: 'http://localhost:5000',
|
|
16
|
+
description: 'URL adres Optima REST API Gateway',
|
|
17
|
+
required: true,
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
displayName: 'Username',
|
|
21
|
+
name: 'username',
|
|
22
|
+
type: 'string',
|
|
23
|
+
default: '',
|
|
24
|
+
placeholder: 'admin',
|
|
25
|
+
description: 'Nazwa użytkownika Optima',
|
|
26
|
+
required: true,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
displayName: 'Password',
|
|
30
|
+
name: 'password',
|
|
31
|
+
type: 'string',
|
|
32
|
+
typeOptions: {
|
|
33
|
+
password: true,
|
|
34
|
+
},
|
|
35
|
+
default: '',
|
|
36
|
+
description: 'Hasło użytkownika Optima',
|
|
37
|
+
required: false,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
displayName: 'Company',
|
|
41
|
+
name: 'company',
|
|
42
|
+
type: 'string',
|
|
43
|
+
default: '',
|
|
44
|
+
placeholder: 'FIRMA01',
|
|
45
|
+
description: 'Kod firmy w Optima',
|
|
46
|
+
required: false,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
displayName: 'Modules',
|
|
50
|
+
name: 'modules',
|
|
51
|
+
type: 'string',
|
|
52
|
+
default: 'KP',
|
|
53
|
+
placeholder: 'KP',
|
|
54
|
+
description: 'Moduły Optima (KP - księgowość i płace, CDN - handel)',
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
this.authenticate = {
|
|
58
|
+
type: 'generic',
|
|
59
|
+
properties: {
|
|
60
|
+
headers: {
|
|
61
|
+
Authorization: '={{"Bearer " + $credentials.token}}',
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
this.test = {
|
|
66
|
+
request: {
|
|
67
|
+
baseURL: '={{$credentials.gatewayUrl}}',
|
|
68
|
+
url: '/api/account/login',
|
|
69
|
+
method: 'POST',
|
|
70
|
+
body: {
|
|
71
|
+
username: '={{$credentials.username}}',
|
|
72
|
+
password: '={{$credentials.password}}',
|
|
73
|
+
company: '={{$credentials.company}}',
|
|
74
|
+
modules: '={{$credentials.modules}}',
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
exports.OptimaRestApiCredentials = OptimaRestApiCredentials;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
|
|
2
|
+
export declare class OptimaRestApi implements INodeType {
|
|
3
|
+
description: INodeTypeDescription;
|
|
4
|
+
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
|
|
5
|
+
}
|
|
@@ -0,0 +1,690 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OptimaRestApi = void 0;
|
|
4
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
5
|
+
class OptimaRestApi {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.description = {
|
|
8
|
+
displayName: 'Optima REST API',
|
|
9
|
+
name: 'optimaRestApi',
|
|
10
|
+
icon: 'file:optima.svg',
|
|
11
|
+
group: ['transform'],
|
|
12
|
+
version: 1,
|
|
13
|
+
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
|
14
|
+
description: 'Interact with Comarch Optima ERP via REST API',
|
|
15
|
+
defaults: {
|
|
16
|
+
name: 'Optima REST API',
|
|
17
|
+
},
|
|
18
|
+
inputs: ['main'],
|
|
19
|
+
outputs: ['main'],
|
|
20
|
+
credentials: [
|
|
21
|
+
{
|
|
22
|
+
name: 'optimaRestApiCredentials',
|
|
23
|
+
required: true,
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
properties: [
|
|
27
|
+
{
|
|
28
|
+
displayName: 'Resource',
|
|
29
|
+
name: 'resource',
|
|
30
|
+
type: 'options',
|
|
31
|
+
noDataExpression: true,
|
|
32
|
+
options: [
|
|
33
|
+
{
|
|
34
|
+
name: 'Customer',
|
|
35
|
+
value: 'customer',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: 'Dictionary',
|
|
39
|
+
value: 'dictionary',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'Document',
|
|
43
|
+
value: 'document',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'Product',
|
|
47
|
+
value: 'product',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'Print',
|
|
51
|
+
value: 'print',
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
default: 'customer',
|
|
55
|
+
},
|
|
56
|
+
// Customer Operations
|
|
57
|
+
{
|
|
58
|
+
displayName: 'Operation',
|
|
59
|
+
name: 'operation',
|
|
60
|
+
type: 'options',
|
|
61
|
+
noDataExpression: true,
|
|
62
|
+
displayOptions: {
|
|
63
|
+
show: {
|
|
64
|
+
resource: ['customer'],
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
options: [
|
|
68
|
+
{
|
|
69
|
+
name: 'Create',
|
|
70
|
+
value: 'create',
|
|
71
|
+
description: 'Create a customer',
|
|
72
|
+
action: 'Create a customer',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: 'Get',
|
|
76
|
+
value: 'get',
|
|
77
|
+
description: 'Get a customer',
|
|
78
|
+
action: 'Get a customer',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'Get All',
|
|
82
|
+
value: 'getAll',
|
|
83
|
+
description: 'Get all customers',
|
|
84
|
+
action: 'Get all customers',
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: 'Update',
|
|
88
|
+
value: 'update',
|
|
89
|
+
description: 'Update a customer',
|
|
90
|
+
action: 'Update a customer',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: 'Delete',
|
|
94
|
+
value: 'delete',
|
|
95
|
+
description: 'Delete a customer',
|
|
96
|
+
action: 'Delete a customer',
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
default: 'get',
|
|
100
|
+
},
|
|
101
|
+
// Customer ID field
|
|
102
|
+
{
|
|
103
|
+
displayName: 'Customer ID',
|
|
104
|
+
name: 'customerId',
|
|
105
|
+
type: 'number',
|
|
106
|
+
required: true,
|
|
107
|
+
displayOptions: {
|
|
108
|
+
show: {
|
|
109
|
+
resource: ['customer'],
|
|
110
|
+
operation: ['update', 'delete'],
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
default: 0,
|
|
114
|
+
description: 'The ID of the customer',
|
|
115
|
+
},
|
|
116
|
+
// Customer Get/GetAll - Filter options
|
|
117
|
+
{
|
|
118
|
+
displayName: 'Options',
|
|
119
|
+
name: 'options',
|
|
120
|
+
type: 'collection',
|
|
121
|
+
placeholder: 'Add Option',
|
|
122
|
+
default: {},
|
|
123
|
+
displayOptions: {
|
|
124
|
+
show: {
|
|
125
|
+
resource: ['customer'],
|
|
126
|
+
operation: ['get', 'getAll'],
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
options: [
|
|
130
|
+
{
|
|
131
|
+
displayName: 'Customer ID',
|
|
132
|
+
name: 'id',
|
|
133
|
+
type: 'number',
|
|
134
|
+
default: 0,
|
|
135
|
+
description: 'Specific customer ID to retrieve',
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
displayName: 'Filter',
|
|
139
|
+
name: 'filter',
|
|
140
|
+
type: 'string',
|
|
141
|
+
default: '',
|
|
142
|
+
placeholder: 'Knt_Kod = \'TEST\'',
|
|
143
|
+
description: 'SQL-like filter string for searching customers (e.g., Knt_Kod = \'ACME\' or Knt_Nip = \'1234567890\')',
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
displayName: 'Skip',
|
|
147
|
+
name: 'skip',
|
|
148
|
+
type: 'number',
|
|
149
|
+
default: 0,
|
|
150
|
+
description: 'Number of records to skip (for pagination)',
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
displayName: 'Take',
|
|
154
|
+
name: 'take',
|
|
155
|
+
type: 'number',
|
|
156
|
+
default: 100,
|
|
157
|
+
description: 'Maximum number of records to return (for pagination)',
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
// Customer fields for create/update
|
|
162
|
+
{
|
|
163
|
+
displayName: 'Customer Data',
|
|
164
|
+
name: 'customerData',
|
|
165
|
+
type: 'json',
|
|
166
|
+
required: true,
|
|
167
|
+
displayOptions: {
|
|
168
|
+
show: {
|
|
169
|
+
resource: ['customer'],
|
|
170
|
+
operation: ['create', 'update'],
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
default: '{\n "akronim": "KLIENT01",\n "nazwa": "Firma Example",\n "nip": "1234567890"\n}',
|
|
174
|
+
description: 'Customer data in JSON format',
|
|
175
|
+
},
|
|
176
|
+
// Dictionary Operations
|
|
177
|
+
{
|
|
178
|
+
displayName: 'Dictionary Type',
|
|
179
|
+
name: 'operation',
|
|
180
|
+
type: 'options',
|
|
181
|
+
noDataExpression: true,
|
|
182
|
+
displayOptions: {
|
|
183
|
+
show: {
|
|
184
|
+
resource: ['dictionary'],
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
options: [
|
|
188
|
+
{
|
|
189
|
+
name: 'Payment Methods',
|
|
190
|
+
value: 'paymentMethods',
|
|
191
|
+
description: 'Get payment methods dictionary',
|
|
192
|
+
action: 'Get payment methods',
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: 'Warehouses',
|
|
196
|
+
value: 'warehouses',
|
|
197
|
+
description: 'Get warehouses dictionary',
|
|
198
|
+
action: 'Get warehouses',
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
name: 'Currencies',
|
|
202
|
+
value: 'currencies',
|
|
203
|
+
description: 'Get currencies dictionary',
|
|
204
|
+
action: 'Get currencies',
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
name: 'Document Definitions',
|
|
208
|
+
value: 'documentDefinitions',
|
|
209
|
+
description: 'Get document definitions dictionary',
|
|
210
|
+
action: 'Get document definitions',
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: 'Accounting Categories',
|
|
214
|
+
value: 'accountingCategories',
|
|
215
|
+
description: 'Get accounting categories dictionary',
|
|
216
|
+
action: 'Get accounting categories',
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
default: 'paymentMethods',
|
|
220
|
+
},
|
|
221
|
+
// Document Operations
|
|
222
|
+
{
|
|
223
|
+
displayName: 'Operation',
|
|
224
|
+
name: 'operation',
|
|
225
|
+
type: 'options',
|
|
226
|
+
noDataExpression: true,
|
|
227
|
+
displayOptions: {
|
|
228
|
+
show: {
|
|
229
|
+
resource: ['document'],
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
options: [
|
|
233
|
+
{
|
|
234
|
+
name: 'Create Invoice',
|
|
235
|
+
value: 'createInvoice',
|
|
236
|
+
description: 'Create a sales or purchase invoice',
|
|
237
|
+
action: 'Create an invoice',
|
|
238
|
+
},
|
|
239
|
+
],
|
|
240
|
+
default: 'createInvoice',
|
|
241
|
+
},
|
|
242
|
+
// Document Type
|
|
243
|
+
{
|
|
244
|
+
displayName: 'Document Type',
|
|
245
|
+
name: 'documentType',
|
|
246
|
+
type: 'options',
|
|
247
|
+
displayOptions: {
|
|
248
|
+
show: {
|
|
249
|
+
resource: ['document'],
|
|
250
|
+
operation: ['createInvoice'],
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
options: [
|
|
254
|
+
{
|
|
255
|
+
name: 'Sales Invoice',
|
|
256
|
+
value: 'Sale',
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
name: 'Purchase Invoice',
|
|
260
|
+
value: 'Purchase',
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
default: 'Sale',
|
|
264
|
+
},
|
|
265
|
+
// Document Data
|
|
266
|
+
{
|
|
267
|
+
displayName: 'Document Data',
|
|
268
|
+
name: 'documentData',
|
|
269
|
+
type: 'json',
|
|
270
|
+
required: true,
|
|
271
|
+
displayOptions: {
|
|
272
|
+
show: {
|
|
273
|
+
resource: ['document'],
|
|
274
|
+
operation: ['createInvoice'],
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
default: '{\n "platnik": {\n "akronim": "KLIENT01"\n },\n "pozycje": [\n {\n "kod": "PROD01",\n "ilosc": 1,\n "cena": 100\n }\n ]\n}',
|
|
278
|
+
description: 'Document data in JSON format',
|
|
279
|
+
},
|
|
280
|
+
// Product Operations
|
|
281
|
+
{
|
|
282
|
+
displayName: 'Operation',
|
|
283
|
+
name: 'operation',
|
|
284
|
+
type: 'options',
|
|
285
|
+
noDataExpression: true,
|
|
286
|
+
displayOptions: {
|
|
287
|
+
show: {
|
|
288
|
+
resource: ['product'],
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
options: [
|
|
292
|
+
{
|
|
293
|
+
name: 'Get',
|
|
294
|
+
value: 'get',
|
|
295
|
+
description: 'Get a product',
|
|
296
|
+
action: 'Get a product',
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
name: 'Get All',
|
|
300
|
+
value: 'getAll',
|
|
301
|
+
description: 'Get all products',
|
|
302
|
+
action: 'Get all products',
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
default: 'get',
|
|
306
|
+
},
|
|
307
|
+
// Product ID
|
|
308
|
+
{
|
|
309
|
+
displayName: 'Product ID',
|
|
310
|
+
name: 'productId',
|
|
311
|
+
type: 'number',
|
|
312
|
+
required: true,
|
|
313
|
+
displayOptions: {
|
|
314
|
+
show: {
|
|
315
|
+
resource: ['product'],
|
|
316
|
+
operation: ['get'],
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
default: 0,
|
|
320
|
+
description: 'The ID of the product',
|
|
321
|
+
},
|
|
322
|
+
// Print Operations
|
|
323
|
+
{
|
|
324
|
+
displayName: 'Operation',
|
|
325
|
+
name: 'operation',
|
|
326
|
+
type: 'options',
|
|
327
|
+
noDataExpression: true,
|
|
328
|
+
displayOptions: {
|
|
329
|
+
show: {
|
|
330
|
+
resource: ['print'],
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
options: [
|
|
334
|
+
{
|
|
335
|
+
name: 'Print Document',
|
|
336
|
+
value: 'printDocument',
|
|
337
|
+
description: 'Print/export document to PDF',
|
|
338
|
+
action: 'Print a document',
|
|
339
|
+
},
|
|
340
|
+
],
|
|
341
|
+
default: 'printDocument',
|
|
342
|
+
},
|
|
343
|
+
// Print SQL Filter
|
|
344
|
+
{
|
|
345
|
+
displayName: 'SQL Filter',
|
|
346
|
+
name: 'filtrSQL',
|
|
347
|
+
type: 'string',
|
|
348
|
+
required: true,
|
|
349
|
+
displayOptions: {
|
|
350
|
+
show: {
|
|
351
|
+
resource: ['print'],
|
|
352
|
+
operation: ['printDocument'],
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
default: '',
|
|
356
|
+
placeholder: 'TrN_TrnId = 123',
|
|
357
|
+
description: 'SQL filter to select documents',
|
|
358
|
+
},
|
|
359
|
+
// Print Format ID
|
|
360
|
+
{
|
|
361
|
+
displayName: 'Format ID',
|
|
362
|
+
name: 'formatId',
|
|
363
|
+
type: 'number',
|
|
364
|
+
required: true,
|
|
365
|
+
displayOptions: {
|
|
366
|
+
show: {
|
|
367
|
+
resource: ['print'],
|
|
368
|
+
operation: ['printDocument'],
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
default: 1,
|
|
372
|
+
description: 'ID of the print format from Optima',
|
|
373
|
+
},
|
|
374
|
+
],
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
async execute() {
|
|
378
|
+
const items = this.getInputData();
|
|
379
|
+
const returnData = [];
|
|
380
|
+
const resource = this.getNodeParameter('resource', 0);
|
|
381
|
+
const operation = this.getNodeParameter('operation', 0);
|
|
382
|
+
// Get credentials
|
|
383
|
+
const credentials = await this.getCredentials('optimaRestApiCredentials');
|
|
384
|
+
const gatewayUrl = credentials.gatewayUrl;
|
|
385
|
+
// First, authenticate and get token
|
|
386
|
+
const loginResponse = await this.helpers.request({
|
|
387
|
+
method: 'POST',
|
|
388
|
+
url: `${gatewayUrl}/api/account/login`,
|
|
389
|
+
body: {
|
|
390
|
+
username: credentials.username,
|
|
391
|
+
password: credentials.password,
|
|
392
|
+
company: credentials.company,
|
|
393
|
+
modules: credentials.modules || 'KP',
|
|
394
|
+
},
|
|
395
|
+
json: true,
|
|
396
|
+
});
|
|
397
|
+
const token = loginResponse.Token;
|
|
398
|
+
for (let i = 0; i < items.length; i++) {
|
|
399
|
+
try {
|
|
400
|
+
if (resource === 'customer') {
|
|
401
|
+
if (operation === 'get' || operation === 'getAll') {
|
|
402
|
+
const options = this.getNodeParameter('options', i, {});
|
|
403
|
+
// Build query parameters (use PascalCase to match C# API)
|
|
404
|
+
const queryParams = new URLSearchParams();
|
|
405
|
+
if (options.id) {
|
|
406
|
+
queryParams.append('Id', String(options.id));
|
|
407
|
+
}
|
|
408
|
+
if (options.filter) {
|
|
409
|
+
queryParams.append('Filter', String(options.filter));
|
|
410
|
+
}
|
|
411
|
+
if (options.skip) {
|
|
412
|
+
queryParams.append('Skip', String(options.skip));
|
|
413
|
+
}
|
|
414
|
+
if (options.take) {
|
|
415
|
+
queryParams.append('Take', String(options.take));
|
|
416
|
+
}
|
|
417
|
+
const queryString = queryParams.toString();
|
|
418
|
+
const url = queryString
|
|
419
|
+
? `${gatewayUrl}/api/customer?${queryString}`
|
|
420
|
+
: `${gatewayUrl}/api/customer`;
|
|
421
|
+
const response = await this.helpers.request({
|
|
422
|
+
method: 'GET',
|
|
423
|
+
url,
|
|
424
|
+
headers: {
|
|
425
|
+
Authorization: `Bearer ${token}`,
|
|
426
|
+
},
|
|
427
|
+
json: true,
|
|
428
|
+
});
|
|
429
|
+
// API returns { Success: true, Customers: [...], TotalCount: ... }
|
|
430
|
+
const responseData = response;
|
|
431
|
+
// Extract customers array from response
|
|
432
|
+
if (responseData.Customers && Array.isArray(responseData.Customers)) {
|
|
433
|
+
const customers = responseData.Customers;
|
|
434
|
+
customers.forEach((item) => {
|
|
435
|
+
const result = {
|
|
436
|
+
json: item
|
|
437
|
+
};
|
|
438
|
+
if (items[i].binary) {
|
|
439
|
+
result.binary = items[i].binary;
|
|
440
|
+
}
|
|
441
|
+
returnData.push(result);
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
else if (responseData.Customers) {
|
|
445
|
+
// Single customer - preserve binary
|
|
446
|
+
const result = {
|
|
447
|
+
json: responseData.Customers
|
|
448
|
+
};
|
|
449
|
+
if (items[i].binary) {
|
|
450
|
+
result.binary = items[i].binary;
|
|
451
|
+
}
|
|
452
|
+
returnData.push(result);
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
// Fallback - return entire response if structure is unexpected
|
|
456
|
+
const result = {
|
|
457
|
+
json: responseData
|
|
458
|
+
};
|
|
459
|
+
if (items[i].binary) {
|
|
460
|
+
result.binary = items[i].binary;
|
|
461
|
+
}
|
|
462
|
+
returnData.push(result);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
else if (operation === 'create') {
|
|
466
|
+
const customerData = JSON.parse(this.getNodeParameter('customerData', i));
|
|
467
|
+
const response = await this.helpers.request({
|
|
468
|
+
method: 'POST',
|
|
469
|
+
url: `${gatewayUrl}/api/customer`,
|
|
470
|
+
headers: {
|
|
471
|
+
Authorization: `Bearer ${token}`,
|
|
472
|
+
},
|
|
473
|
+
body: customerData,
|
|
474
|
+
json: true,
|
|
475
|
+
});
|
|
476
|
+
const result = {
|
|
477
|
+
json: response
|
|
478
|
+
};
|
|
479
|
+
if (items[i].binary) {
|
|
480
|
+
result.binary = items[i].binary;
|
|
481
|
+
}
|
|
482
|
+
returnData.push(result);
|
|
483
|
+
}
|
|
484
|
+
else if (operation === 'update') {
|
|
485
|
+
const customerId = this.getNodeParameter('customerId', i);
|
|
486
|
+
const customerData = JSON.parse(this.getNodeParameter('customerData', i));
|
|
487
|
+
// Add ID to the customer data for PUT request
|
|
488
|
+
customerData.ID = customerId;
|
|
489
|
+
const response = await this.helpers.request({
|
|
490
|
+
method: 'PUT',
|
|
491
|
+
url: `${gatewayUrl}/api/customer`,
|
|
492
|
+
headers: {
|
|
493
|
+
Authorization: `Bearer ${token}`,
|
|
494
|
+
},
|
|
495
|
+
body: customerData,
|
|
496
|
+
json: true,
|
|
497
|
+
});
|
|
498
|
+
const result = {
|
|
499
|
+
json: response
|
|
500
|
+
};
|
|
501
|
+
if (items[i].binary) {
|
|
502
|
+
result.binary = items[i].binary;
|
|
503
|
+
}
|
|
504
|
+
returnData.push(result);
|
|
505
|
+
}
|
|
506
|
+
else if (operation === 'delete') {
|
|
507
|
+
const customerId = this.getNodeParameter('customerId', i);
|
|
508
|
+
await this.helpers.request({
|
|
509
|
+
method: 'DELETE',
|
|
510
|
+
url: `${gatewayUrl}/api/customer/${customerId}`,
|
|
511
|
+
headers: {
|
|
512
|
+
Authorization: `Bearer ${token}`,
|
|
513
|
+
},
|
|
514
|
+
json: true,
|
|
515
|
+
});
|
|
516
|
+
const result = {
|
|
517
|
+
json: { success: true, id: customerId }
|
|
518
|
+
};
|
|
519
|
+
if (items[i].binary) {
|
|
520
|
+
result.binary = items[i].binary;
|
|
521
|
+
}
|
|
522
|
+
returnData.push(result);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
else if (resource === 'document') {
|
|
526
|
+
if (operation === 'createInvoice') {
|
|
527
|
+
const documentType = this.getNodeParameter('documentType', i);
|
|
528
|
+
const documentData = JSON.parse(this.getNodeParameter('documentData', i));
|
|
529
|
+
const response = await this.helpers.request({
|
|
530
|
+
method: 'POST',
|
|
531
|
+
url: `${gatewayUrl}/api/Documents/Invoice/${documentType}`,
|
|
532
|
+
headers: {
|
|
533
|
+
Authorization: `Bearer ${token}`,
|
|
534
|
+
},
|
|
535
|
+
body: documentData,
|
|
536
|
+
json: true,
|
|
537
|
+
});
|
|
538
|
+
const result = {
|
|
539
|
+
json: response
|
|
540
|
+
};
|
|
541
|
+
if (items[i].binary) {
|
|
542
|
+
result.binary = items[i].binary;
|
|
543
|
+
}
|
|
544
|
+
returnData.push(result);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
else if (resource === 'dictionary') {
|
|
548
|
+
// Dictionary operations - map operation names to API endpoints
|
|
549
|
+
const dictionaryEndpoints = {
|
|
550
|
+
paymentMethods: { endpoint: 'Dictionary/PaymentMethods', dataKey: 'PaymentMethods' },
|
|
551
|
+
warehouses: { endpoint: 'Dictionary/Warehouses', dataKey: 'Warehouses' },
|
|
552
|
+
currencies: { endpoint: 'Dictionary/Currencies', dataKey: 'Currencies' },
|
|
553
|
+
documentDefinitions: { endpoint: 'Dictionary/DocumentDefinitions', dataKey: 'DocumentDefinitions' },
|
|
554
|
+
accountingCategories: { endpoint: 'Dictionary/AccountingCategories', dataKey: 'AccountingCategories' },
|
|
555
|
+
};
|
|
556
|
+
const dictConfig = dictionaryEndpoints[operation];
|
|
557
|
+
if (dictConfig) {
|
|
558
|
+
const response = await this.helpers.request({
|
|
559
|
+
method: 'GET',
|
|
560
|
+
url: `${gatewayUrl}/api/${dictConfig.endpoint}`,
|
|
561
|
+
headers: {
|
|
562
|
+
Authorization: `Bearer ${token}`,
|
|
563
|
+
},
|
|
564
|
+
json: true,
|
|
565
|
+
});
|
|
566
|
+
// API returns { Success: true, [DataKey]: [...], TotalCount: ... }
|
|
567
|
+
const responseData = response;
|
|
568
|
+
// Extract dictionary array from response
|
|
569
|
+
if (responseData[dictConfig.dataKey] && Array.isArray(responseData[dictConfig.dataKey])) {
|
|
570
|
+
const dictItems = responseData[dictConfig.dataKey];
|
|
571
|
+
dictItems.forEach((item) => {
|
|
572
|
+
const result = {
|
|
573
|
+
json: item
|
|
574
|
+
};
|
|
575
|
+
if (items[i].binary) {
|
|
576
|
+
result.binary = items[i].binary;
|
|
577
|
+
}
|
|
578
|
+
returnData.push(result);
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
// Fallback - return entire response if structure is unexpected
|
|
583
|
+
const result = {
|
|
584
|
+
json: responseData
|
|
585
|
+
};
|
|
586
|
+
if (items[i].binary) {
|
|
587
|
+
result.binary = items[i].binary;
|
|
588
|
+
}
|
|
589
|
+
returnData.push(result);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
else if (resource === 'product') {
|
|
594
|
+
if (operation === 'get') {
|
|
595
|
+
const productId = this.getNodeParameter('productId', i);
|
|
596
|
+
const response = await this.helpers.request({
|
|
597
|
+
method: 'GET',
|
|
598
|
+
url: `${gatewayUrl}/api/product?id=${productId}`,
|
|
599
|
+
headers: {
|
|
600
|
+
Authorization: `Bearer ${token}`,
|
|
601
|
+
},
|
|
602
|
+
json: true,
|
|
603
|
+
});
|
|
604
|
+
const result = {
|
|
605
|
+
json: response
|
|
606
|
+
};
|
|
607
|
+
if (items[i].binary) {
|
|
608
|
+
result.binary = items[i].binary;
|
|
609
|
+
}
|
|
610
|
+
returnData.push(result);
|
|
611
|
+
}
|
|
612
|
+
else if (operation === 'getAll') {
|
|
613
|
+
const response = await this.helpers.request({
|
|
614
|
+
method: 'GET',
|
|
615
|
+
url: `${gatewayUrl}/api/product`,
|
|
616
|
+
headers: {
|
|
617
|
+
Authorization: `Bearer ${token}`,
|
|
618
|
+
},
|
|
619
|
+
json: true,
|
|
620
|
+
});
|
|
621
|
+
const products = response;
|
|
622
|
+
products.forEach((item) => {
|
|
623
|
+
const result = {
|
|
624
|
+
json: item
|
|
625
|
+
};
|
|
626
|
+
if (items[i].binary) {
|
|
627
|
+
result.binary = items[i].binary;
|
|
628
|
+
}
|
|
629
|
+
returnData.push(result);
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
else if (resource === 'print') {
|
|
634
|
+
if (operation === 'printDocument') {
|
|
635
|
+
const filtrSQL = this.getNodeParameter('filtrSQL', i);
|
|
636
|
+
const formatId = this.getNodeParameter('formatId', i);
|
|
637
|
+
const requestBody = {
|
|
638
|
+
FormatId: formatId,
|
|
639
|
+
FiltrSQL: filtrSQL,
|
|
640
|
+
};
|
|
641
|
+
const response = await this.helpers.request({
|
|
642
|
+
method: 'POST',
|
|
643
|
+
url: `${gatewayUrl}/api/Documents/Print`,
|
|
644
|
+
headers: {
|
|
645
|
+
Authorization: `Bearer ${token}`,
|
|
646
|
+
},
|
|
647
|
+
body: requestBody,
|
|
648
|
+
json: false,
|
|
649
|
+
encoding: null, // Get binary data (PDF file)
|
|
650
|
+
});
|
|
651
|
+
// API returns PDF file directly as binary
|
|
652
|
+
const buffer = Buffer.isBuffer(response)
|
|
653
|
+
? response
|
|
654
|
+
: Buffer.from(response);
|
|
655
|
+
const fileName = `document_${Date.now()}.pdf`;
|
|
656
|
+
returnData.push({
|
|
657
|
+
json: {
|
|
658
|
+
success: true,
|
|
659
|
+
fileName: fileName,
|
|
660
|
+
},
|
|
661
|
+
binary: {
|
|
662
|
+
data: {
|
|
663
|
+
data: buffer.toString('base64'),
|
|
664
|
+
mimeType: 'application/pdf',
|
|
665
|
+
fileName: fileName,
|
|
666
|
+
},
|
|
667
|
+
},
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
catch (error) {
|
|
673
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
674
|
+
if (this.continueOnFail()) {
|
|
675
|
+
const result = {
|
|
676
|
+
json: { error: errorMessage }
|
|
677
|
+
};
|
|
678
|
+
if (items[i].binary) {
|
|
679
|
+
result.binary = items[i].binary;
|
|
680
|
+
}
|
|
681
|
+
returnData.push(result);
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), errorMessage);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
return [returnData];
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
exports.OptimaRestApi = OptimaRestApi;
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pokash/n8n-nodes-optima-rest-api",
|
|
3
|
+
"version": "1.1.8",
|
|
4
|
+
"description": "n8n node for Comarch Optima REST API integration",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"n8n-community-node-package",
|
|
7
|
+
"n8n",
|
|
8
|
+
"optima",
|
|
9
|
+
"comarch",
|
|
10
|
+
"erp"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"homepage": "https://github.com/pokash-pl/n8n-nodes-optima-rest-api",
|
|
14
|
+
"author": {
|
|
15
|
+
"name": "POKASH.PL Sp. z o. o.",
|
|
16
|
+
"email": "hello@pokash.cloud"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/pokash-pl/n8n-nodes-optima-rest-api.git"
|
|
21
|
+
},
|
|
22
|
+
"main": "index.js",
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc && gulp build:icons",
|
|
25
|
+
"dev": "tsc --watch",
|
|
26
|
+
"format": "prettier nodes --write",
|
|
27
|
+
"lint": "eslint nodes/**/*.ts package.json",
|
|
28
|
+
"lintfix": "eslint nodes/**/*.ts package.json --fix",
|
|
29
|
+
"prepublishOnly": "npm run build"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist"
|
|
33
|
+
],
|
|
34
|
+
"n8n": {
|
|
35
|
+
"n8nNodesApiVersion": 1,
|
|
36
|
+
"credentials": [
|
|
37
|
+
"dist/credentials/OptimaRestApiCredentials.credentials.js"
|
|
38
|
+
],
|
|
39
|
+
"nodes": [
|
|
40
|
+
"dist/nodes/OptimaRestApi/OptimaRestApi.node.js"
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/node": "^20.0.0",
|
|
45
|
+
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
|
46
|
+
"@typescript-eslint/parser": "^6.21.0",
|
|
47
|
+
"eslint": "^8.57.0",
|
|
48
|
+
"eslint-plugin-n8n-nodes-base": "^1.16.0",
|
|
49
|
+
"gulp": "^5.0.0",
|
|
50
|
+
"n8n-workflow": "^2.2.2",
|
|
51
|
+
"prettier": "^3.3.0",
|
|
52
|
+
"typescript": "~5.6.0"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"n8n-workflow": "^2.0.0"
|
|
56
|
+
},
|
|
57
|
+
"overrides": {
|
|
58
|
+
"form-data": "^4.0.4"
|
|
59
|
+
}
|
|
60
|
+
}
|