@hed-hog/contact 0.0.279 → 0.0.285

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 (70) hide show
  1. package/README.md +2 -0
  2. package/dist/person/dto/create-followup.dto.d.ts +5 -0
  3. package/dist/person/dto/create-followup.dto.d.ts.map +1 -0
  4. package/dist/person/dto/create-followup.dto.js +31 -0
  5. package/dist/person/dto/create-followup.dto.js.map +1 -0
  6. package/dist/person/dto/create-interaction.dto.d.ts +12 -0
  7. package/dist/person/dto/create-interaction.dto.d.ts.map +1 -0
  8. package/dist/person/dto/create-interaction.dto.js +39 -0
  9. package/dist/person/dto/create-interaction.dto.js.map +1 -0
  10. package/dist/person/dto/create.dto.d.ts +24 -0
  11. package/dist/person/dto/create.dto.d.ts.map +1 -1
  12. package/dist/person/dto/create.dto.js +56 -1
  13. package/dist/person/dto/create.dto.js.map +1 -1
  14. package/dist/person/dto/duplicates-query.dto.d.ts +8 -0
  15. package/dist/person/dto/duplicates-query.dto.d.ts.map +1 -0
  16. package/dist/person/dto/duplicates-query.dto.js +45 -0
  17. package/dist/person/dto/duplicates-query.dto.js.map +1 -0
  18. package/dist/person/dto/merge.dto.d.ts +6 -0
  19. package/dist/person/dto/merge.dto.d.ts.map +1 -0
  20. package/dist/person/dto/merge.dto.js +35 -0
  21. package/dist/person/dto/merge.dto.js.map +1 -0
  22. package/dist/person/dto/update-lifecycle-stage.dto.d.ts +13 -0
  23. package/dist/person/dto/update-lifecycle-stage.dto.d.ts.map +1 -0
  24. package/dist/person/dto/update-lifecycle-stage.dto.js +34 -0
  25. package/dist/person/dto/update-lifecycle-stage.dto.js.map +1 -0
  26. package/dist/person/dto/update.dto.d.ts +8 -1
  27. package/dist/person/dto/update.dto.d.ts.map +1 -1
  28. package/dist/person/dto/update.dto.js +36 -0
  29. package/dist/person/dto/update.dto.js.map +1 -1
  30. package/dist/person/person.controller.d.ts +57 -1
  31. package/dist/person/person.controller.d.ts.map +1 -1
  32. package/dist/person/person.controller.js +85 -3
  33. package/dist/person/person.controller.js.map +1 -1
  34. package/dist/person/person.service.d.ts +79 -0
  35. package/dist/person/person.service.d.ts.map +1 -1
  36. package/dist/person/person.service.js +730 -9
  37. package/dist/person/person.service.js.map +1 -1
  38. package/hedhog/data/route.yaml +18 -0
  39. package/hedhog/frontend/app/_components/crm-coming-soon.tsx.ejs +110 -110
  40. package/hedhog/frontend/app/_components/crm-nav.tsx.ejs +73 -73
  41. package/hedhog/frontend/app/_lib/crm-mocks.ts.ejs +498 -256
  42. package/hedhog/frontend/app/_lib/crm-sections.tsx.ejs +81 -81
  43. package/hedhog/frontend/app/accounts/_components/account-form-sheet.tsx.ejs +477 -0
  44. package/hedhog/frontend/app/accounts/_components/account-types.ts.ejs +62 -0
  45. package/hedhog/frontend/app/accounts/page.tsx.ejs +886 -15
  46. package/hedhog/frontend/app/activities/page.tsx.ejs +15 -15
  47. package/hedhog/frontend/app/contact-type/page.tsx.ejs +105 -91
  48. package/hedhog/frontend/app/dashboard/page.tsx.ejs +506 -573
  49. package/hedhog/frontend/app/document-type/page.tsx.ejs +105 -91
  50. package/hedhog/frontend/app/follow-ups/page.tsx.ejs +15 -15
  51. package/hedhog/frontend/app/page.tsx.ejs +5 -5
  52. package/hedhog/frontend/app/person/_components/person-form-sheet.tsx.ejs +1440 -1103
  53. package/hedhog/frontend/app/person/_components/person-interaction-dialog.tsx.ejs +4 -3
  54. package/hedhog/frontend/app/person/_components/person-types.ts.ejs +14 -0
  55. package/hedhog/frontend/app/person/page.tsx.ejs +108 -190
  56. package/hedhog/frontend/app/pipeline/_components/lead-detail-sheet.tsx.ejs +599 -0
  57. package/hedhog/frontend/app/pipeline/page.tsx.ejs +1074 -299
  58. package/hedhog/frontend/app/reports/page.tsx.ejs +15 -15
  59. package/hedhog/frontend/messages/en.json +107 -0
  60. package/hedhog/frontend/messages/pt.json +106 -0
  61. package/package.json +6 -6
  62. package/src/person/dto/create-followup.dto.ts +15 -0
  63. package/src/person/dto/create-interaction.dto.ts +23 -0
  64. package/src/person/dto/create.dto.ts +50 -0
  65. package/src/person/dto/duplicates-query.dto.ts +34 -0
  66. package/src/person/dto/merge.dto.ts +15 -0
  67. package/src/person/dto/update-lifecycle-stage.dto.ts +19 -0
  68. package/src/person/dto/update.dto.ts +31 -1
  69. package/src/person/person.controller.ts +63 -2
  70. package/src/person/person.service.ts +1096 -7
@@ -1,15 +1,15 @@
1
- 'use client';
2
-
3
- import { CrmComingSoon } from '../_components/crm-coming-soon';
4
- import { BarChart3 } from 'lucide-react';
5
-
6
- export default function CrmReportsPage() {
7
- return (
8
- <CrmComingSoon
9
- currentHref="/contact/reports"
10
- titleKey="reports"
11
- descriptionKey="reports"
12
- icon={BarChart3}
13
- />
14
- );
15
- }
1
+ 'use client';
2
+
3
+ import { CrmComingSoon } from '../_components/crm-coming-soon';
4
+ import { BarChart3 } from 'lucide-react';
5
+
6
+ export default function CrmReportsPage() {
7
+ return (
8
+ <CrmComingSoon
9
+ currentHref="/contact/reports"
10
+ titleKey="reports"
11
+ descriptionKey="reports"
12
+ icon={BarChart3}
13
+ />
14
+ );
15
+ }
@@ -173,6 +173,15 @@
173
173
  "rowsPerPage": "Rows per page",
174
174
  "paginationLabel": "Page {current} of {total}",
175
175
  "register": "Register",
176
+ "duplicateWarning": "Possible duplicate found: {names}.",
177
+ "duplicateDialogTitle": "Possible duplicate record",
178
+ "duplicateDialogDescription": "We found records with matching key data. Choose a primary record to merge into, or continue anyway.",
179
+ "duplicateTargetLabel": "Primary record",
180
+ "duplicateSelectTarget": "Select a record to merge into",
181
+ "duplicateContinueAction": "Continue anyway",
182
+ "duplicateMergeAction": "Merge into selected record",
183
+ "duplicateMergeSuccess": "Merge completed successfully into {name}.",
184
+ "duplicateMergeError": "Failed to merge records.",
176
185
  "owner": "Owner",
177
186
  "unassigned": "Unassigned",
178
187
  "registerInteraction": "Register Interaction",
@@ -292,6 +301,8 @@
292
301
  "tableCreatedAt": "Created At",
293
302
  "loading": "Loading...",
294
303
  "noResults": "No contact types found",
304
+ "emptyStateDescription": "Create the first contact type or adjust the search to find existing records.",
305
+ "emptyStateAction": "Create Contact Type",
295
306
  "menuOpen": "Open menu",
296
307
  "menuActions": "Actions",
297
308
  "menuEdit": "Edit",
@@ -334,6 +345,8 @@
334
345
  "tableCreatedAt": "Created At",
335
346
  "loading": "Loading...",
336
347
  "noResults": "No document types found",
348
+ "emptyStateDescription": "Create the first document type or adjust the search to find existing records.",
349
+ "emptyStateAction": "Create Document Type",
337
350
  "menuOpen": "Open menu",
338
351
  "menuActions": "Actions",
339
352
  "menuEdit": "Edit",
@@ -521,8 +534,10 @@
521
534
  "filterBadge": "Owner-based reading",
522
535
  "heroTitle": "Visualize the funnel by stage and distribute operations clearly.",
523
536
  "heroDescription": "Track each owner's workload, see lead aging, and identify where the next opportunities to advance are.",
537
+ "newPerson": "New lead",
524
538
  "ownerFilterLabel": "Filter by owner",
525
539
  "ownerFilterAll": "All owners",
540
+ "searchPlaceholder": "Search by lead, company, or trade name...",
526
541
  "summary": {
527
542
  "total": {
528
543
  "title": "Leads in filter",
@@ -552,6 +567,47 @@
552
567
  "days": "{count} days",
553
568
  "nextAction": "Next action",
554
569
  "noFollowup": "No follow-up"
570
+ },
571
+ "controls": {
572
+ "expandAll": "Expand all",
573
+ "collapseAll": "Collapse all",
574
+ "showDetails": "Show details",
575
+ "hideDetails": "Hide details"
576
+ },
577
+ "detail": {
578
+ "title": "Lead overview",
579
+ "lost": "Lost opportunity",
580
+ "owner": "Owner",
581
+ "source": "Source",
582
+ "advanceStage": "Advance stage",
583
+ "moveTo": "Move to {stage}",
584
+ "timeline": "Timeline",
585
+ "noInteractions": "No interactions recorded for this lead yet.",
586
+ "loadingTimeline": "Loading interaction history...",
587
+ "createdAt": "Created at",
588
+ "lastInteraction": "Last interaction",
589
+ "editCadastro": "Edit full record",
590
+ "stageMoveSuccess": "Lead moved to {stage} successfully",
591
+ "stageMoveError": "Failed to move lead to a new stage",
592
+ "interactionType": {
593
+ "call": "Call",
594
+ "email": "Email",
595
+ "whatsapp": "WhatsApp",
596
+ "meeting": "Meeting",
597
+ "note": "Note"
598
+ }
599
+ },
600
+ "tooltips": {
601
+ "statusActive": "Active lead — being followed up by the owner.",
602
+ "statusInactive": "Inactive lead — not currently in the operational flow.",
603
+ "owner": "Person responsible for this lead.",
604
+ "dealValue": "Estimated value of this opportunity.",
605
+ "nextActionOverdue": "Action overdue — date has already passed.",
606
+ "nextActionSoon": "Next action within 3 days — attention needed.",
607
+ "nextActionOk": "Next action scheduled — no immediate urgency.",
608
+ "nextActionNone": "No follow-up scheduled for this lead.",
609
+ "score": "Conversion probability score (0–100).",
610
+ "age": "Days since first contact with this lead."
555
611
  }
556
612
  },
557
613
  "CrmFuturePage": {
@@ -568,5 +624,56 @@
568
624
  "stepOne": "Define indicators, filters, and operational rules for this area with the commercial team.",
569
625
  "stepTwo": "Connect the page to the real data already available in the people and interactions modules.",
570
626
  "stepThree": "Add automations, reports, and quick actions based on daily usage."
627
+ },
628
+ "AccountsPage": {
629
+ "title": "Accounts",
630
+ "description": "Manage strategic accounts, prospects, and customer relationships",
631
+ "newAccount": "New Account",
632
+ "edit": "Edit",
633
+ "delete": "Delete",
634
+ "cancel": "Cancel",
635
+ "deleting": "Deleting...",
636
+ "searchPlaceholder": "Search accounts by name, trade name, email, or city...",
637
+ "viewMode": "View",
638
+ "viewModeTable": "Table",
639
+ "viewModeCards": "Cards",
640
+ "filterByStatus": "Filter by status",
641
+ "filterByStage": "Filter by stage",
642
+ "allStatuses": "All statuses",
643
+ "allStages": "All stages",
644
+ "columnName": "Company",
645
+ "columnIndustry": "Industry",
646
+ "columnStage": "Stage",
647
+ "columnStatus": "Status",
648
+ "columnOwner": "Owner",
649
+ "columnCity": "City",
650
+ "columnCreatedAt": "Created",
651
+ "status_active": "Active",
652
+ "status_inactive": "Inactive",
653
+ "stage_prospect": "Prospect",
654
+ "stage_customer": "Customer",
655
+ "stage_churned": "Churned",
656
+ "stage_inactive": "Inactive",
657
+ "statsTotal": "Total Accounts",
658
+ "statsActive": "Active",
659
+ "statsCustomers": "Customers",
660
+ "statsProspects": "Prospects",
661
+ "emptyStateTitle": "No accounts found",
662
+ "emptyStateDescription": "Start by adding your first account or adjust the filters to see more results.",
663
+ "deleteTitle": "Delete Account",
664
+ "deleteDescription": "Are you sure you want to delete {name}? This action cannot be undone.",
665
+ "createSuccess": "Account created successfully",
666
+ "editSuccess": "Account updated successfully",
667
+ "deleteSuccess": "Account deleted successfully",
668
+ "owner": "Owner",
669
+ "unassigned": "Unassigned",
670
+ "tileEmail": "Email",
671
+ "tilePhone": "Phone",
672
+ "tileWebsite": "Website",
673
+ "tileIndustry": "Industry",
674
+ "email": "Email",
675
+ "phone": "Phone",
676
+ "website": "Website",
677
+ "industry": "Industry"
571
678
  }
572
679
  }
@@ -203,6 +203,14 @@
203
203
  "saveAndNew": "Salvar e novo",
204
204
  "formShortcutsHint": "Atalhos: Ctrl/Cmd+S para salvar, Esc para fechar.",
205
205
  "duplicateWarning": "Possível duplicidade encontrada: {names}.",
206
+ "duplicateDialogTitle": "Possível cadastro duplicado",
207
+ "duplicateDialogDescription": "Encontramos registros com os mesmos dados principais. Escolha um cadastro principal para mesclar ou continue mesmo assim.",
208
+ "duplicateTargetLabel": "Cadastro principal",
209
+ "duplicateSelectTarget": "Selecione um cadastro para mesclar",
210
+ "duplicateContinueAction": "Continuar mesmo assim",
211
+ "duplicateMergeAction": "Mesclar com cadastro selecionado",
212
+ "duplicateMergeSuccess": "Mesclagem concluída com sucesso em {name}.",
213
+ "duplicateMergeError": "Falha ao mesclar cadastros.",
206
214
  "createdAt": "Criado em",
207
215
  "close": "Fechar",
208
216
  "copiedToClipboard": "Copiado para a área de transferência!",
@@ -326,6 +334,8 @@
326
334
  "tableCreatedAt": "Data de Criação",
327
335
  "loading": "Carregando...",
328
336
  "noResults": "Nenhum tipo de contato encontrado",
337
+ "emptyStateDescription": "Crie o primeiro tipo de contato ou ajuste a busca para encontrar registros existentes.",
338
+ "emptyStateAction": "Criar Tipo de Contato",
329
339
  "menuOpen": "Abrir menu",
330
340
  "menuActions": "Ações",
331
341
  "menuEdit": "Editar",
@@ -368,6 +378,8 @@
368
378
  "tableCreatedAt": "Data de Criação",
369
379
  "loading": "Carregando...",
370
380
  "noResults": "Nenhum tipo de documento encontrado",
381
+ "emptyStateDescription": "Crie o primeiro tipo de documento ou ajuste a busca para encontrar registros existentes.",
382
+ "emptyStateAction": "Criar Tipo de Documento",
371
383
  "menuOpen": "Abrir menu",
372
384
  "menuActions": "Ações",
373
385
  "menuEdit": "Editar",
@@ -555,8 +567,10 @@
555
567
  "filterBadge": "Leitura por responsável",
556
568
  "heroTitle": "Visualize o funil por estágio e distribua a operação com clareza.",
557
569
  "heroDescription": "Acompanhe a carga de cada owner, veja o tempo de maturação dos leads e identifique onde estão as próximas oportunidades de avanço.",
570
+ "newPerson": "Novo lead",
558
571
  "ownerFilterLabel": "Filtrar por owner",
559
572
  "ownerFilterAll": "Todos os owners",
573
+ "searchPlaceholder": "Buscar por lead, empresa ou nome fantasia...",
560
574
  "summary": {
561
575
  "total": {
562
576
  "title": "Leads no filtro",
@@ -586,6 +600,47 @@
586
600
  "days": "{count} dias",
587
601
  "nextAction": "Próxima ação",
588
602
  "noFollowup": "Sem follow-up"
603
+ },
604
+ "controls": {
605
+ "expandAll": "Abrir todos",
606
+ "collapseAll": "Fechar todos",
607
+ "showDetails": "Ver detalhes",
608
+ "hideDetails": "Ocultar detalhes"
609
+ },
610
+ "detail": {
611
+ "title": "Visão do lead",
612
+ "lost": "Oportunidade perdida",
613
+ "owner": "Owner",
614
+ "source": "Origem",
615
+ "advanceStage": "Avançar etapa",
616
+ "moveTo": "Mover para {stage}",
617
+ "timeline": "Linha do tempo",
618
+ "noInteractions": "Nenhuma interação registrada para este lead ainda.",
619
+ "loadingTimeline": "Carregando histórico de interações...",
620
+ "createdAt": "Criado em",
621
+ "lastInteraction": "Última interação",
622
+ "editCadastro": "Editar cadastro completo",
623
+ "stageMoveSuccess": "Lead movido para {stage} com sucesso",
624
+ "stageMoveError": "Falha ao mover lead de etapa",
625
+ "interactionType": {
626
+ "call": "Ligação",
627
+ "email": "E-mail",
628
+ "whatsapp": "WhatsApp",
629
+ "meeting": "Reunião",
630
+ "note": "Anotação"
631
+ }
632
+ },
633
+ "tooltips": {
634
+ "statusActive": "Lead ativo — em acompanhamento pelo responsável.",
635
+ "statusInactive": "Lead inativo — fora do fluxo operacional do momento.",
636
+ "owner": "Responsável por este lead.",
637
+ "dealValue": "Valor estimado desta oportunidade.",
638
+ "nextActionOverdue": "Ação atrasada — data já passou.",
639
+ "nextActionSoon": "Próxima ação em até 3 dias — atenção.",
640
+ "nextActionOk": "Próxima ação agendada — sem urgência.",
641
+ "nextActionNone": "Nenhum follow-up agendado para este lead.",
642
+ "score": "Pontuação de probabilidade de conversão (0–100).",
643
+ "age": "Dias desde o primeiro contato com este lead."
589
644
  }
590
645
  },
591
646
  "CrmFuturePage": {
@@ -602,5 +657,56 @@
602
657
  "stepOne": "Definir indicadores, filtros e regras operacionais desta área com o time comercial.",
603
658
  "stepTwo": "Conectar a página aos dados reais já existentes no módulo de pessoas e interações.",
604
659
  "stepThree": "Adicionar automações, relatórios e ações rápidas com base no uso diário."
660
+ },
661
+ "AccountsPage": {
662
+ "title": "Contas",
663
+ "description": "Gerencie contas estratégicas, prospects e relacionamentos com clientes",
664
+ "newAccount": "Nova Conta",
665
+ "edit": "Editar",
666
+ "delete": "Excluir",
667
+ "cancel": "Cancelar",
668
+ "deleting": "Excluindo...",
669
+ "searchPlaceholder": "Pesquisar contas por nome, razão social, email ou cidade...",
670
+ "viewMode": "Visualização",
671
+ "viewModeTable": "Tabela",
672
+ "viewModeCards": "Cards",
673
+ "filterByStatus": "Filtrar por status",
674
+ "filterByStage": "Filtrar por estágio",
675
+ "allStatuses": "Todos os status",
676
+ "allStages": "Todos os estágios",
677
+ "columnName": "Empresa",
678
+ "columnIndustry": "Setor",
679
+ "columnStage": "Estágio",
680
+ "columnStatus": "Status",
681
+ "columnOwner": "Responsável",
682
+ "columnCity": "Cidade",
683
+ "columnCreatedAt": "Criado em",
684
+ "status_active": "Ativo",
685
+ "status_inactive": "Inativo",
686
+ "stage_prospect": "Prospect",
687
+ "stage_customer": "Cliente",
688
+ "stage_churned": "Desativado",
689
+ "stage_inactive": "Inativo",
690
+ "statsTotal": "Total de Contas",
691
+ "statsActive": "Ativas",
692
+ "statsCustomers": "Clientes",
693
+ "statsProspects": "Prospects",
694
+ "emptyStateTitle": "Nenhuma conta encontrada",
695
+ "emptyStateDescription": "Comece adicionando sua primeira conta ou ajuste os filtros para ver mais resultados.",
696
+ "deleteTitle": "Excluir Conta",
697
+ "deleteDescription": "Tem certeza de que deseja excluir {name}? Esta ação não pode ser desfeita.",
698
+ "createSuccess": "Conta criada com sucesso",
699
+ "editSuccess": "Conta atualizada com sucesso",
700
+ "deleteSuccess": "Conta excluída com sucesso",
701
+ "owner": "Responsável",
702
+ "unassigned": "Não atribuído",
703
+ "tileEmail": "Email",
704
+ "tilePhone": "Telefone",
705
+ "tileWebsite": "Site",
706
+ "tileIndustry": "Setor",
707
+ "email": "Email",
708
+ "phone": "Telefone",
709
+ "website": "Site",
710
+ "industry": "Setor"
605
711
  }
606
712
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hed-hog/contact",
3
- "version": "0.0.279",
3
+ "version": "0.0.285",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "dependencies": {
@@ -9,13 +9,13 @@
9
9
  "@nestjs/core": "^11",
10
10
  "@nestjs/jwt": "^11",
11
11
  "@nestjs/mapped-types": "*",
12
- "@hed-hog/core": "0.0.279",
13
- "@hed-hog/address": "0.0.279",
12
+ "@hed-hog/address": "0.0.285",
13
+ "@hed-hog/api-mail": "0.0.8",
14
+ "@hed-hog/core": "0.0.285",
15
+ "@hed-hog/api": "0.0.4",
14
16
  "@hed-hog/api-pagination": "0.0.6",
15
- "@hed-hog/api-locale": "0.0.13",
16
17
  "@hed-hog/api-prisma": "0.0.5",
17
- "@hed-hog/api-mail": "0.0.8",
18
- "@hed-hog/api": "0.0.4"
18
+ "@hed-hog/api-locale": "0.0.13"
19
19
  },
20
20
  "exports": {
21
21
  ".": {
@@ -0,0 +1,15 @@
1
+ import { getLocaleText } from '@hed-hog/api-locale';
2
+ import { IsDateString, IsOptional, IsString } from 'class-validator';
3
+
4
+ export class CreateFollowupDTO {
5
+ @IsDateString({}, {
6
+ message: (args) => getLocaleText('validation.dateMustBeString', args.value),
7
+ })
8
+ next_action_at: string;
9
+
10
+ @IsOptional()
11
+ @IsString({
12
+ message: (args) => getLocaleText('validation.notesMustBeString', args.value),
13
+ })
14
+ notes?: string | null;
15
+ }
@@ -0,0 +1,23 @@
1
+ import { getLocaleText } from '@hed-hog/api-locale';
2
+ import { IsEnum, IsOptional, IsString } from 'class-validator';
3
+
4
+ export enum PersonInteractionTypeDTO {
5
+ CALL = 'call',
6
+ EMAIL = 'email',
7
+ WHATSAPP = 'whatsapp',
8
+ MEETING = 'meeting',
9
+ NOTE = 'note',
10
+ }
11
+
12
+ export class CreateInteractionDTO {
13
+ @IsEnum(PersonInteractionTypeDTO, {
14
+ message: (args) => getLocaleText('validation.typeMustBeEnum', args.value),
15
+ })
16
+ type: PersonInteractionTypeDTO;
17
+
18
+ @IsOptional()
19
+ @IsString({
20
+ message: (args) => getLocaleText('validation.notesMustBeString', args.value),
21
+ })
22
+ notes?: string | null;
23
+ }
@@ -1,9 +1,11 @@
1
1
  import { getLocaleText } from '@hed-hog/api-locale';
2
2
  import {
3
+ IsArray,
3
4
  IsDateString,
4
5
  IsEnum,
5
6
  IsInt,
6
7
  IsOptional,
8
+ IsNumber,
7
9
  IsString,
8
10
  } from 'class-validator';
9
11
 
@@ -23,6 +25,25 @@ export enum PersonGender {
23
25
  OTHER = 'other'
24
26
  }
25
27
 
28
+ export enum PersonSource {
29
+ REFERRAL = 'referral',
30
+ WEBSITE = 'website',
31
+ SOCIAL = 'social',
32
+ INBOUND = 'inbound',
33
+ OUTBOUND = 'outbound',
34
+ OTHER = 'other',
35
+ }
36
+
37
+ export enum PersonLifecycleStage {
38
+ NEW = 'new',
39
+ CONTACTED = 'contacted',
40
+ QUALIFIED = 'qualified',
41
+ PROPOSAL = 'proposal',
42
+ NEGOTIATION = 'negotiation',
43
+ CUSTOMER = 'customer',
44
+ LOST = 'lost',
45
+ }
46
+
26
47
  export class CreateDTO {
27
48
  @IsString({ message: (args) => getLocaleText('validation.nameMustBeString', args.value) })
28
49
  name: string;
@@ -68,4 +89,33 @@ export class CreateDTO {
68
89
  @IsOptional()
69
90
  @IsInt({ message: (args) => getLocaleText('validation.idMustBeInteger', args.value) })
70
91
  employer_company_id?: number | null;
92
+
93
+ @IsOptional()
94
+ @IsInt({ message: (args) => getLocaleText('validation.idMustBeInteger', args.value) })
95
+ owner_user_id?: number | null;
96
+
97
+ @IsOptional()
98
+ @IsEnum(PersonSource, { message: (args) => getLocaleText('validation.typeMustBeEnum', args.value) })
99
+ source?: PersonSource | null;
100
+
101
+ @IsOptional()
102
+ @IsEnum(PersonLifecycleStage, { message: (args) => getLocaleText('validation.typeMustBeEnum', args.value) })
103
+ lifecycle_stage?: PersonLifecycleStage | null;
104
+
105
+ @IsOptional()
106
+ @IsDateString({}, { message: (args) => getLocaleText('validation.dateMustBeString', args.value) })
107
+ next_action_at?: string | null;
108
+
109
+ @IsOptional()
110
+ @IsNumber({}, { message: (args) => getLocaleText('validation.numberMustBeNumber', args.value) })
111
+ score?: number | null;
112
+
113
+ @IsOptional()
114
+ @IsNumber({}, { message: (args) => getLocaleText('validation.numberMustBeNumber', args.value) })
115
+ deal_value?: number | null;
116
+
117
+ @IsOptional()
118
+ @IsArray({ message: (args) => getLocaleText('validation.tagsMustBeArray', args.value) })
119
+ @IsString({ each: true, message: (args) => getLocaleText('validation.stringRequired', args.value) })
120
+ tags?: string[] | null;
71
121
  }
@@ -0,0 +1,34 @@
1
+ import { Type } from 'class-transformer';
2
+ import {
3
+ IsEmail,
4
+ IsInt,
5
+ IsOptional,
6
+ IsString,
7
+ ValidateIf,
8
+ } from 'class-validator';
9
+
10
+ export class CheckPersonDuplicatesQueryDTO {
11
+ @IsOptional()
12
+ @Type(() => Number)
13
+ @IsInt()
14
+ person_id?: number;
15
+
16
+ @IsOptional()
17
+ @IsEmail()
18
+ email?: string;
19
+
20
+ @IsOptional()
21
+ @IsString()
22
+ phone?: string;
23
+
24
+ @IsOptional()
25
+ @IsString()
26
+ document_value?: string;
27
+
28
+ @ValidateIf((value: CheckPersonDuplicatesQueryDTO) =>
29
+ Boolean(value.document_value),
30
+ )
31
+ @Type(() => Number)
32
+ @IsInt()
33
+ document_type_id?: number;
34
+ }
@@ -0,0 +1,15 @@
1
+ import { Type } from 'class-transformer';
2
+ import { IsIn, IsInt } from 'class-validator';
3
+
4
+ export class MergePersonDTO {
5
+ @Type(() => Number)
6
+ @IsInt()
7
+ source_person_id: number;
8
+
9
+ @Type(() => Number)
10
+ @IsInt()
11
+ target_person_id: number;
12
+
13
+ @IsIn(['contact_only'])
14
+ strategy: 'contact_only' = 'contact_only';
15
+ }
@@ -0,0 +1,19 @@
1
+ import { getLocaleText } from '@hed-hog/api-locale';
2
+ import { IsEnum } from 'class-validator';
3
+
4
+ export enum PersonLifecycleStageDTO {
5
+ NEW = 'new',
6
+ CONTACTED = 'contacted',
7
+ QUALIFIED = 'qualified',
8
+ PROPOSAL = 'proposal',
9
+ NEGOTIATION = 'negotiation',
10
+ CUSTOMER = 'customer',
11
+ LOST = 'lost',
12
+ }
13
+
14
+ export class UpdateLifecycleStageDTO {
15
+ @IsEnum(PersonLifecycleStageDTO, {
16
+ message: (args) => getLocaleText('validation.typeMustBeEnum', args.value),
17
+ })
18
+ lifecycle_stage: PersonLifecycleStageDTO;
19
+ }
@@ -1,5 +1,5 @@
1
1
  import { AddressTypeEnum } from '../../address-type.enum';
2
- import { PersonGender } from './create.dto';
2
+ import { PersonGender, PersonLifecycleStage, PersonSource } from './create.dto';
3
3
  import { getLocaleText } from '@hed-hog/api-locale';
4
4
  import { Type } from 'class-transformer';
5
5
  import {
@@ -8,6 +8,7 @@ import {
8
8
  IsDateString,
9
9
  IsEnum,
10
10
  IsInt,
11
+ IsNumber,
11
12
  IsOptional,
12
13
  IsString,
13
14
  ValidateNested,
@@ -121,6 +122,35 @@ export class UpdateAllPersonDTO {
121
122
  @IsInt({ message: (args) => getLocaleText('validation.idMustBeInteger', args.value) })
122
123
  employer_company_id?: number | null;
123
124
 
125
+ @IsOptional()
126
+ @IsInt({ message: (args) => getLocaleText('validation.idMustBeInteger', args.value) })
127
+ owner_user_id?: number | null;
128
+
129
+ @IsOptional()
130
+ @IsEnum(PersonSource, { message: (args) => getLocaleText('validation.typeMustBeEnum', args.value) })
131
+ source?: PersonSource | null;
132
+
133
+ @IsOptional()
134
+ @IsEnum(PersonLifecycleStage, { message: (args) => getLocaleText('validation.typeMustBeEnum', args.value) })
135
+ lifecycle_stage?: PersonLifecycleStage | null;
136
+
137
+ @IsOptional()
138
+ @IsDateString({}, { message: (args) => getLocaleText('validation.dateMustBeString', args.value) })
139
+ next_action_at?: string | null;
140
+
141
+ @IsOptional()
142
+ @IsNumber({}, { message: (args) => getLocaleText('validation.numberMustBeNumber', args.value) })
143
+ score?: number | null;
144
+
145
+ @IsOptional()
146
+ @IsNumber({}, { message: (args) => getLocaleText('validation.numberMustBeNumber', args.value) })
147
+ deal_value?: number | null;
148
+
149
+ @IsOptional()
150
+ @IsArray({ message: (args) => getLocaleText('validation.tagsMustBeArray', args.value) })
151
+ @IsString({ each: true, message: (args) => getLocaleText('validation.stringRequired', args.value) })
152
+ tags?: string[] | null;
153
+
124
154
  @IsOptional()
125
155
  @IsInt({ message: (args) => getLocaleText('validation.idMustBeInteger', args.value) })
126
156
  headquarter_id?: number | null;
@@ -16,7 +16,12 @@ import {
16
16
  forwardRef
17
17
  } from '@nestjs/common';
18
18
  import { Response } from 'express';
19
+ import { CreateFollowupDTO } from './dto/create-followup.dto';
20
+ import { CreateInteractionDTO } from './dto/create-interaction.dto';
19
21
  import { CreateDTO } from './dto/create.dto';
22
+ import { CheckPersonDuplicatesQueryDTO } from './dto/duplicates-query.dto';
23
+ import { MergePersonDTO } from './dto/merge.dto';
24
+ import { UpdateLifecycleStageDTO } from './dto/update-lifecycle-stage.dto';
20
25
  import { UpdateAllPersonDTO } from './dto/update.dto';
21
26
  import { PersonService } from './person.service';
22
27
 
@@ -38,9 +43,17 @@ export class PersonController {
38
43
  return this.personService.getOwnerOptions(Number(user?.id || 0));
39
44
  }
40
45
 
46
+ @Get('duplicates')
47
+ async checkDuplicates(@Query() query: CheckPersonDuplicatesQueryDTO) {
48
+ return this.personService.checkDuplicates(query);
49
+ }
50
+
41
51
  @Get()
42
- async list(@Pagination() paginationParams, @Query() filters) {
43
- return this.personService.list({ ...paginationParams, ...filters });
52
+ async list(@Pagination() paginationParams, @Query() filters, @User() user) {
53
+ return this.personService.list(
54
+ { ...paginationParams, ...filters },
55
+ Number(user?.id || 0),
56
+ );
44
57
  }
45
58
 
46
59
  @Public()
@@ -63,6 +76,54 @@ export class PersonController {
63
76
  return this.personService.create(data, locale);
64
77
  }
65
78
 
79
+ @Post('merge')
80
+ async merge(@Body() data: MergePersonDTO, @Locale() locale: string) {
81
+ return this.personService.merge(data, locale);
82
+ }
83
+
84
+ @Get(':id/interaction')
85
+ async listInteractions(
86
+ @Param('id', ParseIntPipe) id: number,
87
+ @Locale() locale: string,
88
+ ) {
89
+ return this.personService.listInteractions(id, locale);
90
+ }
91
+
92
+ @Post(':id/interaction')
93
+ async createInteraction(
94
+ @Param('id', ParseIntPipe) id: number,
95
+ @Body() data: CreateInteractionDTO,
96
+ @Locale() locale: string,
97
+ @User() user,
98
+ ) {
99
+ return this.personService.createInteraction(id, data, locale, {
100
+ id: Number(user?.id || 0),
101
+ name: user?.name,
102
+ });
103
+ }
104
+
105
+ @Post(':id/followup')
106
+ async createFollowup(
107
+ @Param('id', ParseIntPipe) id: number,
108
+ @Body() data: CreateFollowupDTO,
109
+ @Locale() locale: string,
110
+ @User() user,
111
+ ) {
112
+ return this.personService.scheduleFollowup(id, data, locale, {
113
+ id: Number(user?.id || 0),
114
+ name: user?.name,
115
+ });
116
+ }
117
+
118
+ @Post(':id/lifecycle-stage')
119
+ async updateLifecycleStage(
120
+ @Param('id', ParseIntPipe) id: number,
121
+ @Body() data: UpdateLifecycleStageDTO,
122
+ @Locale() locale: string,
123
+ ) {
124
+ return this.personService.updateLifecycleStage(id, data, locale);
125
+ }
126
+
66
127
  @Patch(':id')
67
128
  async update(
68
129
  @Param('id', ParseIntPipe) id: number,