@hed-hog/contact 0.0.301 → 0.0.303

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 (36) hide show
  1. package/dist/person/person.service.d.ts +2 -0
  2. package/dist/person/person.service.d.ts.map +1 -1
  3. package/dist/person/person.service.js +111 -127
  4. package/dist/person/person.service.js.map +1 -1
  5. package/dist/person/person.service.spec.d.ts +2 -0
  6. package/dist/person/person.service.spec.d.ts.map +1 -0
  7. package/dist/person/person.service.spec.js +106 -0
  8. package/dist/person/person.service.spec.js.map +1 -0
  9. package/dist/proposal/proposal.service.d.ts +5 -0
  10. package/dist/proposal/proposal.service.d.ts.map +1 -1
  11. package/dist/proposal/proposal.service.js +242 -19
  12. package/dist/proposal/proposal.service.js.map +1 -1
  13. package/dist/proposal/proposal.service.spec.js +153 -165
  14. package/dist/proposal/proposal.service.spec.js.map +1 -1
  15. package/hedhog/data/menu.yaml +35 -18
  16. package/hedhog/frontend/app/accounts/_components/account-form-sheet.tsx.ejs +517 -346
  17. package/hedhog/frontend/app/activities/_components/activity-detail-sheet.tsx.ejs +42 -17
  18. package/hedhog/frontend/app/activities/_components/activity-types.ts.ejs +1 -1
  19. package/hedhog/frontend/app/activities/page.tsx.ejs +315 -101
  20. package/hedhog/frontend/app/follow-ups/page.tsx.ejs +172 -22
  21. package/hedhog/frontend/app/page.tsx.ejs +1 -1
  22. package/hedhog/frontend/app/person/_components/person-form-sheet.tsx.ejs +1 -1
  23. package/hedhog/frontend/app/pipeline/_components/lead-detail-sheet.tsx.ejs +1 -1
  24. package/hedhog/frontend/app/pipeline/_components/lead-proposals-tab.tsx.ejs +509 -441
  25. package/hedhog/frontend/app/pipeline/page.tsx.ejs +30 -4
  26. package/hedhog/frontend/app/proposals/_components/proposals-management-page.tsx.ejs +773 -0
  27. package/hedhog/frontend/app/proposals/approvals/page.tsx.ejs +5 -0
  28. package/hedhog/frontend/app/proposals/page.tsx.ejs +5 -0
  29. package/hedhog/frontend/app/reports/page.tsx.ejs +431 -375
  30. package/hedhog/frontend/messages/en.json +100 -1
  31. package/hedhog/frontend/messages/pt.json +100 -1
  32. package/package.json +6 -6
  33. package/src/person/person.service.spec.ts +143 -0
  34. package/src/person/person.service.ts +147 -158
  35. package/src/proposal/proposal.service.spec.ts +196 -0
  36. package/src/proposal/proposal.service.ts +348 -18
@@ -688,6 +688,52 @@
688
688
  "invalidRange": "The start date must be less than or equal to the end date."
689
689
  }
690
690
  },
691
+ "ProposalsPage": {
692
+ "title": "Proposal center",
693
+ "description": "View, approve and track all commercial proposals outside the pipeline.",
694
+ "pendingTitle": "Pending approvals",
695
+ "pendingDescription": "Quickly review the proposals waiting for a decision.",
696
+ "listTitle": "Proposal list",
697
+ "listDescription": "Use the filters to focus on drafts, pending approvals and approved proposals.",
698
+ "searchPlaceholder": "Search by code, title or customer...",
699
+ "viewMode": "View",
700
+ "viewModeTable": "Table",
701
+ "viewModeCards": "Cards",
702
+ "resultsCount": "{count} proposal(s)",
703
+ "visibleTotal": "Visible value in this list: {value}",
704
+ "emptyTitle": "No proposals found",
705
+ "emptyDescription": "Adjust the filters or create a new proposal from the pipeline.",
706
+ "inboxTitle": "Approval inbox",
707
+ "inboxDescription": "{count} proposal(s) are waiting for a decision.",
708
+ "cards": {
709
+ "total": "Total proposals",
710
+ "pending": "Pending approval",
711
+ "approved": "Approved",
712
+ "converted": "Converted"
713
+ },
714
+ "filters": {
715
+ "statusPlaceholder": "Status",
716
+ "all": "All",
717
+ "pending_approval": "Pending",
718
+ "approved": "Approved",
719
+ "draft": "Drafts",
720
+ "rejected": "Rejected"
721
+ },
722
+ "columns": {
723
+ "proposal": "Proposal",
724
+ "customer": "Customer",
725
+ "status": "Status",
726
+ "total": "Amount",
727
+ "validUntil": "Valid until",
728
+ "updatedAt": "Updated at",
729
+ "actions": "Actions"
730
+ },
731
+ "actions": {
732
+ "refresh": "Refresh",
733
+ "viewPending": "View pending",
734
+ "showAll": "Show all"
735
+ }
736
+ },
691
737
  "CrmPipeline": {
692
738
  "title": "Conversion pipeline",
693
739
  "subtitle": "Commercial Kanban with owner filtering to follow registration progress inside the CRM.",
@@ -793,6 +839,35 @@
793
839
  "expired": "Expired",
794
840
  "contract_generated": "Contract generated"
795
841
  },
842
+ "enum": {
843
+ "contractCategory": {
844
+ "employee": "Employee",
845
+ "contractor": "Contractor",
846
+ "client": "Client",
847
+ "supplier": "Supplier",
848
+ "vendor": "Vendor",
849
+ "partner": "Partner",
850
+ "internal": "Internal",
851
+ "other": "Other"
852
+ },
853
+ "contractType": {
854
+ "clt": "CLT",
855
+ "pj": "PJ",
856
+ "freelancer_agreement": "Freelancer agreement",
857
+ "service_agreement": "Service agreement",
858
+ "fixed_term": "Fixed-term contract",
859
+ "recurring_service": "Recurring service",
860
+ "nda": "Non-disclosure agreement",
861
+ "amendment": "Amendment",
862
+ "addendum": "Addendum",
863
+ "other": "Other"
864
+ },
865
+ "billingModel": {
866
+ "time_and_material": "Time and material",
867
+ "monthly_retainer": "Monthly retainer",
868
+ "fixed_price": "Fixed price"
869
+ }
870
+ },
796
871
  "actions": {
797
872
  "save": "Save proposal",
798
873
  "cancel": "Cancel",
@@ -872,6 +947,9 @@
872
947
  "CrmFollowups": {
873
948
  "title": "Follow-ups",
874
949
  "description": "Track upcoming actions, overdue items, and owner commitments in one operational list.",
950
+ "viewMode": "View",
951
+ "viewModeTable": "Table",
952
+ "viewModeCards": "Cards",
875
953
  "newFollowup": "New Follow-up",
876
954
  "reschedule": "Reschedule",
877
955
  "unassigned": "Unassigned",
@@ -934,7 +1012,7 @@
934
1012
  "description": "Operational activity queue with calls, meetings, messages, and tasks tracked by owner and due date.",
935
1013
  "viewMode": "View",
936
1014
  "viewModeTable": "Table",
937
- "viewModeTimeline": "Timeline",
1015
+ "viewModeCards": "Cards",
938
1016
  "unassignedPerson": "Unassigned lead",
939
1017
  "unassignedOwner": "Unassigned",
940
1018
  "stats": {
@@ -1111,6 +1189,27 @@
1111
1189
  "city": "City",
1112
1190
  "state": "State",
1113
1191
  "owner": "Owner",
1192
+ "summary": {
1193
+ "newCompany": "New account in progress",
1194
+ "createHint": "Fill in the essential details for a fast account setup.",
1195
+ "editHint": "Review the core data and update the commercial relationship."
1196
+ },
1197
+ "sections": {
1198
+ "identityTitle": "Identity",
1199
+ "identityDescription": "How this account will appear in the CRM.",
1200
+ "relationshipTitle": "Relationship",
1201
+ "relationshipDescription": "Status, stage, and ownership for the account.",
1202
+ "contactTitle": "Contact",
1203
+ "contactDescription": "Primary channels for outreach and follow-up.",
1204
+ "locationTitle": "Location",
1205
+ "locationDescription": "City and state for quick context.",
1206
+ "additionalTitle": "Additional details",
1207
+ "additionalDescription": "Complementary information and account metrics."
1208
+ },
1209
+ "additional": {
1210
+ "show": "Expand",
1211
+ "hide": "Hide"
1212
+ },
1114
1213
  "createSubmit": "Create Account",
1115
1214
  "updateSubmit": "Save Changes",
1116
1215
  "saving": "Saving...",
@@ -687,6 +687,52 @@
687
687
  "invalidRange": "A data inicial deve ser menor ou igual à data final."
688
688
  }
689
689
  },
690
+ "ProposalsPage": {
691
+ "title": "Central de propostas",
692
+ "description": "Visualize, aprove e acompanhe todas as propostas comerciais fora do pipeline.",
693
+ "pendingTitle": "Aprovações pendentes",
694
+ "pendingDescription": "Revise rapidamente as propostas aguardando decisão.",
695
+ "listTitle": "Lista de propostas",
696
+ "listDescription": "Use os filtros para focar em rascunhos, aprovações pendentes e propostas aprovadas.",
697
+ "searchPlaceholder": "Buscar por código, título ou cliente...",
698
+ "viewMode": "Visualização",
699
+ "viewModeTable": "Tabela",
700
+ "viewModeCards": "Cards",
701
+ "resultsCount": "{count} proposta(s)",
702
+ "visibleTotal": "Valor visível nesta lista: {value}",
703
+ "emptyTitle": "Nenhuma proposta encontrada",
704
+ "emptyDescription": "Ajuste os filtros ou crie uma nova proposta a partir do pipeline.",
705
+ "inboxTitle": "Caixa de aprovação",
706
+ "inboxDescription": "{count} proposta(s) aguardando decisão.",
707
+ "cards": {
708
+ "total": "Total de propostas",
709
+ "pending": "Pendentes de aprovação",
710
+ "approved": "Aprovadas",
711
+ "converted": "Convertidas"
712
+ },
713
+ "filters": {
714
+ "statusPlaceholder": "Status",
715
+ "all": "Todas",
716
+ "pending_approval": "Pendentes",
717
+ "approved": "Aprovadas",
718
+ "draft": "Rascunhos",
719
+ "rejected": "Rejeitadas"
720
+ },
721
+ "columns": {
722
+ "proposal": "Proposta",
723
+ "customer": "Cliente",
724
+ "status": "Status",
725
+ "total": "Valor",
726
+ "validUntil": "Validade",
727
+ "updatedAt": "Atualizada em",
728
+ "actions": "Ações"
729
+ },
730
+ "actions": {
731
+ "refresh": "Atualizar",
732
+ "viewPending": "Ver pendentes",
733
+ "showAll": "Mostrar todas"
734
+ }
735
+ },
690
736
  "CrmPipeline": {
691
737
  "title": "Pipeline de conversão",
692
738
  "subtitle": "Kanban comercial com filtro por owner para acompanhar a evolução dos cadastros no CRM.",
@@ -792,6 +838,35 @@
792
838
  "expired": "Expirada",
793
839
  "contract_generated": "Contrato gerado"
794
840
  },
841
+ "enum": {
842
+ "contractCategory": {
843
+ "employee": "Funcionário",
844
+ "contractor": "Contratado",
845
+ "client": "Cliente",
846
+ "supplier": "Fornecedor",
847
+ "vendor": "Vendedor",
848
+ "partner": "Parceiro",
849
+ "internal": "Interno",
850
+ "other": "Outro"
851
+ },
852
+ "contractType": {
853
+ "clt": "CLT",
854
+ "pj": "PJ",
855
+ "freelancer_agreement": "Contrato de freelancer",
856
+ "service_agreement": "Contrato de prestação de serviços",
857
+ "fixed_term": "Prazo determinado",
858
+ "recurring_service": "Serviço recorrente",
859
+ "nda": "Acordo de confidencialidade",
860
+ "amendment": "Aditivo",
861
+ "addendum": "Anexo",
862
+ "other": "Outro"
863
+ },
864
+ "billingModel": {
865
+ "time_and_material": "Tempo e material",
866
+ "monthly_retainer": "Retainer mensal",
867
+ "fixed_price": "Preço fixo"
868
+ }
869
+ },
795
870
  "actions": {
796
871
  "save": "Salvar proposta",
797
872
  "cancel": "Cancelar",
@@ -871,6 +946,9 @@
871
946
  "CrmFollowups": {
872
947
  "title": "Follow-ups",
873
948
  "description": "Acompanhe próximas ações, vencidos e compromissos por responsável em uma lista operacional.",
949
+ "viewMode": "Visualização",
950
+ "viewModeTable": "Tabela",
951
+ "viewModeCards": "Cards",
874
952
  "newFollowup": "Novo Follow-up",
875
953
  "reschedule": "Reagendar",
876
954
  "unassigned": "Não atribuído",
@@ -933,7 +1011,7 @@
933
1011
  "description": "Fila operacional de atividades com ligações, reuniões, mensagens e tarefas por responsável e prazo.",
934
1012
  "viewMode": "Visualização",
935
1013
  "viewModeTable": "Tabela",
936
- "viewModeTimeline": "Timeline",
1014
+ "viewModeCards": "Cards",
937
1015
  "unassignedPerson": "Lead não atribuído",
938
1016
  "unassignedOwner": "Não atribuído",
939
1017
  "stats": {
@@ -1110,6 +1188,27 @@
1110
1188
  "city": "Cidade",
1111
1189
  "state": "Estado",
1112
1190
  "owner": "Responsavel",
1191
+ "summary": {
1192
+ "newCompany": "Nova conta em preparacao",
1193
+ "createHint": "Preencha os dados essenciais para cadastrar a conta rapidamente.",
1194
+ "editHint": "Revise os dados principais e atualize o relacionamento comercial."
1195
+ },
1196
+ "sections": {
1197
+ "identityTitle": "Identificacao",
1198
+ "identityDescription": "Como a conta sera reconhecida no CRM.",
1199
+ "relationshipTitle": "Relacionamento",
1200
+ "relationshipDescription": "Status, etapa e ownership da conta.",
1201
+ "contactTitle": "Contato",
1202
+ "contactDescription": "Canais principais para abordagem comercial.",
1203
+ "locationTitle": "Localizacao",
1204
+ "locationDescription": "Cidade e estado para contexto rapido.",
1205
+ "additionalTitle": "Detalhes adicionais",
1206
+ "additionalDescription": "Informacoes complementares e metricas da conta."
1207
+ },
1208
+ "additional": {
1209
+ "show": "Expandir",
1210
+ "hide": "Ocultar"
1211
+ },
1113
1212
  "createSubmit": "Criar conta",
1114
1213
  "updateSubmit": "Salvar alteracoes",
1115
1214
  "saving": "Salvando...",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hed-hog/contact",
3
- "version": "0.0.301",
3
+ "version": "0.0.303",
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.301",
13
- "@hed-hog/api-mail": "0.0.9",
14
12
  "@hed-hog/api-prisma": "0.0.6",
15
- "@hed-hog/address": "0.0.301",
13
+ "@hed-hog/core": "0.0.303",
14
+ "@hed-hog/api-locale": "0.0.14",
15
+ "@hed-hog/api-mail": "0.0.9",
16
+ "@hed-hog/address": "0.0.303",
16
17
  "@hed-hog/api": "0.0.6",
17
- "@hed-hog/api-pagination": "0.0.7",
18
- "@hed-hog/api-locale": "0.0.14"
18
+ "@hed-hog/api-pagination": "0.0.7"
19
19
  },
20
20
  "exports": {
21
21
  ".": {
@@ -0,0 +1,143 @@
1
+ /// <reference types="jest" />
2
+ import { PersonInteractionTypeDTO } from './dto/create-interaction.dto';
3
+ import { PersonService } from './person.service';
4
+
5
+ describe('PersonService CRM interaction/follow-up regression coverage', () => {
6
+ let prisma: any;
7
+ let tx: any;
8
+ let service: PersonService;
9
+
10
+ beforeEach(() => {
11
+ tx = {
12
+ crm_activity: {
13
+ findFirst: jest.fn().mockResolvedValue(null),
14
+ create: jest.fn().mockResolvedValue({ id: 1 }),
15
+ update: jest.fn().mockResolvedValue({ id: 1 }),
16
+ },
17
+ };
18
+
19
+ prisma = {
20
+ $transaction: jest.fn().mockImplementation(async (callback: any) =>
21
+ callback(tx)
22
+ ),
23
+ };
24
+
25
+ service = new PersonService(
26
+ prisma as any,
27
+ {} as any,
28
+ {} as any,
29
+ {
30
+ getSettingValues: jest.fn().mockResolvedValue({}),
31
+ } as any
32
+ );
33
+
34
+ jest
35
+ .spyOn(service as any, 'ensurePersonAccessible')
36
+ .mockResolvedValue({ id: 42, type: 'individual', status: 'active' });
37
+ jest.spyOn(service as any, 'getPersonOwnerUserId').mockResolvedValue(7);
38
+ jest.spyOn(service as any, 'loadInteractionsFromTx').mockResolvedValue([]);
39
+ jest.spyOn(service as any, 'upsertMetadataValue').mockResolvedValue(undefined);
40
+ });
41
+
42
+ it('persists a completed crm activity when registering an interaction', async () => {
43
+ await service.createInteraction(
44
+ 42,
45
+ {
46
+ type: PersonInteractionTypeDTO.CALL,
47
+ notes: 'Ligação inicial',
48
+ },
49
+ 'en',
50
+ { id: 9, name: 'Root User' }
51
+ );
52
+
53
+ expect(tx.crm_activity.create).toHaveBeenCalledWith(
54
+ expect.objectContaining({
55
+ data: expect.objectContaining({
56
+ person_id: 42,
57
+ owner_user_id: 7,
58
+ created_by_user_id: 9,
59
+ completed_by_user_id: 9,
60
+ type: PersonInteractionTypeDTO.CALL,
61
+ subject: 'Call',
62
+ notes: 'Ligação inicial',
63
+ priority: 'medium',
64
+ source_kind: 'interaction',
65
+ due_at: expect.any(Date),
66
+ completed_at: expect.any(Date),
67
+ created_at: expect.any(Date),
68
+ updated_at: expect.any(Date),
69
+ }),
70
+ })
71
+ );
72
+ });
73
+
74
+ it('creates an open follow-up activity when none exists', async () => {
75
+ tx.crm_activity.findFirst.mockResolvedValue(null);
76
+
77
+ await service.scheduleFollowup(
78
+ 42,
79
+ {
80
+ next_action_at: '2026-04-09T13:48:31.715Z',
81
+ notes: 'Retornar por telefone',
82
+ },
83
+ 'en',
84
+ { id: 9, name: 'Root User' }
85
+ );
86
+
87
+ expect(tx.crm_activity.findFirst).toHaveBeenCalledWith({
88
+ where: {
89
+ person_id: 42,
90
+ source_kind: 'followup',
91
+ completed_at: null,
92
+ },
93
+ orderBy: {
94
+ id: 'desc',
95
+ },
96
+ });
97
+
98
+ expect(tx.crm_activity.create).toHaveBeenCalledWith(
99
+ expect.objectContaining({
100
+ data: expect.objectContaining({
101
+ person_id: 42,
102
+ owner_user_id: 7,
103
+ created_by_user_id: 9,
104
+ type: 'task',
105
+ subject: 'Follow-up',
106
+ notes: 'Retornar por telefone',
107
+ priority: 'medium',
108
+ source_kind: 'followup',
109
+ due_at: new Date('2026-04-09T13:48:31.715Z'),
110
+ }),
111
+ })
112
+ );
113
+ });
114
+
115
+ it('updates the existing open follow-up activity when rescheduling', async () => {
116
+ tx.crm_activity.findFirst.mockResolvedValue({ id: 88 });
117
+
118
+ await service.scheduleFollowup(
119
+ 42,
120
+ {
121
+ next_action_at: '2026-04-10T15:00:00.000Z',
122
+ notes: 'Reagendar reunião',
123
+ },
124
+ 'en',
125
+ { id: 11, name: 'Owner User' }
126
+ );
127
+
128
+ expect(tx.crm_activity.update).toHaveBeenCalledWith({
129
+ where: {
130
+ id: 88,
131
+ },
132
+ data: expect.objectContaining({
133
+ owner_user_id: 7,
134
+ type: 'task',
135
+ subject: 'Follow-up',
136
+ notes: 'Reagendar reunião',
137
+ due_at: new Date('2026-04-10T15:00:00.000Z'),
138
+ priority: 'medium',
139
+ updated_at: expect.any(Date),
140
+ }),
141
+ });
142
+ });
143
+ });