n8n-nodes-confirm8 0.19.0 → 0.21.0

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.
@@ -1,5 +1,9 @@
1
1
  import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
2
  export declare class Confirm8AgentTool implements INodeType {
3
3
  description: INodeTypeDescription;
4
+ /**
5
+ * Parse natural date queries into filter JSON
6
+ */
7
+ private parseDateQuery;
4
8
  execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
9
  }
@@ -10,7 +10,7 @@ class Confirm8AgentTool {
10
10
  icon: 'file:tool.svg',
11
11
  group: ['transform'],
12
12
  version: 1,
13
- description: 'AI Agent tool for Confirm8 API - use EXACT enum values: resource (user/client/item/task/service/product/order/ticket/property) and operation (getAll/get/create/update/activate/deactivate)',
13
+ description: 'AI tool for Confirm8. IMPORTANT: For "OS/ordem de serviço" use resource="order". For dates use filters with start_date/end_date. Example: {"start_date":{"gte":"2025-11-23"},"end_date":{"lte":"2025-11-25"}}',
14
14
  defaults: {
15
15
  name: 'Confirm8 AI Tool',
16
16
  },
@@ -50,59 +50,132 @@ class Confirm8AgentTool {
50
50
  default: '',
51
51
  required: true,
52
52
  },
53
- // Tool parameters - MUST use exact enum values
53
+ // Tool parameters
54
54
  {
55
55
  displayName: 'Resource',
56
56
  name: 'resource',
57
57
  type: 'options',
58
58
  options: [
59
- { name: 'User', value: 'user', description: 'Users/employees' },
60
- { name: 'Client', value: 'client', description: 'Clients/customers' },
61
- { name: 'Item', value: 'item', description: 'Items/inventory' },
62
- { name: 'Item Type', value: 'itemType', description: 'Item categories' },
63
- { name: 'Task', value: 'task', description: 'Tasks/checklists' },
64
- { name: 'Service', value: 'service', description: 'Services' },
65
- { name: 'Product', value: 'product', description: 'Products' },
66
- { name: 'Order', value: 'order', description: 'Work orders' },
67
- { name: 'Modality', value: 'modality', description: 'Order modalities' },
68
- { name: 'Ticket', value: 'ticket', description: 'Tickets' },
69
- { name: 'Property', value: 'property', description: 'Properties' },
59
+ { name: 'User', value: 'user', description: 'Users/Employees/Funcionários' },
60
+ { name: 'Client', value: 'client', description: 'Clients/Customers/Clientes' },
61
+ { name: 'Item', value: 'item', description: 'Items/Itens' },
62
+ { name: 'Item Type', value: 'itemType', description: 'Item Types/Tipos' },
63
+ { name: 'Task', value: 'task', description: 'Tasks/Tarefas/Checklists' },
64
+ { name: 'Service', value: 'service', description: 'Services/Serviços' },
65
+ { name: 'Product', value: 'product', description: 'Products/Produtos' },
66
+ { name: 'Order (OS)', value: 'order', description: 'Work Orders/OS/Ordens de Serviço/WOS' },
67
+ { name: 'Modality', value: 'modality', description: 'Modalities/Modalidades' },
68
+ { name: 'Ticket', value: 'ticket', description: 'Tickets/Chamados' },
69
+ { name: 'Property', value: 'property', description: 'Properties/Propriedades' },
70
70
  ],
71
71
  default: 'user',
72
- description: 'IMPORTANT: Use EXACT value from list',
72
+ description: 'CRITICAL: For "OS" or "ordem de serviço" ALWAYS use "order"',
73
73
  },
74
74
  {
75
75
  displayName: 'Operation',
76
76
  name: 'operation',
77
77
  type: 'options',
78
78
  options: [
79
- { name: 'Get All', value: 'getAll', description: 'List all records' },
80
- { name: 'Get', value: 'get', description: 'Get single record' },
81
- { name: 'Create', value: 'create', description: 'Create new record' },
82
- { name: 'Update', value: 'update', description: 'Update record' },
83
- { name: 'Activate', value: 'activate', description: 'Activate record' },
84
- { name: 'Deactivate', value: 'deactivate', description: 'Deactivate record' },
79
+ { name: 'Get All (List)', value: 'getAll', description: 'List/Listar/Buscar todos/Mostrar todos' },
80
+ { name: 'Get (Single)', value: 'get', description: 'Get one/Buscar um/Obter' },
81
+ { name: 'Create', value: 'create', description: 'Create/Criar/Adicionar' },
82
+ { name: 'Update', value: 'update', description: 'Update/Atualizar/Modificar' },
83
+ { name: 'Activate', value: 'activate', description: 'Activate/Ativar' },
84
+ { name: 'Deactivate', value: 'deactivate', description: 'Deactivate/Desativar' },
85
85
  ],
86
86
  default: 'getAll',
87
- description: 'IMPORTANT: Use EXACT value from list',
87
+ description: 'CRITICAL: To list/search use "getAll"',
88
88
  },
89
89
  {
90
90
  displayName: 'Record ID',
91
91
  name: 'recordId',
92
92
  type: 'string',
93
93
  default: '',
94
- description: 'ID of record (for get/update/activate/deactivate)',
94
+ description: 'Record ID (only for get/update/activate/deactivate)',
95
95
  },
96
96
  {
97
97
  displayName: 'Data',
98
98
  name: 'data',
99
99
  type: 'string',
100
100
  default: '',
101
- description: 'JSON data for create/update',
101
+ description: 'JSON data (only for create/update)',
102
+ },
103
+ {
104
+ displayName: 'Filters',
105
+ name: 'filters',
106
+ type: 'string',
107
+ default: '',
108
+ description: 'Filters as JSON. For dates: {"start_date":{"gte":"YYYY-MM-DD"},"end_date":{"lte":"YYYY-MM-DD"}}. For status: {"status":{"eq":"complete"}}',
102
109
  },
103
110
  ],
104
111
  };
105
112
  }
113
+ /**
114
+ * Parse natural date queries into filter JSON
115
+ */
116
+ parseDateQuery(query) {
117
+ const now = new Date();
118
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
119
+ query = query.toLowerCase().trim();
120
+ // Helper to format date as YYYY-MM-DD
121
+ const formatDate = (date) => {
122
+ return date.toISOString().split('T')[0];
123
+ };
124
+ // Today
125
+ if (query.includes('hoje') || query.includes('today')) {
126
+ const endOfDay = new Date(today);
127
+ endOfDay.setHours(23, 59, 59, 999);
128
+ return {
129
+ start_date: { gte: formatDate(today) },
130
+ end_date: { lte: formatDate(endOfDay) }
131
+ };
132
+ }
133
+ // This week / semana atual
134
+ if (query.includes('semana') || query.includes('week')) {
135
+ const dayOfWeek = today.getDay();
136
+ const diff = dayOfWeek === 0 ? -6 : 1 - dayOfWeek; // Monday is start
137
+ const weekStart = new Date(today);
138
+ weekStart.setDate(today.getDate() + diff);
139
+ const weekEnd = new Date(weekStart);
140
+ weekEnd.setDate(weekStart.getDate() + 6);
141
+ return {
142
+ start_date: { gte: formatDate(weekStart) },
143
+ end_date: { lte: formatDate(weekEnd) }
144
+ };
145
+ }
146
+ // This month / mês atual
147
+ if (query.includes('mês') || query.includes('mes') || query.includes('month')) {
148
+ const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
149
+ const monthEnd = new Date(now.getFullYear(), now.getMonth() + 1, 0);
150
+ return {
151
+ start_date: { gte: formatDate(monthStart) },
152
+ end_date: { lte: formatDate(monthEnd) }
153
+ };
154
+ }
155
+ // Last N days
156
+ const lastDaysMatch = query.match(/últimos?\s+(\d+)\s+dias?|last\s+(\d+)\s+days?/);
157
+ if (lastDaysMatch) {
158
+ const days = parseInt(lastDaysMatch[1] || lastDaysMatch[2]);
159
+ const startDate = new Date(today);
160
+ startDate.setDate(today.getDate() - days);
161
+ return {
162
+ start_date: { gte: formatDate(startDate) },
163
+ end_date: { lte: formatDate(today) }
164
+ };
165
+ }
166
+ // Next N days
167
+ const nextDaysMatch = query.match(/próximos?\s+(\d+)\s+dias?|next\s+(\d+)\s+days?/);
168
+ if (nextDaysMatch) {
169
+ const days = parseInt(nextDaysMatch[1] || nextDaysMatch[2]);
170
+ const endDate = new Date(today);
171
+ endDate.setDate(today.getDate() + days);
172
+ return {
173
+ start_date: { gte: formatDate(today) },
174
+ end_date: { lte: formatDate(endDate) }
175
+ };
176
+ }
177
+ return null;
178
+ }
106
179
  async execute() {
107
180
  const items = this.getInputData();
108
181
  const returnData = [];
@@ -123,104 +196,233 @@ class Confirm8AgentTool {
123
196
  data = JSON.parse(dataParam);
124
197
  }
125
198
  catch (e) {
126
- // Ignore parse errors
199
+ // Ignore
127
200
  }
128
201
  }
129
- // Normalize common variations
202
+ // Parse filters
203
+ let filters = {};
204
+ const filtersParam = this.getNodeParameter('filters', i, '');
205
+ if (filtersParam) {
206
+ try {
207
+ // Try to parse as JSON first
208
+ filters = JSON.parse(filtersParam);
209
+ }
210
+ catch (e) {
211
+ // If not JSON, try to parse as natural language date query
212
+ const parsedDate = this.parseDateQuery(filtersParam);
213
+ if (parsedDate) {
214
+ filters = parsedDate;
215
+ }
216
+ }
217
+ }
218
+ // Aggressive normalization - EXPAND THIS
130
219
  const resourceMap = {
131
- 'users': 'user', 'usuarios': 'user', 'usuários': 'user', 'employees': 'user', 'funcionários': 'user',
220
+ // Standard
221
+ 'users': 'user', 'usuarios': 'user', 'usuários': 'user', 'employees': 'user', 'funcionários': 'user', 'funcionarios': 'user',
132
222
  'clients': 'client', 'clientes': 'client', 'customers': 'client',
133
223
  'items': 'item', 'itens': 'item',
134
224
  'itemtypes': 'itemType', 'tipos': 'itemType',
135
225
  'tasks': 'task', 'tarefas': 'task',
136
226
  'services': 'service', 'serviços': 'service', 'servicos': 'service',
137
227
  'products': 'product', 'produtos': 'product',
138
- 'orders': 'order', 'ordens': 'order', 'pedidos': 'order', 'wos': 'order',
139
228
  'modalities': 'modality', 'modalidades': 'modality',
140
229
  'tickets': 'ticket', 'chamados': 'ticket',
141
230
  'properties': 'property', 'propriedades': 'property',
231
+ // CRITICAL: OS mappings
232
+ 'os': 'order',
233
+ 'o.s': 'order',
234
+ 'o.s.': 'order',
235
+ 'orders': 'order',
236
+ 'ordens': 'order',
237
+ 'ordem': 'order',
238
+ 'ordem de serviço': 'order',
239
+ 'ordem de servico': 'order',
240
+ 'ordens de serviço': 'order',
241
+ 'ordens de servico': 'order',
242
+ 'wos': 'order',
243
+ 'work order': 'order',
244
+ 'work orders': 'order',
245
+ 'pedidos': 'order',
246
+ 'pedido': 'order',
142
247
  };
143
248
  const operationMap = {
144
- 'list': 'getAll', 'listar': 'getAll', 'mostrar': 'getAll', 'show': 'getAll',
145
- 'buscar': 'getAll', 'busque': 'getAll', 'buscar todos': 'getAll', 'get all': 'getAll',
146
- 'show all': 'getAll', 'list all': 'getAll', 'todos': 'getAll',
147
- 'obter': 'get', 'pegar': 'get',
148
- 'criar': 'create', 'adicionar': 'create', 'add': 'create', 'novo': 'create', 'new': 'create',
149
- 'atualizar': 'update', 'modificar': 'update', 'modify': 'update', 'editar': 'update',
150
- 'ativar': 'activate', 'enable': 'activate', 'habilitar': 'activate',
151
- 'desativar': 'deactivate', 'disable': 'deactivate', 'desabilitar': 'deactivate',
249
+ // List/Get All
250
+ 'list': 'getAll',
251
+ 'listar': 'getAll',
252
+ 'mostrar': 'getAll',
253
+ 'buscar': 'getAll',
254
+ 'busque': 'getAll',
255
+ 'buscar todos': 'getAll',
256
+ 'buscar todas': 'getAll',
257
+ 'buscar todo': 'getAll',
258
+ 'buscar toda': 'getAll',
259
+ 'get all': 'getAll',
260
+ 'show all': 'getAll',
261
+ 'list all': 'getAll',
262
+ 'todos': 'getAll',
263
+ 'todas': 'getAll',
264
+ 'obter todos': 'getAll',
265
+ 'obter todas': 'getAll',
266
+ 'pegar todos': 'getAll',
267
+ 'pegar todas': 'getAll',
268
+ // Get Single
269
+ 'obter': 'get',
270
+ 'pegar': 'get',
271
+ 'ver': 'get',
272
+ 'visualizar': 'get',
273
+ // Create
274
+ 'criar': 'create',
275
+ 'adicionar': 'create',
276
+ 'add': 'create',
277
+ 'novo': 'create',
278
+ 'nova': 'create',
279
+ 'new': 'create',
280
+ // Update
281
+ 'atualizar': 'update',
282
+ 'modificar': 'update',
283
+ 'modify': 'update',
284
+ 'editar': 'update',
285
+ 'edit': 'update',
286
+ // Activate/Deactivate
287
+ 'ativar': 'activate',
288
+ 'enable': 'activate',
289
+ 'habilitar': 'activate',
290
+ 'desativar': 'deactivate',
291
+ 'disable': 'deactivate',
292
+ 'desabilitar': 'deactivate',
152
293
  };
153
- // Normalize
154
- resource = resourceMap[resource.toLowerCase().trim()] || resource;
155
- operation = operationMap[operation.toLowerCase().trim()] || operation;
156
- if (!resource || !operation) {
157
- throw new Error('Resource and operation are required');
294
+ // Normalize - clean and map
295
+ const cleanResource = resource.toLowerCase().trim().replace(/\s+/g, ' ');
296
+ const cleanOperation = operation.toLowerCase().trim().replace(/\s+/g, ' ');
297
+ resource = resourceMap[cleanResource] || resource;
298
+ operation = operationMap[cleanOperation] || operation;
299
+ // If still not valid, throw clear error
300
+ const validResources = ['user', 'client', 'item', 'itemType', 'task', 'service', 'product', 'order', 'modality', 'ticket', 'property'];
301
+ const validOperations = ['getAll', 'get', 'create', 'update', 'activate', 'deactivate'];
302
+ if (!validResources.includes(resource)) {
303
+ throw new Error(`Invalid resource: "${resource}". Must be one of: ${validResources.join(', ')}. ` +
304
+ `For OS/Ordem de Serviço use "order".`);
158
305
  }
159
- // Map to endpoints
160
- const endpointMap = {
161
- user: 'users',
162
- client: 'clients',
163
- item: 'items',
164
- itemType: 'itemTypes',
165
- task: 'tasks',
166
- service: 'services',
167
- product: 'products',
168
- order: 'wos',
169
- modality: 'modalities',
170
- ticket: 'tickets',
171
- property: 'properties',
172
- };
173
- const baseEndpoint = endpointMap[resource];
174
- if (!baseEndpoint) {
175
- throw new Error(`Unknown resource: ${resource}. Must be one of: user, client, item, itemType, task, service, product, order, modality, ticket, property`);
306
+ if (!validOperations.includes(operation)) {
307
+ throw new Error(`Invalid operation: "${operation}". Must be one of: ${validOperations.join(', ')}. ` +
308
+ `To list/search use "getAll".`);
176
309
  }
310
+ // Resource config with relations
311
+ const resourceConfig = {
312
+ user: {
313
+ endpoint: 'users',
314
+ relations: ['clients', 'attachments', 'permissions', 'device', 'employee']
315
+ },
316
+ client: {
317
+ endpoint: 'clients',
318
+ relations: ['wos', 'items', 'employees', 'headquarter', 'files', 'userGroup', 'properties']
319
+ },
320
+ item: {
321
+ endpoint: 'items',
322
+ relations: ['client', 'item_type', 'properties', 'parent', 'children', 'collects', 'wos']
323
+ },
324
+ itemType: {
325
+ endpoint: 'itemTypes',
326
+ relations: ['properties']
327
+ },
328
+ task: {
329
+ endpoint: 'tasks',
330
+ relations: ['itemType', 'activities', 'wos', 'modalities']
331
+ },
332
+ service: {
333
+ endpoint: 'services',
334
+ relations: ['task']
335
+ },
336
+ product: {
337
+ endpoint: 'products',
338
+ relations: []
339
+ },
340
+ order: {
341
+ endpoint: 'wos',
342
+ relations: ['client', 'modalities', 'tasks', 'pivot_tasks', 'products', 'users', 'items', 'tickets', 'services', 'collects', 'attachments']
343
+ },
344
+ modality: {
345
+ endpoint: 'modalities',
346
+ relations: ['tasks']
347
+ },
348
+ ticket: {
349
+ endpoint: 'tickets',
350
+ relations: ['client', 'subject_category', 'category', 'status', 'attachments', 'item', 'owner', 'priority', 'users', 'orders', 'properties']
351
+ },
352
+ property: {
353
+ endpoint: 'properties',
354
+ relations: []
355
+ },
356
+ };
357
+ const config = resourceConfig[resource];
358
+ const baseEndpoint = config.endpoint;
177
359
  let endpoint = '';
178
360
  let method = 'GET';
179
361
  let body = {};
180
- // Build request
362
+ // Build endpoint
181
363
  switch (operation) {
182
364
  case 'getAll':
183
- endpoint = `/${baseEndpoint}`;
365
+ endpoint = `/v3/${baseEndpoint}`;
184
366
  break;
185
367
  case 'get':
186
368
  if (!recordId)
187
- throw new Error('recordId required for get operation');
188
- endpoint = `/${baseEndpoint}/${recordId}`;
369
+ throw new Error('recordId required');
370
+ endpoint = `/v3/${baseEndpoint}/${recordId}`;
189
371
  break;
190
372
  case 'create':
191
- endpoint = `/${baseEndpoint}`;
373
+ endpoint = `/v3/${baseEndpoint}`;
192
374
  method = 'POST';
193
375
  body = data;
194
376
  break;
195
377
  case 'update':
196
378
  if (!recordId)
197
- throw new Error('recordId required for update operation');
198
- endpoint = `/${baseEndpoint}/${recordId}`;
379
+ throw new Error('recordId required');
380
+ endpoint = `/v3/${baseEndpoint}/${recordId}`;
199
381
  method = 'PUT';
200
382
  body = data;
201
383
  break;
202
384
  case 'activate':
203
385
  if (!recordId)
204
- throw new Error('recordId required for activate operation');
205
- endpoint = `/${baseEndpoint}/${recordId}/active`;
386
+ throw new Error('recordId required');
387
+ endpoint = `/v3/${baseEndpoint}/${recordId}/active`;
206
388
  method = ['client', 'item', 'itemType', 'task', 'service', 'product', 'order', 'modality'].includes(resource)
207
389
  ? 'PUT' : 'PATCH';
208
390
  break;
209
391
  case 'deactivate':
210
392
  if (!recordId)
211
- throw new Error('recordId required for deactivate operation');
212
- endpoint = `/${baseEndpoint}/${recordId}/inactive`;
393
+ throw new Error('recordId required');
394
+ endpoint = `/v3/${baseEndpoint}/${recordId}/inactive`;
213
395
  method = ['client', 'item', 'itemType', 'task', 'service', 'product', 'order', 'modality'].includes(resource)
214
396
  ? 'PUT' : 'PATCH';
215
397
  break;
216
- default:
217
- throw new Error(`Unknown operation: ${operation}. Must be one of: getAll, get, create, update, activate, deactivate`);
398
+ }
399
+ // Build query string
400
+ const queryParams = [];
401
+ // Add relations (only GET)
402
+ if (method === 'GET' && config.relations.length > 0) {
403
+ config.relations.forEach(relation => {
404
+ queryParams.push(`relations=${relation}`);
405
+ });
406
+ }
407
+ // Add filters
408
+ if (Object.keys(filters).length > 0) {
409
+ Object.keys(filters).forEach(field => {
410
+ const operators = filters[field];
411
+ Object.keys(operators).forEach(operator => {
412
+ const value = operators[operator];
413
+ queryParams.push(`filters[${field}][${operator}]=${encodeURIComponent(String(value))}`);
414
+ });
415
+ });
416
+ }
417
+ // Full URL
418
+ let fullUrl = `${baseUrl}${endpoint}`;
419
+ if (queryParams.length > 0) {
420
+ fullUrl += '?' + queryParams.join('&');
218
421
  }
219
422
  // Make request
220
- const url = `${baseUrl}${endpoint}`;
221
423
  const options = {
222
424
  method,
223
- uri: url,
425
+ uri: fullUrl,
224
426
  headers: {
225
427
  'Authorization': `Bearer ${bearerToken}`,
226
428
  'X-API-DOMAIN': apiDomain,
@@ -236,15 +438,18 @@ class Confirm8AgentTool {
236
438
  returnData.push({
237
439
  json: {
238
440
  success: true,
239
- operation,
240
- resource,
241
441
  normalized: {
242
442
  originalResource: this.getNodeParameter('resource', i, ''),
243
443
  originalOperation: this.getNodeParameter('operation', i, ''),
244
444
  normalizedResource: resource,
245
445
  normalizedOperation: operation,
246
446
  },
447
+ operation,
448
+ resource,
247
449
  endpoint,
450
+ fullUrl,
451
+ relations: config.relations,
452
+ filters: filters,
248
453
  method,
249
454
  data: responseData,
250
455
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-confirm8",
3
- "version": "0.19.0",
3
+ "version": "0.21.0",
4
4
  "description": "Simple n8n node for Confirm8 API - no credentials needed",
5
5
  "license": "MIT",
6
6
  "author": "Bill Hebert",