@slates-integrations/zoho 0.2.0-rc.5
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.
- package/README.md +11 -0
- package/docs/SPEC.md +155 -0
- package/logo.png +0 -0
- package/package.json +19 -0
- package/slate.json +17 -0
- package/src/auth.ts +339 -0
- package/src/config.ts +11 -0
- package/src/index.ts +44 -0
- package/src/lib/client.ts +733 -0
- package/src/lib/errors.ts +90 -0
- package/src/lib/urls.ts +77 -0
- package/src/spec.ts +12 -0
- package/src/tools/books-get-invoices.ts +114 -0
- package/src/tools/books-manage-contact.ts +168 -0
- package/src/tools/books-manage-expense.ts +142 -0
- package/src/tools/books-manage-invoice.ts +190 -0
- package/src/tools/crm-get-modules.ts +93 -0
- package/src/tools/crm-get-records.ts +137 -0
- package/src/tools/crm-get-related-records.ts +104 -0
- package/src/tools/crm-manage-record.ts +117 -0
- package/src/tools/crm-search-records.ts +109 -0
- package/src/tools/desk-get-tickets.ts +146 -0
- package/src/tools/desk-manage-contact.ts +125 -0
- package/src/tools/desk-manage-ticket.ts +126 -0
- package/src/tools/index.ts +16 -0
- package/src/tools/people-manage-employee.ts +163 -0
- package/src/tools/projects-get-portals.ts +50 -0
- package/src/tools/projects-manage-project.ts +164 -0
- package/src/tools/projects-manage-task.ts +108 -0
- package/src/triggers/crm-record-events.ts +143 -0
- package/src/triggers/desk-events.ts +121 -0
- package/src/triggers/index.ts +2 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { SlateTool } from 'slates';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { spec } from '../spec';
|
|
4
|
+
import { ZohoBooksClient } from '../lib/client';
|
|
5
|
+
import type { Datacenter } from '../lib/urls';
|
|
6
|
+
import { zohoServiceError } from '../lib/errors';
|
|
7
|
+
|
|
8
|
+
let lineItemSchema = z.object({
|
|
9
|
+
itemId: z.string().optional().describe('Item ID from Zoho Books inventory'),
|
|
10
|
+
name: z.string().optional().describe('Item name'),
|
|
11
|
+
description: z.string().optional().describe('Item description'),
|
|
12
|
+
rate: z.number().optional().describe('Unit price'),
|
|
13
|
+
quantity: z.number().optional().describe('Quantity'),
|
|
14
|
+
discount: z.string().optional().describe('Discount percentage or flat amount'),
|
|
15
|
+
taxId: z.string().optional().describe('Tax ID to apply')
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export let booksManageInvoice = SlateTool.create(spec, {
|
|
19
|
+
name: 'Books Manage Invoice',
|
|
20
|
+
key: 'books_manage_invoice',
|
|
21
|
+
description: `Create, update, delete, or change the status of invoices in Zoho Books. Supports setting customer, line items, payment terms, notes, and more. Can also mark invoices as sent, void, or draft.`,
|
|
22
|
+
instructions: [
|
|
23
|
+
'The organizationId is required for all Zoho Books operations.',
|
|
24
|
+
'For create, provide customerId and at least one line item.',
|
|
25
|
+
'For status changes (sent, void, draft), use the statusAction parameter.',
|
|
26
|
+
'Use the "list" action with a Books contact ID in customerId to list invoices for a specific customer.'
|
|
27
|
+
],
|
|
28
|
+
tags: {
|
|
29
|
+
destructive: true
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
.input(
|
|
33
|
+
z.object({
|
|
34
|
+
organizationId: z.string().describe('Zoho Books organization ID'),
|
|
35
|
+
action: z
|
|
36
|
+
.enum(['create', 'update', 'delete', 'status'])
|
|
37
|
+
.describe('Operation to perform'),
|
|
38
|
+
invoiceId: z
|
|
39
|
+
.string()
|
|
40
|
+
.optional()
|
|
41
|
+
.describe('Invoice ID (required for update, delete, status)'),
|
|
42
|
+
customerId: z.string().optional().describe('Customer/contact ID (required for create)'),
|
|
43
|
+
invoiceNumber: z.string().optional().describe('Custom invoice number'),
|
|
44
|
+
date: z.string().optional().describe('Invoice date (YYYY-MM-DD)'),
|
|
45
|
+
dueDate: z.string().optional().describe('Due date (YYYY-MM-DD)'),
|
|
46
|
+
lineItems: z.array(lineItemSchema).optional().describe('Invoice line items'),
|
|
47
|
+
notes: z.string().optional().describe('Notes to appear on the invoice'),
|
|
48
|
+
terms: z.string().optional().describe('Terms and conditions'),
|
|
49
|
+
discount: z
|
|
50
|
+
.string()
|
|
51
|
+
.optional()
|
|
52
|
+
.describe('Discount at the invoice level (e.g., "10%" or "50.00")'),
|
|
53
|
+
statusAction: z
|
|
54
|
+
.enum(['sent', 'void', 'draft'])
|
|
55
|
+
.optional()
|
|
56
|
+
.describe('Status action to apply (for action="status")'),
|
|
57
|
+
customFields: z
|
|
58
|
+
.array(
|
|
59
|
+
z.object({
|
|
60
|
+
label: z.string(),
|
|
61
|
+
value: z.string()
|
|
62
|
+
})
|
|
63
|
+
)
|
|
64
|
+
.optional()
|
|
65
|
+
.describe('Custom field values')
|
|
66
|
+
})
|
|
67
|
+
)
|
|
68
|
+
.output(
|
|
69
|
+
z.object({
|
|
70
|
+
invoiceId: z.string().optional(),
|
|
71
|
+
invoiceNumber: z.string().optional(),
|
|
72
|
+
status: z.string().optional(),
|
|
73
|
+
total: z.number().optional(),
|
|
74
|
+
balance: z.number().optional(),
|
|
75
|
+
message: z.string().optional(),
|
|
76
|
+
deleted: z.boolean().optional()
|
|
77
|
+
})
|
|
78
|
+
)
|
|
79
|
+
.handleInvocation(async ctx => {
|
|
80
|
+
let dc = (ctx.auth.datacenter || ctx.config.datacenter || 'us') as Datacenter;
|
|
81
|
+
let client = new ZohoBooksClient({
|
|
82
|
+
token: ctx.auth.token,
|
|
83
|
+
datacenter: dc,
|
|
84
|
+
organizationId: ctx.input.organizationId
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
if (ctx.input.action === 'create') {
|
|
88
|
+
if (!ctx.input.customerId) throw zohoServiceError('customerId is required for create');
|
|
89
|
+
if (!ctx.input.lineItems?.length)
|
|
90
|
+
throw zohoServiceError('lineItems is required for create');
|
|
91
|
+
let data: Record<string, any> = {};
|
|
92
|
+
if (ctx.input.customerId) data.customer_id = ctx.input.customerId;
|
|
93
|
+
if (ctx.input.invoiceNumber) data.invoice_number = ctx.input.invoiceNumber;
|
|
94
|
+
if (ctx.input.date) data.date = ctx.input.date;
|
|
95
|
+
if (ctx.input.dueDate) data.due_date = ctx.input.dueDate;
|
|
96
|
+
if (ctx.input.notes) data.notes = ctx.input.notes;
|
|
97
|
+
if (ctx.input.terms) data.terms = ctx.input.terms;
|
|
98
|
+
if (ctx.input.discount) data.discount = ctx.input.discount;
|
|
99
|
+
if (ctx.input.lineItems) {
|
|
100
|
+
data.line_items = ctx.input.lineItems.map(item => ({
|
|
101
|
+
item_id: item.itemId,
|
|
102
|
+
name: item.name,
|
|
103
|
+
description: item.description,
|
|
104
|
+
rate: item.rate,
|
|
105
|
+
quantity: item.quantity,
|
|
106
|
+
discount: item.discount,
|
|
107
|
+
tax_id: item.taxId
|
|
108
|
+
}));
|
|
109
|
+
}
|
|
110
|
+
if (ctx.input.customFields) data.custom_fields = ctx.input.customFields;
|
|
111
|
+
|
|
112
|
+
let result = await client.createInvoice(data);
|
|
113
|
+
let inv = result?.invoice;
|
|
114
|
+
return {
|
|
115
|
+
output: {
|
|
116
|
+
invoiceId: inv?.invoice_id,
|
|
117
|
+
invoiceNumber: inv?.invoice_number,
|
|
118
|
+
status: inv?.status,
|
|
119
|
+
total: inv?.total,
|
|
120
|
+
balance: inv?.balance,
|
|
121
|
+
message: result?.message
|
|
122
|
+
},
|
|
123
|
+
message: `Created invoice **${inv?.invoice_number}** for **${inv?.total}** (${inv?.status}).`
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (ctx.input.action === 'update') {
|
|
128
|
+
if (!ctx.input.invoiceId) throw zohoServiceError('invoiceId is required for update');
|
|
129
|
+
let data: Record<string, any> = {};
|
|
130
|
+
if (ctx.input.customerId) data.customer_id = ctx.input.customerId;
|
|
131
|
+
if (ctx.input.date) data.date = ctx.input.date;
|
|
132
|
+
if (ctx.input.dueDate) data.due_date = ctx.input.dueDate;
|
|
133
|
+
if (ctx.input.notes) data.notes = ctx.input.notes;
|
|
134
|
+
if (ctx.input.terms) data.terms = ctx.input.terms;
|
|
135
|
+
if (ctx.input.discount) data.discount = ctx.input.discount;
|
|
136
|
+
if (ctx.input.lineItems) {
|
|
137
|
+
data.line_items = ctx.input.lineItems.map(item => ({
|
|
138
|
+
item_id: item.itemId,
|
|
139
|
+
name: item.name,
|
|
140
|
+
description: item.description,
|
|
141
|
+
rate: item.rate,
|
|
142
|
+
quantity: item.quantity,
|
|
143
|
+
discount: item.discount,
|
|
144
|
+
tax_id: item.taxId
|
|
145
|
+
}));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let result = await client.updateInvoice(ctx.input.invoiceId, data);
|
|
149
|
+
let inv = result?.invoice;
|
|
150
|
+
return {
|
|
151
|
+
output: {
|
|
152
|
+
invoiceId: inv?.invoice_id,
|
|
153
|
+
invoiceNumber: inv?.invoice_number,
|
|
154
|
+
status: inv?.status,
|
|
155
|
+
total: inv?.total,
|
|
156
|
+
balance: inv?.balance,
|
|
157
|
+
message: result?.message
|
|
158
|
+
},
|
|
159
|
+
message: `Updated invoice **${inv?.invoice_number || ctx.input.invoiceId}**.`
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (ctx.input.action === 'delete') {
|
|
164
|
+
if (!ctx.input.invoiceId) throw zohoServiceError('invoiceId is required for delete');
|
|
165
|
+
let result = await client.deleteInvoice(ctx.input.invoiceId);
|
|
166
|
+
return {
|
|
167
|
+
output: { invoiceId: ctx.input.invoiceId, deleted: true, message: result?.message },
|
|
168
|
+
message: `Deleted invoice **${ctx.input.invoiceId}**.`
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (ctx.input.action === 'status') {
|
|
173
|
+
if (!ctx.input.invoiceId)
|
|
174
|
+
throw zohoServiceError('invoiceId is required for status change');
|
|
175
|
+
if (!ctx.input.statusAction)
|
|
176
|
+
throw zohoServiceError('statusAction is required for status change');
|
|
177
|
+
let result = await client.markInvoiceStatus(ctx.input.invoiceId, ctx.input.statusAction);
|
|
178
|
+
return {
|
|
179
|
+
output: {
|
|
180
|
+
invoiceId: ctx.input.invoiceId,
|
|
181
|
+
status: ctx.input.statusAction,
|
|
182
|
+
message: result?.message
|
|
183
|
+
},
|
|
184
|
+
message: `Marked invoice **${ctx.input.invoiceId}** as **${ctx.input.statusAction}**.`
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
throw zohoServiceError('Invalid Books invoice action.');
|
|
189
|
+
})
|
|
190
|
+
.build();
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { SlateTool } from 'slates';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { spec } from '../spec';
|
|
4
|
+
import { ZohoCrmClient } from '../lib/client';
|
|
5
|
+
import type { Datacenter } from '../lib/urls';
|
|
6
|
+
|
|
7
|
+
export let crmGetModules = SlateTool.create(spec, {
|
|
8
|
+
name: 'CRM Get Modules',
|
|
9
|
+
key: 'crm_get_modules',
|
|
10
|
+
description: `List all available modules in Zoho CRM along with their metadata. Returns module API names, labels, capabilities (creatable, viewable, editable, deletable), and status. Also supports listing CRM users.`,
|
|
11
|
+
tags: {
|
|
12
|
+
readOnly: true
|
|
13
|
+
}
|
|
14
|
+
})
|
|
15
|
+
.input(
|
|
16
|
+
z.object({
|
|
17
|
+
status: z
|
|
18
|
+
.string()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe('Filter modules by status, e.g. "visible" or "user_hidden,system_hidden"'),
|
|
21
|
+
includeUsers: z.boolean().optional().describe('Also fetch and include CRM user list'),
|
|
22
|
+
userType: z
|
|
23
|
+
.string()
|
|
24
|
+
.optional()
|
|
25
|
+
.describe('Filter users by type (e.g., "AllUsers", "ActiveUsers", "AdminUsers")')
|
|
26
|
+
})
|
|
27
|
+
)
|
|
28
|
+
.output(
|
|
29
|
+
z.object({
|
|
30
|
+
modules: z
|
|
31
|
+
.array(
|
|
32
|
+
z.object({
|
|
33
|
+
moduleId: z.string().optional(),
|
|
34
|
+
apiName: z.string(),
|
|
35
|
+
pluralLabel: z.string().optional(),
|
|
36
|
+
singularLabel: z.string().optional(),
|
|
37
|
+
creatable: z.boolean().optional(),
|
|
38
|
+
viewable: z.boolean().optional(),
|
|
39
|
+
editable: z.boolean().optional(),
|
|
40
|
+
deletable: z.boolean().optional(),
|
|
41
|
+
apiSupported: z.boolean().optional()
|
|
42
|
+
})
|
|
43
|
+
)
|
|
44
|
+
.describe('Available CRM modules'),
|
|
45
|
+
users: z
|
|
46
|
+
.array(
|
|
47
|
+
z.object({
|
|
48
|
+
userId: z.string().optional(),
|
|
49
|
+
fullName: z.string().optional(),
|
|
50
|
+
email: z.string().optional(),
|
|
51
|
+
role: z.string().optional(),
|
|
52
|
+
status: z.string().optional()
|
|
53
|
+
})
|
|
54
|
+
)
|
|
55
|
+
.optional()
|
|
56
|
+
.describe('CRM users (if includeUsers is true)')
|
|
57
|
+
})
|
|
58
|
+
)
|
|
59
|
+
.handleInvocation(async ctx => {
|
|
60
|
+
let dc = (ctx.auth.datacenter || ctx.config.datacenter || 'us') as Datacenter;
|
|
61
|
+
let client = new ZohoCrmClient({ token: ctx.auth.token, datacenter: dc });
|
|
62
|
+
|
|
63
|
+
let modulesResult = await client.getModules({ status: ctx.input.status });
|
|
64
|
+
let modules = (modulesResult?.modules || []).map((m: any) => ({
|
|
65
|
+
moduleId: m.id,
|
|
66
|
+
apiName: m.api_name,
|
|
67
|
+
pluralLabel: m.plural_label,
|
|
68
|
+
singularLabel: m.singular_label,
|
|
69
|
+
creatable: m.creatable,
|
|
70
|
+
viewable: m.viewable,
|
|
71
|
+
editable: m.editable,
|
|
72
|
+
deletable: m.deletable,
|
|
73
|
+
apiSupported: m.api_supported
|
|
74
|
+
}));
|
|
75
|
+
|
|
76
|
+
let users: any[] | undefined;
|
|
77
|
+
if (ctx.input.includeUsers) {
|
|
78
|
+
let usersResult = await client.getUsers({ type: ctx.input.userType });
|
|
79
|
+
users = (usersResult?.users || []).map((u: any) => ({
|
|
80
|
+
userId: u.id,
|
|
81
|
+
fullName: u.full_name,
|
|
82
|
+
email: u.email,
|
|
83
|
+
role: u.role?.name,
|
|
84
|
+
status: u.status
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
output: { modules, users },
|
|
90
|
+
message: `Found **${modules.length}** CRM modules${users ? ` and **${users.length}** users` : ''}.`
|
|
91
|
+
};
|
|
92
|
+
})
|
|
93
|
+
.build();
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { SlateTool } from 'slates';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { spec } from '../spec';
|
|
4
|
+
import { ZohoCrmClient } from '../lib/client';
|
|
5
|
+
import type { Datacenter } from '../lib/urls';
|
|
6
|
+
import { zohoServiceError } from '../lib/errors';
|
|
7
|
+
|
|
8
|
+
export let crmGetRecords = SlateTool.create(spec, {
|
|
9
|
+
name: 'CRM Get Records',
|
|
10
|
+
key: 'crm_get_records',
|
|
11
|
+
description: `Retrieve records from any Zoho CRM module (Leads, Contacts, Accounts, Deals, Tasks, etc.). Supports pagination, field selection, sorting, and filtering by custom view ID. Can also fetch a single record by ID.`,
|
|
12
|
+
instructions: [
|
|
13
|
+
'Specify the module API name (e.g., "Leads", "Contacts", "Accounts", "Deals").',
|
|
14
|
+
'To fetch a single record, provide the recordId. To list records, omit recordId.',
|
|
15
|
+
'Use the fields parameter to limit which fields are returned (comma-separated API field names, max 50).'
|
|
16
|
+
],
|
|
17
|
+
constraints: [
|
|
18
|
+
'Maximum 200 records per page.',
|
|
19
|
+
'Maximum 2000 records accessible via page-based pagination.'
|
|
20
|
+
],
|
|
21
|
+
tags: {
|
|
22
|
+
readOnly: true
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
.input(
|
|
26
|
+
z.object({
|
|
27
|
+
module: z
|
|
28
|
+
.string()
|
|
29
|
+
.describe(
|
|
30
|
+
'CRM module API name (e.g., "Leads", "Contacts", "Accounts", "Deals", "Tasks")'
|
|
31
|
+
),
|
|
32
|
+
recordId: z
|
|
33
|
+
.string()
|
|
34
|
+
.optional()
|
|
35
|
+
.describe('Specific record ID to fetch. If provided, returns a single record.'),
|
|
36
|
+
fields: z
|
|
37
|
+
.string()
|
|
38
|
+
.optional()
|
|
39
|
+
.describe('Comma-separated field API names to return (max 50)'),
|
|
40
|
+
ids: z
|
|
41
|
+
.string()
|
|
42
|
+
.optional()
|
|
43
|
+
.describe('Comma-separated record IDs to fetch from the module'),
|
|
44
|
+
page: z.number().optional().describe('Page number for pagination (default 1)'),
|
|
45
|
+
perPage: z.number().optional().describe('Number of records per page (max 200)'),
|
|
46
|
+
pageToken: z
|
|
47
|
+
.string()
|
|
48
|
+
.optional()
|
|
49
|
+
.describe('Page token for retrieving records after 2000'),
|
|
50
|
+
sortBy: z
|
|
51
|
+
.string()
|
|
52
|
+
.optional()
|
|
53
|
+
.describe('Field to sort by (e.g., "Created_Time", "Modified_Time")'),
|
|
54
|
+
sortOrder: z.enum(['asc', 'desc']).optional().describe('Sort order'),
|
|
55
|
+
customViewId: z.string().optional().describe('Custom view ID to filter records'),
|
|
56
|
+
converted: z
|
|
57
|
+
.enum(['true', 'false', 'both'])
|
|
58
|
+
.optional()
|
|
59
|
+
.describe('Converted-record filter for supported modules'),
|
|
60
|
+
territoryId: z.string().optional().describe('Territory ID filter for supported modules'),
|
|
61
|
+
includeChild: z
|
|
62
|
+
.boolean()
|
|
63
|
+
.optional()
|
|
64
|
+
.describe('Include child territory records when territoryId is provided')
|
|
65
|
+
})
|
|
66
|
+
)
|
|
67
|
+
.output(
|
|
68
|
+
z.object({
|
|
69
|
+
records: z.array(z.record(z.string(), z.any())).describe('Array of CRM records'),
|
|
70
|
+
moreRecords: z.boolean().optional().describe('Whether more records are available'),
|
|
71
|
+
count: z.number().optional().describe('Number of records returned'),
|
|
72
|
+
nextPageToken: z.string().nullable().optional().describe('Token for the next page'),
|
|
73
|
+
previousPageToken: z
|
|
74
|
+
.string()
|
|
75
|
+
.nullable()
|
|
76
|
+
.optional()
|
|
77
|
+
.describe('Token for the previous page'),
|
|
78
|
+
pageTokenExpiry: z.string().nullable().optional().describe('Page token expiry timestamp')
|
|
79
|
+
})
|
|
80
|
+
)
|
|
81
|
+
.handleInvocation(async ctx => {
|
|
82
|
+
let dc = (ctx.auth.datacenter || ctx.config.datacenter || 'us') as Datacenter;
|
|
83
|
+
let client = new ZohoCrmClient({ token: ctx.auth.token, datacenter: dc });
|
|
84
|
+
|
|
85
|
+
if (ctx.input.recordId) {
|
|
86
|
+
let result = await client.getRecord(
|
|
87
|
+
ctx.input.module,
|
|
88
|
+
ctx.input.recordId,
|
|
89
|
+
ctx.input.fields
|
|
90
|
+
);
|
|
91
|
+
let records = result?.data || [];
|
|
92
|
+
return {
|
|
93
|
+
output: { records, moreRecords: false, count: records.length },
|
|
94
|
+
message: `Fetched record **${ctx.input.recordId}** from **${ctx.input.module}**.`
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!ctx.input.fields) {
|
|
99
|
+
throw zohoServiceError('fields is required when listing CRM records.');
|
|
100
|
+
}
|
|
101
|
+
if (ctx.input.pageToken && ctx.input.page) {
|
|
102
|
+
throw zohoServiceError('pageToken cannot be used with page.');
|
|
103
|
+
}
|
|
104
|
+
if (ctx.input.customViewId && ctx.input.sortBy) {
|
|
105
|
+
throw zohoServiceError('customViewId cannot be used with sortBy.');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let result = await client.getRecords(ctx.input.module, {
|
|
109
|
+
fields: ctx.input.fields,
|
|
110
|
+
ids: ctx.input.ids,
|
|
111
|
+
page: ctx.input.page,
|
|
112
|
+
perPage: ctx.input.perPage,
|
|
113
|
+
pageToken: ctx.input.pageToken,
|
|
114
|
+
sortBy: ctx.input.sortBy,
|
|
115
|
+
sortOrder: ctx.input.sortOrder,
|
|
116
|
+
cvid: ctx.input.customViewId,
|
|
117
|
+
converted: ctx.input.converted,
|
|
118
|
+
territoryId: ctx.input.territoryId,
|
|
119
|
+
includeChild: ctx.input.includeChild
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
let records = result?.data || [];
|
|
123
|
+
let info = result?.info;
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
output: {
|
|
127
|
+
records,
|
|
128
|
+
moreRecords: info?.more_records ?? false,
|
|
129
|
+
count: info?.count ?? records.length,
|
|
130
|
+
nextPageToken: info?.next_page_token,
|
|
131
|
+
previousPageToken: info?.previous_page_token,
|
|
132
|
+
pageTokenExpiry: info?.page_token_expiry
|
|
133
|
+
},
|
|
134
|
+
message: `Retrieved **${records.length}** records from **${ctx.input.module}**${info?.more_records ? ' (more available)' : ''}.`
|
|
135
|
+
};
|
|
136
|
+
})
|
|
137
|
+
.build();
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { SlateTool } from 'slates';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { spec } from '../spec';
|
|
4
|
+
import { ZohoCrmClient } from '../lib/client';
|
|
5
|
+
import type { Datacenter } from '../lib/urls';
|
|
6
|
+
import { zohoServiceError } from '../lib/errors';
|
|
7
|
+
|
|
8
|
+
export let crmGetRelatedRecords = SlateTool.create(spec, {
|
|
9
|
+
name: 'CRM Get Related Records',
|
|
10
|
+
key: 'crm_get_related_records',
|
|
11
|
+
description:
|
|
12
|
+
'Retrieve related records for a Zoho CRM record, such as Notes, Attachments, Emails, Deals, Contacts, or custom related lists. Supports field selection, pagination, sorting, and ID filtering.',
|
|
13
|
+
instructions: [
|
|
14
|
+
'Provide the parent module API name, parent record ID, and related list API name.',
|
|
15
|
+
'Use relatedListApiName values from CRM related list metadata, such as "Notes", "Attachments", or a custom related list API name.',
|
|
16
|
+
'The fields parameter is required by Zoho CRM for related-record list retrieval.'
|
|
17
|
+
],
|
|
18
|
+
tags: {
|
|
19
|
+
readOnly: true
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
.input(
|
|
23
|
+
z.object({
|
|
24
|
+
module: z.string().describe('Parent CRM module API name, e.g. "Leads" or "Contacts"'),
|
|
25
|
+
recordId: z.string().describe('Parent CRM record ID'),
|
|
26
|
+
relatedListApiName: z.string().describe('Related list API name, e.g. "Notes"'),
|
|
27
|
+
fields: z
|
|
28
|
+
.string()
|
|
29
|
+
.describe('Comma-separated field API names to return from the related records'),
|
|
30
|
+
ids: z.string().optional().describe('Comma-separated related record IDs to fetch'),
|
|
31
|
+
page: z.number().optional().describe('Page number for pagination'),
|
|
32
|
+
perPage: z.number().optional().describe('Records per page, max 200'),
|
|
33
|
+
pageToken: z
|
|
34
|
+
.string()
|
|
35
|
+
.optional()
|
|
36
|
+
.describe('Page token for retrieving records after 2000'),
|
|
37
|
+
sortBy: z
|
|
38
|
+
.string()
|
|
39
|
+
.optional()
|
|
40
|
+
.describe('Sort field, typically id, Created_Time, or Modified_Time'),
|
|
41
|
+
sortOrder: z.enum(['asc', 'desc']).optional().describe('Sort order'),
|
|
42
|
+
converted: z
|
|
43
|
+
.enum(['true', 'false', 'both'])
|
|
44
|
+
.optional()
|
|
45
|
+
.describe('Converted-record filter for supported modules')
|
|
46
|
+
})
|
|
47
|
+
)
|
|
48
|
+
.output(
|
|
49
|
+
z.object({
|
|
50
|
+
records: z.array(z.record(z.string(), z.any())).describe('Related CRM records'),
|
|
51
|
+
moreRecords: z
|
|
52
|
+
.boolean()
|
|
53
|
+
.optional()
|
|
54
|
+
.describe('Whether more related records are available'),
|
|
55
|
+
count: z.number().optional().describe('Number of records returned'),
|
|
56
|
+
nextPageToken: z.string().nullable().optional().describe('Token for the next page'),
|
|
57
|
+
previousPageToken: z
|
|
58
|
+
.string()
|
|
59
|
+
.nullable()
|
|
60
|
+
.optional()
|
|
61
|
+
.describe('Token for the previous page'),
|
|
62
|
+
pageTokenExpiry: z.string().nullable().optional().describe('Page token expiry timestamp')
|
|
63
|
+
})
|
|
64
|
+
)
|
|
65
|
+
.handleInvocation(async ctx => {
|
|
66
|
+
let dc = (ctx.auth.datacenter || ctx.config.datacenter || 'us') as Datacenter;
|
|
67
|
+
let client = new ZohoCrmClient({ token: ctx.auth.token, datacenter: dc });
|
|
68
|
+
|
|
69
|
+
if (ctx.input.pageToken && ctx.input.page) {
|
|
70
|
+
throw zohoServiceError('pageToken cannot be used with page.');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let result = await client.getRelatedRecords(
|
|
74
|
+
ctx.input.module,
|
|
75
|
+
ctx.input.recordId,
|
|
76
|
+
ctx.input.relatedListApiName,
|
|
77
|
+
{
|
|
78
|
+
fields: ctx.input.fields,
|
|
79
|
+
ids: ctx.input.ids,
|
|
80
|
+
page: ctx.input.page,
|
|
81
|
+
perPage: ctx.input.perPage,
|
|
82
|
+
pageToken: ctx.input.pageToken,
|
|
83
|
+
sortBy: ctx.input.sortBy,
|
|
84
|
+
sortOrder: ctx.input.sortOrder,
|
|
85
|
+
converted: ctx.input.converted
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
let records = result?.data || [];
|
|
90
|
+
let info = result?.info;
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
output: {
|
|
94
|
+
records,
|
|
95
|
+
moreRecords: info?.more_records ?? false,
|
|
96
|
+
count: info?.count ?? records.length,
|
|
97
|
+
nextPageToken: info?.next_page_token,
|
|
98
|
+
previousPageToken: info?.previous_page_token,
|
|
99
|
+
pageTokenExpiry: info?.page_token_expiry
|
|
100
|
+
},
|
|
101
|
+
message: `Retrieved **${records.length}** related records from **${ctx.input.relatedListApiName}**.`
|
|
102
|
+
};
|
|
103
|
+
})
|
|
104
|
+
.build();
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { SlateTool } from 'slates';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { spec } from '../spec';
|
|
4
|
+
import { ZohoCrmClient } from '../lib/client';
|
|
5
|
+
import type { Datacenter } from '../lib/urls';
|
|
6
|
+
import { zohoServiceError } from '../lib/errors';
|
|
7
|
+
|
|
8
|
+
export let crmManageRecord = SlateTool.create(spec, {
|
|
9
|
+
name: 'CRM Manage Record',
|
|
10
|
+
key: 'crm_manage_record',
|
|
11
|
+
description: `Create, update, or delete records in any Zoho CRM module (Leads, Contacts, Accounts, Deals, etc.). Supports creating single or multiple records, updating fields on existing records, and deleting records by ID.`,
|
|
12
|
+
instructions: [
|
|
13
|
+
'Set action to "create" to insert new records, "update" to modify existing, or "delete" to remove.',
|
|
14
|
+
'For create, provide recordData as an array of objects with field API names as keys.',
|
|
15
|
+
'For update, provide recordId and recordData with fields to change.',
|
|
16
|
+
'For delete, provide recordId.',
|
|
17
|
+
'Field names must be CRM API field names (e.g., "Last_Name", "Email", "Company").'
|
|
18
|
+
],
|
|
19
|
+
tags: {
|
|
20
|
+
destructive: true
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
.input(
|
|
24
|
+
z.object({
|
|
25
|
+
module: z
|
|
26
|
+
.string()
|
|
27
|
+
.describe('CRM module API name (e.g., "Leads", "Contacts", "Accounts", "Deals")'),
|
|
28
|
+
action: z.enum(['create', 'update', 'delete']).describe('Operation to perform'),
|
|
29
|
+
recordId: z.string().optional().describe('Record ID (required for update and delete)'),
|
|
30
|
+
recordData: z
|
|
31
|
+
.union([z.array(z.record(z.string(), z.any())), z.record(z.string(), z.any())])
|
|
32
|
+
.optional()
|
|
33
|
+
.describe(
|
|
34
|
+
'Record data: an object for update, or array of objects for create. Field API names as keys.'
|
|
35
|
+
),
|
|
36
|
+
triggers: z
|
|
37
|
+
.array(z.enum(['workflow', 'approval', 'blueprint']))
|
|
38
|
+
.optional()
|
|
39
|
+
.describe('Workflow triggers to execute on this operation')
|
|
40
|
+
})
|
|
41
|
+
)
|
|
42
|
+
.output(
|
|
43
|
+
z.object({
|
|
44
|
+
results: z
|
|
45
|
+
.array(
|
|
46
|
+
z.object({
|
|
47
|
+
recordId: z.string().optional(),
|
|
48
|
+
status: z.string(),
|
|
49
|
+
message: z.string().optional(),
|
|
50
|
+
code: z.string().optional()
|
|
51
|
+
})
|
|
52
|
+
)
|
|
53
|
+
.describe('Result for each record in the operation')
|
|
54
|
+
})
|
|
55
|
+
)
|
|
56
|
+
.handleInvocation(async ctx => {
|
|
57
|
+
let dc = (ctx.auth.datacenter || ctx.config.datacenter || 'us') as Datacenter;
|
|
58
|
+
let client = new ZohoCrmClient({ token: ctx.auth.token, datacenter: dc });
|
|
59
|
+
|
|
60
|
+
if (ctx.input.action === 'create') {
|
|
61
|
+
let records = Array.isArray(ctx.input.recordData)
|
|
62
|
+
? ctx.input.recordData
|
|
63
|
+
: [ctx.input.recordData || {}];
|
|
64
|
+
let result = await client.createRecords(ctx.input.module, records, ctx.input.triggers);
|
|
65
|
+
let results = (result?.data || []).map((r: any) => ({
|
|
66
|
+
recordId: r?.details?.id,
|
|
67
|
+
status: r?.status || 'unknown',
|
|
68
|
+
message: r?.message,
|
|
69
|
+
code: r?.code
|
|
70
|
+
}));
|
|
71
|
+
return {
|
|
72
|
+
output: { results },
|
|
73
|
+
message: `Created **${results.filter((r: any) => r.status === 'success').length}** record(s) in **${ctx.input.module}**.`
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (ctx.input.action === 'update') {
|
|
78
|
+
if (!ctx.input.recordId) throw zohoServiceError('recordId is required for update');
|
|
79
|
+
let data = Array.isArray(ctx.input.recordData)
|
|
80
|
+
? ctx.input.recordData[0] || {}
|
|
81
|
+
: ctx.input.recordData || {};
|
|
82
|
+
let result = await client.updateRecord(
|
|
83
|
+
ctx.input.module,
|
|
84
|
+
ctx.input.recordId,
|
|
85
|
+
data,
|
|
86
|
+
ctx.input.triggers
|
|
87
|
+
);
|
|
88
|
+
let results = (result?.data || []).map((r: any) => ({
|
|
89
|
+
recordId: r?.details?.id || ctx.input.recordId,
|
|
90
|
+
status: r?.status || 'unknown',
|
|
91
|
+
message: r?.message,
|
|
92
|
+
code: r?.code
|
|
93
|
+
}));
|
|
94
|
+
return {
|
|
95
|
+
output: { results },
|
|
96
|
+
message: `Updated record **${ctx.input.recordId}** in **${ctx.input.module}**.`
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (ctx.input.action === 'delete') {
|
|
101
|
+
if (!ctx.input.recordId) throw zohoServiceError('recordId is required for delete');
|
|
102
|
+
let result = await client.deleteRecord(ctx.input.module, ctx.input.recordId);
|
|
103
|
+
let results = (result?.data || []).map((r: any) => ({
|
|
104
|
+
recordId: r?.details?.id || ctx.input.recordId,
|
|
105
|
+
status: r?.status || 'unknown',
|
|
106
|
+
message: r?.message,
|
|
107
|
+
code: r?.code
|
|
108
|
+
}));
|
|
109
|
+
return {
|
|
110
|
+
output: { results },
|
|
111
|
+
message: `Deleted record **${ctx.input.recordId}** from **${ctx.input.module}**.`
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
throw zohoServiceError('Invalid CRM record action.');
|
|
116
|
+
})
|
|
117
|
+
.build();
|