@hed-hog/contact 0.0.303 → 0.0.305

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.
Files changed (44) hide show
  1. package/README.md +225 -17
  2. package/dist/person/dto/account.dto.d.ts +5 -0
  3. package/dist/person/dto/account.dto.d.ts.map +1 -1
  4. package/dist/person/dto/account.dto.js +29 -0
  5. package/dist/person/dto/account.dto.js.map +1 -1
  6. package/dist/person/dto/import-preview.dto.d.ts +7 -0
  7. package/dist/person/dto/import-preview.dto.d.ts.map +1 -0
  8. package/dist/person/dto/import-preview.dto.js +7 -0
  9. package/dist/person/dto/import-preview.dto.js.map +1 -0
  10. package/dist/person/dto/import.dto.d.ts +15 -0
  11. package/dist/person/dto/import.dto.d.ts.map +1 -0
  12. package/dist/person/dto/import.dto.js +51 -0
  13. package/dist/person/dto/import.dto.js.map +1 -0
  14. package/dist/person/person.controller.d.ts +14 -0
  15. package/dist/person/person.controller.d.ts.map +1 -1
  16. package/dist/person/person.controller.js +53 -0
  17. package/dist/person/person.controller.js.map +1 -1
  18. package/dist/person/person.service.d.ts +19 -0
  19. package/dist/person/person.service.d.ts.map +1 -1
  20. package/dist/person/person.service.js +481 -67
  21. package/dist/person/person.service.js.map +1 -1
  22. package/dist/person-relation-type/person-relation-type.controller.d.ts +2 -2
  23. package/dist/person-relation-type/person-relation-type.service.d.ts +2 -2
  24. package/hedhog/data/route.yaml +6 -0
  25. package/hedhog/frontend/app/accounts/_components/account-form-sheet.tsx.ejs +2242 -484
  26. package/hedhog/frontend/app/accounts/_components/account-types.ts.ejs +51 -0
  27. package/hedhog/frontend/app/accounts/page.tsx.ejs +181 -16
  28. package/hedhog/frontend/app/contact-type/page.tsx.ejs +223 -29
  29. package/hedhog/frontend/app/document-type/page.tsx.ejs +248 -37
  30. package/hedhog/frontend/app/follow-ups/page.tsx.ejs +129 -19
  31. package/hedhog/frontend/app/person/_components/person-field-with-create.tsx.ejs +78 -212
  32. package/hedhog/frontend/app/person/_components/person-form-sheet.tsx.ejs +760 -178
  33. package/hedhog/frontend/app/person/_components/person-import-sheet.tsx.ejs +1120 -0
  34. package/hedhog/frontend/app/person/_components/person-interaction-dialog.tsx.ejs +171 -4
  35. package/hedhog/frontend/app/person/page.tsx.ejs +17 -0
  36. package/hedhog/frontend/app/pipeline/_components/lead-proposals-tab.tsx.ejs +696 -82
  37. package/hedhog/frontend/messages/en.json +140 -2
  38. package/hedhog/frontend/messages/pt.json +147 -9
  39. package/package.json +5 -5
  40. package/src/person/dto/account.dto.ts +31 -0
  41. package/src/person/dto/import-preview.dto.ts +6 -0
  42. package/src/person/dto/import.dto.ts +61 -0
  43. package/src/person/person.controller.ts +74 -12
  44. package/src/person/person.service.ts +615 -68
@@ -230,7 +230,80 @@
230
230
  "interactionError": "Failed to register interaction",
231
231
  "followupDate": "Follow-up Date",
232
232
  "followupSuccess": "Follow-up scheduled successfully",
233
- "followupError": "Failed to schedule follow-up"
233
+ "followupError": "Failed to schedule follow-up",
234
+ "importLeads": "Import Leads",
235
+ "importStepUpload": "Upload",
236
+ "importStepPreview": "Preview",
237
+ "importStepMapping": "Mapping",
238
+ "importStepConfirm": "Confirmation",
239
+ "importStepResult": "Result",
240
+ "importSheetTitle": "Import Leads via CSV",
241
+ "importSheetDescription": "Upload a CSV file, map the columns and import contacts into the CRM.",
242
+ "importDropzoneLabel": "Click or drag a CSV file here",
243
+ "importDropzoneHint": "Accepted format: .csv — max. 5,000 rows",
244
+ "importDropzoneChange": "Change file",
245
+ "importFileSelected": "File selected",
246
+ "importPreviewTitle": "File Preview",
247
+ "importPreviewDescription": "Showing the first rows of the uploaded file. Check whether the data looks correct before proceeding.",
248
+ "importTotalEstimated": "Estimated rows",
249
+ "importColumnsDetected": "Columns detected",
250
+ "importMappingTitle": "Column Mapping",
251
+ "importMappingDescription": "Map each CSV column to the corresponding CRM field. Choose \"Ignore\" to skip a column.",
252
+ "importMappingColumnLabel": "CSV Column",
253
+ "importMappingFieldLabel": "CRM Field",
254
+ "importMappingIgnore": "Ignore",
255
+ "importMappingDuplicateWarning": "The field {field} is mapped more than once.",
256
+ "importMappingNameRequired": "You must map at least one column to the \"Name\" field.",
257
+ "importFieldName": "Name",
258
+ "importFieldType": "Type",
259
+ "importFieldStatus": "Status",
260
+ "importFieldEmail": "Email",
261
+ "importFieldPhone": "Phone",
262
+ "importFieldMobile": "Mobile",
263
+ "importFieldCpf": "CPF",
264
+ "importFieldCnpj": "CNPJ",
265
+ "importFieldJobTitle": "Job Title",
266
+ "importFieldCompanyName": "Company (employer)",
267
+ "importFieldTradeName": "Trade Name",
268
+ "importFieldWebsite": "Website",
269
+ "importFieldNotes": "Notes",
270
+ "importFieldSource": "Source",
271
+ "importFieldAddressStreet": "Address — Street",
272
+ "importFieldAddressCity": "Address — City",
273
+ "importFieldAddressState": "Address — State",
274
+ "importFieldAddressZip": "Address — ZIP Code",
275
+ "importFieldAddressCountry": "Address — Country",
276
+ "importConfirmTitle": "Review and Confirm",
277
+ "importConfirmDescription": "Review the import settings before proceeding.",
278
+ "importConfirmFile": "File",
279
+ "importConfirmRows": "Estimated rows",
280
+ "importConfirmMappedFields": "Mapped fields",
281
+ "importConfirmCompanyLabel": "Associate all contacts with a company (optional)",
282
+ "importConfirmCompanyPlaceholder": "Search for a company...",
283
+ "importConfirmNoCompany": "No company (import as individual contacts)",
284
+ "importCreateCompanyTitle": "New Company",
285
+ "importCreateCompanyDescription": "Quickly create a company to associate with this import.",
286
+ "importCreateCompanyName": "Company name",
287
+ "importCreateCompanyNamePlaceholder": "e.g. Acme Corp",
288
+ "importCreateCompanyTradeName": "Trade name (optional)",
289
+ "importCreateCompanyTradeNamePlaceholder": "e.g. Acme",
290
+ "importCreateCompanySave": "Create company",
291
+ "importResultTitle": "Import Complete",
292
+ "importResultDescription": "Import finished. Review the results below.",
293
+ "importResultImported": "Imported",
294
+ "importResultSkipped": "Skipped",
295
+ "importResultErrors": "Errors",
296
+ "importResultErrorsLabel": "Error details",
297
+ "importResultRow": "Row {row}",
298
+ "importResultSuccess": "Leads imported successfully!",
299
+ "importResultPartial": "{imported} leads imported with {errors} errors.",
300
+ "importBack": "Back",
301
+ "importNext": "Next",
302
+ "importStart": "Import",
303
+ "importClose": "Close",
304
+ "importErrorFileRequired": "Select a CSV file to continue.",
305
+ "importErrorFileTooLarge": "File exceeds the 10 MB limit. Split the file and try again.",
306
+ "importErrorGeneric": "An error occurred during import. Try again."
234
307
  },
235
308
  "PersonFieldWithCreate": {
236
309
  "sheet": {
@@ -866,6 +939,21 @@
866
939
  "time_and_material": "Time and material",
867
940
  "monthly_retainer": "Monthly retainer",
868
941
  "fixed_price": "Fixed price"
942
+ },
943
+ "itemType": {
944
+ "service": "Service",
945
+ "product": "Product",
946
+ "fee": "Fee",
947
+ "discount": "Discount",
948
+ "note": "Note",
949
+ "other": "Other"
950
+ },
951
+ "recurrence": {
952
+ "one_time": "One-time",
953
+ "monthly": "Monthly",
954
+ "quarterly": "Quarterly",
955
+ "yearly": "Yearly",
956
+ "other": "Other"
869
957
  }
870
958
  },
871
959
  "actions": {
@@ -909,6 +997,27 @@
909
997
  "contractCategory": "Contract category",
910
998
  "contractType": "Contract type",
911
999
  "billingModel": "Billing model",
1000
+ "items": "Proposal items",
1001
+ "itemsDescription": "Add the services, products, fees, or discounts that make up this commercial proposal.",
1002
+ "addItem": "Add item",
1003
+ "itemLabel": "Item {number}",
1004
+ "itemHint": "Describe the scope, recurrence, and pricing for this item.",
1005
+ "discountHint": "Use positive values; the system will apply this item as a discount.",
1006
+ "removeItem": "Remove item",
1007
+ "itemName": "Item name",
1008
+ "itemNamePlaceholder": "E.g. Implementation, monthly license, or commercial discount",
1009
+ "itemType": "Item type",
1010
+ "recurrence": "Recurrence",
1011
+ "quantity": "Quantity",
1012
+ "unitAmount": "Unit amount",
1013
+ "itemDescription": "Item description",
1014
+ "itemDescriptionPlaceholder": "Describe what is included in this item...",
1015
+ "startDate": "Start date",
1016
+ "endDate": "End date",
1017
+ "lineTotal": "Line total",
1018
+ "subtotal": "Subtotal",
1019
+ "discount": "Discounts",
1020
+ "calculatedTotal": "Calculated total",
912
1021
  "summary": "Summary",
913
1022
  "summaryPlaceholder": "Write a short commercial summary for this proposal...",
914
1023
  "notes": "Internal notes",
@@ -1126,6 +1235,7 @@
1126
1235
  "edit": "Edit",
1127
1236
  "delete": "Delete",
1128
1237
  "cancel": "Cancel",
1238
+ "importContacts": "Import contacts",
1129
1239
  "deleting": "Deleting...",
1130
1240
  "searchPlaceholder": "Search accounts by name, trade name, email, or city...",
1131
1241
  "viewMode": "View",
@@ -1204,12 +1314,40 @@
1204
1314
  "locationTitle": "Location",
1205
1315
  "locationDescription": "City and state for quick context.",
1206
1316
  "additionalTitle": "Additional details",
1207
- "additionalDescription": "Complementary information and account metrics."
1317
+ "additionalDescription": "Complementary information and account metrics.",
1318
+ "contactsTitle": "Contacts",
1319
+ "contactsDescription": "Add email, phone, and other outreach channels.",
1320
+ "addressesTitle": "Addresses",
1321
+ "addressesDescription": "Manage headquarters, billing, or delivery locations.",
1322
+ "documentsTitle": "Documents",
1323
+ "documentsDescription": "Register company identifiers and legal records.",
1324
+ "collaboratorsTitle": "Collaborators",
1325
+ "collaboratorsDescription": "Manage people linked to this company."
1208
1326
  },
1209
1327
  "additional": {
1210
1328
  "show": "Expand",
1211
1329
  "hide": "Hide"
1212
1330
  },
1331
+ "collaborators": {
1332
+ "title": "Collaborators",
1333
+ "description": "People linked to this company through the employer field.",
1334
+ "empty": "No collaborators linked yet.",
1335
+ "saveFirst": "You can add collaborators before the first save.",
1336
+ "saveFirstHint": "Their person ids will be linked automatically when the company is saved.",
1337
+ "ready": "Company saved. You can now manage collaborators here.",
1338
+ "addAction": "Add collaborator",
1339
+ "editAction": "Edit collaborator",
1340
+ "editActionLabel": "Edit collaborator",
1341
+ "viewMode": "View mode",
1342
+ "viewModeCards": "Cards",
1343
+ "viewModeTable": "Table",
1344
+ "tableColName": "Name",
1345
+ "tableColJobTitle": "Job Title",
1346
+ "tableColStatus": "Status",
1347
+ "tableColEmail": "E-mail",
1348
+ "tableColPhone": "Phone",
1349
+ "tableColActions": "Actions"
1350
+ },
1213
1351
  "createSubmit": "Create Account",
1214
1352
  "updateSubmit": "Save Changes",
1215
1353
  "saving": "Saving...",
@@ -229,7 +229,80 @@
229
229
  "interactionError": "Falha ao registrar interação",
230
230
  "followupDate": "Data do Follow-up",
231
231
  "followupSuccess": "Follow-up agendado com sucesso",
232
- "followupError": "Falha ao agendar follow-up"
232
+ "followupError": "Falha ao agendar follow-up",
233
+ "importLeads": "Importar Leads",
234
+ "importStepUpload": "Upload",
235
+ "importStepPreview": "Prévia",
236
+ "importStepMapping": "Mapeamento",
237
+ "importStepConfirm": "Confirmação",
238
+ "importStepResult": "Resultado",
239
+ "importSheetTitle": "Importar Leads via CSV",
240
+ "importSheetDescription": "Faça upload de um arquivo CSV, mapeie as colunas e importe contatos para o CRM.",
241
+ "importDropzoneLabel": "Clique ou arraste um arquivo CSV aqui",
242
+ "importDropzoneHint": "Formato aceito: .csv — máx. 5.000 linhas",
243
+ "importDropzoneChange": "Trocar arquivo",
244
+ "importFileSelected": "Arquivo selecionado",
245
+ "importPreviewTitle": "Prévia do Arquivo",
246
+ "importPreviewDescription": "Exibindo as primeiras linhas do arquivo enviado. Verifique se os dados estão corretos antes de avançar.",
247
+ "importTotalEstimated": "Linhas estimadas",
248
+ "importColumnsDetected": "Colunas detectadas",
249
+ "importMappingTitle": "Mapeamento de Colunas",
250
+ "importMappingDescription": "Mapeie cada coluna do CSV para o campo correspondente do CRM. Escolha \"Ignorar\" para pular uma coluna.",
251
+ "importMappingColumnLabel": "Coluna CSV",
252
+ "importMappingFieldLabel": "Campo CRM",
253
+ "importMappingIgnore": "Ignorar",
254
+ "importMappingDuplicateWarning": "O campo {field} está mapeado mais de uma vez.",
255
+ "importMappingNameRequired": "Mapeie ao menos uma coluna para o campo \"Nome\".",
256
+ "importFieldName": "Nome",
257
+ "importFieldType": "Tipo",
258
+ "importFieldStatus": "Status",
259
+ "importFieldEmail": "E-mail",
260
+ "importFieldPhone": "Telefone",
261
+ "importFieldMobile": "Celular",
262
+ "importFieldCpf": "CPF",
263
+ "importFieldCnpj": "CNPJ",
264
+ "importFieldJobTitle": "Cargo",
265
+ "importFieldCompanyName": "Empresa (empregadora)",
266
+ "importFieldTradeName": "Nome Fantasia",
267
+ "importFieldWebsite": "Website",
268
+ "importFieldNotes": "Observações",
269
+ "importFieldSource": "Origem",
270
+ "importFieldAddressStreet": "Endereço — Rua",
271
+ "importFieldAddressCity": "Endereço — Cidade",
272
+ "importFieldAddressState": "Endereço — Estado",
273
+ "importFieldAddressZip": "Endereço — CEP",
274
+ "importFieldAddressCountry": "Endereço — País",
275
+ "importConfirmTitle": "Revisar e Confirmar",
276
+ "importConfirmDescription": "Revise as configurações de importação antes de prosseguir.",
277
+ "importConfirmFile": "Arquivo",
278
+ "importConfirmRows": "Linhas estimadas",
279
+ "importConfirmMappedFields": "Campos mapeados",
280
+ "importConfirmCompanyLabel": "Associar todos os contatos a uma empresa (opcional)",
281
+ "importConfirmCompanyPlaceholder": "Buscar empresa...",
282
+ "importConfirmNoCompany": "Sem empresa (importar como contatos individuais)",
283
+ "importCreateCompanyTitle": "Nova empresa",
284
+ "importCreateCompanyDescription": "Crie rapidamente uma empresa para associar a esta importação.",
285
+ "importCreateCompanyName": "Nome da empresa",
286
+ "importCreateCompanyNamePlaceholder": "ex: Acme Ltda",
287
+ "importCreateCompanyTradeName": "Nome fantasia (opcional)",
288
+ "importCreateCompanyTradeNamePlaceholder": "ex: Acme",
289
+ "importCreateCompanySave": "Criar empresa",
290
+ "importResultTitle": "Importação Concluída",
291
+ "importResultDescription": "A importação foi finalizada. Veja os resultados abaixo.",
292
+ "importResultImported": "Importados",
293
+ "importResultSkipped": "Ignorados",
294
+ "importResultErrors": "Erros",
295
+ "importResultErrorsLabel": "Detalhes dos erros",
296
+ "importResultRow": "Linha {row}",
297
+ "importResultSuccess": "Leads importados com sucesso!",
298
+ "importResultPartial": "{imported} leads importados com {errors} erros.",
299
+ "importBack": "Voltar",
300
+ "importNext": "Avançar",
301
+ "importStart": "Importar",
302
+ "importClose": "Fechar",
303
+ "importErrorFileRequired": "Selecione um arquivo CSV para continuar.",
304
+ "importErrorFileTooLarge": "O arquivo excede o limite de 10 MB. Divida o arquivo e tente novamente.",
305
+ "importErrorGeneric": "Ocorreu um erro durante a importação. Tente novamente."
233
306
  },
234
307
  "PersonFieldWithCreate": {
235
308
  "sheet": {
@@ -865,6 +938,21 @@
865
938
  "time_and_material": "Tempo e material",
866
939
  "monthly_retainer": "Retainer mensal",
867
940
  "fixed_price": "Preço fixo"
941
+ },
942
+ "itemType": {
943
+ "service": "Serviço",
944
+ "product": "Produto",
945
+ "fee": "Taxa",
946
+ "discount": "Desconto",
947
+ "note": "Observação",
948
+ "other": "Outro"
949
+ },
950
+ "recurrence": {
951
+ "one_time": "Pontual",
952
+ "monthly": "Mensal",
953
+ "quarterly": "Trimestral",
954
+ "yearly": "Anual",
955
+ "other": "Outra"
868
956
  }
869
957
  },
870
958
  "actions": {
@@ -908,6 +996,27 @@
908
996
  "contractCategory": "Categoria do contrato",
909
997
  "contractType": "Tipo do contrato",
910
998
  "billingModel": "Modelo de cobrança",
999
+ "items": "Itens da proposta",
1000
+ "itemsDescription": "Adicione serviços, produtos, taxas ou descontos que compõem a proposta comercial.",
1001
+ "addItem": "Adicionar item",
1002
+ "itemLabel": "Item {number}",
1003
+ "itemHint": "Detalhe o escopo, a recorrência e os valores deste item.",
1004
+ "discountHint": "Use valores positivos; o sistema aplicará este item como desconto.",
1005
+ "removeItem": "Remover item",
1006
+ "itemName": "Nome do item",
1007
+ "itemNamePlaceholder": "Ex: Implantação, licença mensal ou desconto comercial",
1008
+ "itemType": "Tipo do item",
1009
+ "recurrence": "Recorrência",
1010
+ "quantity": "Quantidade",
1011
+ "unitAmount": "Valor unitário",
1012
+ "itemDescription": "Descrição do item",
1013
+ "itemDescriptionPlaceholder": "Detalhe o que está incluído neste item...",
1014
+ "startDate": "Início",
1015
+ "endDate": "Fim",
1016
+ "lineTotal": "Total da linha",
1017
+ "subtotal": "Subtotal",
1018
+ "discount": "Descontos",
1019
+ "calculatedTotal": "Total calculado",
911
1020
  "summary": "Resumo",
912
1021
  "summaryPlaceholder": "Escreva um resumo comercial desta proposta...",
913
1022
  "notes": "Notas internas",
@@ -1125,6 +1234,7 @@
1125
1234
  "edit": "Editar",
1126
1235
  "delete": "Excluir",
1127
1236
  "cancel": "Cancelar",
1237
+ "importContacts": "Importar contatos",
1128
1238
  "deleting": "Excluindo...",
1129
1239
  "searchPlaceholder": "Pesquisar contas por nome, razão social, email ou cidade...",
1130
1240
  "viewMode": "Visualização",
@@ -1178,7 +1288,7 @@
1178
1288
  "companyName": "Nome da empresa",
1179
1289
  "tradeName": "Nome fantasia",
1180
1290
  "status": "Status",
1181
- "lifecycleStage": "Estagio",
1291
+ "lifecycleStage": "Estágio",
1182
1292
  "email": "Email",
1183
1293
  "phone": "Telefone",
1184
1294
  "website": "Site",
@@ -1187,28 +1297,56 @@
1187
1297
  "employeeCount": "Colaboradores",
1188
1298
  "city": "Cidade",
1189
1299
  "state": "Estado",
1190
- "owner": "Responsavel",
1300
+ "owner": "Responsável",
1191
1301
  "summary": {
1192
- "newCompany": "Nova conta em preparacao",
1302
+ "newCompany": "Nova conta em preparação",
1193
1303
  "createHint": "Preencha os dados essenciais para cadastrar a conta rapidamente.",
1194
1304
  "editHint": "Revise os dados principais e atualize o relacionamento comercial."
1195
1305
  },
1196
1306
  "sections": {
1197
- "identityTitle": "Identificacao",
1198
- "identityDescription": "Como a conta sera reconhecida no CRM.",
1307
+ "identityTitle": "Identificação",
1308
+ "identityDescription": "Como a conta será reconhecida no CRM.",
1199
1309
  "relationshipTitle": "Relacionamento",
1200
1310
  "relationshipDescription": "Status, etapa e ownership da conta.",
1201
1311
  "contactTitle": "Contato",
1202
1312
  "contactDescription": "Canais principais para abordagem comercial.",
1203
- "locationTitle": "Localizacao",
1204
- "locationDescription": "Cidade e estado para contexto rapido.",
1313
+ "locationTitle": "Localização",
1314
+ "locationDescription": "Cidade e estado para contexto rápido.",
1205
1315
  "additionalTitle": "Detalhes adicionais",
1206
- "additionalDescription": "Informacoes complementares e metricas da conta."
1316
+ "additionalDescription": "Informações complementares e métricas da conta.",
1317
+ "contactsTitle": "Contatos",
1318
+ "contactsDescription": "Adicione email, telefone e outros canais da empresa.",
1319
+ "addressesTitle": "Endereços",
1320
+ "addressesDescription": "Gerencie sede, cobrança ou locais de entrega.",
1321
+ "documentsTitle": "Documentos",
1322
+ "documentsDescription": "Cadastre identificadores e registros legais da empresa.",
1323
+ "collaboratorsTitle": "Colaboradores",
1324
+ "collaboratorsDescription": "Gerencie as pessoas vinculadas a esta empresa."
1207
1325
  },
1208
1326
  "additional": {
1209
1327
  "show": "Expandir",
1210
1328
  "hide": "Ocultar"
1211
1329
  },
1330
+ "collaborators": {
1331
+ "title": "Colaboradores",
1332
+ "description": "Pessoas vinculadas a esta empresa pelo campo de empregador.",
1333
+ "empty": "Nenhum colaborador vinculado ainda.",
1334
+ "saveFirst": "Voce pode adicionar colaboradores antes do primeiro salvamento.",
1335
+ "saveFirstHint": "Os ids das pessoas serao vinculados automaticamente quando a empresa for salva.",
1336
+ "ready": "Empresa salva. Agora voce pode gerenciar os colaboradores aqui.",
1337
+ "addAction": "Adicionar colaborador",
1338
+ "editAction": "Editar colaborador",
1339
+ "editActionLabel": "Editar colaborador",
1340
+ "viewMode": "Modo de visualização",
1341
+ "viewModeCards": "Cards",
1342
+ "viewModeTable": "Tabela",
1343
+ "tableColName": "Nome",
1344
+ "tableColJobTitle": "Cargo",
1345
+ "tableColStatus": "Status",
1346
+ "tableColEmail": "E-mail",
1347
+ "tableColPhone": "Telefone",
1348
+ "tableColActions": "Ações"
1349
+ },
1212
1350
  "createSubmit": "Criar conta",
1213
1351
  "updateSubmit": "Salvar alteracoes",
1214
1352
  "saving": "Salvando...",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hed-hog/contact",
3
- "version": "0.0.303",
3
+ "version": "0.0.305",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "dependencies": {
@@ -10,12 +10,12 @@
10
10
  "@nestjs/jwt": "^11",
11
11
  "@nestjs/mapped-types": "*",
12
12
  "@hed-hog/api-prisma": "0.0.6",
13
- "@hed-hog/core": "0.0.303",
14
- "@hed-hog/api-locale": "0.0.14",
13
+ "@hed-hog/core": "0.0.305",
14
+ "@hed-hog/address": "0.0.305",
15
15
  "@hed-hog/api-mail": "0.0.9",
16
- "@hed-hog/address": "0.0.303",
17
16
  "@hed-hog/api": "0.0.6",
18
- "@hed-hog/api-pagination": "0.0.7"
17
+ "@hed-hog/api-pagination": "0.0.7",
18
+ "@hed-hog/api-locale": "0.0.14"
19
19
  },
20
20
  "exports": {
21
21
  ".": {
@@ -1,5 +1,6 @@
1
1
  import { Type } from 'class-transformer';
2
2
  import {
3
+ IsArray,
3
4
  IsEmail,
4
5
  IsIn,
5
6
  IsInt,
@@ -7,8 +8,14 @@ import {
7
8
  IsOptional,
8
9
  IsString,
9
10
  MaxLength,
11
+ ValidateNested,
10
12
  } from 'class-validator';
11
13
  import { PersonStatus } from './create.dto';
14
+ import {
15
+ UpdateAllAddressDTO,
16
+ UpdateAllContactDTO,
17
+ UpdateAllDocumentDTO,
18
+ } from './update.dto';
12
19
 
13
20
  export const ACCOUNT_LIFECYCLE_STAGES = [
14
21
  'prospect',
@@ -95,6 +102,30 @@ export class CreateAccountDTO {
95
102
  @IsString()
96
103
  @MaxLength(2)
97
104
  state?: string | null;
105
+
106
+ @IsOptional()
107
+ @IsArray()
108
+ @Type(() => Number)
109
+ @IsInt({ each: true })
110
+ collaborator_person_ids?: number[];
111
+
112
+ @IsOptional()
113
+ @IsArray()
114
+ @ValidateNested({ each: true })
115
+ @Type(() => UpdateAllContactDTO)
116
+ contacts?: UpdateAllContactDTO[];
117
+
118
+ @IsOptional()
119
+ @IsArray()
120
+ @ValidateNested({ each: true })
121
+ @Type(() => UpdateAllAddressDTO)
122
+ addresses?: UpdateAllAddressDTO[];
123
+
124
+ @IsOptional()
125
+ @IsArray()
126
+ @ValidateNested({ each: true })
127
+ @Type(() => UpdateAllDocumentDTO)
128
+ documents?: UpdateAllDocumentDTO[];
98
129
  }
99
130
 
100
131
  export class UpdateAccountDTO extends CreateAccountDTO {}
@@ -0,0 +1,6 @@
1
+ export class ImportPreviewResponseDTO {
2
+ fileName: string;
3
+ totalEstimated: number;
4
+ columns: string[];
5
+ preview: Record<string, string>[];
6
+ }
@@ -0,0 +1,61 @@
1
+ import { IsNumber, IsObject, IsOptional } from 'class-validator';
2
+
3
+ export class ImportPersonDTO {
4
+ @IsObject()
5
+ mapping: Record<string, string>;
6
+
7
+ @IsOptional()
8
+ @IsNumber()
9
+ company_id?: number;
10
+ }
11
+
12
+ export type CrmImportField =
13
+ | 'name'
14
+ | 'type'
15
+ | 'status'
16
+ | 'email'
17
+ | 'phone'
18
+ | 'mobile'
19
+ | 'cpf'
20
+ | 'cnpj'
21
+ | 'job_title'
22
+ | 'company_name'
23
+ | 'trade_name'
24
+ | 'website'
25
+ | 'notes'
26
+ | 'source'
27
+ | 'address_street'
28
+ | 'address_city'
29
+ | 'address_state'
30
+ | 'address_zip'
31
+ | 'address_country'
32
+ | '_ignore';
33
+
34
+ export const CRM_IMPORT_FIELDS: CrmImportField[] = [
35
+ 'name',
36
+ 'type',
37
+ 'status',
38
+ 'email',
39
+ 'phone',
40
+ 'mobile',
41
+ 'cpf',
42
+ 'cnpj',
43
+ 'job_title',
44
+ 'company_name',
45
+ 'trade_name',
46
+ 'website',
47
+ 'notes',
48
+ 'source',
49
+ 'address_street',
50
+ 'address_city',
51
+ 'address_state',
52
+ 'address_zip',
53
+ 'address_country',
54
+ '_ignore',
55
+ ];
56
+
57
+ export class ImportPersonResponseDTO {
58
+ imported: number;
59
+ skipped: number;
60
+ errors: Array<{ row: number; message: string }>;
61
+ }
@@ -2,19 +2,23 @@ import { DeleteDTO, Public, Role, User } from '@hed-hog/api';
2
2
  import { Locale } from '@hed-hog/api-locale';
3
3
  import { Pagination } from '@hed-hog/api-pagination';
4
4
  import {
5
- Body,
6
- Controller,
7
- Delete,
8
- Get,
9
- Inject,
10
- Param,
11
- ParseIntPipe,
12
- Patch,
13
- Post,
14
- Query,
15
- Res,
16
- forwardRef
5
+ BadRequestException,
6
+ Body,
7
+ Controller,
8
+ Delete,
9
+ Get,
10
+ Inject,
11
+ Param,
12
+ ParseIntPipe,
13
+ Patch,
14
+ Post,
15
+ Query,
16
+ Res,
17
+ UploadedFile,
18
+ UseInterceptors,
19
+ forwardRef
17
20
  } from '@nestjs/common';
21
+ import { FileInterceptor } from '@nestjs/platform-express';
18
22
  import { Response } from 'express';
19
23
  import {
20
24
  AccountListQueryDTO,
@@ -37,6 +41,24 @@ import { UpdateLifecycleStageDTO } from './dto/update-lifecycle-stage.dto';
37
41
  import { UpdateAllPersonDTO } from './dto/update.dto';
38
42
  import { PersonService } from './person.service';
39
43
 
44
+ const CSV_FILE_FILTER = (
45
+ _req: any,
46
+ file: MulterFile,
47
+ cb: (error: Error | null, acceptFile: boolean) => void,
48
+ ) => {
49
+ const isAllowedMime = /text\/(csv|plain)/.test(file.mimetype) || file.mimetype === 'application/vnd.ms-excel';
50
+ const isAllowedExt = file.originalname.toLowerCase().endsWith('.csv');
51
+ if (!isAllowedMime && !isAllowedExt) {
52
+ return cb(new BadRequestException('Only CSV files are allowed'), false);
53
+ }
54
+ cb(null, true);
55
+ };
56
+
57
+ const CSV_UPLOAD_OPTIONS = {
58
+ limits: { fileSize: 10 * 1024 * 1024 }, // 10 MB
59
+ fileFilter: CSV_FILE_FILTER,
60
+ };
61
+
40
62
  @Role()
41
63
  @Controller('person')
42
64
  export class PersonController {
@@ -196,6 +218,46 @@ export class PersonController {
196
218
  return this.personService.get(locale, id);
197
219
  }
198
220
 
221
+ @Post('import/preview')
222
+ @UseInterceptors(FileInterceptor('file', CSV_UPLOAD_OPTIONS))
223
+ async previewCsvImport(@UploadedFile() file: MulterFile) {
224
+ if (!file) {
225
+ throw new BadRequestException('No file uploaded');
226
+ }
227
+ return this.personService.previewCsvImport(file);
228
+ }
229
+
230
+ @Post('import')
231
+ @UseInterceptors(FileInterceptor('file', CSV_UPLOAD_OPTIONS))
232
+ async importFromCsv(
233
+ @UploadedFile() file: MulterFile,
234
+ @Body('mapping') mappingJson: string,
235
+ @Body('company_id') companyIdRaw: string,
236
+ @Locale() locale: string,
237
+ @User() user,
238
+ ) {
239
+ if (!file) {
240
+ throw new BadRequestException('No file uploaded');
241
+ }
242
+
243
+ let mapping: Record<string, string>;
244
+ try {
245
+ mapping = JSON.parse(mappingJson);
246
+ } catch {
247
+ throw new BadRequestException('Invalid mapping JSON');
248
+ }
249
+
250
+ const companyId = companyIdRaw ? Number(companyIdRaw) : undefined;
251
+
252
+ return this.personService.importFromCsv(
253
+ file,
254
+ mapping,
255
+ Number.isNaN(companyId) ? undefined : companyId,
256
+ locale,
257
+ Number(user?.id || 0),
258
+ );
259
+ }
260
+
199
261
  @Post()
200
262
  async create(@Body() data: CreateDTO, @Locale() locale: string) {
201
263
  return this.personService.create(data, locale);