@ixiam/n8n-nodes-civicrm 1.1.41 → 2.1.2
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/dist/credentials/CiviCrmApi.credentials.js +1 -0
- package/dist/src/nodes/CiviCrm/CiviCrm.node.js +432 -422
- package/dist/src/nodes/transport/GenericFunctions.js +5 -5
- package/package.json +2 -5
- package/.release-it.json +0 -6
- package/credentials/CiviCrmApi.credentials.ts +0 -58
- package/credentials/CiviCrmApi.credentials.ts.bak +0 -52
- package/debug-auth.js +0 -94
- package/eslint.config.mjs +0 -3
- package/jest.config.cjs +0 -9
- package/reload-in-docker.md +0 -68
- package/src/index.ts +0 -12
- package/src/nodes/CiviCrm/CiviCrm.node.ts +0 -1257
- package/src/nodes/CiviCrm/civicrm.svg +0 -28
- package/src/nodes/CiviCrm/descriptions/generic.ts +0 -9
- package/src/nodes/CiviCrm/descriptions/resources.ts +0 -43
- package/src/nodes/transport/GenericFunctions.ts +0 -52
- package/sxkeychain erase +0 -7
- package/test/helpers/mockServer.ts +0 -27
- package/test/nodes/CiviCrm.test.ts +0 -67
- package/test/tsconfig.json +0 -10
- package/tsconfig.json +0 -23
|
@@ -1,1257 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
IExecuteFunctions,
|
|
3
|
-
ILoadOptionsFunctions,
|
|
4
|
-
INodeExecutionData,
|
|
5
|
-
INodePropertyOptions,
|
|
6
|
-
INodeType,
|
|
7
|
-
INodeTypeDescription,
|
|
8
|
-
IDataObject,
|
|
9
|
-
} from 'n8n-workflow';
|
|
10
|
-
|
|
11
|
-
import { civicrmApiRequest } from '../transport/GenericFunctions';
|
|
12
|
-
import { resourceProp, operationProp } from './descriptions/resources';
|
|
13
|
-
import { genericFields, upsertFields } from './descriptions/generic';
|
|
14
|
-
|
|
15
|
-
/* ============================================================================
|
|
16
|
-
RESOURCES
|
|
17
|
-
============================================================================ */
|
|
18
|
-
|
|
19
|
-
type Resource =
|
|
20
|
-
| 'contact'
|
|
21
|
-
| 'membership'
|
|
22
|
-
| 'group'
|
|
23
|
-
| 'relationship'
|
|
24
|
-
| 'activity'
|
|
25
|
-
| 'customApi';
|
|
26
|
-
|
|
27
|
-
type Operation = 'get' | 'getMany' | 'create' | 'update' | 'delete';
|
|
28
|
-
|
|
29
|
-
const ENTITY_MAP: Record<Exclude<Resource, 'customApi'>, string> = {
|
|
30
|
-
contact: 'Contact',
|
|
31
|
-
membership: 'Membership',
|
|
32
|
-
group: 'Group',
|
|
33
|
-
relationship: 'Relationship',
|
|
34
|
-
activity: 'Activity',
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
/* ============================================================================
|
|
38
|
-
LOCATION TYPE CACHE
|
|
39
|
-
============================================================================ */
|
|
40
|
-
|
|
41
|
-
let locationTypeCache: Record<string, string> | null = null;
|
|
42
|
-
|
|
43
|
-
function normalizeLocationKey(s: string): string {
|
|
44
|
-
return String(s || '')
|
|
45
|
-
.toLowerCase()
|
|
46
|
-
.replace(/[^a-z0-9]+/g, '');
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async function getLocationTypeMap(this: IExecuteFunctions): Promise<Record<string, string>> {
|
|
50
|
-
if (locationTypeCache) return locationTypeCache;
|
|
51
|
-
|
|
52
|
-
const res = await civicrmApiRequest.call(this, 'POST', '/civicrm/ajax/api4/OptionValue/get', {
|
|
53
|
-
where: [['option_group_id:name', '=', 'location_type']],
|
|
54
|
-
select: ['name', 'label'],
|
|
55
|
-
limit: 0,
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
const map: Record<string, string> = {};
|
|
59
|
-
const values = (res?.values || []) as Array<{ name?: string; label?: string }>;
|
|
60
|
-
|
|
61
|
-
for (const v of values) {
|
|
62
|
-
const name = v.name || '';
|
|
63
|
-
const label = v.label || '';
|
|
64
|
-
const k1 = normalizeLocationKey(name);
|
|
65
|
-
const k2 = normalizeLocationKey(label);
|
|
66
|
-
if (k1) map[k1] = name;
|
|
67
|
-
if (k2) map[k2] = name;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
locationTypeCache = map;
|
|
71
|
-
return map;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/* ============================================================================
|
|
75
|
-
NODE DEFINITION
|
|
76
|
-
============================================================================ */
|
|
77
|
-
|
|
78
|
-
export class CiviCrm implements INodeType {
|
|
79
|
-
description: INodeTypeDescription = {
|
|
80
|
-
displayName: 'CiviCRM',
|
|
81
|
-
name: 'civiCrm',
|
|
82
|
-
icon: 'file:civicrm.svg',
|
|
83
|
-
group: ['transform'],
|
|
84
|
-
version: 1,
|
|
85
|
-
description:
|
|
86
|
-
'Interact with CiviCRM API v4 (Civi-Go compatible).\n\n' +
|
|
87
|
-
'Supports Contact, Membership, Group, Relationship and Activity entities.\n' +
|
|
88
|
-
'Includes dynamic mapping of email, phone, address and location types.\n' +
|
|
89
|
-
'Includes birth_date validation and JSON filters for GET MANY.\n',
|
|
90
|
-
defaults: { name: 'CiviCRM' },
|
|
91
|
-
inputs: ['main'],
|
|
92
|
-
outputs: ['main'],
|
|
93
|
-
|
|
94
|
-
// @ts-ignore
|
|
95
|
-
usableAsTool: true,
|
|
96
|
-
// @ts-ignore
|
|
97
|
-
actions: [
|
|
98
|
-
// ======================================================================
|
|
99
|
-
// CONTACT
|
|
100
|
-
// ======================================================================
|
|
101
|
-
{
|
|
102
|
-
displayName: 'Get Contact',
|
|
103
|
-
name: 'getContact',
|
|
104
|
-
action: 'Get a contact',
|
|
105
|
-
description: 'Retrieve a contact from CiviCRM by ID or email.',
|
|
106
|
-
displayOptions: { show: { resource: ['contact'], operation: ['get'] } },
|
|
107
|
-
properties: [
|
|
108
|
-
{
|
|
109
|
-
displayName: 'ID',
|
|
110
|
-
name: 'id',
|
|
111
|
-
type: 'number',
|
|
112
|
-
default: 0,
|
|
113
|
-
description: 'The internal CiviCRM ID of the contact to retrieve.',
|
|
114
|
-
},
|
|
115
|
-
],
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
displayName: 'Get Many Contacts',
|
|
119
|
-
name: 'getManyContacts',
|
|
120
|
-
action: 'Get many contacts',
|
|
121
|
-
description: 'Retrieve multiple contacts from CiviCRM with filtering.',
|
|
122
|
-
displayOptions: { show: { resource: ['contact'], operation: ['getMany'] } },
|
|
123
|
-
properties: [...genericFields],
|
|
124
|
-
},
|
|
125
|
-
{
|
|
126
|
-
displayName: 'Create Contact',
|
|
127
|
-
name: 'createContact',
|
|
128
|
-
action: 'Create a contact',
|
|
129
|
-
description: 'Create a new contact in CiviCRM.',
|
|
130
|
-
displayOptions: { show: { resource: ['contact'], operation: ['create'] } },
|
|
131
|
-
properties: [...upsertFields],
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
displayName: 'Update Contact',
|
|
135
|
-
name: 'updateContact',
|
|
136
|
-
action: 'Update a contact',
|
|
137
|
-
description: 'Update an existing contact in CiviCRM.',
|
|
138
|
-
displayOptions: { show: { resource: ['contact'], operation: ['update'] } },
|
|
139
|
-
properties: [
|
|
140
|
-
{
|
|
141
|
-
displayName: 'ID',
|
|
142
|
-
name: 'id',
|
|
143
|
-
type: 'number',
|
|
144
|
-
default: 0,
|
|
145
|
-
description: 'The ID of the contact to update.',
|
|
146
|
-
},
|
|
147
|
-
...upsertFields,
|
|
148
|
-
],
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
displayName: 'Delete Contact',
|
|
152
|
-
name: 'deleteContact',
|
|
153
|
-
action: 'Delete a contact',
|
|
154
|
-
description: 'Delete a contact from CiviCRM.',
|
|
155
|
-
displayOptions: { show: { resource: ['contact'], operation: ['delete'] } },
|
|
156
|
-
properties: [
|
|
157
|
-
{
|
|
158
|
-
displayName: 'ID',
|
|
159
|
-
name: 'id',
|
|
160
|
-
type: 'number',
|
|
161
|
-
default: 0,
|
|
162
|
-
description: 'The ID of the contact to delete.',
|
|
163
|
-
},
|
|
164
|
-
],
|
|
165
|
-
},
|
|
166
|
-
|
|
167
|
-
// ======================================================================
|
|
168
|
-
// MEMBERSHIP
|
|
169
|
-
// ======================================================================
|
|
170
|
-
{
|
|
171
|
-
displayName: 'Get Membership',
|
|
172
|
-
name: 'getMembership',
|
|
173
|
-
action: 'Get a membership',
|
|
174
|
-
description: 'Retrieve a membership by ID.',
|
|
175
|
-
displayOptions: { show: { resource: ['membership'], operation: ['get'] } },
|
|
176
|
-
properties: [
|
|
177
|
-
{
|
|
178
|
-
displayName: 'ID',
|
|
179
|
-
name: 'id',
|
|
180
|
-
type: 'number',
|
|
181
|
-
default: 0,
|
|
182
|
-
description: 'The internal CiviCRM ID of the membership.',
|
|
183
|
-
},
|
|
184
|
-
],
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
displayName: 'Get Many Memberships',
|
|
188
|
-
name: 'getManyMemberships',
|
|
189
|
-
action: 'Get many memberships',
|
|
190
|
-
description: 'Retrieve multiple memberships.',
|
|
191
|
-
displayOptions: { show: { resource: ['membership'], operation: ['getMany'] } },
|
|
192
|
-
properties: [...genericFields],
|
|
193
|
-
},
|
|
194
|
-
{
|
|
195
|
-
displayName: 'Create Membership',
|
|
196
|
-
name: 'createMembership',
|
|
197
|
-
action: 'Create a membership',
|
|
198
|
-
description: 'Create a new membership.',
|
|
199
|
-
displayOptions: { show: { resource: ['membership'], operation: ['create'] } },
|
|
200
|
-
properties: [...upsertFields],
|
|
201
|
-
},
|
|
202
|
-
{
|
|
203
|
-
displayName: 'Update Membership',
|
|
204
|
-
name: 'updateMembership',
|
|
205
|
-
action: 'Update a membership',
|
|
206
|
-
description: 'Update an existing membership.',
|
|
207
|
-
displayOptions: { show: { resource: ['membership'], operation: ['update'] } },
|
|
208
|
-
properties: [
|
|
209
|
-
{
|
|
210
|
-
displayName: 'ID',
|
|
211
|
-
name: 'id',
|
|
212
|
-
type: 'number',
|
|
213
|
-
default: 0,
|
|
214
|
-
description: 'The ID of the membership to update.',
|
|
215
|
-
},
|
|
216
|
-
...upsertFields,
|
|
217
|
-
],
|
|
218
|
-
},
|
|
219
|
-
{
|
|
220
|
-
displayName: 'Delete Membership',
|
|
221
|
-
name: 'deleteMembership',
|
|
222
|
-
action: 'Delete a membership',
|
|
223
|
-
description: 'Delete a membership.',
|
|
224
|
-
displayOptions: { show: { resource: ['membership'], operation: ['delete'] } },
|
|
225
|
-
properties: [
|
|
226
|
-
{
|
|
227
|
-
displayName: 'ID',
|
|
228
|
-
name: 'id',
|
|
229
|
-
type: 'number',
|
|
230
|
-
default: 0,
|
|
231
|
-
description: 'The ID of the membership to delete.',
|
|
232
|
-
},
|
|
233
|
-
],
|
|
234
|
-
},
|
|
235
|
-
|
|
236
|
-
// ======================================================================
|
|
237
|
-
// GROUP
|
|
238
|
-
// ======================================================================
|
|
239
|
-
{
|
|
240
|
-
displayName: 'Get Group',
|
|
241
|
-
name: 'getGroup',
|
|
242
|
-
action: 'Get a group',
|
|
243
|
-
description: 'Retrieve a group by ID.',
|
|
244
|
-
displayOptions: { show: { resource: ['group'], operation: ['get'] } },
|
|
245
|
-
properties: [
|
|
246
|
-
{
|
|
247
|
-
displayName: 'ID',
|
|
248
|
-
name: 'id',
|
|
249
|
-
type: 'number',
|
|
250
|
-
default: 0,
|
|
251
|
-
description: 'The internal CiviCRM ID of the group.',
|
|
252
|
-
},
|
|
253
|
-
],
|
|
254
|
-
},
|
|
255
|
-
{
|
|
256
|
-
displayName: 'Get Many Groups',
|
|
257
|
-
name: 'getManyGroups',
|
|
258
|
-
action: 'Get many groups',
|
|
259
|
-
description: 'Retrieve multiple groups.',
|
|
260
|
-
displayOptions: { show: { resource: ['group'], operation: ['getMany'] } },
|
|
261
|
-
properties: [...genericFields],
|
|
262
|
-
},
|
|
263
|
-
{
|
|
264
|
-
displayName: 'Create Group',
|
|
265
|
-
name: 'createGroup',
|
|
266
|
-
action: 'Create a group',
|
|
267
|
-
description: 'Create a new group.',
|
|
268
|
-
displayOptions: { show: { resource: ['group'], operation: ['create'] } },
|
|
269
|
-
properties: [...upsertFields],
|
|
270
|
-
},
|
|
271
|
-
{
|
|
272
|
-
displayName: 'Update Group',
|
|
273
|
-
name: 'updateGroup',
|
|
274
|
-
action: 'Update a group',
|
|
275
|
-
description: 'Update an existing group.',
|
|
276
|
-
displayOptions: { show: { resource: ['group'], operation: ['update'] } },
|
|
277
|
-
properties: [
|
|
278
|
-
{
|
|
279
|
-
displayName: 'ID',
|
|
280
|
-
name: 'id',
|
|
281
|
-
type: 'number',
|
|
282
|
-
default: 0,
|
|
283
|
-
description: 'The ID of the group to update.',
|
|
284
|
-
},
|
|
285
|
-
...upsertFields,
|
|
286
|
-
],
|
|
287
|
-
},
|
|
288
|
-
{
|
|
289
|
-
displayName: 'Delete Group',
|
|
290
|
-
name: 'deleteGroup',
|
|
291
|
-
action: 'Delete a group',
|
|
292
|
-
description: 'Delete a group.',
|
|
293
|
-
displayOptions: { show: { resource: ['group'], operation: ['delete'] } },
|
|
294
|
-
properties: [
|
|
295
|
-
{
|
|
296
|
-
displayName: 'ID',
|
|
297
|
-
name: 'id',
|
|
298
|
-
type: 'number',
|
|
299
|
-
default: 0,
|
|
300
|
-
description: 'The ID of the group to delete.',
|
|
301
|
-
},
|
|
302
|
-
],
|
|
303
|
-
},
|
|
304
|
-
|
|
305
|
-
// ======================================================================
|
|
306
|
-
// RELATIONSHIP
|
|
307
|
-
// ======================================================================
|
|
308
|
-
{
|
|
309
|
-
displayName: 'Get Relationship',
|
|
310
|
-
name: 'getRelationship',
|
|
311
|
-
action: 'Get a relationship',
|
|
312
|
-
description: 'Retrieve a relationship by ID.',
|
|
313
|
-
displayOptions: { show: { resource: ['relationship'], operation: ['get'] } },
|
|
314
|
-
properties: [
|
|
315
|
-
{
|
|
316
|
-
displayName: 'ID',
|
|
317
|
-
name: 'id',
|
|
318
|
-
type: 'number',
|
|
319
|
-
default: 0,
|
|
320
|
-
description: 'The internal CiviCRM ID of the relationship.',
|
|
321
|
-
},
|
|
322
|
-
],
|
|
323
|
-
},
|
|
324
|
-
{
|
|
325
|
-
displayName: 'Get Many Relationships',
|
|
326
|
-
name: 'getManyRelationships',
|
|
327
|
-
action: 'Get many relationships',
|
|
328
|
-
description: 'Retrieve multiple relationships.',
|
|
329
|
-
displayOptions: { show: { resource: ['relationship'], operation: ['getMany'] } },
|
|
330
|
-
properties: [...genericFields],
|
|
331
|
-
},
|
|
332
|
-
{
|
|
333
|
-
displayName: 'Create Relationship',
|
|
334
|
-
name: 'createRelationship',
|
|
335
|
-
action: 'Create a relationship',
|
|
336
|
-
description: 'Create a new relationship.',
|
|
337
|
-
displayOptions: { show: { resource: ['relationship'], operation: ['create'] } },
|
|
338
|
-
properties: [...upsertFields],
|
|
339
|
-
},
|
|
340
|
-
{
|
|
341
|
-
displayName: 'Update Relationship',
|
|
342
|
-
name: 'updateRelationship',
|
|
343
|
-
action: 'Update a relationship',
|
|
344
|
-
description: 'Update an existing relationship.',
|
|
345
|
-
displayOptions: { show: { resource: ['relationship'], operation: ['update'] } },
|
|
346
|
-
properties: [
|
|
347
|
-
{
|
|
348
|
-
displayName: 'ID',
|
|
349
|
-
name: 'id',
|
|
350
|
-
type: 'number',
|
|
351
|
-
default: 0,
|
|
352
|
-
description: 'The ID of the relationship to update.',
|
|
353
|
-
},
|
|
354
|
-
...upsertFields,
|
|
355
|
-
],
|
|
356
|
-
},
|
|
357
|
-
{
|
|
358
|
-
displayName: 'Delete Relationship',
|
|
359
|
-
name: 'deleteRelationship',
|
|
360
|
-
action: 'Delete a relationship',
|
|
361
|
-
description: 'Delete a relationship.',
|
|
362
|
-
displayOptions: { show: { resource: ['relationship'], operation: ['delete'] } },
|
|
363
|
-
properties: [
|
|
364
|
-
{
|
|
365
|
-
displayName: 'ID',
|
|
366
|
-
name: 'id',
|
|
367
|
-
type: 'number',
|
|
368
|
-
default: 0,
|
|
369
|
-
description: 'The ID of the relationship to delete.',
|
|
370
|
-
},
|
|
371
|
-
],
|
|
372
|
-
},
|
|
373
|
-
|
|
374
|
-
// ======================================================================
|
|
375
|
-
// ACTIVITY
|
|
376
|
-
// ======================================================================
|
|
377
|
-
{
|
|
378
|
-
displayName: 'Get Activity',
|
|
379
|
-
name: 'getActivity',
|
|
380
|
-
action: 'Get an activity',
|
|
381
|
-
description: 'Retrieve an activity by ID.',
|
|
382
|
-
displayOptions: { show: { resource: ['activity'], operation: ['get'] } },
|
|
383
|
-
properties: [
|
|
384
|
-
{
|
|
385
|
-
displayName: 'ID',
|
|
386
|
-
name: 'id',
|
|
387
|
-
type: 'number',
|
|
388
|
-
default: 0,
|
|
389
|
-
description: 'The internal CiviCRM ID of the activity.',
|
|
390
|
-
},
|
|
391
|
-
],
|
|
392
|
-
},
|
|
393
|
-
{
|
|
394
|
-
displayName: 'Get Many Activities',
|
|
395
|
-
name: 'getManyActivities',
|
|
396
|
-
action: 'Get many activities',
|
|
397
|
-
description: 'Retrieve multiple activities.',
|
|
398
|
-
displayOptions: { show: { resource: ['activity'], operation: ['getMany'] } },
|
|
399
|
-
properties: [...genericFields],
|
|
400
|
-
},
|
|
401
|
-
{
|
|
402
|
-
displayName: 'Create Activity',
|
|
403
|
-
name: 'createActivity',
|
|
404
|
-
action: 'Create an activity',
|
|
405
|
-
description: 'Create a new activity.',
|
|
406
|
-
displayOptions: { show: { resource: ['activity'], operation: ['create'] } },
|
|
407
|
-
properties: [...upsertFields],
|
|
408
|
-
},
|
|
409
|
-
{
|
|
410
|
-
displayName: 'Update Activity',
|
|
411
|
-
name: 'updateActivity',
|
|
412
|
-
action: 'Update an activity',
|
|
413
|
-
description: 'Update an existing activity.',
|
|
414
|
-
displayOptions: { show: { resource: ['activity'], operation: ['update'] } },
|
|
415
|
-
properties: [
|
|
416
|
-
{
|
|
417
|
-
displayName: 'ID',
|
|
418
|
-
name: 'id',
|
|
419
|
-
type: 'number',
|
|
420
|
-
default: 0,
|
|
421
|
-
description: 'The ID of the activity to update.',
|
|
422
|
-
},
|
|
423
|
-
...upsertFields,
|
|
424
|
-
],
|
|
425
|
-
},
|
|
426
|
-
{
|
|
427
|
-
displayName: 'Delete Activity',
|
|
428
|
-
name: 'deleteActivity',
|
|
429
|
-
action: 'Delete an activity',
|
|
430
|
-
description: 'Delete an activity.',
|
|
431
|
-
displayOptions: { show: { resource: ['activity'], operation: ['delete'] } },
|
|
432
|
-
properties: [
|
|
433
|
-
{
|
|
434
|
-
displayName: 'ID',
|
|
435
|
-
name: 'id',
|
|
436
|
-
type: 'number',
|
|
437
|
-
default: 0,
|
|
438
|
-
description: 'The ID of the activity to delete.',
|
|
439
|
-
},
|
|
440
|
-
],
|
|
441
|
-
},
|
|
442
|
-
],
|
|
443
|
-
credentials: [{ name: 'civiCrmApi', required: true }],
|
|
444
|
-
|
|
445
|
-
properties: [
|
|
446
|
-
//
|
|
447
|
-
// RESOURCE + OPERATION
|
|
448
|
-
//
|
|
449
|
-
resourceProp,
|
|
450
|
-
operationProp,
|
|
451
|
-
|
|
452
|
-
//
|
|
453
|
-
// CONTACT TYPE
|
|
454
|
-
//
|
|
455
|
-
{
|
|
456
|
-
displayName: 'Contact Type',
|
|
457
|
-
name: 'contactType',
|
|
458
|
-
type: 'options',
|
|
459
|
-
default: 'Individual',
|
|
460
|
-
description: 'Type of contact to create or search for.',
|
|
461
|
-
options: [
|
|
462
|
-
{ name: 'Individual', value: 'Individual' },
|
|
463
|
-
{ name: 'Organization', value: 'Organization' },
|
|
464
|
-
{ name: 'Household', value: 'Household' },
|
|
465
|
-
],
|
|
466
|
-
displayOptions: { show: { resource: ['contact'] } },
|
|
467
|
-
},
|
|
468
|
-
|
|
469
|
-
//
|
|
470
|
-
// LOCATION TYPES
|
|
471
|
-
//
|
|
472
|
-
{
|
|
473
|
-
displayName: 'Email Location Type',
|
|
474
|
-
name: 'emailLocation',
|
|
475
|
-
type: 'options',
|
|
476
|
-
default: 'Work',
|
|
477
|
-
description: 'Location type for the email address.',
|
|
478
|
-
options: [
|
|
479
|
-
{ name: 'Home', value: 'Home' },
|
|
480
|
-
{ name: 'Work', value: 'Work' },
|
|
481
|
-
{ name: 'Other', value: 'Other' },
|
|
482
|
-
],
|
|
483
|
-
displayOptions: { show: { resource: ['contact'], operation: ['create', 'update'] } },
|
|
484
|
-
},
|
|
485
|
-
{
|
|
486
|
-
displayName: 'Mark Email as Primary',
|
|
487
|
-
name: 'isPrimaryEmail',
|
|
488
|
-
type: 'boolean',
|
|
489
|
-
default: true,
|
|
490
|
-
description: 'Whether this email should be the primary email for the contact.',
|
|
491
|
-
displayOptions: { show: { resource: ['contact'], operation: ['create', 'update'] } },
|
|
492
|
-
},
|
|
493
|
-
{
|
|
494
|
-
displayName: 'Phone Location Type',
|
|
495
|
-
name: 'phoneLocation',
|
|
496
|
-
type: 'options',
|
|
497
|
-
default: 'Work',
|
|
498
|
-
description: 'Location type for the phone number.',
|
|
499
|
-
options: [
|
|
500
|
-
{ name: 'Home', value: 'Home' },
|
|
501
|
-
{ name: 'Work', value: 'Work' },
|
|
502
|
-
{ name: 'Mobile', value: 'Mobile' },
|
|
503
|
-
{ name: 'Other', value: 'Other' },
|
|
504
|
-
],
|
|
505
|
-
displayOptions: { show: { resource: ['contact'], operation: ['create', 'update'] } },
|
|
506
|
-
},
|
|
507
|
-
{
|
|
508
|
-
displayName: 'Mark Phone as Primary',
|
|
509
|
-
name: 'isPrimaryPhone',
|
|
510
|
-
type: 'boolean',
|
|
511
|
-
default: true,
|
|
512
|
-
description: 'Whether this phone should be the primary phone for the contact.',
|
|
513
|
-
displayOptions: { show: { resource: ['contact'], operation: ['create', 'update'] } },
|
|
514
|
-
},
|
|
515
|
-
{
|
|
516
|
-
displayName: 'Address Location Type',
|
|
517
|
-
name: 'addressLocation',
|
|
518
|
-
type: 'options',
|
|
519
|
-
default: 'Home',
|
|
520
|
-
description: 'Location type for the address.',
|
|
521
|
-
options: [
|
|
522
|
-
{ name: 'Home', value: 'Home' },
|
|
523
|
-
{ name: 'Work', value: 'Work' },
|
|
524
|
-
{ name: 'Billing', value: 'Billing' },
|
|
525
|
-
{ name: 'Other', value: 'Other' },
|
|
526
|
-
],
|
|
527
|
-
displayOptions: { show: { resource: ['contact'], operation: ['create', 'update'] } },
|
|
528
|
-
},
|
|
529
|
-
{
|
|
530
|
-
displayName: 'Mark Address as Primary',
|
|
531
|
-
name: 'isPrimaryAddress',
|
|
532
|
-
type: 'boolean',
|
|
533
|
-
default: true,
|
|
534
|
-
description: 'Whether this address should be the primary address for the contact.',
|
|
535
|
-
displayOptions: { show: { resource: ['contact'], operation: ['create', 'update'] } },
|
|
536
|
-
},
|
|
537
|
-
|
|
538
|
-
//
|
|
539
|
-
// ID for GET / UPDATE / DELETE
|
|
540
|
-
//
|
|
541
|
-
{
|
|
542
|
-
displayName: 'ID',
|
|
543
|
-
name: 'id',
|
|
544
|
-
type: 'number',
|
|
545
|
-
default: 0,
|
|
546
|
-
required: true,
|
|
547
|
-
description: 'The internal CiviCRM ID of the entity.',
|
|
548
|
-
displayOptions: {
|
|
549
|
-
show: {
|
|
550
|
-
operation: ['get', 'update', 'delete'],
|
|
551
|
-
resource: ['contact', 'membership', 'group', 'relationship', 'activity'],
|
|
552
|
-
},
|
|
553
|
-
},
|
|
554
|
-
},
|
|
555
|
-
|
|
556
|
-
//
|
|
557
|
-
// CUSTOM API CALL
|
|
558
|
-
//
|
|
559
|
-
{
|
|
560
|
-
displayName: 'Entity',
|
|
561
|
-
name: 'customEntity',
|
|
562
|
-
type: 'string',
|
|
563
|
-
default: 'Contact',
|
|
564
|
-
required: true,
|
|
565
|
-
description:
|
|
566
|
-
'CiviCRM API4 entity, for example Contact, Membership, Group, Activity, CustomValue, etc.',
|
|
567
|
-
displayOptions: { show: { resource: ['customApi'] } },
|
|
568
|
-
},
|
|
569
|
-
{
|
|
570
|
-
displayName: 'Action',
|
|
571
|
-
name: 'customAction',
|
|
572
|
-
type: 'string',
|
|
573
|
-
default: 'get',
|
|
574
|
-
required: true,
|
|
575
|
-
description:
|
|
576
|
-
'CiviCRM API4 action, for example get, getFields, create, update, delete, getOne, etc.',
|
|
577
|
-
displayOptions: { show: { resource: ['customApi'] } },
|
|
578
|
-
},
|
|
579
|
-
{
|
|
580
|
-
displayName: 'Params (JSON)',
|
|
581
|
-
name: 'customParamsJson',
|
|
582
|
-
type: 'string',
|
|
583
|
-
typeOptions: {
|
|
584
|
-
rows: 4,
|
|
585
|
-
},
|
|
586
|
-
default: '{\n "limit": 25\n}',
|
|
587
|
-
description:
|
|
588
|
-
'Raw API4 params JSON passed as-is to CiviCRM. It must be a valid JSON object (no trailing commas).',
|
|
589
|
-
displayOptions: { show: { resource: ['customApi'] } },
|
|
590
|
-
},
|
|
591
|
-
|
|
592
|
-
//
|
|
593
|
-
// DYNAMIC FIELDS
|
|
594
|
-
//
|
|
595
|
-
...genericFields,
|
|
596
|
-
...upsertFields,
|
|
597
|
-
],
|
|
598
|
-
};
|
|
599
|
-
|
|
600
|
-
/* ============================================================================
|
|
601
|
-
LOAD OPTIONS (required by n8n verification)
|
|
602
|
-
============================================================================ */
|
|
603
|
-
|
|
604
|
-
methods = {
|
|
605
|
-
loadOptions: {
|
|
606
|
-
async loadOptionValues(this: ILoadOptionsFunctions) {
|
|
607
|
-
const { url, apiKey } = (await this.getCredentials('civiCrmApi')) as {
|
|
608
|
-
url: string;
|
|
609
|
-
apiKey: string;
|
|
610
|
-
};
|
|
611
|
-
|
|
612
|
-
const res = await this.helpers.httpRequest({
|
|
613
|
-
method: 'POST',
|
|
614
|
-
url: `${url.replace(/\/$/, '')}/civicrm/ajax/api4/OptionValue/get`,
|
|
615
|
-
headers: {
|
|
616
|
-
Authorization: `Bearer ${apiKey}`,
|
|
617
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
618
|
-
},
|
|
619
|
-
body: { params: JSON.stringify({ limit: 50, select: ['id', 'label'] }) },
|
|
620
|
-
json: true,
|
|
621
|
-
});
|
|
622
|
-
|
|
623
|
-
const values = (res?.values || []) as Array<{ id: number; label: string }>;
|
|
624
|
-
|
|
625
|
-
return values.map((v) => ({
|
|
626
|
-
name: v.label,
|
|
627
|
-
value: v.id,
|
|
628
|
-
}));
|
|
629
|
-
},
|
|
630
|
-
},
|
|
631
|
-
};
|
|
632
|
-
|
|
633
|
-
/* ============================================================================
|
|
634
|
-
EXECUTE
|
|
635
|
-
============================================================================ */
|
|
636
|
-
|
|
637
|
-
async execute(this: IExecuteFunctions) {
|
|
638
|
-
const items = this.getInputData();
|
|
639
|
-
const out: INodeExecutionData[] = [];
|
|
640
|
-
|
|
641
|
-
const resource = this.getNodeParameter('resource', 0) as Resource;
|
|
642
|
-
const operation = this.getNodeParameter('operation', 0) as Operation;
|
|
643
|
-
|
|
644
|
-
// Custom API call: generic passthrough to any API4 entity/action
|
|
645
|
-
if (resource === 'customApi') {
|
|
646
|
-
const entity = this.getNodeParameter('customEntity', 0) as string;
|
|
647
|
-
const action = this.getNodeParameter('customAction', 0) as string;
|
|
648
|
-
const paramsJson = this.getNodeParameter('customParamsJson', 0, '') as string;
|
|
649
|
-
|
|
650
|
-
let params: Record<string, unknown> = {};
|
|
651
|
-
if (paramsJson) {
|
|
652
|
-
try {
|
|
653
|
-
params = JSON.parse(paramsJson);
|
|
654
|
-
} catch (error) {
|
|
655
|
-
throw new Error('Invalid JSON in "Params (JSON)"');
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
const res = await civicrmApiRequest.call(
|
|
660
|
-
this,
|
|
661
|
-
'POST',
|
|
662
|
-
`/civicrm/ajax/api4/${entity}/${action}`,
|
|
663
|
-
params,
|
|
664
|
-
);
|
|
665
|
-
|
|
666
|
-
// Return the raw API4 response so advanced users can work with values and metadata
|
|
667
|
-
out.push({
|
|
668
|
-
json: res as IDataObject,
|
|
669
|
-
pairedItem: { item: 0 },
|
|
670
|
-
});
|
|
671
|
-
return [out];
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
const entity = ENTITY_MAP[resource];
|
|
675
|
-
|
|
676
|
-
for (let i = 0; i < items.length; i++) {
|
|
677
|
-
const emailLocationParam = this.getNodeParameter('emailLocation', i, 'Work') as string;
|
|
678
|
-
const phoneLocationParam = this.getNodeParameter('phoneLocation', i, 'Work') as string;
|
|
679
|
-
const addressLocationParam = this.getNodeParameter('addressLocation', i, 'Home') as string;
|
|
680
|
-
const isPrimaryEmail = this.getNodeParameter('isPrimaryEmail', i, true) as boolean;
|
|
681
|
-
const isPrimaryPhone = this.getNodeParameter('isPrimaryPhone', i, true) as boolean;
|
|
682
|
-
const isPrimaryAddress = this.getNodeParameter('isPrimaryAddress', i, true) as boolean;
|
|
683
|
-
|
|
684
|
-
let emailLocationName = emailLocationParam;
|
|
685
|
-
let phoneLocationName = phoneLocationParam;
|
|
686
|
-
let addressLocationName = addressLocationParam;
|
|
687
|
-
|
|
688
|
-
/* ======================================================
|
|
689
|
-
GET
|
|
690
|
-
====================================================== */
|
|
691
|
-
if (operation === 'get') {
|
|
692
|
-
const id = this.getNodeParameter('id', i) as number;
|
|
693
|
-
|
|
694
|
-
const params =
|
|
695
|
-
resource === 'contact'
|
|
696
|
-
? {
|
|
697
|
-
where: [['id', '=', id]],
|
|
698
|
-
limit: 1,
|
|
699
|
-
select: [
|
|
700
|
-
'id',
|
|
701
|
-
'display_name',
|
|
702
|
-
'first_name',
|
|
703
|
-
'last_name',
|
|
704
|
-
'organization_name',
|
|
705
|
-
'legal_name',
|
|
706
|
-
'contact_type',
|
|
707
|
-
'gender_id',
|
|
708
|
-
'gender_id:name',
|
|
709
|
-
'birth_date',
|
|
710
|
-
],
|
|
711
|
-
chain: {
|
|
712
|
-
emails: ['Email', 'get', { where: [['contact_id', '=', '$id']] }],
|
|
713
|
-
phones: ['Phone', 'get', { where: [['contact_id', '=', '$id']] }],
|
|
714
|
-
addresses: ['Address', 'get', { where: [['contact_id', '=', '$id']] }],
|
|
715
|
-
},
|
|
716
|
-
}
|
|
717
|
-
: {
|
|
718
|
-
where: [['id', '=', id]],
|
|
719
|
-
limit: 1,
|
|
720
|
-
select: ['id', 'name', 'title', 'subject', 'display_name'],
|
|
721
|
-
};
|
|
722
|
-
|
|
723
|
-
const res = await civicrmApiRequest.call(
|
|
724
|
-
this,
|
|
725
|
-
'POST',
|
|
726
|
-
`/civicrm/ajax/api4/${entity}/get`,
|
|
727
|
-
params,
|
|
728
|
-
);
|
|
729
|
-
|
|
730
|
-
out.push({
|
|
731
|
-
json: res?.values?.[0] ?? {},
|
|
732
|
-
pairedItem: { item: i },
|
|
733
|
-
});
|
|
734
|
-
continue;
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
/* ======================================================
|
|
738
|
-
GET MANY
|
|
739
|
-
====================================================== */
|
|
740
|
-
if (operation === 'getMany') {
|
|
741
|
-
const limit = this.getNodeParameter('limit', i, 100) as number;
|
|
742
|
-
const returnAll = this.getNodeParameter('returnAll', i, false) as boolean;
|
|
743
|
-
const whereJson = this.getNodeParameter('whereJson', i, '') as string;
|
|
744
|
-
|
|
745
|
-
let where: any[] = [];
|
|
746
|
-
if (whereJson) {
|
|
747
|
-
try {
|
|
748
|
-
where = JSON.parse(whereJson);
|
|
749
|
-
} catch (error) {
|
|
750
|
-
throw new Error('Invalid JSON in whereJson');
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
if (resource === 'contact') {
|
|
755
|
-
const contactType = this.getNodeParameter('contactType', i, '') as string;
|
|
756
|
-
if (contactType) where.push(['contact_type', '=', contactType]);
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
const params =
|
|
760
|
-
resource === 'contact'
|
|
761
|
-
? {
|
|
762
|
-
where,
|
|
763
|
-
select: [
|
|
764
|
-
'id',
|
|
765
|
-
'display_name',
|
|
766
|
-
'first_name',
|
|
767
|
-
'last_name',
|
|
768
|
-
'organization_name',
|
|
769
|
-
'legal_name',
|
|
770
|
-
'contact_type',
|
|
771
|
-
'gender_id',
|
|
772
|
-
'gender_id:name',
|
|
773
|
-
'birth_date',
|
|
774
|
-
],
|
|
775
|
-
chain: {
|
|
776
|
-
emails: ['Email', 'get', { where: [['contact_id', '=', '$id']] }],
|
|
777
|
-
phones: ['Phone', 'get', { where: [['contact_id', '=', '$id']] }],
|
|
778
|
-
addresses: ['Address', 'get', { where: [['contact_id', '=', '$id']] }],
|
|
779
|
-
},
|
|
780
|
-
}
|
|
781
|
-
: {
|
|
782
|
-
where,
|
|
783
|
-
select: ['id', 'name', 'title', 'subject', 'display_name'],
|
|
784
|
-
};
|
|
785
|
-
|
|
786
|
-
if (returnAll) {
|
|
787
|
-
let offset = 0;
|
|
788
|
-
const page = 500;
|
|
789
|
-
let hasMore = true;
|
|
790
|
-
|
|
791
|
-
while (hasMore) {
|
|
792
|
-
const r = await civicrmApiRequest.call(
|
|
793
|
-
this,
|
|
794
|
-
'POST',
|
|
795
|
-
`/civicrm/ajax/api4/${entity}/get`,
|
|
796
|
-
{ ...params, limit: page, offset },
|
|
797
|
-
);
|
|
798
|
-
|
|
799
|
-
const vals = r?.values ?? [];
|
|
800
|
-
for (const v of vals) {
|
|
801
|
-
out.push({
|
|
802
|
-
json: v,
|
|
803
|
-
pairedItem: { item: i },
|
|
804
|
-
});
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
if (vals.length < page) {
|
|
808
|
-
hasMore = false;
|
|
809
|
-
} else {
|
|
810
|
-
offset += page;
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
} else {
|
|
814
|
-
const r = await civicrmApiRequest.call(
|
|
815
|
-
this,
|
|
816
|
-
'POST',
|
|
817
|
-
`/civicrm/ajax/api4/${entity}/get`,
|
|
818
|
-
{ ...params, limit },
|
|
819
|
-
);
|
|
820
|
-
|
|
821
|
-
const vals = r?.values ?? [];
|
|
822
|
-
for (const v of vals) {
|
|
823
|
-
out.push({
|
|
824
|
-
json: v,
|
|
825
|
-
pairedItem: { item: i },
|
|
826
|
-
});
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
continue;
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
/* ======================================================
|
|
834
|
-
DELETE
|
|
835
|
-
====================================================== */
|
|
836
|
-
if (operation === 'delete') {
|
|
837
|
-
const id = this.getNodeParameter('id', i) as number;
|
|
838
|
-
|
|
839
|
-
const r = await civicrmApiRequest.call(
|
|
840
|
-
this,
|
|
841
|
-
'POST',
|
|
842
|
-
`/civicrm/ajax/api4/${entity}/delete`,
|
|
843
|
-
{
|
|
844
|
-
where: [['id', '=', id]],
|
|
845
|
-
},
|
|
846
|
-
);
|
|
847
|
-
|
|
848
|
-
out.push({
|
|
849
|
-
json: {
|
|
850
|
-
success: true,
|
|
851
|
-
message: `${entity} ${id} deleted`,
|
|
852
|
-
deleted_id: id,
|
|
853
|
-
api_response: r,
|
|
854
|
-
},
|
|
855
|
-
pairedItem: { item: i },
|
|
856
|
-
});
|
|
857
|
-
|
|
858
|
-
continue;
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
/* ======================================================
|
|
862
|
-
CREATE / UPDATE
|
|
863
|
-
====================================================== */
|
|
864
|
-
|
|
865
|
-
const isCreate = operation === 'create';
|
|
866
|
-
const id = !isCreate ? (this.getNodeParameter('id', i) as number) : undefined;
|
|
867
|
-
|
|
868
|
-
const pairs = this.getNodeParameter('fields.field', i, []) as Array<{
|
|
869
|
-
fieldName: string;
|
|
870
|
-
fieldValue: string;
|
|
871
|
-
}>;
|
|
872
|
-
|
|
873
|
-
const values: Record<string, unknown> = {};
|
|
874
|
-
const emailData: Record<string, unknown> = {};
|
|
875
|
-
const phoneData: Record<string, unknown> = {};
|
|
876
|
-
const addressData: Record<string, unknown> = {};
|
|
877
|
-
|
|
878
|
-
let locationTypeMap: Record<string, string> = {};
|
|
879
|
-
if (resource === 'contact') {
|
|
880
|
-
locationTypeMap = await getLocationTypeMap.call(this);
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
for (const p of pairs) {
|
|
884
|
-
if (!p.fieldName) continue;
|
|
885
|
-
|
|
886
|
-
const key = p.fieldName.trim();
|
|
887
|
-
const rawVal = convertValue(p.fieldValue);
|
|
888
|
-
const val = rawVal;
|
|
889
|
-
|
|
890
|
-
/* Email simple */
|
|
891
|
-
if (key === 'email') {
|
|
892
|
-
if (val !== '' && val !== null && val !== undefined) {
|
|
893
|
-
emailData.email = val;
|
|
894
|
-
}
|
|
895
|
-
continue;
|
|
896
|
-
}
|
|
897
|
-
if (key.startsWith('email.')) {
|
|
898
|
-
if (val !== '' && val !== null && val !== undefined) {
|
|
899
|
-
emailData[key.replace(/^email\./, '')] = val;
|
|
900
|
-
}
|
|
901
|
-
continue;
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
/* Phone simple */
|
|
905
|
-
if (key === 'phone') {
|
|
906
|
-
if (val !== '' && val !== null && val !== undefined) {
|
|
907
|
-
phoneData.phone = val;
|
|
908
|
-
}
|
|
909
|
-
continue;
|
|
910
|
-
}
|
|
911
|
-
if (key.startsWith('phone.')) {
|
|
912
|
-
if (val !== '' && val !== null && val !== undefined) {
|
|
913
|
-
phoneData[key.replace(/^phone\./, '')] = val;
|
|
914
|
-
}
|
|
915
|
-
continue;
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
/* Address */
|
|
919
|
-
if (key.startsWith('address.')) {
|
|
920
|
-
if (val !== '' && val !== null && val !== undefined) {
|
|
921
|
-
addressData[key.replace(/^address\./, '')] = val;
|
|
922
|
-
}
|
|
923
|
-
continue;
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
/* Prefijo dinámico */
|
|
927
|
-
const segments = key.split('.');
|
|
928
|
-
if (segments.length >= 2 && resource === 'contact') {
|
|
929
|
-
const prefixRaw = segments[0];
|
|
930
|
-
const root = segments[1];
|
|
931
|
-
const subfield = segments.slice(2).join('.') || '';
|
|
932
|
-
|
|
933
|
-
const normalizedPrefix = normalizeLocationKey(prefixRaw);
|
|
934
|
-
const mappedLocationName = locationTypeMap[normalizedPrefix];
|
|
935
|
-
|
|
936
|
-
if (root === 'email' || root === 'phone' || root === 'address') {
|
|
937
|
-
if (mappedLocationName) {
|
|
938
|
-
if (root === 'email') emailLocationName = mappedLocationName;
|
|
939
|
-
if (root === 'phone') phoneLocationName = mappedLocationName;
|
|
940
|
-
if (root === 'address') addressLocationName = mappedLocationName;
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
if (root === 'email') {
|
|
944
|
-
if (val !== '' && val !== null && val !== undefined) {
|
|
945
|
-
if (!subfield) emailData.email = val;
|
|
946
|
-
else emailData[subfield] = val;
|
|
947
|
-
}
|
|
948
|
-
continue;
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
if (root === 'phone') {
|
|
952
|
-
if (val !== '' && val !== null && val !== undefined) {
|
|
953
|
-
if (!subfield) phoneData.phone = val;
|
|
954
|
-
else phoneData[subfield] = val;
|
|
955
|
-
}
|
|
956
|
-
continue;
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
if (root === 'address') {
|
|
960
|
-
if (val !== '' && val !== null && val !== undefined) {
|
|
961
|
-
if (subfield) addressData[subfield] = val;
|
|
962
|
-
}
|
|
963
|
-
continue;
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
/* gender */
|
|
969
|
-
if (key === 'gender' || key === 'gender_id') {
|
|
970
|
-
if (val !== '' && val !== null && val !== undefined) {
|
|
971
|
-
values.gender_id = val;
|
|
972
|
-
}
|
|
973
|
-
continue;
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
/* birth_date */
|
|
977
|
-
if (key === 'birth_date' || key === 'birth') {
|
|
978
|
-
if (val !== '' && val !== null && val !== undefined) {
|
|
979
|
-
values.birth_date = normalizeBirthDate(String(val));
|
|
980
|
-
}
|
|
981
|
-
continue;
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
/* default */
|
|
985
|
-
if (val !== '' && val !== null && val !== undefined) {
|
|
986
|
-
values[key] = val;
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
/* Contact type */
|
|
991
|
-
const contactType = this.getNodeParameter('contactType', i, '') as string;
|
|
992
|
-
if (resource === 'contact' && contactType) {
|
|
993
|
-
values.contact_type = contactType;
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
/* CREATE/UPDATE contact */
|
|
997
|
-
let contactId = id;
|
|
998
|
-
if (isCreate) {
|
|
999
|
-
const r = await civicrmApiRequest.call(
|
|
1000
|
-
this,
|
|
1001
|
-
'POST',
|
|
1002
|
-
'/civicrm/ajax/api4/Contact/create',
|
|
1003
|
-
{ values },
|
|
1004
|
-
);
|
|
1005
|
-
contactId = r?.values?.[0]?.id;
|
|
1006
|
-
if (!contactId) throw new Error('Failed to create contact.');
|
|
1007
|
-
} else {
|
|
1008
|
-
await civicrmApiRequest.call(
|
|
1009
|
-
this,
|
|
1010
|
-
'POST',
|
|
1011
|
-
`/civicrm/ajax/api4/${entity}/update`,
|
|
1012
|
-
{
|
|
1013
|
-
values,
|
|
1014
|
-
where: [['id', '=', contactId]],
|
|
1015
|
-
},
|
|
1016
|
-
);
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
/* SUBENTITIES */
|
|
1020
|
-
if (resource === 'contact') {
|
|
1021
|
-
if (isCreate && isPrimaryEmail) {
|
|
1022
|
-
await civicrmApiRequest.call(this, 'POST', '/civicrm/ajax/api4/Email/delete', {
|
|
1023
|
-
where: [
|
|
1024
|
-
['contact_id', '=', contactId],
|
|
1025
|
-
['is_primary', '=', true],
|
|
1026
|
-
],
|
|
1027
|
-
});
|
|
1028
|
-
}
|
|
1029
|
-
if (isCreate && isPrimaryPhone) {
|
|
1030
|
-
await civicrmApiRequest.call(this, 'POST', '/civicrm/ajax/api4/Phone/delete', {
|
|
1031
|
-
where: [
|
|
1032
|
-
['contact_id', '=', contactId],
|
|
1033
|
-
['is_primary', '=', true],
|
|
1034
|
-
],
|
|
1035
|
-
});
|
|
1036
|
-
}
|
|
1037
|
-
if (isCreate && isPrimaryAddress) {
|
|
1038
|
-
await civicrmApiRequest.call(this, 'POST', '/civicrm/ajax/api4/Address/delete', {
|
|
1039
|
-
where: [
|
|
1040
|
-
['contact_id', '=', contactId],
|
|
1041
|
-
['is_primary', '=', true],
|
|
1042
|
-
],
|
|
1043
|
-
});
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
if (Object.keys(emailData).length) {
|
|
1047
|
-
if (isCreate) {
|
|
1048
|
-
await civicrmApiRequest.call(this, 'POST', '/civicrm/ajax/api4/Email/create', {
|
|
1049
|
-
values: {
|
|
1050
|
-
...emailData,
|
|
1051
|
-
contact_id: contactId,
|
|
1052
|
-
is_primary: isPrimaryEmail,
|
|
1053
|
-
'location_type_id:name': emailLocationName,
|
|
1054
|
-
},
|
|
1055
|
-
});
|
|
1056
|
-
} else {
|
|
1057
|
-
const existingEmail = await civicrmApiRequest.call(this, 'POST', '/civicrm/ajax/api4/Email/get', {
|
|
1058
|
-
where: [
|
|
1059
|
-
['contact_id', '=', contactId],
|
|
1060
|
-
['location_type_id:name', '=', emailLocationName],
|
|
1061
|
-
],
|
|
1062
|
-
limit: 1,
|
|
1063
|
-
select: ['id'],
|
|
1064
|
-
});
|
|
1065
|
-
const existingEmailId = existingEmail?.values?.[0]?.id as number | undefined;
|
|
1066
|
-
if (existingEmailId) {
|
|
1067
|
-
await civicrmApiRequest.call(this, 'POST', '/civicrm/ajax/api4/Email/update', {
|
|
1068
|
-
values: {
|
|
1069
|
-
id: existingEmailId,
|
|
1070
|
-
...emailData,
|
|
1071
|
-
contact_id: contactId,
|
|
1072
|
-
is_primary: isPrimaryEmail,
|
|
1073
|
-
},
|
|
1074
|
-
});
|
|
1075
|
-
} else {
|
|
1076
|
-
await civicrmApiRequest.call(this, 'POST', '/civicrm/ajax/api4/Email/create', {
|
|
1077
|
-
values: {
|
|
1078
|
-
...emailData,
|
|
1079
|
-
contact_id: contactId,
|
|
1080
|
-
is_primary: isPrimaryEmail,
|
|
1081
|
-
'location_type_id:name': emailLocationName,
|
|
1082
|
-
},
|
|
1083
|
-
});
|
|
1084
|
-
}
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
if (Object.keys(phoneData).length) {
|
|
1089
|
-
if (isCreate) {
|
|
1090
|
-
await civicrmApiRequest.call(this, 'POST', '/civicrm/ajax/api4/Phone/create', {
|
|
1091
|
-
values: {
|
|
1092
|
-
...phoneData,
|
|
1093
|
-
contact_id: contactId,
|
|
1094
|
-
is_primary: isPrimaryPhone,
|
|
1095
|
-
'location_type_id:name': phoneLocationName,
|
|
1096
|
-
},
|
|
1097
|
-
});
|
|
1098
|
-
} else {
|
|
1099
|
-
const existingPhone = await civicrmApiRequest.call(this, 'POST', '/civicrm/ajax/api4/Phone/get', {
|
|
1100
|
-
where: [
|
|
1101
|
-
['contact_id', '=', contactId],
|
|
1102
|
-
['location_type_id:name', '=', phoneLocationName],
|
|
1103
|
-
],
|
|
1104
|
-
limit: 1,
|
|
1105
|
-
select: ['id'],
|
|
1106
|
-
});
|
|
1107
|
-
const existingPhoneId = existingPhone?.values?.[0]?.id as number | undefined;
|
|
1108
|
-
if (existingPhoneId) {
|
|
1109
|
-
await civicrmApiRequest.call(this, 'POST', '/civicrm/ajax/api4/Phone/update', {
|
|
1110
|
-
values: {
|
|
1111
|
-
id: existingPhoneId,
|
|
1112
|
-
...phoneData,
|
|
1113
|
-
contact_id: contactId,
|
|
1114
|
-
is_primary: isPrimaryPhone,
|
|
1115
|
-
},
|
|
1116
|
-
});
|
|
1117
|
-
} else {
|
|
1118
|
-
await civicrmApiRequest.call(this, 'POST', '/civicrm/ajax/api4/Phone/create', {
|
|
1119
|
-
values: {
|
|
1120
|
-
...phoneData,
|
|
1121
|
-
contact_id: contactId,
|
|
1122
|
-
is_primary: isPrimaryPhone,
|
|
1123
|
-
'location_type_id:name': phoneLocationName,
|
|
1124
|
-
},
|
|
1125
|
-
});
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
if (Object.keys(addressData).length) {
|
|
1132
|
-
if (isCreate) {
|
|
1133
|
-
await civicrmApiRequest.call(this, 'POST', '/civicrm/ajax/api4/Address/create', {
|
|
1134
|
-
values: {
|
|
1135
|
-
...addressData,
|
|
1136
|
-
contact_id: contactId,
|
|
1137
|
-
is_primary: isPrimaryAddress,
|
|
1138
|
-
'location_type_id:name': addressLocationName,
|
|
1139
|
-
},
|
|
1140
|
-
});
|
|
1141
|
-
} else {
|
|
1142
|
-
const existingAddress = await civicrmApiRequest.call(this, 'POST', '/civicrm/ajax/api4/Address/get', {
|
|
1143
|
-
where: [
|
|
1144
|
-
['contact_id', '=', contactId],
|
|
1145
|
-
['location_type_id:name', '=', addressLocationName],
|
|
1146
|
-
],
|
|
1147
|
-
limit: 1,
|
|
1148
|
-
select: ['id'],
|
|
1149
|
-
});
|
|
1150
|
-
const existingAddressId = existingAddress?.values?.[0]?.id as number | undefined;
|
|
1151
|
-
if (existingAddressId) {
|
|
1152
|
-
await civicrmApiRequest.call(this, 'POST', '/civicrm/ajax/api4/Address/update', {
|
|
1153
|
-
values: {
|
|
1154
|
-
id: existingAddressId,
|
|
1155
|
-
...addressData,
|
|
1156
|
-
contact_id: contactId,
|
|
1157
|
-
is_primary: isPrimaryAddress,
|
|
1158
|
-
},
|
|
1159
|
-
});
|
|
1160
|
-
} else {
|
|
1161
|
-
await civicrmApiRequest.call(this, 'POST', '/civicrm/ajax/api4/Address/create', {
|
|
1162
|
-
values: {
|
|
1163
|
-
...addressData,
|
|
1164
|
-
contact_id: contactId,
|
|
1165
|
-
is_primary: isPrimaryAddress,
|
|
1166
|
-
'location_type_id:name': addressLocationName,
|
|
1167
|
-
},
|
|
1168
|
-
});
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
|
-
/* FINAL GET */
|
|
1175
|
-
const res = await civicrmApiRequest.call(
|
|
1176
|
-
this,
|
|
1177
|
-
'POST',
|
|
1178
|
-
`/civicrm/ajax/api4/${entity}/get`,
|
|
1179
|
-
{
|
|
1180
|
-
where: [['id', '=', contactId]],
|
|
1181
|
-
select: [
|
|
1182
|
-
'id',
|
|
1183
|
-
'display_name',
|
|
1184
|
-
'first_name',
|
|
1185
|
-
'last_name',
|
|
1186
|
-
'organization_name',
|
|
1187
|
-
'legal_name',
|
|
1188
|
-
'contact_type',
|
|
1189
|
-
'gender_id',
|
|
1190
|
-
'gender_id:name',
|
|
1191
|
-
'birth_date',
|
|
1192
|
-
],
|
|
1193
|
-
...(resource === 'contact'
|
|
1194
|
-
? {
|
|
1195
|
-
chain: {
|
|
1196
|
-
emails: ['Email', 'get', { where: [['contact_id', '=', '$id']] }],
|
|
1197
|
-
phones: ['Phone', 'get', { where: [['contact_id', '=', '$id']] }],
|
|
1198
|
-
addresses: ['Address', 'get', { where: [['contact_id', '=', '$id']] }],
|
|
1199
|
-
},
|
|
1200
|
-
}
|
|
1201
|
-
: {}),
|
|
1202
|
-
},
|
|
1203
|
-
);
|
|
1204
|
-
|
|
1205
|
-
out.push({
|
|
1206
|
-
json: res?.values?.[0] ?? {},
|
|
1207
|
-
pairedItem: { item: i },
|
|
1208
|
-
});
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
return [out];
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
/* ============================================================================
|
|
1216
|
-
UTILS
|
|
1217
|
-
============================================================================ */
|
|
1218
|
-
|
|
1219
|
-
function convertValue(val: string): unknown {
|
|
1220
|
-
const t = String(val ?? '').trim();
|
|
1221
|
-
if (t === '') return '';
|
|
1222
|
-
if (t === 'true') return true;
|
|
1223
|
-
if (t === 'false') return false;
|
|
1224
|
-
if (/^-?\d+(\.\d+)?$/.test(t)) return Number(t);
|
|
1225
|
-
try {
|
|
1226
|
-
const j = JSON.parse(t);
|
|
1227
|
-
if (typeof j === 'object') return j;
|
|
1228
|
-
} catch (error) {
|
|
1229
|
-
// Not valid JSON, return original value
|
|
1230
|
-
}
|
|
1231
|
-
return val;
|
|
1232
|
-
}
|
|
1233
|
-
|
|
1234
|
-
function normalizeBirthDate(input: string): string {
|
|
1235
|
-
if (!input) return input;
|
|
1236
|
-
let v = input.trim();
|
|
1237
|
-
|
|
1238
|
-
if (/^\d{4}-\d{2}-\d{2}$/.test(v)) {
|
|
1239
|
-
// Already in correct format
|
|
1240
|
-
} else if (/^\d{2}\/\d{2}\/\d{4}$/.test(v)) {
|
|
1241
|
-
const [d, m, y] = v.split('/');
|
|
1242
|
-
v = `${y}-${m}-${d}`;
|
|
1243
|
-
} else if (/^\d{2}-\d{2}-\d{4}$/.test(v)) {
|
|
1244
|
-
const [d, m, y] = v.split('-');
|
|
1245
|
-
v = `${y}-${m}-${d}`;
|
|
1246
|
-
} else if (/^\d{4}\/\d{2}\/\d{2}$/.test(v)) {
|
|
1247
|
-
v = v.replace(/\//g, '-');
|
|
1248
|
-
} else if (/^\d{4}\.\d{2}\.\d{2}$/.test(v)) {
|
|
1249
|
-
v = v.replace(/\./g, '-');
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
const dt = new Date(v);
|
|
1253
|
-
if (isNaN(dt.getTime())) throw new Error(`Invalid birth_date: ${input}`);
|
|
1254
|
-
return v;
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
export default CiviCrm;
|