@ixiam/n8n-nodes-civicrm 0.3.8 → 0.4.1
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 +2 -1
- package/dist/nodes/CiviCrm/CiviCrm.node.js +191 -84
- package/nodes/CiviCrm/CiviCrm.node.ts +222 -158
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,4 +9,5 @@ Credentials:
|
|
|
9
9
|
- Use the **Save** button to verify connectivity.
|
|
10
10
|
|
|
11
11
|
Entities: Contact, Event, Case, Contribution, Membership → `get|getMany|create|update|delete`
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
Node developed by Ixiam Global Solution: `https://www.ixiam.com'
|
|
@@ -35,6 +35,65 @@ class CiviCrm {
|
|
|
35
35
|
properties: [
|
|
36
36
|
resources_1.resourceProp,
|
|
37
37
|
resources_1.operationProp,
|
|
38
|
+
{
|
|
39
|
+
displayName: 'Contact Type',
|
|
40
|
+
name: 'contactType',
|
|
41
|
+
type: 'options',
|
|
42
|
+
default: 'Individual',
|
|
43
|
+
options: [
|
|
44
|
+
{ name: 'Individual', value: 'Individual' },
|
|
45
|
+
{ name: 'Organization', value: 'Organization' },
|
|
46
|
+
{ name: 'Household', value: 'Household' },
|
|
47
|
+
],
|
|
48
|
+
displayOptions: { show: { resource: ['contact'] } },
|
|
49
|
+
description: 'Filter or assign contact type',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
displayName: 'Email Location Type',
|
|
53
|
+
name: 'emailLocation',
|
|
54
|
+
type: 'options',
|
|
55
|
+
default: 'Work',
|
|
56
|
+
options: [
|
|
57
|
+
{ name: 'Home', value: 'Home' },
|
|
58
|
+
{ name: 'Work', value: 'Work' },
|
|
59
|
+
{ name: 'Other', value: 'Other' },
|
|
60
|
+
],
|
|
61
|
+
displayOptions: { show: { resource: ['contact'], operation: ['create', 'update'] } },
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
displayName: 'Phone Location Type',
|
|
65
|
+
name: 'phoneLocation',
|
|
66
|
+
type: 'options',
|
|
67
|
+
default: 'Work',
|
|
68
|
+
options: [
|
|
69
|
+
{ name: 'Home', value: 'Home' },
|
|
70
|
+
{ name: 'Work', value: 'Work' },
|
|
71
|
+
{ name: 'Mobile', value: 'Mobile' },
|
|
72
|
+
{ name: 'Other', value: 'Other' },
|
|
73
|
+
],
|
|
74
|
+
displayOptions: { show: { resource: ['contact'], operation: ['create', 'update'] } },
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
displayName: 'Address Location Type',
|
|
78
|
+
name: 'addressLocation',
|
|
79
|
+
type: 'options',
|
|
80
|
+
default: 'Home',
|
|
81
|
+
options: [
|
|
82
|
+
{ name: 'Home', value: 'Home' },
|
|
83
|
+
{ name: 'Work', value: 'Work' },
|
|
84
|
+
{ name: 'Billing', value: 'Billing' },
|
|
85
|
+
{ name: 'Other', value: 'Other' },
|
|
86
|
+
],
|
|
87
|
+
displayOptions: { show: { resource: ['contact'], operation: ['create', 'update'] } },
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
displayName: 'Mark as Primary',
|
|
91
|
+
name: 'isPrimary',
|
|
92
|
+
type: 'boolean',
|
|
93
|
+
default: true,
|
|
94
|
+
displayOptions: { show: { resource: ['contact'], operation: ['create', 'update'] } },
|
|
95
|
+
description: 'If enabled, replaces previous primary email/phone/address with this one',
|
|
96
|
+
},
|
|
38
97
|
{
|
|
39
98
|
displayName: 'ID',
|
|
40
99
|
name: 'id',
|
|
@@ -59,9 +118,7 @@ class CiviCrm {
|
|
|
59
118
|
'X-Civi-Auth': `Bearer ${apiToken}`,
|
|
60
119
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
61
120
|
},
|
|
62
|
-
body: {
|
|
63
|
-
params: JSON.stringify({ limit: 5 }),
|
|
64
|
-
},
|
|
121
|
+
body: { params: JSON.stringify({ limit: 5 }) },
|
|
65
122
|
json: true,
|
|
66
123
|
});
|
|
67
124
|
const values = (res?.values || []);
|
|
@@ -80,61 +137,69 @@ class CiviCrm {
|
|
|
80
137
|
const operation = this.getNodeParameter('operation', 0);
|
|
81
138
|
const entity = ENTITY_MAP[resource];
|
|
82
139
|
for (let i = 0; i < items.length; i++) {
|
|
140
|
+
const emailLocation = this.getNodeParameter('emailLocation', i, 'Work');
|
|
141
|
+
const phoneLocation = this.getNodeParameter('phoneLocation', i, 'Work');
|
|
142
|
+
const addressLocation = this.getNodeParameter('addressLocation', i, 'Home');
|
|
143
|
+
const isPrimary = this.getNodeParameter('isPrimary', i, true);
|
|
83
144
|
// === GET ===
|
|
84
145
|
if (operation === 'get') {
|
|
85
146
|
const id = this.getNodeParameter('id', i);
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
'first_name',
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
147
|
+
const where = [['id', '=', id]];
|
|
148
|
+
// Solo para Contact → usar chain
|
|
149
|
+
if (resource === 'contact') {
|
|
150
|
+
const params = {
|
|
151
|
+
where,
|
|
152
|
+
limit: 1,
|
|
153
|
+
select: ['id', 'display_name', 'first_name', 'last_name', 'contact_type'],
|
|
154
|
+
chain: {
|
|
155
|
+
emails: ['Email', 'get', { where: [['contact_id', '=', '$id']] }],
|
|
156
|
+
phones: ['Phone', 'get', { where: [['contact_id', '=', '$id']] }],
|
|
157
|
+
addresses: ['Address', 'get', { where: [['contact_id', '=', '$id']] }],
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
const res = await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/Contact/get`, params);
|
|
161
|
+
out.push({ json: (res?.values?.[0] ?? {}) });
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
// Otras entidades → select estándar
|
|
165
|
+
const params = {
|
|
166
|
+
where,
|
|
167
|
+
limit: 1,
|
|
168
|
+
select: ['id', 'name', 'title', 'subject', 'display_name'],
|
|
169
|
+
};
|
|
170
|
+
const res = await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/${entity}/get`, params);
|
|
171
|
+
out.push({ json: (res?.values?.[0] ?? {}) });
|
|
172
|
+
}
|
|
110
173
|
}
|
|
111
174
|
// === GET MANY ===
|
|
112
175
|
if (operation === 'getMany') {
|
|
113
176
|
const returnAll = this.getNodeParameter('returnAll', i, false);
|
|
114
177
|
const limit = this.getNodeParameter('limit', i, 100);
|
|
115
178
|
const whereJson = this.getNodeParameter('whereJson', i, '');
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
'
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
'
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
179
|
+
let where = whereJson ? JSON.parse(whereJson) : [];
|
|
180
|
+
if (resource === 'contact') {
|
|
181
|
+
const contactType = this.getNodeParameter('contactType', i, '');
|
|
182
|
+
if (contactType)
|
|
183
|
+
where.push(['contact_type', '=', contactType]);
|
|
184
|
+
}
|
|
185
|
+
let baseParams;
|
|
186
|
+
if (resource === 'contact') {
|
|
187
|
+
baseParams = {
|
|
188
|
+
where,
|
|
189
|
+
select: ['id', 'display_name', 'first_name', 'last_name', 'contact_type'],
|
|
190
|
+
chain: {
|
|
191
|
+
emails: ['Email', 'get', { where: [['contact_id', '=', '$id']] }],
|
|
192
|
+
phones: ['Phone', 'get', { where: [['contact_id', '=', '$id']] }],
|
|
193
|
+
addresses: ['Address', 'get', { where: [['contact_id', '=', '$id']] }],
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
baseParams = {
|
|
199
|
+
where,
|
|
200
|
+
select: ['id', 'name', 'title', 'subject', 'display_name'],
|
|
201
|
+
};
|
|
202
|
+
}
|
|
138
203
|
if (returnAll) {
|
|
139
204
|
let offset = 0;
|
|
140
205
|
const page = 500;
|
|
@@ -155,47 +220,90 @@ class CiviCrm {
|
|
|
155
220
|
out.push({ json: v });
|
|
156
221
|
}
|
|
157
222
|
}
|
|
158
|
-
// === CREATE ===
|
|
159
|
-
|
|
223
|
+
// === CREATE y UPDATE ===
|
|
224
|
+
const isCreate = operation === 'create';
|
|
225
|
+
if (isCreate || operation === 'update') {
|
|
226
|
+
const id = !isCreate ? this.getNodeParameter('id', i) : undefined;
|
|
160
227
|
const pairs = this.getNodeParameter('fields.field', i, []);
|
|
161
|
-
const values =
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
228
|
+
const values = {};
|
|
229
|
+
const emailData = {};
|
|
230
|
+
const phoneData = {};
|
|
231
|
+
const addressData = {};
|
|
232
|
+
for (const p of pairs) {
|
|
233
|
+
if (!p.fieldName)
|
|
234
|
+
continue;
|
|
235
|
+
const key = p.fieldName.trim();
|
|
236
|
+
const val = convertValue(p.fieldValue);
|
|
237
|
+
if (key.startsWith('email.'))
|
|
238
|
+
emailData[key.replace(/^email\./, '')] = val;
|
|
239
|
+
else if (key.startsWith('phone.'))
|
|
240
|
+
phoneData[key.replace(/^phone\./, '')] = val;
|
|
241
|
+
else if (key.startsWith('address.'))
|
|
242
|
+
addressData[key.replace(/^address\./, '')] = val;
|
|
243
|
+
else
|
|
244
|
+
values[key] = val;
|
|
245
|
+
}
|
|
246
|
+
const contactType = this.getNodeParameter('contactType', i, '');
|
|
247
|
+
if (resource === 'contact' && contactType)
|
|
248
|
+
values.contact_type = contactType;
|
|
249
|
+
let contactId = id;
|
|
250
|
+
if (isCreate) {
|
|
251
|
+
const contactRes = await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/Contact/create`, { values });
|
|
252
|
+
contactId = contactRes?.values?.[0]?.id;
|
|
253
|
+
if (!contactId)
|
|
254
|
+
throw new Error('Failed to create contact.');
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/${entity}/update`, {
|
|
258
|
+
values,
|
|
259
|
+
where: [['id', '=', contactId]],
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
if (resource === 'contact') {
|
|
263
|
+
if (isPrimary) {
|
|
264
|
+
await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/Email/delete`, {
|
|
265
|
+
where: [['contact_id', '=', contactId], ['is_primary', '=', true]],
|
|
266
|
+
});
|
|
267
|
+
await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/Phone/delete`, {
|
|
268
|
+
where: [['contact_id', '=', contactId], ['is_primary', '=', true]],
|
|
269
|
+
});
|
|
270
|
+
await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/Address/delete`, {
|
|
271
|
+
where: [['contact_id', '=', contactId], ['is_primary', '=', true]],
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
if (Object.keys(emailData).length)
|
|
275
|
+
await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/Email/create`, {
|
|
276
|
+
values: { ...emailData, contact_id: contactId, is_primary: isPrimary, 'location_type_id:name': emailLocation },
|
|
277
|
+
});
|
|
278
|
+
if (Object.keys(phoneData).length)
|
|
279
|
+
await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/Phone/create`, {
|
|
280
|
+
values: { ...phoneData, contact_id: contactId, is_primary: isPrimary, 'location_type_id:name': phoneLocation },
|
|
281
|
+
});
|
|
282
|
+
if (Object.keys(addressData).length)
|
|
283
|
+
await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/Address/create`, {
|
|
284
|
+
values: { ...addressData, contact_id: contactId, is_primary: isPrimary, 'location_type_id:name': addressLocation },
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
const res = await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/${entity}/get`, {
|
|
288
|
+
where: [['id', '=', contactId]],
|
|
289
|
+
select: ['id', 'display_name', 'first_name', 'last_name', 'contact_type'],
|
|
290
|
+
...(resource === 'contact'
|
|
291
|
+
? {
|
|
292
|
+
chain: {
|
|
293
|
+
emails: ['Email', 'get', { where: [['contact_id', '=', '$id']] }],
|
|
294
|
+
phones: ['Phone', 'get', { where: [['contact_id', '=', '$id']] }],
|
|
295
|
+
addresses: ['Address', 'get', { where: [['contact_id', '=', '$id']] }],
|
|
296
|
+
},
|
|
297
|
+
}
|
|
298
|
+
: {}),
|
|
299
|
+
});
|
|
300
|
+
out.push({ json: (res?.values?.[0] ?? {}) });
|
|
190
301
|
}
|
|
191
302
|
}
|
|
192
303
|
return [out];
|
|
193
304
|
}
|
|
194
305
|
}
|
|
195
306
|
exports.CiviCrm = CiviCrm;
|
|
196
|
-
/**
|
|
197
|
-
* Convierte string a número, boolean, objeto o deja string
|
|
198
|
-
*/
|
|
199
307
|
function convertValue(val) {
|
|
200
308
|
const t = String(val ?? '').trim();
|
|
201
309
|
if (t === '')
|
|
@@ -214,5 +322,4 @@ function convertValue(val) {
|
|
|
214
322
|
catch { }
|
|
215
323
|
return val;
|
|
216
324
|
}
|
|
217
|
-
// Requerido por n8n >=1.110
|
|
218
325
|
exports.default = CiviCrm;
|
|
@@ -60,6 +60,65 @@ export class CiviCrm implements INodeType {
|
|
|
60
60
|
properties: [
|
|
61
61
|
resourceProp,
|
|
62
62
|
operationProp,
|
|
63
|
+
{
|
|
64
|
+
displayName: 'Contact Type',
|
|
65
|
+
name: 'contactType',
|
|
66
|
+
type: 'options',
|
|
67
|
+
default: 'Individual',
|
|
68
|
+
options: [
|
|
69
|
+
{ name: 'Individual', value: 'Individual' },
|
|
70
|
+
{ name: 'Organization', value: 'Organization' },
|
|
71
|
+
{ name: 'Household', value: 'Household' },
|
|
72
|
+
],
|
|
73
|
+
displayOptions: { show: { resource: ['contact'] } },
|
|
74
|
+
description: 'Filter or assign contact type',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
displayName: 'Email Location Type',
|
|
78
|
+
name: 'emailLocation',
|
|
79
|
+
type: 'options',
|
|
80
|
+
default: 'Work',
|
|
81
|
+
options: [
|
|
82
|
+
{ name: 'Home', value: 'Home' },
|
|
83
|
+
{ name: 'Work', value: 'Work' },
|
|
84
|
+
{ name: 'Other', value: 'Other' },
|
|
85
|
+
],
|
|
86
|
+
displayOptions: { show: { resource: ['contact'], operation: ['create', 'update'] } },
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
displayName: 'Phone Location Type',
|
|
90
|
+
name: 'phoneLocation',
|
|
91
|
+
type: 'options',
|
|
92
|
+
default: 'Work',
|
|
93
|
+
options: [
|
|
94
|
+
{ name: 'Home', value: 'Home' },
|
|
95
|
+
{ name: 'Work', value: 'Work' },
|
|
96
|
+
{ name: 'Mobile', value: 'Mobile' },
|
|
97
|
+
{ name: 'Other', value: 'Other' },
|
|
98
|
+
],
|
|
99
|
+
displayOptions: { show: { resource: ['contact'], operation: ['create', 'update'] } },
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
displayName: 'Address Location Type',
|
|
103
|
+
name: 'addressLocation',
|
|
104
|
+
type: 'options',
|
|
105
|
+
default: 'Home',
|
|
106
|
+
options: [
|
|
107
|
+
{ name: 'Home', value: 'Home' },
|
|
108
|
+
{ name: 'Work', value: 'Work' },
|
|
109
|
+
{ name: 'Billing', value: 'Billing' },
|
|
110
|
+
{ name: 'Other', value: 'Other' },
|
|
111
|
+
],
|
|
112
|
+
displayOptions: { show: { resource: ['contact'], operation: ['create', 'update'] } },
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
displayName: 'Mark as Primary',
|
|
116
|
+
name: 'isPrimary',
|
|
117
|
+
type: 'boolean',
|
|
118
|
+
default: true,
|
|
119
|
+
displayOptions: { show: { resource: ['contact'], operation: ['create', 'update'] } },
|
|
120
|
+
description: 'If enabled, replaces previous primary email/phone/address with this one',
|
|
121
|
+
},
|
|
63
122
|
{
|
|
64
123
|
displayName: 'ID',
|
|
65
124
|
name: 'id',
|
|
@@ -82,7 +141,6 @@ export class CiviCrm implements INodeType {
|
|
|
82
141
|
};
|
|
83
142
|
|
|
84
143
|
const url = `${baseUrl.replace(/\/$/, '')}/civicrm/ajax/api4/OptionValue/get`;
|
|
85
|
-
|
|
86
144
|
const res = await this.helpers.httpRequest({
|
|
87
145
|
method: 'POST',
|
|
88
146
|
url,
|
|
@@ -90,9 +148,7 @@ export class CiviCrm implements INodeType {
|
|
|
90
148
|
'X-Civi-Auth': `Bearer ${apiToken}`,
|
|
91
149
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
92
150
|
},
|
|
93
|
-
body: {
|
|
94
|
-
params: JSON.stringify({ limit: 5 }),
|
|
95
|
-
},
|
|
151
|
+
body: { params: JSON.stringify({ limit: 5 }) },
|
|
96
152
|
json: true,
|
|
97
153
|
});
|
|
98
154
|
|
|
@@ -105,186 +161,195 @@ export class CiviCrm implements INodeType {
|
|
|
105
161
|
},
|
|
106
162
|
};
|
|
107
163
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const resource = this.getNodeParameter('resource', 0) as Resource;
|
|
113
|
-
const operation = this.getNodeParameter('operation', 0) as Operation;
|
|
114
|
-
const entity = ENTITY_MAP[resource];
|
|
115
|
-
|
|
116
|
-
for (let i = 0; i < items.length; i++) {
|
|
117
|
-
// === GET ===
|
|
118
|
-
if (operation === 'get') {
|
|
119
|
-
const id = this.getNodeParameter('id', i) as number;
|
|
164
|
+
async execute(this: IExecuteFunctions) {
|
|
165
|
+
const items = this.getInputData();
|
|
166
|
+
const out: INodeExecutionData[] = [];
|
|
120
167
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
select: [
|
|
125
|
-
'id',
|
|
126
|
-
'display_name',
|
|
127
|
-
'first_name',
|
|
128
|
-
'last_name',
|
|
129
|
-
'contact_type',
|
|
130
|
-
'email.email',
|
|
131
|
-
'phone.phone',
|
|
132
|
-
'address.city',
|
|
133
|
-
'address.country_id:label',
|
|
134
|
-
'address.postal_code',
|
|
135
|
-
'address.street_address',
|
|
136
|
-
],
|
|
137
|
-
join: [
|
|
138
|
-
['Email AS email', 'LEFT'],
|
|
139
|
-
['Phone AS phone', 'LEFT'],
|
|
140
|
-
['Address AS address', 'LEFT'],
|
|
141
|
-
],
|
|
142
|
-
};
|
|
168
|
+
const resource = this.getNodeParameter('resource', 0) as Resource;
|
|
169
|
+
const operation = this.getNodeParameter('operation', 0) as Operation;
|
|
170
|
+
const entity = ENTITY_MAP[resource];
|
|
143
171
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
);
|
|
172
|
+
for (let i = 0; i < items.length; i++) {
|
|
173
|
+
const emailLocation = this.getNodeParameter('emailLocation', i, 'Work') as string;
|
|
174
|
+
const phoneLocation = this.getNodeParameter('phoneLocation', i, 'Work') as string;
|
|
175
|
+
const addressLocation = this.getNodeParameter('addressLocation', i, 'Home') as string;
|
|
176
|
+
const isPrimary = this.getNodeParameter('isPrimary', i, true) as boolean;
|
|
150
177
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if (operation === 'getMany') {
|
|
156
|
-
const returnAll = this.getNodeParameter('returnAll', i, false) as boolean;
|
|
157
|
-
const limit = this.getNodeParameter('limit', i, 100) as number;
|
|
158
|
-
const whereJson = this.getNodeParameter('whereJson', i, '') as string;
|
|
159
|
-
const where = whereJson ? JSON.parse(whereJson) : undefined;
|
|
178
|
+
// === GET ===
|
|
179
|
+
if (operation === 'get') {
|
|
180
|
+
const id = this.getNodeParameter('id', i) as number;
|
|
181
|
+
const where = [['id', '=', id]];
|
|
160
182
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
183
|
+
// Solo para Contact → usar chain
|
|
184
|
+
if (resource === 'contact') {
|
|
185
|
+
const params = {
|
|
186
|
+
where,
|
|
187
|
+
limit: 1,
|
|
188
|
+
select: ['id', 'display_name', 'first_name', 'last_name', 'contact_type'],
|
|
189
|
+
chain: {
|
|
190
|
+
emails: ['Email', 'get', { where: [['contact_id', '=', '$id']] }],
|
|
191
|
+
phones: ['Phone', 'get', { where: [['contact_id', '=', '$id']] }],
|
|
192
|
+
addresses: ['Address', 'get', { where: [['contact_id', '=', '$id']] }],
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
const res = await civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/Contact/get`, params);
|
|
196
|
+
out.push({ json: (res?.values?.[0] ?? {}) as IDataObject });
|
|
197
|
+
} else {
|
|
198
|
+
// Otras entidades → select estándar
|
|
199
|
+
const params = {
|
|
200
|
+
where,
|
|
201
|
+
limit: 1,
|
|
202
|
+
select: ['id', 'name', 'title', 'subject', 'display_name'],
|
|
203
|
+
};
|
|
204
|
+
const res = await civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/${entity}/get`, params);
|
|
205
|
+
out.push({ json: (res?.values?.[0] ?? {}) as IDataObject });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
182
208
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
this,
|
|
189
|
-
'POST',
|
|
190
|
-
`/civicrm/ajax/api4/${entity}/get`,
|
|
191
|
-
{ ...baseParams, limit: page, offset },
|
|
192
|
-
);
|
|
193
|
-
const vals = (res?.values ?? []) as IDataObject[];
|
|
194
|
-
for (const v of vals) out.push({ json: v });
|
|
195
|
-
if (vals.length < page) break;
|
|
196
|
-
offset += page;
|
|
197
|
-
}
|
|
198
|
-
} else {
|
|
199
|
-
const res = await civicrmApiRequest.call(
|
|
200
|
-
this,
|
|
201
|
-
'POST',
|
|
202
|
-
`/civicrm/ajax/api4/${entity}/get`,
|
|
203
|
-
{ ...baseParams, limit },
|
|
204
|
-
);
|
|
205
|
-
const vals = (res?.values ?? []) as IDataObject[];
|
|
206
|
-
for (const v of vals) out.push({ json: v });
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
+
// === GET MANY ===
|
|
210
|
+
if (operation === 'getMany') {
|
|
211
|
+
const returnAll = this.getNodeParameter('returnAll', i, false) as boolean;
|
|
212
|
+
const limit = this.getNodeParameter('limit', i, 100) as number;
|
|
213
|
+
const whereJson = this.getNodeParameter('whereJson', i, '') as string;
|
|
209
214
|
|
|
215
|
+
let where = whereJson ? JSON.parse(whereJson) : [];
|
|
210
216
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
fieldValue: string;
|
|
216
|
-
}>;
|
|
217
|
+
if (resource === 'contact') {
|
|
218
|
+
const contactType = this.getNodeParameter('contactType', i, '') as string;
|
|
219
|
+
if (contactType) where.push(['contact_type', '=', contactType]);
|
|
220
|
+
}
|
|
217
221
|
|
|
218
|
-
|
|
219
|
-
pairs
|
|
220
|
-
.filter((p) => p.fieldName)
|
|
221
|
-
.map((p) => [p.fieldName, convertValue(p.fieldValue)]),
|
|
222
|
-
);
|
|
222
|
+
let baseParams: Record<string, unknown>;
|
|
223
223
|
|
|
224
|
-
|
|
224
|
+
if (resource === 'contact') {
|
|
225
|
+
baseParams = {
|
|
226
|
+
where,
|
|
227
|
+
select: ['id', 'display_name', 'first_name', 'last_name', 'contact_type'],
|
|
228
|
+
chain: {
|
|
229
|
+
emails: ['Email', 'get', { where: [['contact_id', '=', '$id']] }],
|
|
230
|
+
phones: ['Phone', 'get', { where: [['contact_id', '=', '$id']] }],
|
|
231
|
+
addresses: ['Address', 'get', { where: [['contact_id', '=', '$id']] }],
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
} else {
|
|
235
|
+
baseParams = {
|
|
236
|
+
where,
|
|
237
|
+
select: ['id', 'name', 'title', 'subject', 'display_name'],
|
|
238
|
+
};
|
|
239
|
+
}
|
|
225
240
|
|
|
241
|
+
if (returnAll) {
|
|
242
|
+
let offset = 0;
|
|
243
|
+
const page = 500;
|
|
244
|
+
while (true) {
|
|
245
|
+
const res = await civicrmApiRequest.call(
|
|
246
|
+
this,
|
|
247
|
+
'POST',
|
|
248
|
+
`/civicrm/ajax/api4/${entity}/get`,
|
|
249
|
+
{ ...baseParams, limit: page, offset },
|
|
250
|
+
);
|
|
251
|
+
const vals = (res?.values ?? []) as IDataObject[];
|
|
252
|
+
for (const v of vals) out.push({ json: v });
|
|
253
|
+
if (vals.length < page) break;
|
|
254
|
+
offset += page;
|
|
255
|
+
}
|
|
256
|
+
} else {
|
|
226
257
|
const res = await civicrmApiRequest.call(
|
|
227
258
|
this,
|
|
228
259
|
'POST',
|
|
229
|
-
`/civicrm/ajax/api4/${entity}/
|
|
230
|
-
|
|
260
|
+
`/civicrm/ajax/api4/${entity}/get`,
|
|
261
|
+
{ ...baseParams, limit },
|
|
231
262
|
);
|
|
232
|
-
|
|
263
|
+
const vals = (res?.values ?? []) as IDataObject[];
|
|
264
|
+
for (const v of vals) out.push({ json: v });
|
|
233
265
|
}
|
|
266
|
+
}
|
|
234
267
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
268
|
+
// === CREATE y UPDATE ===
|
|
269
|
+
const isCreate = operation === 'create';
|
|
270
|
+
if (isCreate || operation === 'update') {
|
|
271
|
+
const id = !isCreate ? (this.getNodeParameter('id', i) as number) : undefined;
|
|
272
|
+
const pairs = this.getNodeParameter('fields.field', i, []) as Array<{ fieldName: string; fieldValue: string }>;
|
|
273
|
+
const values: Record<string, unknown> = {};
|
|
274
|
+
const emailData: Record<string, unknown> = {};
|
|
275
|
+
const phoneData: Record<string, unknown> = {};
|
|
276
|
+
const addressData: Record<string, unknown> = {};
|
|
277
|
+
|
|
278
|
+
for (const p of pairs) {
|
|
279
|
+
if (!p.fieldName) continue;
|
|
280
|
+
const key = p.fieldName.trim();
|
|
281
|
+
const val = convertValue(p.fieldValue);
|
|
282
|
+
if (key.startsWith('email.')) emailData[key.replace(/^email\./, '')] = val;
|
|
283
|
+
else if (key.startsWith('phone.')) phoneData[key.replace(/^phone\./, '')] = val;
|
|
284
|
+
else if (key.startsWith('address.')) addressData[key.replace(/^address\./, '')] = val;
|
|
285
|
+
else values[key] = val;
|
|
286
|
+
}
|
|
242
287
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
.filter((p) => p.fieldName)
|
|
246
|
-
.map((p) => [p.fieldName, convertValue(p.fieldValue)]),
|
|
247
|
-
);
|
|
288
|
+
const contactType = this.getNodeParameter('contactType', i, '') as string;
|
|
289
|
+
if (resource === 'contact' && contactType) values.contact_type = contactType;
|
|
248
290
|
|
|
249
|
-
|
|
291
|
+
let contactId = id;
|
|
292
|
+
if (isCreate) {
|
|
293
|
+
const contactRes = await civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/Contact/create`, { values });
|
|
294
|
+
contactId = contactRes?.values?.[0]?.id;
|
|
295
|
+
if (!contactId) throw new Error('Failed to create contact.');
|
|
296
|
+
} else {
|
|
297
|
+
await civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/${entity}/update`, {
|
|
250
298
|
values,
|
|
251
|
-
where: [['id', '=',
|
|
252
|
-
};
|
|
299
|
+
where: [['id', '=', contactId]],
|
|
300
|
+
});
|
|
301
|
+
}
|
|
253
302
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
'POST',
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
303
|
+
if (resource === 'contact') {
|
|
304
|
+
if (isPrimary) {
|
|
305
|
+
await civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/Email/delete`, {
|
|
306
|
+
where: [['contact_id', '=', contactId], ['is_primary', '=', true]],
|
|
307
|
+
});
|
|
308
|
+
await civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/Phone/delete`, {
|
|
309
|
+
where: [['contact_id', '=', contactId], ['is_primary', '=', true]],
|
|
310
|
+
});
|
|
311
|
+
await civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/Address/delete`, {
|
|
312
|
+
where: [['contact_id', '=', contactId], ['is_primary', '=', true]],
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (Object.keys(emailData).length)
|
|
317
|
+
await civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/Email/create`, {
|
|
318
|
+
values: { ...emailData, contact_id: contactId, is_primary: isPrimary, 'location_type_id:name': emailLocation },
|
|
319
|
+
});
|
|
320
|
+
if (Object.keys(phoneData).length)
|
|
321
|
+
await civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/Phone/create`, {
|
|
322
|
+
values: { ...phoneData, contact_id: contactId, is_primary: isPrimary, 'location_type_id:name': phoneLocation },
|
|
323
|
+
});
|
|
324
|
+
if (Object.keys(addressData).length)
|
|
325
|
+
await civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/Address/create`, {
|
|
326
|
+
values: { ...addressData, contact_id: contactId, is_primary: isPrimary, 'location_type_id:name': addressLocation },
|
|
327
|
+
});
|
|
261
328
|
}
|
|
262
329
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
330
|
+
const res = await civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/${entity}/get`, {
|
|
331
|
+
where: [['id', '=', contactId]],
|
|
332
|
+
select: ['id', 'display_name', 'first_name', 'last_name', 'contact_type'],
|
|
333
|
+
...(resource === 'contact'
|
|
334
|
+
? {
|
|
335
|
+
chain: {
|
|
336
|
+
emails: ['Email', 'get', { where: [['contact_id', '=', '$id']] }],
|
|
337
|
+
phones: ['Phone', 'get', { where: [['contact_id', '=', '$id']] }],
|
|
338
|
+
addresses: ['Address', 'get', { where: [['contact_id', '=', '$id']] }],
|
|
339
|
+
},
|
|
340
|
+
}
|
|
341
|
+
: {}),
|
|
342
|
+
});
|
|
343
|
+
out.push({ json: (res?.values?.[0] ?? {}) as IDataObject });
|
|
344
|
+
}
|
|
345
|
+
}
|
|
266
346
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
};
|
|
347
|
+
return [out];
|
|
348
|
+
}
|
|
270
349
|
|
|
271
|
-
const res = await civicrmApiRequest.call(
|
|
272
|
-
this,
|
|
273
|
-
'POST',
|
|
274
|
-
`/civicrm/ajax/api4/${entity}/delete`,
|
|
275
|
-
params,
|
|
276
|
-
);
|
|
277
|
-
out.push({ json: res as IDataObject });
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
350
|
|
|
281
|
-
return [out];
|
|
282
|
-
}
|
|
283
351
|
}
|
|
284
352
|
|
|
285
|
-
/**
|
|
286
|
-
* Convierte string a número, boolean, objeto o deja string
|
|
287
|
-
*/
|
|
288
353
|
function convertValue(val: string): unknown {
|
|
289
354
|
const t = String(val ?? '').trim();
|
|
290
355
|
if (t === '') return '';
|
|
@@ -298,5 +363,4 @@ function convertValue(val: string): unknown {
|
|
|
298
363
|
return val;
|
|
299
364
|
}
|
|
300
365
|
|
|
301
|
-
// Requerido por n8n >=1.110
|
|
302
366
|
export default CiviCrm;
|