@trg-admin/n8n-nodes-zoho-desk 0.1.14 → 0.1.16

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.
@@ -4,3 +4,7 @@ export declare function zohoDeskApiRequest(this: IExecuteFunctions | ILoadOption
4
4
  * Make an API request to paginate through all items
5
5
  */
6
6
  export declare function zohoDeskApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, method: IHttpRequestMethods, endpoint: string, body?: IDataObject, qs?: IDataObject, itemIndex?: number): Promise<any>;
7
+ /**
8
+ * Helper to look up a contact by email address in Zoho Desk
9
+ */
10
+ export declare function getContactByEmail(this: IExecuteFunctions | ILoadOptionsFunctions, email: string, itemIndex?: number): Promise<IDataObject>;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.zohoDeskApiRequest = zohoDeskApiRequest;
4
4
  exports.zohoDeskApiRequestAllItems = zohoDeskApiRequestAllItems;
5
+ exports.getContactByEmail = getContactByEmail;
5
6
  const n8n_workflow_1 = require("n8n-workflow");
6
7
  function getDeskBaseUrl(tokenUrl = '', apiDomain = '', overrideBaseUrl = '') {
7
8
  if (overrideBaseUrl) {
@@ -102,3 +103,23 @@ async function zohoDeskApiRequestAllItems(method, endpoint, body = {}, qs = {},
102
103
  } while (true);
103
104
  return returnData;
104
105
  }
106
+ /**
107
+ * Helper to look up a contact by email address in Zoho Desk
108
+ */
109
+ async function getContactByEmail(email, itemIndex = 0) {
110
+ const trimmedEmail = email.trim();
111
+ if (!trimmedEmail) {
112
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), new Error('Contact Email cannot be empty'), { itemIndex });
113
+ }
114
+ const responseData = await zohoDeskApiRequest.call(this, 'GET', '/contacts/search', {}, { email: trimmedEmail, searchStr: trimmedEmail }, itemIndex);
115
+ const contacts = Array.isArray(responseData.data)
116
+ ? responseData.data
117
+ : Array.isArray(responseData)
118
+ ? responseData
119
+ : [];
120
+ if (contacts.length === 0) {
121
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), new Error(`No contact found with email "${trimmedEmail}" in Zoho Desk`), { itemIndex });
122
+ }
123
+ const exactMatch = contacts.find((c) => String(c.email || '').toLowerCase() === trimmedEmail.toLowerCase());
124
+ return exactMatch || contacts[0];
125
+ }
@@ -159,7 +159,7 @@ class ZohoDesk {
159
159
  name: 'contactId',
160
160
  type: 'string',
161
161
  default: '',
162
- description: 'ID of existing contact (alternative to email)',
162
+ description: 'ID of existing contact (or enter email address)',
163
163
  },
164
164
  {
165
165
  displayName: 'Task ID',
@@ -169,13 +169,38 @@ class ZohoDesk {
169
169
  description: 'Custom field Task ID (maps to cf.cf_task_id)',
170
170
  },
171
171
  {
172
- displayName: 'Custom Fields (JSON)',
173
- name: 'customFieldsJson',
174
- type: 'string',
175
- typeOptions: { alwaysOpenEditWindow: true },
176
- default: '',
177
- placeholder: '{"cf_serial_number":"SN-99821","cf_warranty_expiry":"2025-12-31"}',
178
- description: 'Additional custom fields to update, as a JSON object with Zoho API names (for example cf_serial_number). Sent as cf in the request payload.',
172
+ displayName: 'Custom Fields',
173
+ name: 'customFieldsList',
174
+ type: 'fixedCollection',
175
+ typeOptions: {
176
+ multipleValues: true,
177
+ },
178
+ default: {},
179
+ placeholder: 'Add Custom Field',
180
+ description: 'Add one or more custom fields by API name and value',
181
+ options: [
182
+ {
183
+ name: 'field',
184
+ displayName: 'Field',
185
+ values: [
186
+ {
187
+ displayName: 'Field API Name',
188
+ name: 'fieldName',
189
+ type: 'string',
190
+ default: '',
191
+ placeholder: 'cf_serial_number',
192
+ description: 'Zoho custom field API name (for example cf_serial_number)',
193
+ },
194
+ {
195
+ displayName: 'Value',
196
+ name: 'fieldValue',
197
+ type: 'string',
198
+ default: '',
199
+ description: 'Value to set for this custom field',
200
+ },
201
+ ],
202
+ },
203
+ ],
179
204
  },
180
205
  {
181
206
  displayName: 'Due Date',
@@ -223,14 +248,61 @@ class ZohoDesk {
223
248
  description: 'Whether to return all results or limit to specified number',
224
249
  },
225
250
  // ================== Contact Resource Parameters ==================
251
+ {
252
+ displayName: 'By',
253
+ name: 'by',
254
+ type: 'options',
255
+ options: [
256
+ {
257
+ name: 'Contact ID',
258
+ value: 'id',
259
+ description: 'Identify contact by Zoho Desk Contact ID',
260
+ },
261
+ {
262
+ name: 'Email',
263
+ value: 'email',
264
+ description: 'Identify contact by Email Address',
265
+ },
266
+ ],
267
+ default: 'id',
268
+ displayOptions: {
269
+ show: {
270
+ resource: ['contact'],
271
+ operation: ['get', 'update'],
272
+ },
273
+ },
274
+ description: 'How to identify the contact',
275
+ },
226
276
  {
227
277
  displayName: 'Contact ID',
228
278
  name: 'contactId',
229
279
  type: 'string',
230
280
  default: '',
231
281
  required: true,
232
- displayOptions: { show: { resource: ['contact'], operation: ['get', 'update'] } },
233
- description: 'The unique ID of the contact',
282
+ displayOptions: {
283
+ show: {
284
+ resource: ['contact'],
285
+ operation: ['get', 'update'],
286
+ by: ['id'],
287
+ },
288
+ },
289
+ description: 'The unique ID of the contact. You can also enter an email address here.',
290
+ },
291
+ {
292
+ displayName: 'Contact Email',
293
+ name: 'contactEmail',
294
+ type: 'string',
295
+ placeholder: 'e.g. name@example.com',
296
+ default: '',
297
+ required: true,
298
+ displayOptions: {
299
+ show: {
300
+ resource: ['contact'],
301
+ operation: ['get', 'update'],
302
+ by: ['email'],
303
+ },
304
+ },
305
+ description: 'Email address of the contact',
234
306
  },
235
307
  {
236
308
  displayName: 'Last Name',
@@ -402,7 +474,7 @@ class ZohoDesk {
402
474
  async execute() {
403
475
  const items = this.getInputData();
404
476
  const returnData = [];
405
- const parseCustomFields = (raw, itemIndex) => {
477
+ const parseLegacyCustomFieldsJson = (raw, itemIndex) => {
406
478
  if (!raw)
407
479
  return {};
408
480
  if (typeof raw === 'object' && raw !== null) {
@@ -425,6 +497,23 @@ class ZohoDesk {
425
497
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), new Error(`Invalid Custom Fields (JSON): ${error.message}`), { itemIndex });
426
498
  }
427
499
  };
500
+ const parseCustomFieldEntries = (raw) => {
501
+ if (!raw || typeof raw !== 'object') {
502
+ return {};
503
+ }
504
+ const fieldRows = Array.isArray(raw.field)
505
+ ? raw.field
506
+ : [];
507
+ const customFields = {};
508
+ for (const row of fieldRows) {
509
+ const fieldName = String(row.fieldName ?? '').trim();
510
+ if (!fieldName) {
511
+ continue;
512
+ }
513
+ customFields[fieldName] = row.fieldValue ?? '';
514
+ }
515
+ return customFields;
516
+ };
428
517
  for (let i = 0; i < items.length; i++) {
429
518
  const resource = this.getNodeParameter('resource', i);
430
519
  const operation = this.getNodeParameter('operation', i);
@@ -462,8 +551,9 @@ class ZohoDesk {
462
551
  const departmentId = this.getNodeParameter('departmentId', i);
463
552
  const description = this.getNodeParameter('description', i, '');
464
553
  const additionalFields = this.getNodeParameter('additionalFields', i, {});
465
- const { cfTaskId, customFieldsJson, ...otherAdditionalFields } = additionalFields;
466
- const parsedCustomFields = parseCustomFields(customFieldsJson, i);
554
+ const { cfTaskId, customFieldsList, customFieldsJson, ...otherAdditionalFields } = additionalFields;
555
+ const legacyCustomFields = parseLegacyCustomFieldsJson(customFieldsJson, i);
556
+ const listCustomFields = parseCustomFieldEntries(customFieldsList);
467
557
  const body = {
468
558
  subject,
469
559
  email,
@@ -471,7 +561,8 @@ class ZohoDesk {
471
561
  ...otherAdditionalFields,
472
562
  };
473
563
  const customFields = {
474
- ...parsedCustomFields,
564
+ ...legacyCustomFields,
565
+ ...listCustomFields,
475
566
  };
476
567
  if (cfTaskId) {
477
568
  customFields.cf_task_id = cfTaskId;
@@ -482,6 +573,10 @@ class ZohoDesk {
482
573
  if (description) {
483
574
  body.description = description;
484
575
  }
576
+ if (body.contactId && typeof body.contactId === 'string' && body.contactId.includes('@')) {
577
+ const contact = await GenericFunctions_1.getContactByEmail.call(this, body.contactId, i);
578
+ body.contactId = contact.id;
579
+ }
485
580
  const responseData = await GenericFunctions_1.zohoDeskApiRequest.call(this, 'POST', '/tickets', body, {}, i);
486
581
  returnData.push({ json: responseData });
487
582
  }
@@ -489,13 +584,15 @@ class ZohoDesk {
489
584
  const ticketId = this.getNodeParameter('ticketId', i);
490
585
  const description = this.getNodeParameter('description', i, '');
491
586
  const additionalFields = this.getNodeParameter('additionalFields', i, {});
492
- const { cfTaskId, customFieldsJson, ...otherAdditionalFields } = additionalFields;
493
- const parsedCustomFields = parseCustomFields(customFieldsJson, i);
587
+ const { cfTaskId, customFieldsList, customFieldsJson, ...otherAdditionalFields } = additionalFields;
588
+ const legacyCustomFields = parseLegacyCustomFieldsJson(customFieldsJson, i);
589
+ const listCustomFields = parseCustomFieldEntries(customFieldsList);
494
590
  const body = {
495
591
  ...otherAdditionalFields,
496
592
  };
497
593
  const customFields = {
498
- ...parsedCustomFields,
594
+ ...legacyCustomFields,
595
+ ...listCustomFields,
499
596
  };
500
597
  if (cfTaskId) {
501
598
  customFields.cf_task_id = cfTaskId;
@@ -506,6 +603,10 @@ class ZohoDesk {
506
603
  if (description) {
507
604
  body.description = description;
508
605
  }
606
+ if (body.contactId && typeof body.contactId === 'string' && body.contactId.includes('@')) {
607
+ const contact = await GenericFunctions_1.getContactByEmail.call(this, body.contactId, i);
608
+ body.contactId = contact.id;
609
+ }
509
610
  const responseData = await GenericFunctions_1.zohoDeskApiRequest.call(this, 'PATCH', `/tickets/${ticketId}`, body, {}, i);
510
611
  returnData.push({ json: responseData });
511
612
  }
@@ -513,8 +614,25 @@ class ZohoDesk {
513
614
  // ==================== CONTACT OPERATIONS ====================
514
615
  if (resource === 'contact') {
515
616
  if (operation === 'get') {
516
- const contactId = this.getNodeParameter('contactId', i);
517
- const responseData = await GenericFunctions_1.zohoDeskApiRequest.call(this, 'GET', `/contacts/${contactId}`, {}, {}, i);
617
+ const by = this.getNodeParameter('by', i, 'id');
618
+ let identifier = '';
619
+ let isEmail = by === 'email';
620
+ if (by === 'email') {
621
+ identifier = this.getNodeParameter('contactEmail', i);
622
+ }
623
+ else {
624
+ identifier = this.getNodeParameter('contactId', i);
625
+ if (identifier.includes('@')) {
626
+ isEmail = true;
627
+ }
628
+ }
629
+ let responseData;
630
+ if (isEmail) {
631
+ responseData = await GenericFunctions_1.getContactByEmail.call(this, identifier, i);
632
+ }
633
+ else {
634
+ responseData = (await GenericFunctions_1.zohoDeskApiRequest.call(this, 'GET', `/contacts/${identifier}`, {}, {}, i));
635
+ }
518
636
  returnData.push({ json: responseData });
519
637
  }
520
638
  if (operation === 'getAll') {
@@ -549,9 +667,25 @@ class ZohoDesk {
549
667
  returnData.push({ json: responseData });
550
668
  }
551
669
  if (operation === 'update') {
552
- const contactId = this.getNodeParameter('contactId', i);
670
+ const by = this.getNodeParameter('by', i, 'id');
671
+ let identifier = '';
672
+ let isEmail = by === 'email';
673
+ if (by === 'email') {
674
+ identifier = this.getNodeParameter('contactEmail', i);
675
+ }
676
+ else {
677
+ identifier = this.getNodeParameter('contactId', i);
678
+ if (identifier.includes('@')) {
679
+ isEmail = true;
680
+ }
681
+ }
682
+ let targetContactId = identifier;
683
+ if (isEmail) {
684
+ const contact = await GenericFunctions_1.getContactByEmail.call(this, identifier, i);
685
+ targetContactId = contact.id;
686
+ }
553
687
  const additionalFields = this.getNodeParameter('additionalFields', i, {});
554
- const responseData = await GenericFunctions_1.zohoDeskApiRequest.call(this, 'PATCH', `/contacts/${contactId}`, additionalFields, {}, i);
688
+ const responseData = await GenericFunctions_1.zohoDeskApiRequest.call(this, 'PATCH', `/contacts/${targetContactId}`, additionalFields, {}, i);
555
689
  returnData.push({ json: responseData });
556
690
  }
557
691
  if (operation === 'search') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trg-admin/n8n-nodes-zoho-desk",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "Community n8n node starter for Zoho Desk using built-in Zoho OAuth2 auth behavior",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",