@belmontdigitalmarketing/n8n-nodes-flowlu 0.3.1 → 0.4.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.
@@ -105,6 +105,15 @@ function applyResourceMapperFields(context, body, paramName, itemIndex) {
105
105
  // No custom fields set - ignore
106
106
  }
107
107
  }
108
+ // Read a resourceLocator parameter (or a legacy scalar value) as its underlying id.
109
+ function getResourceValue(context, name, itemIndex) {
110
+ const raw = context.getNodeParameter(name, itemIndex, '');
111
+ if (raw && typeof raw === 'object' && 'value' in raw) {
112
+ const value = raw.value;
113
+ return value === undefined || value === null ? '' : String(value);
114
+ }
115
+ return raw ? String(raw) : '';
116
+ }
108
117
  // Helper to load an entity's custom fields as name/value options.
109
118
  async function loadCustomFieldsForEntity(context, moduleFilter, modelFilter) {
110
119
  const { baseUrl, apiKey } = await getFlowluCredentials(context);
@@ -1300,13 +1309,21 @@ class Flowlu {
1300
1309
  displayOptions: { show: { resource: ['task'], operation: ['create'] } },
1301
1310
  },
1302
1311
  {
1303
- displayName: 'Assignee Name or ID',
1312
+ displayName: 'Assignee',
1304
1313
  name: 'responsible_id',
1305
- type: 'options',
1306
- typeOptions: { loadOptionsMethod: 'getUsers' },
1314
+ type: 'resourceLocator',
1307
1315
  required: true,
1308
- default: '',
1309
- description: 'The user responsible for completing this task. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
1316
+ default: { mode: 'list', value: '' },
1317
+ description: 'The user responsible for completing this task',
1318
+ modes: [
1319
+ {
1320
+ displayName: 'From List',
1321
+ name: 'list',
1322
+ type: 'list',
1323
+ typeOptions: { searchListMethod: 'searchUsers', searchable: true },
1324
+ },
1325
+ { displayName: 'By ID', name: 'id', type: 'string', placeholder: 'e.g. 176265' },
1326
+ ],
1310
1327
  displayOptions: { show: { resource: ['task'], operation: ['create'] } },
1311
1328
  },
1312
1329
  {
@@ -1318,12 +1335,20 @@ class Flowlu {
1318
1335
  displayOptions: { show: { resource: ['task'], operation: ['create'] } },
1319
1336
  },
1320
1337
  {
1321
- displayName: 'Owner Name or ID',
1338
+ displayName: 'Owner',
1322
1339
  name: 'owner_id',
1323
- type: 'options',
1324
- typeOptions: { loadOptionsMethod: 'getUsers' },
1325
- default: '',
1326
- description: 'The user who owns this task. If left blank, the credential\'s Default Task Owner is used. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
1340
+ type: 'resourceLocator',
1341
+ default: { mode: 'list', value: '' },
1342
+ description: 'The user who owns this task. If left blank, the credential\'s Default Task Owner is used.',
1343
+ modes: [
1344
+ {
1345
+ displayName: 'From List',
1346
+ name: 'list',
1347
+ type: 'list',
1348
+ typeOptions: { searchListMethod: 'searchUsers', searchable: true },
1349
+ },
1350
+ { displayName: 'By ID', name: 'id', type: 'string', placeholder: 'e.g. 176265' },
1351
+ ],
1327
1352
  displayOptions: { show: { resource: ['task'], operation: ['create'] } },
1328
1353
  },
1329
1354
  {
@@ -1339,21 +1364,37 @@ class Flowlu {
1339
1364
  displayOptions: { show: { resource: ['task'], operation: ['create'] } },
1340
1365
  },
1341
1366
  {
1342
- displayName: 'Contact Name or ID',
1367
+ displayName: 'Contact',
1343
1368
  name: 'contact_id',
1344
- type: 'options',
1345
- typeOptions: { loadOptionsMethod: 'getContacts' },
1346
- default: '',
1347
- description: 'The contact this task is related to. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
1369
+ type: 'resourceLocator',
1370
+ default: { mode: 'list', value: '' },
1371
+ description: 'The contact this task is related to. Search by name, or enter a contact ID.',
1372
+ modes: [
1373
+ {
1374
+ displayName: 'From List',
1375
+ name: 'list',
1376
+ type: 'list',
1377
+ typeOptions: { searchListMethod: 'searchContacts', searchable: true },
1378
+ },
1379
+ { displayName: 'By ID', name: 'id', type: 'string', placeholder: 'e.g. 12345' },
1380
+ ],
1348
1381
  displayOptions: { show: { resource: ['task'], operation: ['create'] } },
1349
1382
  },
1350
1383
  {
1351
- displayName: 'Project Name or ID',
1384
+ displayName: 'Project',
1352
1385
  name: 'model_id',
1353
- type: 'options',
1354
- typeOptions: { loadOptionsMethod: 'getProjects' },
1355
- default: '',
1356
- description: 'The project this task belongs to. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
1386
+ type: 'resourceLocator',
1387
+ default: { mode: 'list', value: '' },
1388
+ description: 'The project this task belongs to',
1389
+ modes: [
1390
+ {
1391
+ displayName: 'From List',
1392
+ name: 'list',
1393
+ type: 'list',
1394
+ typeOptions: { searchListMethod: 'searchProjects', searchable: true },
1395
+ },
1396
+ { displayName: 'By ID', name: 'id', type: 'string', placeholder: 'e.g. 678' },
1397
+ ],
1357
1398
  displayOptions: { show: { resource: ['task'], operation: ['create'] } },
1358
1399
  },
1359
1400
  {
@@ -2107,6 +2148,45 @@ class Flowlu {
2107
2148
  }
2108
2149
  },
2109
2150
  },
2151
+ listSearch: {
2152
+ async searchUsers(filter) {
2153
+ const { baseUrl, apiKey } = await getFlowluCredentials(this);
2154
+ const items = await flowluListAll.call(this, baseUrl, '/api/v1/module/core/user/list', apiKey, {
2155
+ 'filter[role_login]': '1',
2156
+ });
2157
+ const term = (filter ?? '').toLowerCase();
2158
+ const results = items
2159
+ .map((u) => ({ name: u.name || u.email || `User ${u.id}`, value: u.id.toString() }))
2160
+ .filter((r) => !term || r.name.toLowerCase().includes(term));
2161
+ return { results };
2162
+ },
2163
+ async searchProjects(filter) {
2164
+ const { baseUrl, apiKey } = await getFlowluCredentials(this);
2165
+ const items = await flowluListAll.call(this, baseUrl, '/api/v1/module/st/projects/list', apiKey, {
2166
+ 'filter[is_archive]': '0',
2167
+ });
2168
+ const term = (filter ?? '').toLowerCase();
2169
+ const results = items
2170
+ .map((p) => ({ name: p.name || `Project ${p.id}`, value: p.id.toString() }))
2171
+ .filter((r) => !term || r.name.toLowerCase().includes(term));
2172
+ return { results };
2173
+ },
2174
+ async searchContacts(filter) {
2175
+ const { baseUrl, apiKey } = await getFlowluCredentials(this);
2176
+ const qs = { 'filter[type]': '2' };
2177
+ // Contacts can number in the thousands, so use Flowlu's server-side search and
2178
+ // take just the first page rather than paginating the whole list.
2179
+ if (filter)
2180
+ qs.search = filter;
2181
+ const response = await flowluApiGetWithRetry.call(this, baseUrl, '/api/v1/module/crm/account/list', apiKey, qs);
2182
+ const items = response?.response?.items ?? [];
2183
+ const results = items.map((c) => ({
2184
+ name: c.name || `Contact ${c.id}`,
2185
+ value: c.id.toString(),
2186
+ }));
2187
+ return { results };
2188
+ },
2189
+ },
2110
2190
  };
2111
2191
  }
2112
2192
  async execute() {
@@ -2530,7 +2610,7 @@ class Flowlu {
2530
2610
  if (operation === 'create') {
2531
2611
  const body = {
2532
2612
  name: this.getNodeParameter('taskName', i),
2533
- responsible_id: this.getNodeParameter('responsible_id', i),
2613
+ responsible_id: getResourceValue(this, 'responsible_id', i),
2534
2614
  };
2535
2615
  const description = this.getNodeParameter('description', i);
2536
2616
  if (description)
@@ -2540,10 +2620,10 @@ class Flowlu {
2540
2620
  body.workflow_stage_id = additional.workflow_stage_id || '1';
2541
2621
  // Owner/Priority/Contact/Project/Start/End moved to top-level fields; fall back to
2542
2622
  // Additional Fields so task-create nodes built before the move keep working.
2543
- const ownerId = this.getNodeParameter('owner_id', i, '') || additional.owner_id || defaultOwnerId;
2623
+ const ownerId = getResourceValue(this, 'owner_id', i) || additional.owner_id || defaultOwnerId;
2544
2624
  if (ownerId)
2545
2625
  body.owner_id = ownerId;
2546
- const contactId = this.getNodeParameter('contact_id', i, '') || additional.contact_id;
2626
+ const contactId = getResourceValue(this, 'contact_id', i) || additional.contact_id;
2547
2627
  if (contactId)
2548
2628
  body.crm_account_id = parseInt(contactId, 10);
2549
2629
  body.priority =
@@ -2576,7 +2656,7 @@ class Flowlu {
2576
2656
  if (additional.parent_id && additional.parent_id > 0) {
2577
2657
  body.parent_id = additional.parent_id;
2578
2658
  }
2579
- const modelId = this.getNodeParameter('model_id', i, '') || additional.model_id;
2659
+ const modelId = getResourceValue(this, 'model_id', i) || additional.model_id;
2580
2660
  if (modelId) {
2581
2661
  body.model_id = modelId;
2582
2662
  body.model = 'project';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@belmontdigitalmarketing/n8n-nodes-flowlu",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "n8n community node for the Flowlu CRM, project management, and task API",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",