@hed-hog/contact 0.0.266 → 0.0.274
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 +470 -0
- package/dist/address-type.enum.d.ts +10 -0
- package/dist/address-type.enum.d.ts.map +1 -0
- package/dist/address-type.enum.js +14 -0
- package/dist/address-type.enum.js.map +1 -0
- package/dist/contact.module.d.ts.map +1 -1
- package/dist/contact.module.js +0 -2
- package/dist/contact.module.js.map +1 -1
- package/dist/contact.service.d.ts +19 -22
- package/dist/contact.service.d.ts.map +1 -1
- package/dist/contact.service.js +22 -2
- package/dist/contact.service.js.map +1 -1
- package/dist/index.d.ts +5 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -4
- package/dist/index.js.map +1 -1
- package/dist/person/dto/create.dto.d.ts +14 -0
- package/dist/person/dto/create.dto.d.ts.map +1 -1
- package/dist/person/dto/create.dto.js +52 -1
- package/dist/person/dto/create.dto.js.map +1 -1
- package/dist/person/dto/interaction-create.dto.d.ts +16 -0
- package/dist/person/dto/interaction-create.dto.d.ts.map +1 -0
- package/dist/person/dto/interaction-create.dto.js +57 -0
- package/dist/person/dto/interaction-create.dto.js.map +1 -0
- package/dist/person/dto/update.dto.d.ts +17 -1
- package/dist/person/dto/update.dto.d.ts.map +1 -1
- package/dist/person/dto/update.dto.js +79 -3
- package/dist/person/dto/update.dto.js.map +1 -1
- package/dist/person/person.controller.d.ts +37 -8
- package/dist/person/person.controller.d.ts.map +1 -1
- package/dist/person/person.controller.js +29 -3
- package/dist/person/person.controller.js.map +1 -1
- package/dist/person/person.service.d.ts +71 -13
- package/dist/person/person.service.d.ts.map +1 -1
- package/dist/person/person.service.js +762 -108
- package/dist/person/person.service.js.map +1 -1
- package/dist/person-relation-type/person-relation-type.controller.d.ts +13 -9
- package/dist/person-relation-type/person-relation-type.controller.d.ts.map +1 -1
- package/dist/person-relation-type/person-relation-type.service.d.ts +16 -20
- package/dist/person-relation-type/person-relation-type.service.d.ts.map +1 -1
- package/dist/person-relation-type/person-relation-type.service.js +48 -41
- package/dist/person-relation-type/person-relation-type.service.js.map +1 -1
- package/hedhog/data/menu.yaml +2 -16
- package/hedhog/data/role.yaml +9 -1
- package/hedhog/data/route.yaml +10 -21
- package/hedhog/data/setting_group.yaml +21 -0
- package/hedhog/frontend/app/person/_components/delete-person-dialog.tsx.ejs +59 -0
- package/hedhog/frontend/app/person/_components/person-field-with-create.tsx.ejs +831 -0
- package/hedhog/frontend/app/person/_components/person-form-sheet.tsx.ejs +2277 -0
- package/hedhog/frontend/app/person/_components/person-types.ts.ejs +157 -0
- package/hedhog/frontend/app/person/page.tsx.ejs +1158 -1335
- package/hedhog/frontend/messages/en.json +114 -4
- package/hedhog/frontend/messages/pt.json +155 -4
- package/hedhog/table/person.yaml +7 -0
- package/hedhog/table/person_address.yaml +18 -0
- package/hedhog/table/person_company.yaml +26 -11
- package/hedhog/table/person_individual.yaml +4 -0
- package/hedhog/table/person_individual_relation.yaml +39 -0
- package/package.json +6 -5
- package/src/address-type.enum.ts +9 -0
- package/src/contact.module.ts +46 -48
- package/src/contact.service.ts +28 -13
- package/src/index.ts +6 -13
- package/src/language/en.json +8 -1
- package/src/language/pt.json +9 -1
- package/src/person/dto/create.dto.ts +49 -1
- package/src/person/dto/update.dto.ts +75 -3
- package/src/person/person.controller.ts +31 -14
- package/src/person/person.service.ts +1019 -121
- package/src/person-relation-type/person-relation-type.service.ts +84 -76
- package/hedhog/data/address_type.yaml +0 -28
- package/hedhog/frontend/app/address-type/page.tsx.ejs +0 -480
- package/hedhog/query/add-unique-address-type-locale.sql +0 -3
- package/hedhog/table/address.yaml +0 -28
- package/hedhog/table/address_type.yaml +0 -11
- package/hedhog/table/person_relation.yaml +0 -20
- package/hedhog/table/person_relation_type.yaml +0 -6
- package/src/address-type/address-type.controller.ts +0 -55
- package/src/address-type/address-type.enum.ts +0 -9
- package/src/address-type/address-type.module.ts +0 -18
- package/src/address-type/address-type.service.ts +0 -121
- package/src/address-type/dto/create.dto.ts +0 -19
- package/src/address-type/dto/update.dto.ts +0 -9
|
@@ -16,13 +16,44 @@ exports.PersonService = void 0;
|
|
|
16
16
|
const api_locale_1 = require("@hed-hog/api-locale");
|
|
17
17
|
const api_pagination_1 = require("@hed-hog/api-pagination");
|
|
18
18
|
const api_prisma_1 = require("@hed-hog/api-prisma");
|
|
19
|
+
const core_1 = require("@hed-hog/core");
|
|
19
20
|
const common_1 = require("@nestjs/common");
|
|
21
|
+
const CONTACT_ALLOW_COMPANY_REGISTRATION_SETTING = 'contact-allow-company-registration';
|
|
22
|
+
const CONTACT_OWNER_ROLE_SLUG = 'owner-contact';
|
|
23
|
+
const CONTACT_OWNER_ALLOWED_ROLE_SLUGS = [
|
|
24
|
+
'admin',
|
|
25
|
+
'admin-contact',
|
|
26
|
+
CONTACT_OWNER_ROLE_SLUG,
|
|
27
|
+
];
|
|
28
|
+
const EMPLOYER_COMPANY_METADATA_KEY = 'employer_company_id';
|
|
29
|
+
const NOTES_METADATA_KEY = 'notes';
|
|
20
30
|
let PersonService = class PersonService {
|
|
21
|
-
constructor(prismaService, paginationService) {
|
|
31
|
+
constructor(prismaService, paginationService, fileService, settingService) {
|
|
22
32
|
this.prismaService = prismaService;
|
|
23
33
|
this.paginationService = paginationService;
|
|
34
|
+
this.fileService = fileService;
|
|
35
|
+
this.settingService = settingService;
|
|
24
36
|
}
|
|
25
37
|
async getStats() {
|
|
38
|
+
const allowCompanyRegistration = await this.isCompanyRegistrationAllowed();
|
|
39
|
+
if (!allowCompanyRegistration) {
|
|
40
|
+
const [individual, active, inactive] = await Promise.all([
|
|
41
|
+
this.prismaService.person.count({ where: { type: 'individual' } }),
|
|
42
|
+
this.prismaService.person.count({
|
|
43
|
+
where: { type: 'individual', status: 'active' },
|
|
44
|
+
}),
|
|
45
|
+
this.prismaService.person.count({
|
|
46
|
+
where: { type: 'individual', status: 'inactive' },
|
|
47
|
+
}),
|
|
48
|
+
]);
|
|
49
|
+
return {
|
|
50
|
+
total: individual,
|
|
51
|
+
individual,
|
|
52
|
+
company: 0,
|
|
53
|
+
active,
|
|
54
|
+
inactive,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
26
57
|
const [total, individual, company, active, inactive] = await Promise.all([
|
|
27
58
|
this.prismaService.person.count(),
|
|
28
59
|
this.prismaService.person.count({ where: { type: 'individual' } }),
|
|
@@ -38,143 +69,192 @@ let PersonService = class PersonService {
|
|
|
38
69
|
inactive,
|
|
39
70
|
};
|
|
40
71
|
}
|
|
41
|
-
async
|
|
72
|
+
async getOwnerOptions(currentUserId) {
|
|
73
|
+
const where = {
|
|
74
|
+
OR: [
|
|
75
|
+
{
|
|
76
|
+
role_user: {
|
|
77
|
+
some: {
|
|
78
|
+
role: {
|
|
79
|
+
slug: {
|
|
80
|
+
in: CONTACT_OWNER_ALLOWED_ROLE_SLUGS,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
...(Number(currentUserId) > 0
|
|
87
|
+
? [
|
|
88
|
+
{
|
|
89
|
+
id: Number(currentUserId),
|
|
90
|
+
},
|
|
91
|
+
]
|
|
92
|
+
: []),
|
|
93
|
+
],
|
|
94
|
+
};
|
|
95
|
+
const users = await this.prismaService.user.findMany({
|
|
96
|
+
where,
|
|
97
|
+
select: {
|
|
98
|
+
id: true,
|
|
99
|
+
name: true,
|
|
100
|
+
},
|
|
101
|
+
orderBy: {
|
|
102
|
+
name: 'asc',
|
|
103
|
+
},
|
|
104
|
+
take: 500,
|
|
105
|
+
});
|
|
106
|
+
const byId = new Map();
|
|
107
|
+
for (const user of users) {
|
|
108
|
+
if (!(user === null || user === void 0 ? void 0 : user.id))
|
|
109
|
+
continue;
|
|
110
|
+
byId.set(user.id, {
|
|
111
|
+
id: user.id,
|
|
112
|
+
name: user.name || `#${user.id}`,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return Array.from(byId.values());
|
|
116
|
+
}
|
|
117
|
+
async list(paginationParams, currentUserId) {
|
|
42
118
|
var _a;
|
|
43
|
-
const
|
|
119
|
+
const allowCompanyRegistration = await this.isCompanyRegistrationAllowed();
|
|
120
|
+
const where = {};
|
|
44
121
|
const search = (_a = paginationParams.search) === null || _a === void 0 ? void 0 : _a.trim();
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
122
|
+
const requestedType = paginationParams.type;
|
|
123
|
+
if (!allowCompanyRegistration && requestedType === 'company') {
|
|
124
|
+
return this.paginationService.paginate(this.prismaService.person, paginationParams, {
|
|
125
|
+
where: {
|
|
126
|
+
id: -1,
|
|
127
|
+
},
|
|
128
|
+
});
|
|
50
129
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
where.OR = OR;
|
|
130
|
+
if (!allowCompanyRegistration) {
|
|
131
|
+
where.type = 'individual';
|
|
54
132
|
}
|
|
55
|
-
if (
|
|
56
|
-
where.type =
|
|
133
|
+
else if (requestedType && requestedType !== 'all') {
|
|
134
|
+
where.type = requestedType;
|
|
57
135
|
}
|
|
58
136
|
if (paginationParams.status && paginationParams.status !== 'all') {
|
|
59
137
|
where.status = paginationParams.status;
|
|
60
138
|
}
|
|
61
|
-
|
|
139
|
+
if (search) {
|
|
140
|
+
where.OR = await this.buildSearchFilters(search);
|
|
141
|
+
}
|
|
142
|
+
const result = await this.paginationService.paginate(this.prismaService.person, paginationParams, {
|
|
62
143
|
where,
|
|
63
144
|
include: {
|
|
64
|
-
|
|
145
|
+
person_address: {
|
|
146
|
+
include: {
|
|
147
|
+
address: true,
|
|
148
|
+
},
|
|
149
|
+
},
|
|
65
150
|
contact: true,
|
|
66
151
|
document: true,
|
|
152
|
+
person_metadata: true,
|
|
67
153
|
},
|
|
68
154
|
});
|
|
155
|
+
const enriched = await this.enrichPeople(result.data, allowCompanyRegistration);
|
|
156
|
+
return Object.assign(Object.assign({}, result), { data: enriched });
|
|
69
157
|
}
|
|
70
158
|
async get(locale, id) {
|
|
71
|
-
const
|
|
159
|
+
const allowCompanyRegistration = await this.isCompanyRegistrationAllowed();
|
|
160
|
+
const person = await this.prismaService.person.findUnique({
|
|
161
|
+
where: { id },
|
|
162
|
+
include: {
|
|
163
|
+
person_address: {
|
|
164
|
+
include: {
|
|
165
|
+
address: true,
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
contact: true,
|
|
169
|
+
document: true,
|
|
170
|
+
person_metadata: true,
|
|
171
|
+
},
|
|
172
|
+
});
|
|
72
173
|
if (!person) {
|
|
73
174
|
throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('personNotFound', locale, `Person with ID ${id} not found`));
|
|
74
175
|
}
|
|
75
|
-
|
|
176
|
+
if (!allowCompanyRegistration && person.type === 'company') {
|
|
177
|
+
throw new common_1.NotFoundException((0, api_locale_1.getLocaleText)('personNotFound', locale, `Person with ID ${id} not found`));
|
|
178
|
+
}
|
|
179
|
+
const [normalized] = await this.enrichPeople([person], allowCompanyRegistration);
|
|
180
|
+
return normalized;
|
|
76
181
|
}
|
|
77
|
-
async create(data) {
|
|
78
|
-
|
|
79
|
-
|
|
182
|
+
async create(data, locale) {
|
|
183
|
+
const allowCompanyRegistration = await this.isCompanyRegistrationAllowed();
|
|
184
|
+
await this.ensureCompanyRegistrationAllowed({
|
|
185
|
+
nextType: data.type,
|
|
186
|
+
locale,
|
|
187
|
+
});
|
|
188
|
+
return this.prismaService.$transaction(async (tx) => {
|
|
189
|
+
var _a, _b;
|
|
190
|
+
const person = await tx.person.create({
|
|
191
|
+
data: {
|
|
192
|
+
name: data.name,
|
|
193
|
+
type: data.type,
|
|
194
|
+
status: data.status,
|
|
195
|
+
avatar_id: (_a = data.avatar_id) !== null && _a !== void 0 ? _a : null,
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
await this.syncPersonSubtypeData(tx, person.id, null, data, locale);
|
|
199
|
+
await this.syncPersonMetadata(tx, person.id, {
|
|
200
|
+
notes: data.notes,
|
|
201
|
+
employer_company_id: allowCompanyRegistration
|
|
202
|
+
? (_b = data.employer_company_id) !== null && _b !== void 0 ? _b : null
|
|
203
|
+
: null,
|
|
204
|
+
});
|
|
205
|
+
return person;
|
|
80
206
|
});
|
|
81
207
|
}
|
|
82
208
|
async update(id, data, locale) {
|
|
209
|
+
const allowCompanyRegistration = await this.isCompanyRegistrationAllowed();
|
|
83
210
|
const person = await this.prismaService.person.findUnique({ where: { id } });
|
|
84
211
|
if (!person) {
|
|
85
212
|
throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('personNotFound', locale, `Person with ID ${id} not found`));
|
|
86
213
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const incomingDocuments = data.documents || [];
|
|
90
|
-
const contactPrimaryMap = new Map();
|
|
91
|
-
for (const c of incomingContacts) {
|
|
92
|
-
if (c.is_primary) {
|
|
93
|
-
const key = String(c.contact_type_id);
|
|
94
|
-
contactPrimaryMap.set(key, (contactPrimaryMap.get(key) || 0) + 1);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
for (const [_, count] of contactPrimaryMap.entries()) {
|
|
98
|
-
if (count > 1) {
|
|
99
|
-
throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('moreThanOnePrimaryContact', locale, 'More than one contact of the same type cannot be marked as primary.'));
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
const addressPrimaryMap = new Map();
|
|
103
|
-
for (const a of incomingAddresses) {
|
|
104
|
-
if (a.is_primary) {
|
|
105
|
-
const key = String(a.address_type_id);
|
|
106
|
-
addressPrimaryMap.set(key, (addressPrimaryMap.get(key) || 0) + 1);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
for (const [_, count] of addressPrimaryMap.entries()) {
|
|
110
|
-
if (count > 1) {
|
|
111
|
-
throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('moreThanOnePrimaryAddress', locale, 'More than one address of the same type cannot be marked as primary.'));
|
|
112
|
-
}
|
|
214
|
+
if (!allowCompanyRegistration && person.type === 'company') {
|
|
215
|
+
throw new common_1.NotFoundException((0, api_locale_1.getLocaleText)('personNotFound', locale, `Person with ID ${id} not found`));
|
|
113
216
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
217
|
+
await this.ensureCompanyRegistrationAllowed({
|
|
218
|
+
currentType: person.type,
|
|
219
|
+
nextType: data.type,
|
|
220
|
+
locale,
|
|
221
|
+
});
|
|
222
|
+
const incomingContacts = Array.isArray(data.contacts) ? data.contacts : [];
|
|
223
|
+
const incomingAddresses = Array.isArray(data.addresses) ? data.addresses : [];
|
|
224
|
+
const incomingDocuments = Array.isArray(data.documents) ? data.documents : [];
|
|
225
|
+
this.validateSinglePrimaryPerType(incomingContacts, 'contact_type_id', locale, 'moreThanOnePrimaryContact', 'More than one contact of the same type cannot be marked as primary.');
|
|
226
|
+
this.validateSinglePrimaryPerType(incomingAddresses, 'address_type', locale, 'moreThanOnePrimaryAddress', 'More than one address of the same type cannot be marked as primary.');
|
|
227
|
+
this.validateSinglePrimaryPerType(incomingDocuments, 'document_type_id', locale, 'moreThanOnePrimaryDocument', 'More than one document of the same type cannot be marked as primary.');
|
|
228
|
+
return this.prismaService
|
|
229
|
+
.$transaction(async (tx) => {
|
|
230
|
+
var _a, _b, _c;
|
|
231
|
+
const nextType = (_a = data.type) !== null && _a !== void 0 ? _a : person.type;
|
|
127
232
|
await tx.person.update({
|
|
128
233
|
where: { id },
|
|
129
234
|
data: {
|
|
130
|
-
name: data.name,
|
|
131
|
-
type:
|
|
132
|
-
status: data.status,
|
|
235
|
+
name: (_b = data.name) !== null && _b !== void 0 ? _b : person.name,
|
|
236
|
+
type: nextType,
|
|
237
|
+
status: (_c = data.status) !== null && _c !== void 0 ? _c : person.status,
|
|
238
|
+
avatar_id: data.avatar_id === undefined ? person.avatar_id : data.avatar_id,
|
|
133
239
|
},
|
|
134
240
|
});
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
const existingAddresses = await tx.address.findMany({ where: { person_id: id } });
|
|
150
|
-
for (const a of incomingAddresses) {
|
|
151
|
-
if (a.id) {
|
|
152
|
-
await tx.address.update({ where: { id: a.id }, data: a });
|
|
153
|
-
}
|
|
154
|
-
else {
|
|
155
|
-
await tx.address.create({ data: Object.assign(Object.assign({}, a), { person_id: id }) });
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
for (const old of existingAddresses) {
|
|
159
|
-
if (!incomingAddresses.find((a) => a.id === old.id)) {
|
|
160
|
-
await tx.address.delete({ where: { id: old.id } });
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
const existingDocuments = await tx.document.findMany({ where: { person_id: id } });
|
|
164
|
-
for (const d of incomingDocuments) {
|
|
165
|
-
if (d.id) {
|
|
166
|
-
await tx.document.update({ where: { id: d.id }, data: d });
|
|
167
|
-
}
|
|
168
|
-
else {
|
|
169
|
-
await tx.document.create({ data: Object.assign(Object.assign({}, d), { person_id: id }) });
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
for (const old of existingDocuments) {
|
|
173
|
-
if (!incomingDocuments.find((d) => d.id === old.id)) {
|
|
174
|
-
await tx.document.delete({ where: { id: old.id } });
|
|
175
|
-
}
|
|
176
|
-
}
|
|
241
|
+
await this.syncPersonSubtypeData(tx, id, person.type, data, locale);
|
|
242
|
+
await this.syncPersonMetadata(tx, id, {
|
|
243
|
+
notes: data.notes,
|
|
244
|
+
employer_company_id: allowCompanyRegistration
|
|
245
|
+
? data.employer_company_id === undefined
|
|
246
|
+
? undefined
|
|
247
|
+
: data.employer_company_id
|
|
248
|
+
: null,
|
|
249
|
+
});
|
|
250
|
+
await this.syncContacts(tx, id, incomingContacts);
|
|
251
|
+
await this.syncAddresses(tx, id, incomingAddresses, locale);
|
|
252
|
+
await this.syncDocuments(tx, id, incomingDocuments);
|
|
177
253
|
return { success: true };
|
|
254
|
+
})
|
|
255
|
+
.then(async (result) => {
|
|
256
|
+
await this.cleanupReplacedAvatar(locale, person.avatar_id, data.avatar_id);
|
|
257
|
+
return result;
|
|
178
258
|
});
|
|
179
259
|
}
|
|
180
260
|
async delete({ ids }, locale) {
|
|
@@ -185,15 +265,53 @@ let PersonService = class PersonService {
|
|
|
185
265
|
where: { id: { in: ids } },
|
|
186
266
|
select: { id: true },
|
|
187
267
|
});
|
|
188
|
-
const existingIds = existing.map(p => p.id);
|
|
189
|
-
const missingIds = ids.filter(
|
|
268
|
+
const existingIds = existing.map((p) => p.id);
|
|
269
|
+
const missingIds = ids.filter((itemId) => !existingIds.includes(itemId));
|
|
190
270
|
if (missingIds.length > 0) {
|
|
191
271
|
throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('personNotFound', locale, `Person(s) with ID(s) ${missingIds.join(', ')} not found.`));
|
|
192
272
|
}
|
|
273
|
+
const personAddresses = await this.prismaService.person_address.findMany({
|
|
274
|
+
where: {
|
|
275
|
+
person_id: { in: ids },
|
|
276
|
+
},
|
|
277
|
+
select: {
|
|
278
|
+
id: true,
|
|
279
|
+
address_id: true,
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
const addressIds = personAddresses.map((item) => item.address_id);
|
|
193
283
|
return this.prismaService.$transaction([
|
|
194
284
|
this.prismaService.contact.deleteMany({ where: { person_id: { in: ids } } }),
|
|
195
|
-
this.prismaService.address.deleteMany({ where: { person_id: { in: ids } } }),
|
|
196
285
|
this.prismaService.document.deleteMany({ where: { person_id: { in: ids } } }),
|
|
286
|
+
this.prismaService.person_individual_relation.deleteMany({
|
|
287
|
+
where: {
|
|
288
|
+
OR: [
|
|
289
|
+
{ person_individual_id: { in: ids } },
|
|
290
|
+
{ related_person_individual_id: { in: ids } },
|
|
291
|
+
],
|
|
292
|
+
},
|
|
293
|
+
}),
|
|
294
|
+
this.prismaService.person_company.deleteMany({
|
|
295
|
+
where: {
|
|
296
|
+
OR: [{ id: { in: ids } }, { headquarter_id: { in: ids } }],
|
|
297
|
+
},
|
|
298
|
+
}),
|
|
299
|
+
this.prismaService.person_individual.deleteMany({
|
|
300
|
+
where: { id: { in: ids } },
|
|
301
|
+
}),
|
|
302
|
+
this.prismaService.person_metadata.deleteMany({
|
|
303
|
+
where: { person_id: { in: ids } },
|
|
304
|
+
}),
|
|
305
|
+
this.prismaService.person_address.deleteMany({
|
|
306
|
+
where: { person_id: { in: ids } },
|
|
307
|
+
}),
|
|
308
|
+
this.prismaService.address.deleteMany({
|
|
309
|
+
where: {
|
|
310
|
+
id: {
|
|
311
|
+
in: addressIds,
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
}),
|
|
197
315
|
this.prismaService.person.deleteMany({
|
|
198
316
|
where: {
|
|
199
317
|
id: {
|
|
@@ -203,13 +321,549 @@ let PersonService = class PersonService {
|
|
|
203
321
|
}),
|
|
204
322
|
]);
|
|
205
323
|
}
|
|
324
|
+
async openPublicAvatar(locale, fileId, res) {
|
|
325
|
+
const personWithAvatar = await this.prismaService.person.findFirst({
|
|
326
|
+
where: {
|
|
327
|
+
avatar_id: fileId,
|
|
328
|
+
},
|
|
329
|
+
select: {
|
|
330
|
+
id: true,
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
if (!personWithAvatar) {
|
|
334
|
+
throw new common_1.NotFoundException((0, api_locale_1.getLocaleText)('personNotFound', locale, 'Person not found'));
|
|
335
|
+
}
|
|
336
|
+
const { file, buffer } = await this.fileService.getBuffer(fileId);
|
|
337
|
+
res.set({
|
|
338
|
+
'Content-Type': file.file_mimetype.name,
|
|
339
|
+
'Content-Length': buffer.length,
|
|
340
|
+
'Cache-Control': 'public, max-age=3600',
|
|
341
|
+
});
|
|
342
|
+
res.send(buffer);
|
|
343
|
+
}
|
|
344
|
+
async enrichPeople(people, allowCompanyRegistration = true) {
|
|
345
|
+
if (people.length === 0) {
|
|
346
|
+
return [];
|
|
347
|
+
}
|
|
348
|
+
const personIds = people.map((person) => person.id);
|
|
349
|
+
const [companies, individuals, companyBranches, employerMetadata] = await Promise.all([
|
|
350
|
+
this.prismaService.person_company.findMany({
|
|
351
|
+
where: { id: { in: personIds } },
|
|
352
|
+
}),
|
|
353
|
+
this.prismaService.person_individual.findMany({
|
|
354
|
+
where: { id: { in: personIds } },
|
|
355
|
+
}),
|
|
356
|
+
this.prismaService.person_company.findMany({
|
|
357
|
+
where: { headquarter_id: { in: personIds } },
|
|
358
|
+
select: { id: true, headquarter_id: true },
|
|
359
|
+
}),
|
|
360
|
+
this.prismaService.person_metadata.findMany({
|
|
361
|
+
where: {
|
|
362
|
+
person_id: { in: personIds },
|
|
363
|
+
key: EMPLOYER_COMPANY_METADATA_KEY,
|
|
364
|
+
},
|
|
365
|
+
select: {
|
|
366
|
+
person_id: true,
|
|
367
|
+
value: true,
|
|
368
|
+
},
|
|
369
|
+
}).then((metadata) => (allowCompanyRegistration ? metadata : [])),
|
|
370
|
+
]);
|
|
371
|
+
const companyById = new Map(companies.map((item) => [item.id, item]));
|
|
372
|
+
const individualById = new Map(individuals.map((item) => [item.id, item]));
|
|
373
|
+
const branchesByHeadquarterId = new Map();
|
|
374
|
+
for (const branch of companyBranches) {
|
|
375
|
+
if (!branch.headquarter_id)
|
|
376
|
+
continue;
|
|
377
|
+
const current = branchesByHeadquarterId.get(branch.headquarter_id) || [];
|
|
378
|
+
current.push(branch.id);
|
|
379
|
+
branchesByHeadquarterId.set(branch.headquarter_id, current);
|
|
380
|
+
}
|
|
381
|
+
const employerCompanyIdByPersonId = new Map();
|
|
382
|
+
for (const meta of employerMetadata) {
|
|
383
|
+
const employerId = this.coerceNumber(meta.value);
|
|
384
|
+
if (employerId > 0) {
|
|
385
|
+
employerCompanyIdByPersonId.set(meta.person_id, employerId);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
const employerCompanyIds = Array.from(new Set(Array.from(employerCompanyIdByPersonId.values())));
|
|
389
|
+
const employerCompanies = employerCompanyIds.length > 0
|
|
390
|
+
? await this.prismaService.person.findMany({
|
|
391
|
+
where: {
|
|
392
|
+
id: { in: employerCompanyIds },
|
|
393
|
+
type: 'company',
|
|
394
|
+
},
|
|
395
|
+
})
|
|
396
|
+
: [];
|
|
397
|
+
const employerCompanyRows = employerCompanyIds.length > 0
|
|
398
|
+
? await this.prismaService.person_company.findMany({
|
|
399
|
+
where: {
|
|
400
|
+
id: { in: employerCompanyIds },
|
|
401
|
+
},
|
|
402
|
+
})
|
|
403
|
+
: [];
|
|
404
|
+
const employerCompanyRowById = new Map(employerCompanyRows.map((item) => [item.id, item]));
|
|
405
|
+
const employerCompanyById = new Map(employerCompanies.map((item) => {
|
|
406
|
+
var _a;
|
|
407
|
+
return [
|
|
408
|
+
item.id,
|
|
409
|
+
Object.assign(Object.assign({}, item), { person_company: (_a = employerCompanyRowById.get(item.id)) !== null && _a !== void 0 ? _a : null }),
|
|
410
|
+
];
|
|
411
|
+
}));
|
|
412
|
+
return people.map((person) => {
|
|
413
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
414
|
+
const metadata = this.metadataArrayToMap(person.person_metadata);
|
|
415
|
+
const companyData = companyById.get(person.id);
|
|
416
|
+
const individualData = individualById.get(person.id);
|
|
417
|
+
const employerCompanyId = allowCompanyRegistration
|
|
418
|
+
? (_a = employerCompanyIdByPersonId.get(person.id)) !== null && _a !== void 0 ? _a : null
|
|
419
|
+
: null;
|
|
420
|
+
const employerCompany = allowCompanyRegistration && employerCompanyId != null
|
|
421
|
+
? this.normalizeRelationPersonSummary(employerCompanyById.get(employerCompanyId))
|
|
422
|
+
: null;
|
|
423
|
+
return {
|
|
424
|
+
id: person.id,
|
|
425
|
+
name: person.name,
|
|
426
|
+
type: person.type,
|
|
427
|
+
status: person.status,
|
|
428
|
+
avatar_id: (_b = person.avatar_id) !== null && _b !== void 0 ? _b : null,
|
|
429
|
+
created_at: person.created_at,
|
|
430
|
+
updated_at: person.updated_at,
|
|
431
|
+
birth_date: (_c = individualData === null || individualData === void 0 ? void 0 : individualData.birth_date) !== null && _c !== void 0 ? _c : null,
|
|
432
|
+
gender: (_d = individualData === null || individualData === void 0 ? void 0 : individualData.gender) !== null && _d !== void 0 ? _d : null,
|
|
433
|
+
job_title: (_e = individualData === null || individualData === void 0 ? void 0 : individualData.job_title) !== null && _e !== void 0 ? _e : null,
|
|
434
|
+
trade_name: (_f = companyData === null || companyData === void 0 ? void 0 : companyData.trade_name) !== null && _f !== void 0 ? _f : null,
|
|
435
|
+
foundation_date: (_g = companyData === null || companyData === void 0 ? void 0 : companyData.foundation_date) !== null && _g !== void 0 ? _g : null,
|
|
436
|
+
legal_nature: (_h = companyData === null || companyData === void 0 ? void 0 : companyData.legal_nature) !== null && _h !== void 0 ? _h : null,
|
|
437
|
+
headquarter_id: (_j = companyData === null || companyData === void 0 ? void 0 : companyData.headquarter_id) !== null && _j !== void 0 ? _j : null,
|
|
438
|
+
branch_ids: branchesByHeadquarterId.get(person.id) || [],
|
|
439
|
+
notes: this.metadataToString(metadata.get(NOTES_METADATA_KEY)),
|
|
440
|
+
employer_company_id: allowCompanyRegistration ? employerCompanyId : null,
|
|
441
|
+
employer_company: allowCompanyRegistration ? employerCompany : null,
|
|
442
|
+
contact: person.contact || [],
|
|
443
|
+
document: person.document || [],
|
|
444
|
+
address: Array.isArray(person.person_address)
|
|
445
|
+
? person.person_address
|
|
446
|
+
.map((item) => item.address)
|
|
447
|
+
.filter((address) => address != null)
|
|
448
|
+
: [],
|
|
449
|
+
};
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
metadataArrayToMap(metadata) {
|
|
453
|
+
const map = new Map();
|
|
454
|
+
for (const item of Array.isArray(metadata) ? metadata : []) {
|
|
455
|
+
map.set(item.key, item.value);
|
|
456
|
+
}
|
|
457
|
+
return map;
|
|
458
|
+
}
|
|
459
|
+
metadataToString(value) {
|
|
460
|
+
if (value == null)
|
|
461
|
+
return null;
|
|
462
|
+
if (typeof value === 'string')
|
|
463
|
+
return value;
|
|
464
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
465
|
+
return String(value);
|
|
466
|
+
}
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
469
|
+
async syncPersonSubtypeData(tx, personId, currentType, data, locale) {
|
|
470
|
+
var _a, _b, _c;
|
|
471
|
+
const targetType = (_a = data.type) !== null && _a !== void 0 ? _a : currentType;
|
|
472
|
+
if (!targetType)
|
|
473
|
+
return;
|
|
474
|
+
if (targetType === 'individual') {
|
|
475
|
+
await tx.person_company.deleteMany({ where: { id: personId } });
|
|
476
|
+
await tx.person_individual.upsert({
|
|
477
|
+
where: { id: personId },
|
|
478
|
+
create: {
|
|
479
|
+
id: personId,
|
|
480
|
+
birth_date: this.parseDateOrNull(data.birth_date),
|
|
481
|
+
gender: (_b = data.gender) !== null && _b !== void 0 ? _b : null,
|
|
482
|
+
job_title: this.normalizeTextOrNull(data.job_title),
|
|
483
|
+
},
|
|
484
|
+
update: {
|
|
485
|
+
birth_date: data.birth_date === undefined
|
|
486
|
+
? undefined
|
|
487
|
+
: this.parseDateOrNull(data.birth_date),
|
|
488
|
+
gender: data.gender === undefined ? undefined : (_c = data.gender) !== null && _c !== void 0 ? _c : null,
|
|
489
|
+
job_title: data.job_title === undefined
|
|
490
|
+
? undefined
|
|
491
|
+
: this.normalizeTextOrNull(data.job_title),
|
|
492
|
+
},
|
|
493
|
+
});
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
await tx.person_individual_relation.deleteMany({
|
|
497
|
+
where: {
|
|
498
|
+
OR: [
|
|
499
|
+
{ person_individual_id: personId },
|
|
500
|
+
{ related_person_individual_id: personId },
|
|
501
|
+
],
|
|
502
|
+
},
|
|
503
|
+
});
|
|
504
|
+
await tx.person_individual.deleteMany({ where: { id: personId } });
|
|
505
|
+
const normalizedHeadquarterId = this.coerceNumber(data.headquarter_id);
|
|
506
|
+
if (normalizedHeadquarterId > 0) {
|
|
507
|
+
if (normalizedHeadquarterId === personId) {
|
|
508
|
+
throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('companyRelationSelfReference', locale, 'A company cannot be its own headquarter.'));
|
|
509
|
+
}
|
|
510
|
+
const headquarter = await tx.person.findFirst({
|
|
511
|
+
where: { id: normalizedHeadquarterId, type: 'company' },
|
|
512
|
+
select: { id: true },
|
|
513
|
+
});
|
|
514
|
+
if (!headquarter) {
|
|
515
|
+
throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('companyRelationInvalidTarget', locale, 'Only company records can be linked as headquarters or branches.'));
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
await tx.person_company.upsert({
|
|
519
|
+
where: { id: personId },
|
|
520
|
+
create: {
|
|
521
|
+
id: personId,
|
|
522
|
+
trade_name: this.normalizeTextOrNull(data.trade_name),
|
|
523
|
+
foundation_date: this.parseDateOrNull(data.foundation_date),
|
|
524
|
+
legal_nature: this.normalizeTextOrNull(data.legal_nature),
|
|
525
|
+
headquarter_id: normalizedHeadquarterId > 0 ? normalizedHeadquarterId : null,
|
|
526
|
+
},
|
|
527
|
+
update: {
|
|
528
|
+
trade_name: data.trade_name === undefined
|
|
529
|
+
? undefined
|
|
530
|
+
: this.normalizeTextOrNull(data.trade_name),
|
|
531
|
+
foundation_date: data.foundation_date === undefined
|
|
532
|
+
? undefined
|
|
533
|
+
: this.parseDateOrNull(data.foundation_date),
|
|
534
|
+
legal_nature: data.legal_nature === undefined
|
|
535
|
+
? undefined
|
|
536
|
+
: this.normalizeTextOrNull(data.legal_nature),
|
|
537
|
+
headquarter_id: data.headquarter_id === undefined
|
|
538
|
+
? undefined
|
|
539
|
+
: normalizedHeadquarterId > 0
|
|
540
|
+
? normalizedHeadquarterId
|
|
541
|
+
: null,
|
|
542
|
+
},
|
|
543
|
+
});
|
|
544
|
+
if (Object.prototype.hasOwnProperty.call(data, 'branch_ids')) {
|
|
545
|
+
await this.syncCompanyBranches(tx, personId, data.branch_ids, locale);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
async syncCompanyBranches(tx, personId, branchIds, locale) {
|
|
549
|
+
const normalizedBranchIds = Array.from(new Set((Array.isArray(branchIds) ? branchIds : [])
|
|
550
|
+
.map((value) => Number(value))
|
|
551
|
+
.filter((value) => Number.isInteger(value) && value > 0 && value !== personId)));
|
|
552
|
+
if (normalizedBranchIds.length > 0) {
|
|
553
|
+
const companies = await tx.person.findMany({
|
|
554
|
+
where: {
|
|
555
|
+
id: { in: normalizedBranchIds },
|
|
556
|
+
type: 'company',
|
|
557
|
+
},
|
|
558
|
+
select: { id: true },
|
|
559
|
+
});
|
|
560
|
+
if (companies.length !== normalizedBranchIds.length) {
|
|
561
|
+
throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('companyRelationInvalidTarget', locale, 'Only company records can be linked as headquarters or branches.'));
|
|
562
|
+
}
|
|
563
|
+
await tx.person_company.updateMany({
|
|
564
|
+
where: {
|
|
565
|
+
id: { in: normalizedBranchIds },
|
|
566
|
+
},
|
|
567
|
+
data: {
|
|
568
|
+
headquarter_id: personId,
|
|
569
|
+
},
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
await tx.person_company.updateMany({
|
|
573
|
+
where: {
|
|
574
|
+
headquarter_id: personId,
|
|
575
|
+
id: { notIn: normalizedBranchIds.length > 0 ? normalizedBranchIds : [-1] },
|
|
576
|
+
},
|
|
577
|
+
data: {
|
|
578
|
+
headquarter_id: null,
|
|
579
|
+
},
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
async syncPersonMetadata(tx, personId, data) {
|
|
583
|
+
if (data.notes !== undefined) {
|
|
584
|
+
await this.upsertMetadataValue(tx, personId, NOTES_METADATA_KEY, data.notes);
|
|
585
|
+
}
|
|
586
|
+
if (data.employer_company_id !== undefined) {
|
|
587
|
+
await this.upsertMetadataValue(tx, personId, EMPLOYER_COMPANY_METADATA_KEY, data.employer_company_id);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
async upsertMetadataValue(tx, personId, key, value) {
|
|
591
|
+
const existing = await tx.person_metadata.findFirst({
|
|
592
|
+
where: {
|
|
593
|
+
person_id: personId,
|
|
594
|
+
key,
|
|
595
|
+
},
|
|
596
|
+
select: { id: true },
|
|
597
|
+
});
|
|
598
|
+
const normalizedValue = this.normalizeMetadataValue(value);
|
|
599
|
+
if (normalizedValue == null) {
|
|
600
|
+
if (existing) {
|
|
601
|
+
await tx.person_metadata.delete({ where: { id: existing.id } });
|
|
602
|
+
}
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
if (existing) {
|
|
606
|
+
await tx.person_metadata.update({
|
|
607
|
+
where: { id: existing.id },
|
|
608
|
+
data: { value: normalizedValue },
|
|
609
|
+
});
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
await tx.person_metadata.create({
|
|
613
|
+
data: {
|
|
614
|
+
person_id: personId,
|
|
615
|
+
key,
|
|
616
|
+
value: normalizedValue,
|
|
617
|
+
},
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
normalizeMetadataValue(value) {
|
|
621
|
+
if (value == null)
|
|
622
|
+
return null;
|
|
623
|
+
if (typeof value === 'string') {
|
|
624
|
+
const trimmed = value.trim();
|
|
625
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
626
|
+
}
|
|
627
|
+
if (typeof value === 'number') {
|
|
628
|
+
return Number.isFinite(value) ? value : null;
|
|
629
|
+
}
|
|
630
|
+
if (typeof value === 'boolean') {
|
|
631
|
+
return value;
|
|
632
|
+
}
|
|
633
|
+
return null;
|
|
634
|
+
}
|
|
635
|
+
async syncContacts(tx, personId, incomingContacts) {
|
|
636
|
+
const existingContacts = await tx.contact.findMany({ where: { person_id: personId } });
|
|
637
|
+
for (const contact of incomingContacts) {
|
|
638
|
+
if (contact.id) {
|
|
639
|
+
await tx.contact.update({ where: { id: contact.id }, data: contact });
|
|
640
|
+
}
|
|
641
|
+
else {
|
|
642
|
+
await tx.contact.create({ data: Object.assign(Object.assign({}, contact), { person_id: personId }) });
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
for (const old of existingContacts) {
|
|
646
|
+
if (!incomingContacts.find((item) => item.id === old.id)) {
|
|
647
|
+
await tx.contact.delete({ where: { id: old.id } });
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
async syncAddresses(tx, personId, incomingAddresses, locale) {
|
|
652
|
+
const existingAddresses = await tx.person_address.findMany({
|
|
653
|
+
where: { person_id: personId },
|
|
654
|
+
include: { address: true },
|
|
655
|
+
});
|
|
656
|
+
for (const address of incomingAddresses) {
|
|
657
|
+
const addressData = {
|
|
658
|
+
line1: address.line1,
|
|
659
|
+
line2: address.line2 || '',
|
|
660
|
+
city: address.city,
|
|
661
|
+
state: address.state,
|
|
662
|
+
country_code: address.country_code || 'BRA',
|
|
663
|
+
postal_code: address.postal_code || '',
|
|
664
|
+
is_primary: address.is_primary,
|
|
665
|
+
address_type: address.address_type,
|
|
666
|
+
};
|
|
667
|
+
if (address.id) {
|
|
668
|
+
const existingLink = existingAddresses.find((item) => item.address_id === address.id);
|
|
669
|
+
if (!existingLink) {
|
|
670
|
+
throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('personNotFound', locale, `Address with ID ${address.id} not found for person ${personId}.`));
|
|
671
|
+
}
|
|
672
|
+
await tx.address.update({
|
|
673
|
+
where: { id: address.id },
|
|
674
|
+
data: addressData,
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
else {
|
|
678
|
+
const createdAddress = await tx.address.create({
|
|
679
|
+
data: addressData,
|
|
680
|
+
});
|
|
681
|
+
await tx.person_address.create({
|
|
682
|
+
data: {
|
|
683
|
+
person_id: personId,
|
|
684
|
+
address_id: createdAddress.id,
|
|
685
|
+
},
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
for (const old of existingAddresses) {
|
|
690
|
+
if (!incomingAddresses.find((item) => item.id === old.address_id)) {
|
|
691
|
+
await tx.person_address.delete({ where: { id: old.id } });
|
|
692
|
+
await tx.address.delete({ where: { id: old.address_id } });
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
async syncDocuments(tx, personId, incomingDocuments) {
|
|
697
|
+
const existingDocuments = await tx.document.findMany({ where: { person_id: personId } });
|
|
698
|
+
for (const document of incomingDocuments) {
|
|
699
|
+
if (document.id) {
|
|
700
|
+
await tx.document.update({ where: { id: document.id }, data: document });
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
await tx.document.create({ data: Object.assign(Object.assign({}, document), { person_id: personId }) });
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
for (const old of existingDocuments) {
|
|
707
|
+
if (!incomingDocuments.find((item) => item.id === old.id)) {
|
|
708
|
+
await tx.document.delete({ where: { id: old.id } });
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
validateSinglePrimaryPerType(items, groupKey, locale, localeKey, fallbackMessage) {
|
|
713
|
+
const map = new Map();
|
|
714
|
+
for (const item of items) {
|
|
715
|
+
if (!item.is_primary)
|
|
716
|
+
continue;
|
|
717
|
+
const key = String(item[groupKey]);
|
|
718
|
+
map.set(key, (map.get(key) || 0) + 1);
|
|
719
|
+
}
|
|
720
|
+
for (const count of map.values()) {
|
|
721
|
+
if (count > 1) {
|
|
722
|
+
throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)(localeKey, locale, fallbackMessage));
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
parseDateOrNull(value) {
|
|
727
|
+
if (!value)
|
|
728
|
+
return null;
|
|
729
|
+
const date = value instanceof Date ? value : new Date(String(value));
|
|
730
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
731
|
+
}
|
|
732
|
+
normalizeTextOrNull(value) {
|
|
733
|
+
if (typeof value !== 'string') {
|
|
734
|
+
return value == null ? null : String(value);
|
|
735
|
+
}
|
|
736
|
+
const trimmed = value.trim();
|
|
737
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
738
|
+
}
|
|
739
|
+
coerceNumber(value) {
|
|
740
|
+
const parsed = Number(value);
|
|
741
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
742
|
+
}
|
|
743
|
+
async buildSearchFilters(search) {
|
|
744
|
+
const normalizedDigits = this.normalizeDigits(search);
|
|
745
|
+
const filters = [];
|
|
746
|
+
if (!Number.isNaN(+search)) {
|
|
747
|
+
filters.push({ id: { equals: +search } });
|
|
748
|
+
}
|
|
749
|
+
filters.push({ name: { contains: search, mode: 'insensitive' } }, {
|
|
750
|
+
person_metadata: {
|
|
751
|
+
some: {
|
|
752
|
+
value: { path: [], equals: search },
|
|
753
|
+
},
|
|
754
|
+
},
|
|
755
|
+
}, {
|
|
756
|
+
contact: {
|
|
757
|
+
some: {
|
|
758
|
+
value: { contains: search, mode: 'insensitive' },
|
|
759
|
+
},
|
|
760
|
+
},
|
|
761
|
+
}, {
|
|
762
|
+
document: {
|
|
763
|
+
some: {
|
|
764
|
+
value: { contains: search, mode: 'insensitive' },
|
|
765
|
+
},
|
|
766
|
+
},
|
|
767
|
+
}, {
|
|
768
|
+
person_address: {
|
|
769
|
+
some: {
|
|
770
|
+
address: {
|
|
771
|
+
OR: [
|
|
772
|
+
{ line1: { contains: search, mode: 'insensitive' } },
|
|
773
|
+
{ line2: { contains: search, mode: 'insensitive' } },
|
|
774
|
+
{ city: { contains: search, mode: 'insensitive' } },
|
|
775
|
+
{ state: { contains: search, mode: 'insensitive' } },
|
|
776
|
+
{ postal_code: { contains: search, mode: 'insensitive' } },
|
|
777
|
+
{ country_code: { contains: search, mode: 'insensitive' } },
|
|
778
|
+
],
|
|
779
|
+
},
|
|
780
|
+
},
|
|
781
|
+
},
|
|
782
|
+
});
|
|
783
|
+
if (normalizedDigits.length > 0) {
|
|
784
|
+
const normalizedMatches = await this.findPersonIdsByNormalizedDigits(normalizedDigits);
|
|
785
|
+
if (normalizedMatches.length > 0) {
|
|
786
|
+
filters.push({
|
|
787
|
+
id: {
|
|
788
|
+
in: normalizedMatches,
|
|
789
|
+
},
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
return filters;
|
|
794
|
+
}
|
|
795
|
+
normalizeDigits(value) {
|
|
796
|
+
return value.replace(/\D/g, '');
|
|
797
|
+
}
|
|
798
|
+
async findPersonIdsByNormalizedDigits(normalizedDigits) {
|
|
799
|
+
const likeValue = `%${normalizedDigits}%`;
|
|
800
|
+
const rows = await this.prismaService.$queryRaw(api_prisma_1.Prisma.sql `
|
|
801
|
+
SELECT DISTINCT p.id
|
|
802
|
+
FROM person p
|
|
803
|
+
LEFT JOIN contact c ON c.person_id = p.id
|
|
804
|
+
LEFT JOIN document d ON d.person_id = p.id
|
|
805
|
+
LEFT JOIN person_address pa ON pa.person_id = p.id
|
|
806
|
+
LEFT JOIN address a ON a.id = pa.address_id
|
|
807
|
+
WHERE regexp_replace(COALESCE(c.value, ''), '[^0-9]+', '', 'g') ILIKE ${likeValue}
|
|
808
|
+
OR regexp_replace(COALESCE(d.value, ''), '[^0-9]+', '', 'g') ILIKE ${likeValue}
|
|
809
|
+
OR regexp_replace(COALESCE(a.postal_code, ''), '[^0-9]+', '', 'g') ILIKE ${likeValue}
|
|
810
|
+
`);
|
|
811
|
+
return rows.map((row) => row.id);
|
|
812
|
+
}
|
|
813
|
+
async ensureCompanyRegistrationAllowed({ currentType, nextType, locale, }) {
|
|
814
|
+
const targetType = nextType !== null && nextType !== void 0 ? nextType : currentType;
|
|
815
|
+
const isNewCompanyRegistration = targetType === 'company' && currentType !== 'company';
|
|
816
|
+
if (!isNewCompanyRegistration) {
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
const settings = await this.settingService.getSettingValues(CONTACT_ALLOW_COMPANY_REGISTRATION_SETTING);
|
|
820
|
+
if (settings[CONTACT_ALLOW_COMPANY_REGISTRATION_SETTING] !== false) {
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('companyRegistrationDisabled', locale, 'Company registration is disabled. Only individual records can be created.'));
|
|
824
|
+
}
|
|
825
|
+
async isCompanyRegistrationAllowed() {
|
|
826
|
+
const settings = await this.settingService.getSettingValues(CONTACT_ALLOW_COMPANY_REGISTRATION_SETTING);
|
|
827
|
+
return settings[CONTACT_ALLOW_COMPANY_REGISTRATION_SETTING] !== false;
|
|
828
|
+
}
|
|
829
|
+
async cleanupReplacedAvatar(locale, previousAvatarId, nextAvatarId) {
|
|
830
|
+
if (!previousAvatarId || previousAvatarId === nextAvatarId) {
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
try {
|
|
834
|
+
await this.fileService.delete(locale, { ids: [previousAvatarId] });
|
|
835
|
+
}
|
|
836
|
+
catch (_a) {
|
|
837
|
+
// Keep the person update successful even if avatar cleanup fails.
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
normalizeRelationPersonSummary(person) {
|
|
841
|
+
var _a, _b;
|
|
842
|
+
if (!person)
|
|
843
|
+
return null;
|
|
844
|
+
const tradeName = ((_a = person.person_company) === null || _a === void 0 ? void 0 : _a.trade_name) != null
|
|
845
|
+
? String(person.person_company.trade_name)
|
|
846
|
+
: null;
|
|
847
|
+
return {
|
|
848
|
+
id: person.id,
|
|
849
|
+
name: person.name,
|
|
850
|
+
type: person.type,
|
|
851
|
+
status: person.status,
|
|
852
|
+
avatar_id: (_b = person.avatar_id) !== null && _b !== void 0 ? _b : null,
|
|
853
|
+
trade_name: tradeName,
|
|
854
|
+
};
|
|
855
|
+
}
|
|
206
856
|
};
|
|
207
857
|
exports.PersonService = PersonService;
|
|
208
858
|
exports.PersonService = PersonService = __decorate([
|
|
209
859
|
(0, common_1.Injectable)(),
|
|
210
860
|
__param(0, (0, common_1.Inject)((0, common_1.forwardRef)(() => api_prisma_1.PrismaService))),
|
|
211
861
|
__param(1, (0, common_1.Inject)((0, common_1.forwardRef)(() => api_pagination_1.PaginationService))),
|
|
862
|
+
__param(2, (0, common_1.Inject)((0, common_1.forwardRef)(() => core_1.FileService))),
|
|
863
|
+
__param(3, (0, common_1.Inject)((0, common_1.forwardRef)(() => core_1.SettingService))),
|
|
212
864
|
__metadata("design:paramtypes", [api_prisma_1.PrismaService,
|
|
213
|
-
api_pagination_1.PaginationService
|
|
865
|
+
api_pagination_1.PaginationService,
|
|
866
|
+
core_1.FileService,
|
|
867
|
+
core_1.SettingService])
|
|
214
868
|
], PersonService);
|
|
215
869
|
//# sourceMappingURL=person.service.js.map
|