@belmontdigitalmarketing/n8n-nodes-flowlu 0.2.1 → 0.3.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.
@@ -9,10 +9,9 @@ async function getFlowluCredentials(context) {
9
9
  const credentials = await context.getCredentials('flowluApi');
10
10
  const subdomain = credentials.subdomain;
11
11
  const apiKey = credentials.apiKey;
12
- const baseUrl = subdomain.endsWith('.flowlu.com')
13
- ? `https://${subdomain}`
14
- : `https://${subdomain}.flowlu.com`;
15
- return { baseUrl, apiKey };
12
+ const defaultOwnerId = (credentials.defaultOwnerId || '').trim();
13
+ const baseUrl = subdomain.endsWith('.flowlu.com') ? `https://${subdomain}` : `https://${subdomain}.flowlu.com`;
14
+ return { baseUrl, apiKey, defaultOwnerId };
16
15
  }
17
16
  async function flowluApiRequest(method, baseUrl, endpoint, apiKey, body, queryParams) {
18
17
  const qs = { api_key: apiKey };
@@ -31,6 +30,59 @@ async function flowluApiRequest(method, baseUrl, endpoint, apiKey, body, queryPa
31
30
  const response = await this.helpers.request(requestOptions);
32
31
  return typeof response === 'string' ? JSON.parse(response) : response;
33
32
  }
33
+ // Detect Flowlu's rate-limit signal in either a thrown error or a returned body.
34
+ function isFlowluRateLimit(value) {
35
+ const text = value instanceof Error ? value.message : typeof value === 'string' ? value : JSON.stringify(value ?? '');
36
+ return /limit exceeded|too many requests|\b429\b/i.test(text);
37
+ }
38
+ // GET wrapper that retries through Flowlu's rate limit with linear backoff. If it
39
+ // never succeeds it throws, so callers surface the real cause instead of silently
40
+ // returning no data (which previously masqueraded as "No fields found").
41
+ async function flowluApiGetWithRetry(baseUrl, endpoint, apiKey, queryParams, maxRetries = 3) {
42
+ let lastMessage = 'Flowlu request failed';
43
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
44
+ try {
45
+ const response = await flowluApiRequest.call(this, 'GET', baseUrl, endpoint, apiKey, undefined, queryParams);
46
+ if (!isFlowluRateLimit(response?.error ?? response)) {
47
+ return response;
48
+ }
49
+ lastMessage = String(response?.error ?? 'Flowlu request limit exceeded');
50
+ }
51
+ catch (error) {
52
+ if (!isFlowluRateLimit(error)) {
53
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Flowlu API error: ${error.message}`);
54
+ }
55
+ lastMessage = error.message || 'Flowlu request limit exceeded';
56
+ }
57
+ // linear backoff before the next attempt (no wait after the final one)
58
+ if (attempt < maxRetries) {
59
+ await new Promise((resolve) => {
60
+ setTimeout(resolve, 1500 * (attempt + 1));
61
+ });
62
+ }
63
+ }
64
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Flowlu API rate limit reached while loading data (${lastMessage}). Wait a moment and click Retry.`);
65
+ }
66
+ // Fetch every page of a Flowlu list endpoint. The API caps responses at 50 items
67
+ // per page, so a single call silently truncates larger result sets.
68
+ async function flowluListAll(baseUrl, endpoint, apiKey, queryParams) {
69
+ const items = [];
70
+ const maxPages = 100; // safety bound
71
+ for (let page = 1; page <= maxPages; page++) {
72
+ const response = await flowluApiGetWithRetry.call(this, baseUrl, endpoint, apiKey, {
73
+ ...(queryParams ?? {}),
74
+ page: String(page),
75
+ });
76
+ const pageItems = response?.response?.items;
77
+ if (!Array.isArray(pageItems) || pageItems.length === 0)
78
+ break;
79
+ items.push(...pageItems);
80
+ const total = Number(response.response.total ?? items.length);
81
+ if (items.length >= total)
82
+ break;
83
+ }
84
+ return items;
85
+ }
34
86
  function appendWorkflowFooter(target, contentKey = 'description') {
35
87
  const { id } = this.getWorkflow();
36
88
  const footer = `<hr><em>Generated via n8n:</em> <a href="${this.getInstanceBaseUrl()}workflow/${id}">View Workflow</a>`;
@@ -52,49 +104,27 @@ function applyResourceMapperFields(context, body, paramName, itemIndex) {
52
104
  // No custom fields set - ignore
53
105
  }
54
106
  }
55
- // Helper to load custom fields by module/model, with fallback approach
107
+ // Helper to load an entity's custom fields as name/value options.
56
108
  async function loadCustomFieldsForEntity(context, moduleFilter, modelFilter) {
109
+ const { baseUrl, apiKey } = await getFlowluCredentials(context);
110
+ let fieldsets;
111
+ let allFields;
57
112
  try {
58
- const { baseUrl, apiKey } = await getFlowluCredentials(context);
59
- // Strategy 1: Get all fieldsets and match by module/model
60
- const fieldsetsResponse = await flowluApiRequest.call(context, 'GET', baseUrl, '/api/v1/module/customfields/fieldsets/list', apiKey);
61
- const fieldsetIds = [];
62
- if (fieldsetsResponse?.response?.items) {
63
- for (const fs of fieldsetsResponse.response.items) {
64
- if (fs.module === moduleFilter && fs.model === modelFilter) {
65
- fieldsetIds.push(fs.id.toString());
66
- }
67
- }
68
- }
69
- // Get all custom fields
70
- const fieldsResponse = await flowluApiRequest.call(context, 'GET', baseUrl, '/api/v1/module/customfields/fields/list', apiKey);
71
- if (fieldsResponse?.response?.items) {
72
- let fields = fieldsResponse.response.items;
73
- // Filter by fieldset if we found matching fieldsets
74
- if (fieldsetIds.length > 0) {
75
- fields = fields.filter((f) => fieldsetIds.includes(f.fieldset_id?.toString()));
76
- }
77
- else {
78
- // Fallback: filter by module/model directly on the field
79
- fields = fields.filter((f) => f.module === moduleFilter && f.model === modelFilter);
80
- }
81
- return fields
82
- .filter((f) => f.active !== 0)
83
- .map((f) => {
84
- const cfKey = (f.api_use_alias && f.alias)
85
- ? `cf_${f.alias}`
86
- : `cf_${f.id}`;
87
- return {
88
- name: f.name || cfKey,
89
- value: cfKey,
90
- };
91
- });
92
- }
93
- return [];
113
+ fieldsets = await flowluListAll.call(context, baseUrl, '/api/v1/module/customfields/fieldsets/list', apiKey);
114
+ allFields = await flowluListAll.call(context, baseUrl, '/api/v1/module/customfields/fields/list', apiKey);
94
115
  }
95
116
  catch (error) {
96
- return [];
117
+ throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Could not load Flowlu custom fields: ${error.message}. If this is a rate limit, wait a moment and try again.`);
97
118
  }
119
+ // Fields don't reliably carry module/model, so match via their fieldset.
120
+ const matchingFieldsetIds = new Set(fieldsets.filter((fs) => fs.module === moduleFilter && fs.model === modelFilter).map((fs) => String(fs.id)));
121
+ return allFields
122
+ .filter((f) => f.active !== 0)
123
+ .filter((f) => matchingFieldsetIds.has(String(f.fieldset_id)) || (f.module === moduleFilter && f.model === modelFilter))
124
+ .map((f) => {
125
+ const cfKey = f.api_use_alias && f.alias ? `cf_${f.alias}` : `cf_${f.id}`;
126
+ return { name: f.name || cfKey, value: cfKey };
127
+ });
98
128
  }
99
129
  // ============================================================
100
130
  // Reusable custom field option definitions
@@ -103,27 +133,16 @@ async function loadCustomFieldsForEntity(context, moduleFilter, modelFilter) {
103
133
  async function loadEntityCustomFieldColumns(moduleFilter, modelFilter) {
104
134
  try {
105
135
  const { baseUrl, apiKey } = await getFlowluCredentials(this);
106
- const fieldsetsResponse = await flowluApiRequest.call(this, 'GET', baseUrl, '/api/v1/module/customfields/fieldsets/list', apiKey);
107
- const fieldsetIds = [];
108
- if (fieldsetsResponse?.response?.items) {
109
- for (const fs of fieldsetsResponse.response.items) {
110
- if (fs.module === moduleFilter && fs.model === modelFilter) {
111
- fieldsetIds.push(fs.id.toString());
112
- }
113
- }
114
- }
115
- const fieldsResponse = await flowluApiRequest.call(this, 'GET', baseUrl, '/api/v1/module/customfields/fields/list', apiKey);
116
- if (!fieldsResponse?.response?.items)
117
- return { fields: [] };
118
- let filtered = fieldsResponse.response.items.filter((f) => f.active !== 0);
119
- if (fieldsetIds.length > 0) {
120
- filtered = filtered.filter((f) => fieldsetIds.includes(f.fieldset_id?.toString()));
121
- }
122
- else {
123
- filtered = filtered.filter((f) => f.module === moduleFilter && f.model === modelFilter);
124
- }
136
+ const fieldsets = await flowluListAll.call(this, baseUrl, '/api/v1/module/customfields/fieldsets/list', apiKey);
137
+ const allFields = await flowluListAll.call(this, baseUrl, '/api/v1/module/customfields/fields/list', apiKey);
138
+ // Fields don't reliably carry module/model, so match via their fieldset.
139
+ const matchingFieldsetIds = new Set(fieldsets
140
+ .filter((fs) => fs.module === moduleFilter && fs.model === modelFilter)
141
+ .map((fs) => String(fs.id)));
142
+ const filtered = allFields.filter((f) => f.active !== 0 &&
143
+ (matchingFieldsetIds.has(String(f.fieldset_id)) || (f.module === moduleFilter && f.model === modelFilter)));
125
144
  const fields = filtered.map((f) => {
126
- const cfKey = (f.api_use_alias && f.alias) ? `cf_${f.alias}` : `cf_${f.id}`;
145
+ const cfKey = f.api_use_alias && f.alias ? `cf_${f.alias}` : `cf_${f.id}`;
127
146
  let fieldType = 'string';
128
147
  if (f.type === 'int')
129
148
  fieldType = 'number';
@@ -145,7 +164,9 @@ async function loadEntityCustomFieldColumns(moduleFilter, modelFilter) {
145
164
  .filter((o) => o.value)
146
165
  .map((o) => ({ name: o.value, value: o.id?.toString() || o.value }));
147
166
  }
148
- catch { /* ignore */ }
167
+ catch {
168
+ /* ignore */
169
+ }
149
170
  }
150
171
  const field = {
151
172
  id: cfKey,
@@ -163,7 +184,9 @@ async function loadEntityCustomFieldColumns(moduleFilter, modelFilter) {
163
184
  return { fields };
164
185
  }
165
186
  catch (error) {
166
- return { fields: [] };
187
+ if (error instanceof n8n_workflow_1.NodeOperationError)
188
+ throw error;
189
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Could not load Flowlu custom fields: ${error.message}. If this is a rate limit, wait a moment and click Retry.`);
167
190
  }
168
191
  }
169
192
  function makeCustomFieldsOption(loadMethod) {
@@ -251,7 +274,12 @@ class Flowlu {
251
274
  displayOptions: { show: { resource: ['tag'] } },
252
275
  options: [
253
276
  { name: 'Add', value: 'add', description: 'Add a tag to an entity', action: 'Add a tag' },
254
- { name: 'Get Many', value: 'getAll', description: 'Get many tags on an entity', action: 'Get tags on entity' },
277
+ {
278
+ name: 'Get Many',
279
+ value: 'getAll',
280
+ description: 'Get many tags on an entity',
281
+ action: 'Get tags on entity',
282
+ },
255
283
  { name: 'List All', value: 'listAll', description: 'List all tags in your account', action: 'List all tags' },
256
284
  { name: 'Remove', value: 'remove', description: 'Remove a tag from an entity', action: 'Remove a tag' },
257
285
  ],
@@ -268,7 +296,12 @@ class Flowlu {
268
296
  displayOptions: { show: { resource: ['comment'] } },
269
297
  options: [
270
298
  { name: 'Create', value: 'create', description: 'Create a comment on an entity', action: 'Create a comment' },
271
- { name: 'Get Many', value: 'getAll', description: 'Get many comments on an entity', action: 'Get comments on entity' },
299
+ {
300
+ name: 'Get Many',
301
+ value: 'getAll',
302
+ description: 'Get many comments on an entity',
303
+ action: 'Get comments on entity',
304
+ },
272
305
  ],
273
306
  default: 'create',
274
307
  },
@@ -303,7 +336,12 @@ class Flowlu {
303
336
  { name: 'Create', value: 'create', description: 'Create a new opportunity', action: 'Create an opportunity' },
304
337
  { name: 'Delete', value: 'delete', description: 'Delete an opportunity', action: 'Delete an opportunity' },
305
338
  { name: 'Get', value: 'get', description: 'Get an opportunity by ID', action: 'Get an opportunity' },
306
- { name: 'Get Many', value: 'getAll', description: 'Get many opportunities', action: 'Get many opportunities' },
339
+ {
340
+ name: 'Get Many',
341
+ value: 'getAll',
342
+ description: 'Get many opportunities',
343
+ action: 'Get many opportunities',
344
+ },
307
345
  { name: 'Update', value: 'update', description: 'Update an opportunity', action: 'Update an opportunity' },
308
346
  ],
309
347
  default: 'create',
@@ -432,7 +470,14 @@ class Flowlu {
432
470
  { displayName: 'Instagram', name: 'social_network_link_6', type: 'string', default: '' },
433
471
  { displayName: 'LinkedIn', name: 'social_network_link_5', type: 'string', default: '' },
434
472
  { displayName: 'Middle Name', name: 'middle_name', type: 'string', default: '' },
435
- { displayName: 'Owner Name or ID', name: 'owner_id', type: 'options', typeOptions: { loadOptionsMethod: 'getUsers' }, default: '', description: 'Assigned user for this contact. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.' },
473
+ {
474
+ displayName: 'Owner Name or ID',
475
+ name: 'owner_id',
476
+ type: 'options',
477
+ typeOptions: { loadOptionsMethod: 'getUsers' },
478
+ default: '',
479
+ description: 'Assigned user for this contact. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
480
+ },
436
481
  { displayName: 'Personal Email', name: 'email_personal', type: 'string', default: '' },
437
482
  { displayName: 'Phone 2', name: 'phone2', type: 'string', default: '' },
438
483
  { displayName: 'Phone 3', name: 'phone3', type: 'string', default: '' },
@@ -491,7 +536,14 @@ class Flowlu {
491
536
  { displayName: 'Last Name', name: 'last_name', type: 'string', default: '' },
492
537
  { displayName: 'LinkedIn', name: 'social_network_link_5', type: 'string', default: '' },
493
538
  { displayName: 'Middle Name', name: 'middle_name', type: 'string', default: '' },
494
- { displayName: 'Owner Name or ID', name: 'owner_id', type: 'options', description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>', typeOptions: { loadOptionsMethod: 'getUsers' }, default: '' },
539
+ {
540
+ displayName: 'Owner Name or ID',
541
+ name: 'owner_id',
542
+ type: 'options',
543
+ description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
544
+ typeOptions: { loadOptionsMethod: 'getUsers' },
545
+ default: '',
546
+ },
495
547
  { displayName: 'Personal Email', name: 'email_personal', type: 'string', default: '' },
496
548
  { displayName: 'Phone', name: 'phone', type: 'string', default: '' },
497
549
  { displayName: 'Phone 2', name: 'phone2', type: 'string', default: '' },
@@ -541,10 +593,23 @@ class Flowlu {
541
593
  displayOptions: { show: { resource: ['contact'], operation: ['getAll'] } },
542
594
  options: [
543
595
  { displayName: 'Name', name: 'name', type: 'string', default: '', description: 'Filter by contact name' },
544
- { displayName: 'Email', name: 'email', type: 'string',
545
- placeholder: 'name@email.com', default: '', description: 'Filter by email address' },
596
+ {
597
+ displayName: 'Email',
598
+ name: 'email',
599
+ type: 'string',
600
+ placeholder: 'name@email.com',
601
+ default: '',
602
+ description: 'Filter by email address',
603
+ },
546
604
  { displayName: 'Phone', name: 'phone', type: 'string', default: '', description: 'Filter by phone number' },
547
- { displayName: 'Owner Name or ID', name: 'owner_id', type: 'options', typeOptions: { loadOptionsMethod: 'getUsers' }, default: '', description: 'Filter by assigned owner. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.' },
605
+ {
606
+ displayName: 'Owner Name or ID',
607
+ name: 'owner_id',
608
+ type: 'options',
609
+ typeOptions: { loadOptionsMethod: 'getUsers' },
610
+ default: '',
611
+ description: 'Filter by assigned owner. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
612
+ },
548
613
  ],
549
614
  },
550
615
  // ========================================
@@ -606,11 +671,23 @@ class Flowlu {
606
671
  default: {},
607
672
  displayOptions: { show: { resource: ['opportunity'], operation: ['create'] } },
608
673
  options: [
609
- { displayName: 'Budget', name: 'budget', type: 'number', default: 0, description: 'Opportunity value/amount' },
674
+ {
675
+ displayName: 'Budget',
676
+ name: 'budget',
677
+ type: 'number',
678
+ default: 0,
679
+ description: 'Opportunity value/amount',
680
+ },
610
681
  { displayName: 'Contact Company', name: 'contact_company', type: 'string', default: '' },
611
682
  { displayName: 'Contact Email', name: 'contact_email', type: 'string', default: '' },
612
683
  { displayName: 'Contact Mobile', name: 'contact_mobile', type: 'string', default: '' },
613
- { displayName: 'Contact Name', name: 'contact_name', type: 'string', default: '', description: 'Quick-add contact name (if not linking to existing contact)' },
684
+ {
685
+ displayName: 'Contact Name',
686
+ name: 'contact_name',
687
+ type: 'string',
688
+ default: '',
689
+ description: 'Quick-add contact name (if not linking to existing contact)',
690
+ },
614
691
  { displayName: 'Contact Phone', name: 'contact_phone', type: 'string', default: '' },
615
692
  { displayName: 'Contact Position', name: 'contact_position', type: 'string', default: '' },
616
693
  { displayName: 'Contact Website', name: 'contact_web', type: 'string', default: '' },
@@ -622,10 +699,31 @@ class Flowlu {
622
699
  default: false,
623
700
  description: 'Whether to append a "Generated via n8n: View Workflow" footer to the opportunity description, linking back to this workflow',
624
701
  },
625
- { displayName: 'Link to Account Name or ID', name: 'link_account_id', type: 'options', typeOptions: { loadOptionsMethod: 'getAccounts' }, default: '', description: 'Link this opportunity to an existing CRM account (organization). Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.' },
626
- { displayName: 'Link to Contact Name or ID', name: 'link_contact_id', type: 'options', typeOptions: { loadOptionsMethod: 'getContacts' }, default: '', description: 'Link this opportunity to an existing CRM contact. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.' },
702
+ {
703
+ displayName: 'Link to Account Name or ID',
704
+ name: 'link_account_id',
705
+ type: 'options',
706
+ typeOptions: { loadOptionsMethod: 'getAccounts' },
707
+ default: '',
708
+ description: 'Link this opportunity to an existing CRM account (organization). Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
709
+ },
710
+ {
711
+ displayName: 'Link to Contact Name or ID',
712
+ name: 'link_contact_id',
713
+ type: 'options',
714
+ typeOptions: { loadOptionsMethod: 'getContacts' },
715
+ default: '',
716
+ description: 'Link this opportunity to an existing CRM contact. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
717
+ },
627
718
  { displayName: 'Planned Close Date', name: 'deadline', type: 'dateTime', default: '' },
628
- { displayName: 'Source Name or ID', name: 'source_id', type: 'options', typeOptions: { loadOptionsMethod: 'getOpportunitySources' }, default: '', description: 'Where this opportunity came from. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.' },
719
+ {
720
+ displayName: 'Source Name or ID',
721
+ name: 'source_id',
722
+ type: 'options',
723
+ typeOptions: { loadOptionsMethod: 'getOpportunitySources' },
724
+ default: '',
725
+ description: 'Where this opportunity came from. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
726
+ },
629
727
  { displayName: 'Start Date', name: 'start_date', type: 'dateTime', default: '' },
630
728
  ],
631
729
  },
@@ -655,9 +753,22 @@ class Flowlu {
655
753
  default: {},
656
754
  displayOptions: { show: { resource: ['opportunity'], operation: ['update'] } },
657
755
  options: [
658
- { displayName: 'Assignee Name or ID', name: 'assignee_id', type: 'options', description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>', typeOptions: { loadOptionsMethod: 'getUsers' }, default: '' },
756
+ {
757
+ displayName: 'Assignee Name or ID',
758
+ name: 'assignee_id',
759
+ type: 'options',
760
+ description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
761
+ typeOptions: { loadOptionsMethod: 'getUsers' },
762
+ default: '',
763
+ },
659
764
  { displayName: 'Budget', name: 'budget', type: 'number', default: 0 },
660
- { displayName: 'Closing Comment', name: 'closing_comment', type: 'string', default: '', description: 'Reason for win/loss' },
765
+ {
766
+ displayName: 'Closing Comment',
767
+ name: 'closing_comment',
768
+ type: 'string',
769
+ default: '',
770
+ description: 'Reason for win/loss',
771
+ },
661
772
  { displayName: 'Contact Email', name: 'contact_email', type: 'string', default: '' },
662
773
  { displayName: 'Contact Name', name: 'contact_name', type: 'string', default: '' },
663
774
  { displayName: 'Contact Phone', name: 'contact_phone', type: 'string', default: '' },
@@ -669,11 +780,39 @@ class Flowlu {
669
780
  default: false,
670
781
  description: 'Whether to append a "Generated via n8n: View Workflow" footer to the opportunity description, linking back to this workflow. Requires Description to be set.',
671
782
  },
672
- { displayName: 'Link to Account Name or ID', name: 'link_account_id', type: 'options', typeOptions: { loadOptionsMethod: 'getAccounts' }, default: '', description: 'Link this opportunity to a CRM account. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.' },
673
- { displayName: 'Link to Contact Name or ID', name: 'link_contact_id', type: 'options', typeOptions: { loadOptionsMethod: 'getContacts' }, default: '', description: 'Link this opportunity to a CRM contact. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.' },
783
+ {
784
+ displayName: 'Link to Account Name or ID',
785
+ name: 'link_account_id',
786
+ type: 'options',
787
+ typeOptions: { loadOptionsMethod: 'getAccounts' },
788
+ default: '',
789
+ description: 'Link this opportunity to a CRM account. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
790
+ },
791
+ {
792
+ displayName: 'Link to Contact Name or ID',
793
+ name: 'link_contact_id',
794
+ type: 'options',
795
+ typeOptions: { loadOptionsMethod: 'getContacts' },
796
+ default: '',
797
+ description: 'Link this opportunity to a CRM contact. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
798
+ },
674
799
  { displayName: 'Name', name: 'name', type: 'string', default: '' },
675
- { displayName: 'Pipeline Name or ID', name: 'pipeline_id', type: 'options', description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>', typeOptions: { loadOptionsMethod: 'getPipelines' }, default: '' },
676
- { displayName: 'Pipeline Stage Name or ID', name: 'pipeline_stage_id', type: 'options', description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>', typeOptions: { loadOptionsMethod: 'getPipelineStages' }, default: '' },
800
+ {
801
+ displayName: 'Pipeline Name or ID',
802
+ name: 'pipeline_id',
803
+ type: 'options',
804
+ description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
805
+ typeOptions: { loadOptionsMethod: 'getPipelines' },
806
+ default: '',
807
+ },
808
+ {
809
+ displayName: 'Pipeline Stage Name or ID',
810
+ name: 'pipeline_stage_id',
811
+ type: 'options',
812
+ description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
813
+ typeOptions: { loadOptionsMethod: 'getPipelineStages' },
814
+ default: '',
815
+ },
677
816
  { displayName: 'Planned Close Date', name: 'deadline', type: 'dateTime', default: '' },
678
817
  { displayName: 'Start Date', name: 'start_date', type: 'dateTime', default: '' },
679
818
  {
@@ -748,8 +887,22 @@ class Flowlu {
748
887
  displayOptions: { show: { resource: ['opportunity'], operation: ['getAll'] } },
749
888
  options: [
750
889
  { displayName: 'Name', name: 'name', type: 'string', default: '', description: 'Filter by opportunity name' },
751
- { displayName: 'Assignee Name or ID', name: 'assignee_id', type: 'options', description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>', typeOptions: { loadOptionsMethod: 'getUsers' }, default: '' },
752
- { displayName: 'Account / Contact Name or ID', name: 'company_id', type: 'options', typeOptions: { loadOptionsMethod: 'getAllCrmAccounts' }, default: '', description: 'Filter by linked company or contact (note: Flowlu may not support this filter). Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.' },
890
+ {
891
+ displayName: 'Assignee Name or ID',
892
+ name: 'assignee_id',
893
+ type: 'options',
894
+ description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
895
+ typeOptions: { loadOptionsMethod: 'getUsers' },
896
+ default: '',
897
+ },
898
+ {
899
+ displayName: 'Account / Contact Name or ID',
900
+ name: 'company_id',
901
+ type: 'options',
902
+ typeOptions: { loadOptionsMethod: 'getAllCrmAccounts' },
903
+ default: '',
904
+ description: 'Filter by linked company or contact (note: Flowlu may not support this filter). Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
905
+ },
753
906
  {
754
907
  displayName: 'Status',
755
908
  name: 'active',
@@ -823,8 +976,22 @@ class Flowlu {
823
976
  default: 0,
824
977
  },
825
978
  { displayName: 'Contract Sum (Revenue)', name: 'estimated_revenue', type: 'number', default: 0 },
826
- { displayName: 'Customer (Company ID) Name or ID', name: 'customer_id', type: 'options', typeOptions: { loadOptionsMethod: 'getContacts' }, default: '', description: 'CRM company linked to this project. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.' },
827
- { displayName: 'Customer Contact Name or ID', name: 'customer_crm_contact_id', type: 'options', typeOptions: { loadOptionsMethod: 'getContacts' }, default: '', description: 'CRM contact linked to this project. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.' },
979
+ {
980
+ displayName: 'Customer (Company ID) Name or ID',
981
+ name: 'customer_id',
982
+ type: 'options',
983
+ typeOptions: { loadOptionsMethod: 'getContacts' },
984
+ default: '',
985
+ description: 'CRM company linked to this project. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
986
+ },
987
+ {
988
+ displayName: 'Customer Contact Name or ID',
989
+ name: 'customer_crm_contact_id',
990
+ type: 'options',
991
+ typeOptions: { loadOptionsMethod: 'getContacts' },
992
+ default: '',
993
+ description: 'CRM contact linked to this project. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
994
+ },
828
995
  { displayName: 'End Date', name: 'enddate', type: 'dateTime', default: '' },
829
996
  { displayName: 'Expense Sum', name: 'estimated_expenses', type: 'number', default: 0 },
830
997
  {
@@ -834,8 +1001,21 @@ class Flowlu {
834
1001
  default: false,
835
1002
  description: 'Whether to append a "Generated via n8n: View Workflow" footer to the project description, linking back to this workflow',
836
1003
  },
837
- { displayName: 'Opportunity', name: 'crm_lead_id', type: 'number', default: 0, description: 'ID of a linked CRM opportunity' },
838
- { displayName: 'Portfolio Name or ID', name: 'briefcase_id', type: 'options', description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>', typeOptions: { loadOptionsMethod: 'getPortfolios' }, default: '' },
1004
+ {
1005
+ displayName: 'Opportunity',
1006
+ name: 'crm_lead_id',
1007
+ type: 'number',
1008
+ default: 0,
1009
+ description: 'ID of a linked CRM opportunity',
1010
+ },
1011
+ {
1012
+ displayName: 'Portfolio Name or ID',
1013
+ name: 'briefcase_id',
1014
+ type: 'options',
1015
+ description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
1016
+ typeOptions: { loadOptionsMethod: 'getPortfolios' },
1017
+ default: '',
1018
+ },
839
1019
  {
840
1020
  displayName: 'Priority',
841
1021
  name: 'priority',
@@ -847,9 +1027,23 @@ class Flowlu {
847
1027
  ],
848
1028
  default: 2,
849
1029
  },
850
- { displayName: 'Project Stage Name or ID', name: 'stage_id', type: 'options', description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>', typeOptions: { loadOptionsMethod: 'getProjectStages' }, default: '' },
1030
+ {
1031
+ displayName: 'Project Stage Name or ID',
1032
+ name: 'stage_id',
1033
+ type: 'options',
1034
+ description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
1035
+ typeOptions: { loadOptionsMethod: 'getProjectStages' },
1036
+ default: '',
1037
+ },
851
1038
  { displayName: 'Start Date', name: 'startdate', type: 'dateTime', default: '' },
852
- { displayName: 'Task Workflow Name or ID', name: 'tasks_workflow_id', type: 'options', typeOptions: { loadOptionsMethod: 'getTaskWorkflows' }, default: '', description: 'Default task workflow for tasks in this project. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.' },
1039
+ {
1040
+ displayName: 'Task Workflow Name or ID',
1041
+ name: 'tasks_workflow_id',
1042
+ type: 'options',
1043
+ typeOptions: { loadOptionsMethod: 'getTaskWorkflows' },
1044
+ default: '',
1045
+ description: 'Default task workflow for tasks in this project. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
1046
+ },
853
1047
  ],
854
1048
  },
855
1049
  {
@@ -895,7 +1089,14 @@ class Flowlu {
895
1089
  default: false,
896
1090
  description: 'Whether to append a "Generated via n8n: View Workflow" footer to the project description, linking back to this workflow. Requires Description to be set.',
897
1091
  },
898
- { displayName: 'Manager Name or ID', name: 'manager_id', type: 'options', description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>', typeOptions: { loadOptionsMethod: 'getUsers' }, default: '' },
1092
+ {
1093
+ displayName: 'Manager Name or ID',
1094
+ name: 'manager_id',
1095
+ type: 'options',
1096
+ description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
1097
+ typeOptions: { loadOptionsMethod: 'getUsers' },
1098
+ default: '',
1099
+ },
899
1100
  {
900
1101
  displayName: 'Priority',
901
1102
  name: 'priority',
@@ -908,7 +1109,14 @@ class Flowlu {
908
1109
  default: 2,
909
1110
  },
910
1111
  { displayName: 'Project Name', name: 'name', type: 'string', default: '' },
911
- { displayName: 'Project Stage Name or ID', name: 'stage_id', type: 'options', description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>', typeOptions: { loadOptionsMethod: 'getProjectStages' }, default: '' },
1112
+ {
1113
+ displayName: 'Project Stage Name or ID',
1114
+ name: 'stage_id',
1115
+ type: 'options',
1116
+ description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
1117
+ typeOptions: { loadOptionsMethod: 'getProjectStages' },
1118
+ default: '',
1119
+ },
912
1120
  { displayName: 'Start Date', name: 'startdate', type: 'dateTime', default: '' },
913
1121
  ],
914
1122
  },
@@ -950,8 +1158,22 @@ class Flowlu {
950
1158
  displayOptions: { show: { resource: ['project'], operation: ['getAll'] } },
951
1159
  options: [
952
1160
  { displayName: 'Name', name: 'name', type: 'string', default: '', description: 'Filter by project name' },
953
- { displayName: 'Manager Name or ID', name: 'manager_id', type: 'options', description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>', typeOptions: { loadOptionsMethod: 'getUsers' }, default: '' },
954
- { displayName: 'Customer Name or ID', name: 'customer_id', type: 'options', typeOptions: { loadOptionsMethod: 'getAccounts' }, default: '', description: 'Filter by linked company. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.' },
1161
+ {
1162
+ displayName: 'Manager Name or ID',
1163
+ name: 'manager_id',
1164
+ type: 'options',
1165
+ description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
1166
+ typeOptions: { loadOptionsMethod: 'getUsers' },
1167
+ default: '',
1168
+ },
1169
+ {
1170
+ displayName: 'Customer Name or ID',
1171
+ name: 'customer_id',
1172
+ type: 'options',
1173
+ typeOptions: { loadOptionsMethod: 'getAccounts' },
1174
+ default: '',
1175
+ description: 'Filter by linked company. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
1176
+ },
955
1177
  {
956
1178
  displayName: 'Include Archived',
957
1179
  name: 'is_archive',
@@ -1046,7 +1268,13 @@ class Flowlu {
1046
1268
  default: {},
1047
1269
  displayOptions: { show: { resource: ['recordList'], operation: ['getAll'] } },
1048
1270
  options: [
1049
- { displayName: 'Search', name: 'search', type: 'string', default: '', description: 'Search records by name/content' },
1271
+ {
1272
+ displayName: 'Search',
1273
+ name: 'search',
1274
+ type: 'string',
1275
+ default: '',
1276
+ description: 'Search records by name/content',
1277
+ },
1050
1278
  ],
1051
1279
  },
1052
1280
  // ========================================
@@ -1088,6 +1316,59 @@ class Flowlu {
1088
1316
  default: '',
1089
1317
  displayOptions: { show: { resource: ['task'], operation: ['create'] } },
1090
1318
  },
1319
+ {
1320
+ displayName: 'Owner Name or ID',
1321
+ name: 'owner_id',
1322
+ type: 'options',
1323
+ typeOptions: { loadOptionsMethod: 'getUsers' },
1324
+ default: '',
1325
+ 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>.',
1326
+ displayOptions: { show: { resource: ['task'], operation: ['create'] } },
1327
+ },
1328
+ {
1329
+ displayName: 'Priority',
1330
+ name: 'priority',
1331
+ type: 'options',
1332
+ options: [
1333
+ { name: 'Low', value: 1 },
1334
+ { name: 'Medium', value: 2 },
1335
+ { name: 'High', value: 3 },
1336
+ ],
1337
+ default: 2,
1338
+ displayOptions: { show: { resource: ['task'], operation: ['create'] } },
1339
+ },
1340
+ {
1341
+ displayName: 'Contact Name or ID',
1342
+ name: 'contact_id',
1343
+ type: 'options',
1344
+ typeOptions: { loadOptionsMethod: 'getContacts' },
1345
+ default: '',
1346
+ 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>.',
1347
+ displayOptions: { show: { resource: ['task'], operation: ['create'] } },
1348
+ },
1349
+ {
1350
+ displayName: 'Project Name or ID',
1351
+ name: 'model_id',
1352
+ type: 'options',
1353
+ typeOptions: { loadOptionsMethod: 'getProjects' },
1354
+ default: '',
1355
+ 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>.',
1356
+ displayOptions: { show: { resource: ['task'], operation: ['create'] } },
1357
+ },
1358
+ {
1359
+ displayName: 'Start Date',
1360
+ name: 'plan_start_date',
1361
+ type: 'dateTime',
1362
+ default: '',
1363
+ displayOptions: { show: { resource: ['task'], operation: ['create'] } },
1364
+ },
1365
+ {
1366
+ displayName: 'End Date',
1367
+ name: 'deadline',
1368
+ type: 'dateTime',
1369
+ default: '',
1370
+ displayOptions: { show: { resource: ['task'], operation: ['create'] } },
1371
+ },
1091
1372
  // Task Create: Additional Fields
1092
1373
  {
1093
1374
  displayName: 'Additional Fields',
@@ -1098,8 +1379,6 @@ class Flowlu {
1098
1379
  displayOptions: { show: { resource: ['task'], operation: ['create'] } },
1099
1380
  options: [
1100
1381
  { displayName: 'Allow End Date Change', name: 'deadline_allowchange', type: 'boolean', default: true },
1101
- { displayName: 'Contact Name or ID', name: 'contact_id', type: 'options', typeOptions: { loadOptionsMethod: 'getContacts' }, default: '', 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>.' },
1102
- { displayName: 'End Date', name: 'deadline', type: 'dateTime', default: '' },
1103
1382
  {
1104
1383
  displayName: 'Include Link to Workflow',
1105
1384
  name: 'includeLinkToWorkflow',
@@ -1107,24 +1386,16 @@ class Flowlu {
1107
1386
  default: false,
1108
1387
  description: 'Whether to append a "Generated via n8n: View Workflow" footer to the task description, linking back to this workflow',
1109
1388
  },
1110
- { displayName: 'Owner Name or ID', name: 'owner_id', type: 'options', typeOptions: { loadOptionsMethod: 'getUsers' }, description: 'The user who owns/created this task. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.', default: '' },
1111
- { displayName: 'Parent Task ID', name: 'parent_id', type: 'number', default: 0, description: 'ID of the parent task (for subtasks)' },
1112
- { displayName: 'Planned Cost', name: 'cost', type: 'number', default: 0, typeOptions: { minValue: 0 } },
1113
- { displayName: 'Planned Income', name: 'price', type: 'number', default: 0, typeOptions: { minValue: 0 } },
1114
1389
  {
1115
- displayName: 'Priority',
1116
- name: 'priority',
1117
- type: 'options',
1118
- options: [
1119
- { name: 'Low', value: 1 },
1120
- { name: 'Medium', value: 2 },
1121
- { name: 'High', value: 3 },
1122
- ],
1123
- default: 2,
1390
+ displayName: 'Parent Task ID',
1391
+ name: 'parent_id',
1392
+ type: 'number',
1393
+ default: 0,
1394
+ description: 'ID of the parent task (for subtasks)',
1124
1395
  },
1125
- { displayName: 'Project Name or ID', name: 'model_id', type: 'options', typeOptions: { loadOptionsMethod: 'getProjects' }, 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>.', default: '' },
1396
+ { displayName: 'Planned Cost', name: 'cost', type: 'number', default: 0, typeOptions: { minValue: 0 } },
1397
+ { displayName: 'Planned Income', name: 'price', type: 'number', default: 0, typeOptions: { minValue: 0 } },
1126
1398
  { displayName: 'Reviewed by Owner', name: 'task_checkbyowner', type: 'boolean', default: false },
1127
- { displayName: 'Start Date', name: 'plan_start_date', type: 'dateTime', default: '' },
1128
1399
  {
1129
1400
  displayName: 'Status',
1130
1401
  name: 'status',
@@ -1137,9 +1408,29 @@ class Flowlu {
1137
1408
  ],
1138
1409
  default: 1,
1139
1410
  },
1140
- { displayName: 'Time Estimate (Minutes)', name: 'time_estimate', type: 'number', default: 0, typeOptions: { minValue: 0 } },
1141
- { displayName: 'Workflow Name or ID', name: 'workflow_id', type: 'options', typeOptions: { loadOptionsMethod: 'getTaskWorkflows' }, default: '', description: 'The task workflow to use (defaults to the first workflow if not set). Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.' },
1142
- { displayName: 'Workflow Stage Name or ID', name: 'workflow_stage_id', type: 'options', typeOptions: { loadOptionsMethod: 'getTaskWorkflowStages' }, default: '', description: 'The starting stage within the workflow. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.' },
1411
+ {
1412
+ displayName: 'Time Estimate (Minutes)',
1413
+ name: 'time_estimate',
1414
+ type: 'number',
1415
+ default: 0,
1416
+ typeOptions: { minValue: 0 },
1417
+ },
1418
+ {
1419
+ displayName: 'Workflow Name or ID',
1420
+ name: 'workflow_id',
1421
+ type: 'options',
1422
+ typeOptions: { loadOptionsMethod: 'getTaskWorkflows' },
1423
+ default: '',
1424
+ description: 'The task workflow to use (defaults to the first workflow if not set). Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
1425
+ },
1426
+ {
1427
+ displayName: 'Workflow Stage Name or ID',
1428
+ name: 'workflow_stage_id',
1429
+ type: 'options',
1430
+ typeOptions: { loadOptionsMethod: 'getTaskWorkflowStages' },
1431
+ default: '',
1432
+ description: 'The starting stage within the workflow. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
1433
+ },
1143
1434
  ],
1144
1435
  },
1145
1436
  {
@@ -1168,7 +1459,14 @@ class Flowlu {
1168
1459
  default: {},
1169
1460
  displayOptions: { show: { resource: ['task'], operation: ['update'] } },
1170
1461
  options: [
1171
- { displayName: 'Assignee Name or ID', name: 'responsible_id', type: 'options', description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>', typeOptions: { loadOptionsMethod: 'getUsers' }, default: '' },
1462
+ {
1463
+ displayName: 'Assignee Name or ID',
1464
+ name: 'responsible_id',
1465
+ type: 'options',
1466
+ description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
1467
+ typeOptions: { loadOptionsMethod: 'getUsers' },
1468
+ default: '',
1469
+ },
1172
1470
  { displayName: 'Description', name: 'description', type: 'string', typeOptions: { rows: 4 }, default: '' },
1173
1471
  { displayName: 'End Date', name: 'deadline', type: 'dateTime', default: '' },
1174
1472
  {
@@ -1178,7 +1476,14 @@ class Flowlu {
1178
1476
  default: false,
1179
1477
  description: 'Whether to append a "Generated via n8n: View Workflow" footer to the task description, linking back to this workflow. Requires Description to be set.',
1180
1478
  },
1181
- { displayName: 'Owner Name or ID', name: 'owner_id', type: 'options', description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>', typeOptions: { loadOptionsMethod: 'getUsers' }, default: '' },
1479
+ {
1480
+ displayName: 'Owner Name or ID',
1481
+ name: 'owner_id',
1482
+ type: 'options',
1483
+ description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
1484
+ typeOptions: { loadOptionsMethod: 'getUsers' },
1485
+ default: '',
1486
+ },
1182
1487
  { displayName: 'Parent Task ID', name: 'parent_id', type: 'number', default: 0 },
1183
1488
  { displayName: 'Planned Cost', name: 'cost', type: 'number', default: 0, typeOptions: { minValue: 0 } },
1184
1489
  { displayName: 'Planned Income', name: 'price', type: 'number', default: 0, typeOptions: { minValue: 0 } },
@@ -1193,7 +1498,14 @@ class Flowlu {
1193
1498
  ],
1194
1499
  default: 2,
1195
1500
  },
1196
- { displayName: 'Project Name or ID', name: 'model_id', type: 'options', description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>', typeOptions: { loadOptionsMethod: 'getProjects' }, default: '' },
1501
+ {
1502
+ displayName: 'Project Name or ID',
1503
+ name: 'model_id',
1504
+ type: 'options',
1505
+ description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
1506
+ typeOptions: { loadOptionsMethod: 'getProjects' },
1507
+ default: '',
1508
+ },
1197
1509
  { displayName: 'Start Date', name: 'plan_start_date', type: 'dateTime', default: '' },
1198
1510
  {
1199
1511
  displayName: 'Status',
@@ -1208,9 +1520,29 @@ class Flowlu {
1208
1520
  default: 1,
1209
1521
  },
1210
1522
  { displayName: 'Task Name', name: 'name', type: 'string', default: '' },
1211
- { displayName: 'Time Estimate (Minutes)', name: 'time_estimate', type: 'number', default: 0, typeOptions: { minValue: 0 } },
1212
- { displayName: 'Workflow Name or ID', name: 'workflow_id', type: 'options', description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>', typeOptions: { loadOptionsMethod: 'getTaskWorkflows' }, default: '' },
1213
- { displayName: 'Workflow Stage Name or ID', name: 'workflow_stage_id', type: 'options', description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>', typeOptions: { loadOptionsMethod: 'getTaskWorkflowStages' }, default: '' },
1523
+ {
1524
+ displayName: 'Time Estimate (Minutes)',
1525
+ name: 'time_estimate',
1526
+ type: 'number',
1527
+ default: 0,
1528
+ typeOptions: { minValue: 0 },
1529
+ },
1530
+ {
1531
+ displayName: 'Workflow Name or ID',
1532
+ name: 'workflow_id',
1533
+ type: 'options',
1534
+ description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
1535
+ typeOptions: { loadOptionsMethod: 'getTaskWorkflows' },
1536
+ default: '',
1537
+ },
1538
+ {
1539
+ displayName: 'Workflow Stage Name or ID',
1540
+ name: 'workflow_stage_id',
1541
+ type: 'options',
1542
+ description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
1543
+ typeOptions: { loadOptionsMethod: 'getTaskWorkflowStages' },
1544
+ default: '',
1545
+ },
1214
1546
  ],
1215
1547
  },
1216
1548
  {
@@ -1250,7 +1582,14 @@ class Flowlu {
1250
1582
  default: {},
1251
1583
  displayOptions: { show: { resource: ['task'], operation: ['getAll'] } },
1252
1584
  options: [
1253
- { displayName: 'Assignee Name or ID', name: 'responsible_id', type: 'options', description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>', typeOptions: { loadOptionsMethod: 'getUsers' }, default: '' },
1585
+ {
1586
+ displayName: 'Assignee Name or ID',
1587
+ name: 'responsible_id',
1588
+ type: 'options',
1589
+ description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
1590
+ typeOptions: { loadOptionsMethod: 'getUsers' },
1591
+ default: '',
1592
+ },
1254
1593
  { displayName: 'Name', name: 'name', type: 'string', default: '', description: 'Filter by task name' },
1255
1594
  {
1256
1595
  displayName: 'Priority',
@@ -1263,7 +1602,14 @@ class Flowlu {
1263
1602
  ],
1264
1603
  default: 2,
1265
1604
  },
1266
- { displayName: 'Project Name or ID', name: 'model_id', type: 'options', description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>', typeOptions: { loadOptionsMethod: 'getProjects' }, default: '' },
1605
+ {
1606
+ displayName: 'Project Name or ID',
1607
+ name: 'model_id',
1608
+ type: 'options',
1609
+ description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
1610
+ typeOptions: { loadOptionsMethod: 'getProjects' },
1611
+ default: '',
1612
+ },
1267
1613
  {
1268
1614
  displayName: 'Status',
1269
1615
  name: 'status',
@@ -1384,274 +1730,195 @@ class Flowlu {
1384
1730
  this.methods = {
1385
1731
  loadOptions: {
1386
1732
  async getUsers() {
1387
- try {
1388
- const { baseUrl, apiKey } = await getFlowluCredentials(this);
1389
- const response = await flowluApiRequest.call(this, 'GET', baseUrl, '/api/v1/module/core/user/list', apiKey, undefined, { 'filter[role_login]': '1' });
1390
- if (response?.response?.items) {
1391
- return response.response.items.map((user) => ({ name: user.name || user.email || `User ${user.id}`, value: user.id.toString() }));
1392
- }
1393
- return [];
1394
- }
1395
- catch (error) {
1396
- return [];
1397
- }
1733
+ const { baseUrl, apiKey } = await getFlowluCredentials(this);
1734
+ const items = await flowluListAll.call(this, baseUrl, '/api/v1/module/core/user/list', apiKey, {
1735
+ 'filter[role_login]': '1',
1736
+ });
1737
+ return items.map((user) => ({
1738
+ name: user.name || user.email || `User ${user.id}`,
1739
+ value: user.id.toString(),
1740
+ }));
1398
1741
  },
1399
1742
  async getAccounts() {
1400
- try {
1401
- const { baseUrl, apiKey } = await getFlowluCredentials(this);
1402
- const response = await flowluApiRequest.call(this, 'GET', baseUrl, '/api/v1/module/crm/account/list', apiKey, undefined, { 'filter[type]': '1' });
1403
- if (response?.response?.items) {
1404
- return response.response.items.map((a) => ({ name: a.name || `Account ${a.id}`, value: a.id.toString() }));
1405
- }
1406
- return [];
1407
- }
1408
- catch (error) {
1409
- return [];
1410
- }
1743
+ const { baseUrl, apiKey } = await getFlowluCredentials(this);
1744
+ const items = await flowluListAll.call(this, baseUrl, '/api/v1/module/crm/account/list', apiKey, {
1745
+ 'filter[type]': '1',
1746
+ });
1747
+ return items.map((a) => ({
1748
+ name: a.name || `Account ${a.id}`,
1749
+ value: a.id.toString(),
1750
+ }));
1411
1751
  },
1412
1752
  async getContacts() {
1413
- try {
1414
- const { baseUrl, apiKey } = await getFlowluCredentials(this);
1415
- const response = await flowluApiRequest.call(this, 'GET', baseUrl, '/api/v1/module/crm/account/list', apiKey, undefined, { 'filter[type]': '2' });
1416
- if (response?.response?.items) {
1417
- return response.response.items.map((c) => ({ name: c.name || `Contact ${c.id}`, value: c.id.toString() }));
1418
- }
1419
- return [];
1420
- }
1421
- catch (error) {
1422
- return [];
1423
- }
1753
+ const { baseUrl, apiKey } = await getFlowluCredentials(this);
1754
+ const items = await flowluListAll.call(this, baseUrl, '/api/v1/module/crm/account/list', apiKey, {
1755
+ 'filter[type]': '2',
1756
+ });
1757
+ return items.map((c) => ({
1758
+ name: c.name || `Contact ${c.id}`,
1759
+ value: c.id.toString(),
1760
+ }));
1424
1761
  },
1425
1762
  async getProjects() {
1426
- try {
1427
- const { baseUrl, apiKey } = await getFlowluCredentials(this);
1428
- const response = await flowluApiRequest.call(this, 'GET', baseUrl, '/api/v1/module/st/projects/list', apiKey, undefined, { 'filter[is_archive]': '0' });
1429
- if (response?.response?.items) {
1430
- return response.response.items.map((p) => ({ name: p.name || `Project ${p.id}`, value: p.id.toString() }));
1431
- }
1432
- return [];
1433
- }
1434
- catch (error) {
1435
- return [];
1436
- }
1763
+ const { baseUrl, apiKey } = await getFlowluCredentials(this);
1764
+ const items = await flowluListAll.call(this, baseUrl, '/api/v1/module/st/projects/list', apiKey, {
1765
+ 'filter[is_archive]': '0',
1766
+ });
1767
+ return items.map((p) => ({
1768
+ name: p.name || `Project ${p.id}`,
1769
+ value: p.id.toString(),
1770
+ }));
1437
1771
  },
1438
1772
  async getTaskWorkflows() {
1439
- try {
1440
- const { baseUrl, apiKey } = await getFlowluCredentials(this);
1441
- const response = await flowluApiRequest.call(this, 'GET', baseUrl, '/api/v1/module/task/workflows/list', apiKey);
1442
- if (response?.response?.items) {
1443
- return response.response.items.map((wf) => ({ name: wf.name || `Workflow ${wf.id}`, value: wf.id.toString() }));
1444
- }
1445
- return [];
1446
- }
1447
- catch (error) {
1448
- return [];
1449
- }
1773
+ const { baseUrl, apiKey } = await getFlowluCredentials(this);
1774
+ const items = await flowluListAll.call(this, baseUrl, '/api/v1/module/task/workflows/list', apiKey);
1775
+ return items.map((wf) => ({
1776
+ name: wf.name || `Workflow ${wf.id}`,
1777
+ value: wf.id.toString(),
1778
+ }));
1450
1779
  },
1451
1780
  async getTaskWorkflowStages() {
1452
- try {
1453
- const { baseUrl, apiKey } = await getFlowluCredentials(this);
1454
- const response = await flowluApiRequest.call(this, 'GET', baseUrl, '/api/v1/module/task/stages/list', apiKey);
1455
- if (response?.response?.items) {
1456
- return response.response.items.map((s) => ({ name: s.name || `Stage ${s.id}`, value: s.id.toString(), description: `Workflow ID: ${s.workflow_id}` }));
1457
- }
1458
- return [];
1459
- }
1460
- catch (error) {
1461
- return [];
1462
- }
1781
+ const { baseUrl, apiKey } = await getFlowluCredentials(this);
1782
+ const items = await flowluListAll.call(this, baseUrl, '/api/v1/module/task/stages/list', apiKey);
1783
+ return items.map((s) => ({
1784
+ name: s.name || `Stage ${s.id}`,
1785
+ value: s.id.toString(),
1786
+ description: `Workflow ID: ${s.workflow_id}`,
1787
+ }));
1463
1788
  },
1464
1789
  async getPipelines() {
1465
- try {
1466
- const { baseUrl, apiKey } = await getFlowluCredentials(this);
1467
- const response = await flowluApiRequest.call(this, 'GET', baseUrl, '/api/v1/module/crm/pipeline/list', apiKey);
1468
- if (response?.response?.items) {
1469
- return response.response.items.map((p) => ({ name: p.name || `Pipeline ${p.id}`, value: p.id.toString() }));
1470
- }
1471
- return [];
1472
- }
1473
- catch (error) {
1474
- return [];
1475
- }
1790
+ const { baseUrl, apiKey } = await getFlowluCredentials(this);
1791
+ const items = await flowluListAll.call(this, baseUrl, '/api/v1/module/crm/pipeline/list', apiKey);
1792
+ return items.map((p) => ({
1793
+ name: p.name || `Pipeline ${p.id}`,
1794
+ value: p.id.toString(),
1795
+ }));
1476
1796
  },
1477
1797
  async getPipelineStages() {
1478
- try {
1479
- const { baseUrl, apiKey } = await getFlowluCredentials(this);
1480
- const response = await flowluApiRequest.call(this, 'GET', baseUrl, '/api/v1/module/crm/pipeline_stage/list', apiKey);
1481
- if (response?.response?.items) {
1482
- return response.response.items.map((s) => ({ name: s.name || `Stage ${s.id}`, value: s.id.toString(), description: `Pipeline ID: ${s.pipeline_id}` }));
1483
- }
1484
- return [];
1485
- }
1486
- catch (error) {
1487
- return [];
1488
- }
1798
+ const { baseUrl, apiKey } = await getFlowluCredentials(this);
1799
+ const items = await flowluListAll.call(this, baseUrl, '/api/v1/module/crm/pipeline_stage/list', apiKey);
1800
+ return items.map((s) => ({
1801
+ name: s.name || `Stage ${s.id}`,
1802
+ value: s.id.toString(),
1803
+ description: `Pipeline ID: ${s.pipeline_id}`,
1804
+ }));
1489
1805
  },
1490
1806
  async getFilteredPipelineStages() {
1807
+ const { baseUrl, apiKey } = await getFlowluCredentials(this);
1808
+ let pipelineId;
1491
1809
  try {
1492
- const { baseUrl, apiKey } = await getFlowluCredentials(this);
1493
- let pipelineId;
1494
- try {
1495
- pipelineId = this.getNodeParameter('opportunityFilterPipeline');
1496
- }
1497
- catch { /* not set yet */ }
1498
- const response = await flowluApiRequest.call(this, 'GET', baseUrl, '/api/v1/module/crm/pipeline_stage/list', apiKey);
1499
- if (response?.response?.items) {
1500
- let stages = response.response.items;
1501
- if (pipelineId) {
1502
- stages = stages.filter((s) => s.pipeline_id?.toString() === pipelineId);
1503
- }
1504
- return stages.map((s) => ({ name: s.name || `Stage ${s.id}`, value: s.id.toString() }));
1505
- }
1506
- return [];
1810
+ pipelineId = this.getNodeParameter('opportunityFilterPipeline');
1507
1811
  }
1508
- catch (error) {
1509
- return [];
1812
+ catch {
1813
+ /* not set yet */
1510
1814
  }
1815
+ const items = await flowluListAll.call(this, baseUrl, '/api/v1/module/crm/pipeline_stage/list', apiKey);
1816
+ const stages = pipelineId
1817
+ ? items.filter((s) => s.pipeline_id?.toString() === pipelineId)
1818
+ : items;
1819
+ return stages.map((s) => ({ name: s.name || `Stage ${s.id}`, value: s.id.toString() }));
1511
1820
  },
1512
1821
  async getAllCrmAccounts() {
1513
- try {
1514
- const { baseUrl, apiKey } = await getFlowluCredentials(this);
1515
- const response = await flowluApiRequest.call(this, 'GET', baseUrl, '/api/v1/module/crm/account/list', apiKey);
1516
- if (response?.response?.items) {
1517
- return response.response.items.map((a) => {
1518
- const typeLabel = a.type === 1 ? '(Company)' : a.type === 2 ? '(Contact)' : '';
1519
- return { name: `${a.name || `#${a.id}`} ${typeLabel}`.trim(), value: a.id.toString() };
1520
- });
1521
- }
1522
- return [];
1523
- }
1524
- catch (error) {
1525
- return [];
1526
- }
1822
+ const { baseUrl, apiKey } = await getFlowluCredentials(this);
1823
+ const items = await flowluListAll.call(this, baseUrl, '/api/v1/module/crm/account/list', apiKey);
1824
+ return items.map((a) => {
1825
+ const typeLabel = a.type === 1 ? '(Company)' : a.type === 2 ? '(Contact)' : '';
1826
+ return { name: `${a.name || `#${a.id}`} ${typeLabel}`.trim(), value: a.id.toString() };
1827
+ });
1527
1828
  },
1528
1829
  async getOpportunitySources() {
1529
- try {
1530
- const { baseUrl, apiKey } = await getFlowluCredentials(this);
1531
- const response = await flowluApiRequest.call(this, 'GET', baseUrl, '/api/v1/module/crm/source/list', apiKey);
1532
- if (response?.response?.items) {
1533
- return response.response.items.map((s) => ({ name: s.name || `Source ${s.id}`, value: s.id.toString() }));
1534
- }
1535
- return [];
1536
- }
1537
- catch (error) {
1538
- return [];
1539
- }
1830
+ const { baseUrl, apiKey } = await getFlowluCredentials(this);
1831
+ const items = await flowluListAll.call(this, baseUrl, '/api/v1/module/crm/source/list', apiKey);
1832
+ return items.map((s) => ({
1833
+ name: s.name || `Source ${s.id}`,
1834
+ value: s.id.toString(),
1835
+ }));
1540
1836
  },
1541
1837
  async getPortfolios() {
1542
- try {
1543
- const { baseUrl, apiKey } = await getFlowluCredentials(this);
1544
- const response = await flowluApiRequest.call(this, 'GET', baseUrl, '/api/v1/module/st/portfolio/list', apiKey);
1545
- if (response?.response?.items) {
1546
- return response.response.items.map((p) => ({ name: p.name || `Portfolio ${p.id}`, value: p.id.toString() }));
1547
- }
1548
- return [];
1549
- }
1550
- catch (error) {
1551
- return [];
1552
- }
1838
+ const { baseUrl, apiKey } = await getFlowluCredentials(this);
1839
+ const items = await flowluListAll.call(this, baseUrl, '/api/v1/module/st/portfolio/list', apiKey);
1840
+ return items.map((p) => ({
1841
+ name: p.name || `Portfolio ${p.id}`,
1842
+ value: p.id.toString(),
1843
+ }));
1553
1844
  },
1554
1845
  async getProjectStages() {
1555
- try {
1556
- const { baseUrl, apiKey } = await getFlowluCredentials(this);
1557
- const response = await flowluApiRequest.call(this, 'GET', baseUrl, '/api/v1/module/st/stages/list', apiKey);
1558
- if (response?.response?.items) {
1559
- return response.response.items.map((s) => ({ name: s.name || `Stage ${s.id}`, value: s.id.toString() }));
1560
- }
1561
- return [];
1562
- }
1563
- catch (error) {
1564
- return [];
1565
- }
1846
+ const { baseUrl, apiKey } = await getFlowluCredentials(this);
1847
+ const items = await flowluListAll.call(this, baseUrl, '/api/v1/module/st/stages/list', apiKey);
1848
+ return items.map((s) => ({
1849
+ name: s.name || `Stage ${s.id}`,
1850
+ value: s.id.toString(),
1851
+ }));
1566
1852
  },
1567
1853
  async getRecordLists() {
1568
- try {
1569
- const { baseUrl, apiKey } = await getFlowluCredentials(this);
1570
- const response = await flowluApiRequest.call(this, 'GET', baseUrl, '/api/v1/module/customlists/lists/list', apiKey);
1571
- if (response?.response?.items) {
1572
- return response.response.items.map((l) => ({ name: l.name || `List ${l.id}`, value: l.id.toString() }));
1573
- }
1574
- return [];
1575
- }
1576
- catch (error) {
1577
- return [];
1578
- }
1854
+ const { baseUrl, apiKey } = await getFlowluCredentials(this);
1855
+ const items = await flowluListAll.call(this, baseUrl, '/api/v1/module/customlists/lists/list', apiKey);
1856
+ return items.map((l) => ({
1857
+ name: l.name || `List ${l.id}`,
1858
+ value: l.id.toString(),
1859
+ }));
1579
1860
  },
1580
1861
  async getRecordListFields() {
1862
+ const { baseUrl, apiKey } = await getFlowluCredentials(this);
1863
+ // Get the selected list ID from the current node parameters
1864
+ let listId;
1581
1865
  try {
1582
- const { baseUrl, apiKey } = await getFlowluCredentials(this);
1583
- // Get the selected list ID from the current node parameters
1584
- let listId;
1585
- try {
1586
- listId = this.getNodeParameter('recordListId');
1587
- }
1588
- catch {
1589
- return [];
1590
- }
1591
- if (!listId)
1592
- return [];
1593
- // Find the fieldset for this list (customlists/items with group_id matching list_id)
1594
- const fieldsetsResponse = await flowluApiRequest.call(this, 'GET', baseUrl, '/api/v1/module/customfields/fieldsets/list', apiKey);
1595
- const fieldsetIds = [];
1596
- if (fieldsetsResponse?.response?.items) {
1597
- for (const fs of fieldsetsResponse.response.items) {
1598
- if (fs.module === 'customlists' && fs.model === 'items' && fs.group_id?.toString() === listId) {
1599
- fieldsetIds.push(fs.id.toString());
1600
- }
1601
- }
1602
- }
1603
- if (fieldsetIds.length === 0)
1604
- return [];
1605
- // Get fields for those fieldsets
1606
- const fieldsResponse = await flowluApiRequest.call(this, 'GET', baseUrl, '/api/v1/module/customfields/fields/list', apiKey);
1607
- if (fieldsResponse?.response?.items) {
1608
- // Also fetch all fieldsets so we can resolve custom list link names
1609
- const allFieldsets = fieldsetsResponse?.response?.items || [];
1610
- return fieldsResponse.response.items
1611
- .filter((f) => fieldsetIds.includes(f.fieldset_id?.toString()))
1612
- .filter((f) => f.active !== 0)
1613
- .map((f) => {
1614
- const cfKey = (f.api_use_alias && f.alias) ? `cf_${f.alias}` : `cf_${f.id}`;
1615
- let label = f.name || cfKey;
1616
- let desc = '';
1617
- // Detect link fields (model.any) and show what they link to
1618
- if (f.type === 'model.any' && f.module && f.model) {
1619
- const linkTargetMap = {
1620
- 'st/project': 'Project',
1621
- 'crm/company': 'Company',
1622
- 'crm/contact': 'Contact',
1623
- 'crm/client': 'Account',
1624
- 'crm/leads': 'Opportunity',
1625
- 'system/user': 'User',
1626
- };
1627
- const key = `${f.module}/${f.model}`;
1628
- if (linkTargetMap[key]) {
1629
- label = `${f.name} → ${linkTargetMap[key]}`;
1630
- desc = `Enter the ${linkTargetMap[key]} ID`;
1631
- }
1632
- else if (f.module === 'customlists' && f.model === 'items' && f.group_id) {
1633
- // Link to another custom list - find its name
1634
- const targetList = allFieldsets.find((fs) => fs.module === 'customlists' && fs.model === 'items' && fs.group_id?.toString() === f.group_id.toString());
1635
- const listName = targetList?.name || `List #${f.group_id}`;
1636
- label = `${f.name} → ${listName}`;
1637
- desc = `Enter the Record ID from "${listName}"`;
1638
- }
1639
- else {
1640
- label = `${f.name} → ${f.module}/${f.model}`;
1641
- desc = `Enter the linked record ID`;
1642
- }
1643
- }
1644
- const option = { name: label, value: cfKey };
1645
- if (desc)
1646
- option.description = desc;
1647
- return option;
1648
- });
1649
- }
1650
- return [];
1866
+ listId = this.getNodeParameter('recordListId');
1651
1867
  }
1652
- catch (error) {
1868
+ catch {
1653
1869
  return [];
1654
1870
  }
1871
+ if (!listId)
1872
+ return [];
1873
+ // Find the fieldset for this list (customlists/items with group_id matching list_id)
1874
+ const allFieldsets = await flowluListAll.call(this, baseUrl, '/api/v1/module/customfields/fieldsets/list', apiKey);
1875
+ const fieldsetIds = new Set(allFieldsets
1876
+ .filter((fs) => fs.module === 'customlists' && fs.model === 'items' && fs.group_id?.toString() === listId)
1877
+ .map((fs) => fs.id.toString()));
1878
+ if (fieldsetIds.size === 0)
1879
+ return [];
1880
+ const allFields = await flowluListAll.call(this, baseUrl, '/api/v1/module/customfields/fields/list', apiKey);
1881
+ return allFields
1882
+ .filter((f) => fieldsetIds.has(f.fieldset_id?.toString()))
1883
+ .filter((f) => f.active !== 0)
1884
+ .map((f) => {
1885
+ const cfKey = f.api_use_alias && f.alias ? `cf_${f.alias}` : `cf_${f.id}`;
1886
+ let label = f.name || cfKey;
1887
+ let desc = '';
1888
+ // Detect link fields (model.any) and show what they link to
1889
+ if (f.type === 'model.any' && f.module && f.model) {
1890
+ const linkTargetMap = {
1891
+ 'st/project': 'Project',
1892
+ 'crm/company': 'Company',
1893
+ 'crm/contact': 'Contact',
1894
+ 'crm/client': 'Account',
1895
+ 'crm/leads': 'Opportunity',
1896
+ 'system/user': 'User',
1897
+ };
1898
+ const key = `${f.module}/${f.model}`;
1899
+ if (linkTargetMap[key]) {
1900
+ label = `${f.name} → ${linkTargetMap[key]}`;
1901
+ desc = `Enter the ${linkTargetMap[key]} ID`;
1902
+ }
1903
+ else if (f.module === 'customlists' && f.model === 'items' && f.group_id) {
1904
+ // Link to another custom list - find its name
1905
+ const targetList = allFieldsets.find((fs) => fs.module === 'customlists' &&
1906
+ fs.model === 'items' &&
1907
+ fs.group_id?.toString() === f.group_id.toString());
1908
+ const listName = targetList?.name || `List #${f.group_id}`;
1909
+ label = `${f.name} → ${listName}`;
1910
+ desc = `Enter the Record ID from "${listName}"`;
1911
+ }
1912
+ else {
1913
+ label = `${f.name} → ${f.module}/${f.model}`;
1914
+ desc = `Enter the linked record ID`;
1915
+ }
1916
+ }
1917
+ const option = { name: label, value: cfKey };
1918
+ if (desc)
1919
+ option.description = desc;
1920
+ return option;
1921
+ });
1655
1922
  },
1656
1923
  async getTaskCustomFields() {
1657
1924
  return loadCustomFieldsForEntity(this, 'task', 'task');
@@ -1692,8 +1959,7 @@ class Flowlu {
1692
1959
  if (!listId)
1693
1960
  return { fields: [] };
1694
1961
  // Get fieldsets to find this list's fieldset
1695
- const fieldsetsResponse = await flowluApiRequest.call(this, 'GET', baseUrl, '/api/v1/module/customfields/fieldsets/list', apiKey);
1696
- const allFieldsets = fieldsetsResponse?.response?.items || [];
1962
+ const allFieldsets = await flowluListAll.call(this, baseUrl, '/api/v1/module/customfields/fieldsets/list', apiKey);
1697
1963
  const fieldsetIds = [];
1698
1964
  for (const fs of allFieldsets) {
1699
1965
  if (fs.module === 'customlists' && fs.model === 'items' && fs.group_id?.toString() === listId) {
@@ -1703,9 +1969,7 @@ class Flowlu {
1703
1969
  if (fieldsetIds.length === 0)
1704
1970
  return { fields: [] };
1705
1971
  // Get all custom fields
1706
- const fieldsResponse = await flowluApiRequest.call(this, 'GET', baseUrl, '/api/v1/module/customfields/fields/list', apiKey);
1707
- if (!fieldsResponse?.response?.items)
1708
- return { fields: [] };
1972
+ const allRecordFields = await flowluListAll.call(this, baseUrl, '/api/v1/module/customfields/fields/list', apiKey);
1709
1973
  // Map of linked entity endpoints for fetching dropdown options
1710
1974
  const linkEndpoints = {
1711
1975
  'st/project': '/api/v1/module/st/projects/list',
@@ -1725,7 +1989,7 @@ class Flowlu {
1725
1989
  };
1726
1990
  // Pre-fetch linked record options for all link field types we encounter
1727
1991
  const linkOptionsCache = {};
1728
- const filteredFields = fieldsResponse.response.items
1992
+ const filteredFields = allRecordFields
1729
1993
  .filter((f) => fieldsetIds.includes(f.fieldset_id?.toString()))
1730
1994
  .filter((f) => f.active !== 0);
1731
1995
  for (const f of filteredFields) {
@@ -1742,8 +2006,8 @@ class Flowlu {
1742
2006
  qs['filter[type]'] = '1';
1743
2007
  if (key === 'system/user')
1744
2008
  qs['filter[role_login]'] = '1';
1745
- const resp = await flowluApiRequest.call(this, 'GET', baseUrl, linkEndpoints[key], apiKey, undefined, qs);
1746
- linkOptionsCache[key] = (resp?.response?.items || []).map((item) => ({
2009
+ const linked = await flowluListAll.call(this, baseUrl, linkEndpoints[key], apiKey, qs);
2010
+ linkOptionsCache[key] = linked.map((item) => ({
1747
2011
  name: item.name || item.email || `#${item.id}`,
1748
2012
  value: item.id,
1749
2013
  }));
@@ -1757,13 +2021,11 @@ class Flowlu {
1757
2021
  const clKey = `customlists/${f.group_id}`;
1758
2022
  if (!linkOptionsCache[clKey]) {
1759
2023
  try {
1760
- const resp = await flowluApiRequest.call(this, 'GET', baseUrl, '/api/v1/module/customlists/items/list', apiKey, undefined, {
1761
- 'filter[list_id]': f.group_id.toString(),
1762
- });
2024
+ const linked = await flowluListAll.call(this, baseUrl, '/api/v1/module/customlists/items/list', apiKey, { 'filter[list_id]': f.group_id.toString() });
1763
2025
  // Find the label field for this list
1764
2026
  const labelFieldId = f.group_label_field_id;
1765
- linkOptionsCache[clKey] = (resp?.response?.items || []).map((item) => {
1766
- const label = labelFieldId ? (item[`cf_${labelFieldId}`] || `#${item.id}`) : `#${item.id}`;
2027
+ linkOptionsCache[clKey] = linked.map((item) => {
2028
+ const label = labelFieldId ? item[`cf_${labelFieldId}`] || `#${item.id}` : `#${item.id}`;
1767
2029
  return { name: label, value: item.id };
1768
2030
  });
1769
2031
  }
@@ -1775,7 +2037,7 @@ class Flowlu {
1775
2037
  }
1776
2038
  }
1777
2039
  const fields = filteredFields.map((f) => {
1778
- const cfKey = (f.api_use_alias && f.alias) ? `cf_${f.alias}` : `cf_${f.id}`;
2040
+ const cfKey = f.api_use_alias && f.alias ? `cf_${f.alias}` : `cf_${f.id}`;
1779
2041
  let displayName = f.name || cfKey;
1780
2042
  let fieldType = 'string';
1781
2043
  let fieldOptions;
@@ -1797,7 +2059,9 @@ class Flowlu {
1797
2059
  .filter((o) => o.value)
1798
2060
  .map((o) => ({ name: o.value, value: o.id?.toString() || o.value }));
1799
2061
  }
1800
- catch { /* ignore parse errors */ }
2062
+ catch {
2063
+ /* ignore parse errors */
2064
+ }
1801
2065
  }
1802
2066
  // Link fields - use dropdown with fetched options
1803
2067
  if (f.type === 'model.any' && f.module && f.model) {
@@ -1806,12 +2070,12 @@ class Flowlu {
1806
2070
  displayName = `${f.name} → ${linkLabels[key]}`;
1807
2071
  }
1808
2072
  else if (f.module === 'customlists' && f.model === 'items' && f.group_id) {
1809
- const targetList = allFieldsets.find((fs) => fs.module === 'customlists' && fs.model === 'items' && fs.group_id?.toString() === f.group_id.toString());
2073
+ const targetList = allFieldsets.find((fs) => fs.module === 'customlists' &&
2074
+ fs.model === 'items' &&
2075
+ fs.group_id?.toString() === f.group_id.toString());
1810
2076
  displayName = `${f.name} → ${targetList?.name || `List #${f.group_id}`}`;
1811
2077
  }
1812
- const cacheKey = (f.module === 'customlists' && f.model === 'items' && f.group_id)
1813
- ? `customlists/${f.group_id}`
1814
- : key;
2078
+ const cacheKey = f.module === 'customlists' && f.model === 'items' && f.group_id ? `customlists/${f.group_id}` : key;
1815
2079
  if (linkOptionsCache[cacheKey]?.length) {
1816
2080
  fieldType = 'options';
1817
2081
  fieldOptions = linkOptionsCache[cacheKey];
@@ -1836,7 +2100,9 @@ class Flowlu {
1836
2100
  return { fields };
1837
2101
  }
1838
2102
  catch (error) {
1839
- return { fields: [] };
2103
+ if (error instanceof n8n_workflow_1.NodeOperationError)
2104
+ throw error;
2105
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Could not load Flowlu record-list fields: ${error.message}. If this is a rate limit, wait a moment and click Retry.`);
1840
2106
  }
1841
2107
  },
1842
2108
  },
@@ -1849,7 +2115,7 @@ class Flowlu {
1849
2115
  const operation = this.getNodeParameter('operation', 0);
1850
2116
  for (let i = 0; i < items.length; i++) {
1851
2117
  try {
1852
- const { baseUrl, apiKey } = await getFlowluCredentials(this);
2118
+ const { baseUrl, apiKey, defaultOwnerId } = await getFlowluCredentials(this);
1853
2119
  let responseData;
1854
2120
  // ============================
1855
2121
  // CONTACT
@@ -1869,15 +2135,32 @@ class Flowlu {
1869
2135
  body.phone = phone;
1870
2136
  const additional = this.getNodeParameter('contactAdditionalFields', i);
1871
2137
  const contactSimpleFields = [
1872
- 'middle_name', 'description', 'owner_id', 'web',
1873
- 'phone2', 'phone3', 'email_personal',
1874
- 'social_network_link_1', 'social_network_link_2',
1875
- 'social_network_link_3', 'social_network_link_4',
1876
- 'social_network_link_5', 'social_network_link_6',
1877
- 'address', 'shipping_address_line_1', 'shipping_city',
1878
- 'shipping_state', 'shipping_zip', 'shipping_country',
1879
- 'billing_address_line_1', 'billing_city', 'billing_state',
1880
- 'billing_zip', 'billing_country', 'VAT', 'timezone',
2138
+ 'middle_name',
2139
+ 'description',
2140
+ 'owner_id',
2141
+ 'web',
2142
+ 'phone2',
2143
+ 'phone3',
2144
+ 'email_personal',
2145
+ 'social_network_link_1',
2146
+ 'social_network_link_2',
2147
+ 'social_network_link_3',
2148
+ 'social_network_link_4',
2149
+ 'social_network_link_5',
2150
+ 'social_network_link_6',
2151
+ 'address',
2152
+ 'shipping_address_line_1',
2153
+ 'shipping_city',
2154
+ 'shipping_state',
2155
+ 'shipping_zip',
2156
+ 'shipping_country',
2157
+ 'billing_address_line_1',
2158
+ 'billing_city',
2159
+ 'billing_state',
2160
+ 'billing_zip',
2161
+ 'billing_country',
2162
+ 'VAT',
2163
+ 'timezone',
1881
2164
  ];
1882
2165
  for (const field of contactSimpleFields) {
1883
2166
  if (additional[field] !== undefined && additional[field] !== '') {
@@ -1910,7 +2193,7 @@ class Flowlu {
1910
2193
  if (filters.owner_id)
1911
2194
  qs['filter[owner_id]'] = filters.owner_id;
1912
2195
  responseData = await flowluApiRequest.call(this, 'GET', baseUrl, '/api/v1/module/crm/account/list', apiKey, undefined, qs);
1913
- for (const item of (responseData?.response?.items || [])) {
2196
+ for (const item of responseData?.response?.items || []) {
1914
2197
  returnData.push({ json: item });
1915
2198
  }
1916
2199
  continue;
@@ -1920,13 +2203,25 @@ class Flowlu {
1920
2203
  const updateFields = this.getNodeParameter('contactUpdateFields', i);
1921
2204
  const body = { id };
1922
2205
  const contactUpdateSimpleFields = [
1923
- 'first_name', 'last_name', 'middle_name', 'email', 'phone',
1924
- 'phone2', 'phone3', 'description', 'owner_id', 'web',
2206
+ 'first_name',
2207
+ 'last_name',
2208
+ 'middle_name',
2209
+ 'email',
2210
+ 'phone',
2211
+ 'phone2',
2212
+ 'phone3',
2213
+ 'description',
2214
+ 'owner_id',
2215
+ 'web',
1925
2216
  'email_personal',
1926
- 'social_network_link_1', 'social_network_link_2',
1927
- 'social_network_link_3', 'social_network_link_4',
1928
- 'social_network_link_5', 'social_network_link_6',
1929
- 'address', 'VAT',
2217
+ 'social_network_link_1',
2218
+ 'social_network_link_2',
2219
+ 'social_network_link_3',
2220
+ 'social_network_link_4',
2221
+ 'social_network_link_5',
2222
+ 'social_network_link_6',
2223
+ 'address',
2224
+ 'VAT',
1930
2225
  ];
1931
2226
  for (const field of contactUpdateSimpleFields) {
1932
2227
  if (updateFields[field] !== undefined && updateFields[field] !== '') {
@@ -1962,9 +2257,16 @@ class Flowlu {
1962
2257
  body.assignee_id = assignee;
1963
2258
  const additional = this.getNodeParameter('opportunityAdditionalFields', i);
1964
2259
  const oppSimpleFields = [
1965
- 'budget', 'description', 'contact_name', 'contact_email',
1966
- 'contact_phone', 'contact_mobile', 'contact_company',
1967
- 'contact_position', 'contact_web', 'source_id',
2260
+ 'budget',
2261
+ 'description',
2262
+ 'contact_name',
2263
+ 'contact_email',
2264
+ 'contact_phone',
2265
+ 'contact_mobile',
2266
+ 'contact_company',
2267
+ 'contact_position',
2268
+ 'contact_web',
2269
+ 'source_id',
1968
2270
  ];
1969
2271
  for (const field of oppSimpleFields) {
1970
2272
  if (additional[field] !== undefined && additional[field] !== '') {
@@ -2022,7 +2324,7 @@ class Flowlu {
2022
2324
  if (filters.active !== undefined)
2023
2325
  qs['filter[active]'] = filters.active.toString();
2024
2326
  responseData = await flowluApiRequest.call(this, 'GET', baseUrl, '/api/v1/module/crm/lead/list', apiKey, undefined, qs);
2025
- for (const item of (responseData?.response?.items || [])) {
2327
+ for (const item of responseData?.response?.items || []) {
2026
2328
  returnData.push({ json: item });
2027
2329
  }
2028
2330
  continue;
@@ -2032,9 +2334,17 @@ class Flowlu {
2032
2334
  const updateFields = this.getNodeParameter('opportunityUpdateFields', i);
2033
2335
  const body = { id };
2034
2336
  const oppUpdateFields = [
2035
- 'name', 'pipeline_id', 'pipeline_stage_id', 'assignee_id',
2036
- 'budget', 'description', 'active', 'closing_comment',
2037
- 'contact_name', 'contact_email', 'contact_phone',
2337
+ 'name',
2338
+ 'pipeline_id',
2339
+ 'pipeline_stage_id',
2340
+ 'assignee_id',
2341
+ 'budget',
2342
+ 'description',
2343
+ 'active',
2344
+ 'closing_comment',
2345
+ 'contact_name',
2346
+ 'contact_email',
2347
+ 'contact_phone',
2038
2348
  ];
2039
2349
  for (const field of oppUpdateFields) {
2040
2350
  if (updateFields[field] !== undefined && updateFields[field] !== '') {
@@ -2077,9 +2387,16 @@ class Flowlu {
2077
2387
  body.description = desc;
2078
2388
  const additional = this.getNodeParameter('projectAdditionalFields', i);
2079
2389
  const projSimpleFields = [
2080
- 'priority', 'estimated_revenue', 'estimated_expenses',
2081
- 'customer_id', 'customer_crm_contact_id', 'briefcase_id',
2082
- 'stage_id', 'tasks_workflow_id', 'billing_type', 'crm_lead_id',
2390
+ 'priority',
2391
+ 'estimated_revenue',
2392
+ 'estimated_expenses',
2393
+ 'customer_id',
2394
+ 'customer_crm_contact_id',
2395
+ 'briefcase_id',
2396
+ 'stage_id',
2397
+ 'tasks_workflow_id',
2398
+ 'billing_type',
2399
+ 'crm_lead_id',
2083
2400
  ];
2084
2401
  for (const field of projSimpleFields) {
2085
2402
  if (additional[field] !== undefined && additional[field] !== '' && additional[field] !== 0) {
@@ -2115,7 +2432,7 @@ class Flowlu {
2115
2432
  if (!filters.is_archive)
2116
2433
  qs['filter[is_archive]'] = '0';
2117
2434
  responseData = await flowluApiRequest.call(this, 'GET', baseUrl, '/api/v1/module/st/projects/list', apiKey, undefined, qs);
2118
- for (const item of (responseData?.response?.items || [])) {
2435
+ for (const item of responseData?.response?.items || []) {
2119
2436
  returnData.push({ json: item });
2120
2437
  }
2121
2438
  continue;
@@ -2125,8 +2442,13 @@ class Flowlu {
2125
2442
  const updateFields = this.getNodeParameter('projectUpdateFields', i);
2126
2443
  const body = { id };
2127
2444
  const projUpdateFields = [
2128
- 'name', 'description', 'manager_id', 'priority',
2129
- 'estimated_revenue', 'estimated_expenses', 'stage_id',
2445
+ 'name',
2446
+ 'description',
2447
+ 'manager_id',
2448
+ 'priority',
2449
+ 'estimated_revenue',
2450
+ 'estimated_expenses',
2451
+ 'stage_id',
2130
2452
  ];
2131
2453
  for (const field of projUpdateFields) {
2132
2454
  if (updateFields[field] !== undefined && updateFields[field] !== '') {
@@ -2179,7 +2501,7 @@ class Flowlu {
2179
2501
  if (filters.search)
2180
2502
  qs['search'] = filters.search;
2181
2503
  responseData = await flowluApiRequest.call(this, 'GET', baseUrl, '/api/v1/module/customlists/items/list', apiKey, undefined, qs);
2182
- for (const item of (responseData?.response?.items || [])) {
2504
+ for (const item of responseData?.response?.items || []) {
2183
2505
  returnData.push({ json: item });
2184
2506
  }
2185
2507
  continue;
@@ -2215,19 +2537,27 @@ class Flowlu {
2215
2537
  const additional = this.getNodeParameter('taskAdditionalFields', i);
2216
2538
  body.workflow_id = additional.workflow_id || '1';
2217
2539
  body.workflow_stage_id = additional.workflow_stage_id || '1';
2218
- if (additional.owner_id)
2219
- body.owner_id = additional.owner_id;
2220
- if (additional.contact_id)
2221
- body.crm_account_id = parseInt(additional.contact_id, 10);
2222
- if (additional.priority !== undefined)
2223
- body.priority = additional.priority;
2540
+ // Owner/Priority/Contact/Project/Start/End moved to top-level fields; fall back to
2541
+ // Additional Fields so task-create nodes built before the move keep working.
2542
+ const ownerId = this.getNodeParameter('owner_id', i, '') || additional.owner_id || defaultOwnerId;
2543
+ if (ownerId)
2544
+ body.owner_id = ownerId;
2545
+ const contactId = this.getNodeParameter('contact_id', i, '') || additional.contact_id;
2546
+ if (contactId)
2547
+ body.crm_account_id = parseInt(contactId, 10);
2548
+ body.priority =
2549
+ additional.priority !== undefined
2550
+ ? additional.priority
2551
+ : this.getNodeParameter('priority', i, 2);
2224
2552
  if (additional.status !== undefined)
2225
2553
  body.status = additional.status;
2226
- if (additional.plan_start_date) {
2227
- body.plan_start_date = new Date(additional.plan_start_date).toISOString().split('T')[0];
2554
+ const planStartDate = this.getNodeParameter('plan_start_date', i, '') || additional.plan_start_date;
2555
+ if (planStartDate) {
2556
+ body.plan_start_date = new Date(planStartDate).toISOString().split('T')[0];
2228
2557
  }
2229
- if (additional.deadline) {
2230
- body.deadline = new Date(additional.deadline).toISOString().split('T')[0];
2558
+ const deadline = this.getNodeParameter('deadline', i, '') || additional.deadline;
2559
+ if (deadline) {
2560
+ body.deadline = new Date(deadline).toISOString().split('T')[0];
2231
2561
  }
2232
2562
  if (additional.time_estimate && additional.time_estimate > 0) {
2233
2563
  body.time_estimate = Math.round(additional.time_estimate * 60);
@@ -2245,8 +2575,9 @@ class Flowlu {
2245
2575
  if (additional.parent_id && additional.parent_id > 0) {
2246
2576
  body.parent_id = additional.parent_id;
2247
2577
  }
2248
- if (additional.model_id) {
2249
- body.model_id = additional.model_id;
2578
+ const modelId = this.getNodeParameter('model_id', i, '') || additional.model_id;
2579
+ if (modelId) {
2580
+ body.model_id = modelId;
2250
2581
  body.model = 'project';
2251
2582
  body.module = 'st';
2252
2583
  }
@@ -2278,7 +2609,7 @@ class Flowlu {
2278
2609
  if (filters.status !== undefined)
2279
2610
  qs['filter[status]'] = filters.status.toString();
2280
2611
  responseData = await flowluApiRequest.call(this, 'GET', baseUrl, '/api/v1/module/task/tasks/list', apiKey, undefined, qs);
2281
- for (const item of (responseData?.response?.items || [])) {
2612
+ for (const item of responseData?.response?.items || []) {
2282
2613
  returnData.push({ json: item });
2283
2614
  }
2284
2615
  continue;
@@ -2349,14 +2680,14 @@ class Flowlu {
2349
2680
  const entityType = this.getNodeParameter('tagEntityType', i);
2350
2681
  const entityId = this.getNodeParameter('tagEntityId', i);
2351
2682
  responseData = await flowluApiRequest.call(this, 'GET', baseUrl, `/api/v1/module/${entityType}/${entityId}/global_tags/list`, apiKey);
2352
- for (const item of (responseData?.response?.items || [])) {
2683
+ for (const item of responseData?.response?.items || []) {
2353
2684
  returnData.push({ json: item });
2354
2685
  }
2355
2686
  continue;
2356
2687
  }
2357
2688
  else if (operation === 'listAll') {
2358
2689
  responseData = await flowluApiRequest.call(this, 'GET', baseUrl, '/api/v1/module/core/tag/list', apiKey);
2359
- for (const item of (responseData?.response?.items || [])) {
2690
+ for (const item of responseData?.response?.items || []) {
2360
2691
  returnData.push({ json: item });
2361
2692
  }
2362
2693
  continue;
@@ -2387,7 +2718,7 @@ class Flowlu {
2387
2718
  const entityId = this.getNodeParameter('commentEntityId', i);
2388
2719
  const limit = this.getNodeParameter('limit', i);
2389
2720
  responseData = await flowluApiRequest.call(this, 'GET', baseUrl, `/api/v1/module/${entityType}/${entityId}/comments/list`, apiKey, undefined, { limit: limit.toString() });
2390
- for (const item of (responseData?.response?.items || [])) {
2721
+ for (const item of responseData?.response?.items || []) {
2391
2722
  returnData.push({ json: item });
2392
2723
  }
2393
2724
  continue;