n8n-nodes-confirm8 0.20.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 with relations and filters support',
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
  },
@@ -56,60 +56,126 @@ class Confirm8AgentTool {
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: 'Resource type',
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: 'Operation to perform',
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',
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
102
  },
103
103
  {
104
104
  displayName: 'Filters',
105
105
  name: 'filters',
106
106
  type: 'string',
107
107
  default: '',
108
- description: 'Filters as JSON object. Example: {"start_date":{"gte":"2025-11-23"},"status":{"eq":"complete"}}',
108
+ description: 'Filters as JSON. For dates: {"start_date":{"gte":"YYYY-MM-DD"},"end_date":{"lte":"YYYY-MM-DD"}}. For status: {"status":{"eq":"complete"}}',
109
109
  },
110
110
  ],
111
111
  };
112
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
+ }
113
179
  async execute() {
114
180
  const items = this.getInputData();
115
181
  const returnData = [];
@@ -138,39 +204,110 @@ class Confirm8AgentTool {
138
204
  const filtersParam = this.getNodeParameter('filters', i, '');
139
205
  if (filtersParam) {
140
206
  try {
207
+ // Try to parse as JSON first
141
208
  filters = JSON.parse(filtersParam);
142
209
  }
143
210
  catch (e) {
144
- // Ignore
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
+ }
145
216
  }
146
217
  }
147
- // Normalize variations
218
+ // Aggressive normalization - EXPAND THIS
148
219
  const resourceMap = {
149
- 'users': 'user', 'usuarios': 'user', 'usuários': 'user', 'employees': 'user',
220
+ // Standard
221
+ 'users': 'user', 'usuarios': 'user', 'usuários': 'user', 'employees': 'user', 'funcionários': 'user', 'funcionarios': 'user',
150
222
  'clients': 'client', 'clientes': 'client', 'customers': 'client',
151
223
  'items': 'item', 'itens': 'item',
152
224
  'itemtypes': 'itemType', 'tipos': 'itemType',
153
225
  'tasks': 'task', 'tarefas': 'task',
154
226
  'services': 'service', 'serviços': 'service', 'servicos': 'service',
155
227
  'products': 'product', 'produtos': 'product',
156
- 'orders': 'order', 'ordens': 'order', 'wos': 'order',
157
228
  'modalities': 'modality', 'modalidades': 'modality',
158
229
  'tickets': 'ticket', 'chamados': 'ticket',
159
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',
160
247
  };
161
248
  const operationMap = {
162
- 'list': 'getAll', 'listar': 'getAll', 'mostrar': 'getAll',
163
- 'buscar todos': 'getAll', 'busque': 'getAll',
164
- 'criar': 'create', 'adicionar': 'create',
165
- 'atualizar': 'update', 'modificar': 'update',
166
- 'ativar': 'activate', 'desativar': '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',
167
293
  };
168
- resource = resourceMap[resource.toLowerCase().trim()] || resource;
169
- operation = operationMap[operation.toLowerCase().trim()] || operation;
170
- if (!resource || !operation) {
171
- 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".`);
305
+ }
306
+ if (!validOperations.includes(operation)) {
307
+ throw new Error(`Invalid operation: "${operation}". Must be one of: ${validOperations.join(', ')}. ` +
308
+ `To list/search use "getAll".`);
172
309
  }
173
- // Map to endpoints with relations
310
+ // Resource config with relations
174
311
  const resourceConfig = {
175
312
  user: {
176
313
  endpoint: 'users',
@@ -218,14 +355,11 @@ class Confirm8AgentTool {
218
355
  },
219
356
  };
220
357
  const config = resourceConfig[resource];
221
- if (!config) {
222
- throw new Error(`Unknown resource: ${resource}`);
223
- }
224
358
  const baseEndpoint = config.endpoint;
225
359
  let endpoint = '';
226
360
  let method = 'GET';
227
361
  let body = {};
228
- // Build endpoint based on operation
362
+ // Build endpoint
229
363
  switch (operation) {
230
364
  case 'getAll':
231
365
  endpoint = `/v3/${baseEndpoint}`;
@@ -261,12 +395,10 @@ class Confirm8AgentTool {
261
395
  method = ['client', 'item', 'itemType', 'task', 'service', 'product', 'order', 'modality'].includes(resource)
262
396
  ? 'PUT' : 'PATCH';
263
397
  break;
264
- default:
265
- throw new Error(`Unknown operation: ${operation}`);
266
398
  }
267
399
  // Build query string
268
400
  const queryParams = [];
269
- // Add relations (only for GET operations)
401
+ // Add relations (only GET)
270
402
  if (method === 'GET' && config.relations.length > 0) {
271
403
  config.relations.forEach(relation => {
272
404
  queryParams.push(`relations=${relation}`);
@@ -282,7 +414,7 @@ class Confirm8AgentTool {
282
414
  });
283
415
  });
284
416
  }
285
- // Construct full URL
417
+ // Full URL
286
418
  let fullUrl = `${baseUrl}${endpoint}`;
287
419
  if (queryParams.length > 0) {
288
420
  fullUrl += '?' + queryParams.join('&');
@@ -306,6 +438,12 @@ class Confirm8AgentTool {
306
438
  returnData.push({
307
439
  json: {
308
440
  success: true,
441
+ normalized: {
442
+ originalResource: this.getNodeParameter('resource', i, ''),
443
+ originalOperation: this.getNodeParameter('operation', i, ''),
444
+ normalizedResource: resource,
445
+ normalizedOperation: operation,
446
+ },
309
447
  operation,
310
448
  resource,
311
449
  endpoint,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-confirm8",
3
- "version": "0.20.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",