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