@hed-hog/finance 0.0.252 → 0.0.256

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 (32) hide show
  1. package/dist/dto/reverse-settlement.dto.d.ts +1 -0
  2. package/dist/dto/reverse-settlement.dto.d.ts.map +1 -1
  3. package/dist/dto/reverse-settlement.dto.js +5 -0
  4. package/dist/dto/reverse-settlement.dto.js.map +1 -1
  5. package/dist/finance-installments.controller.d.ts +106 -4
  6. package/dist/finance-installments.controller.d.ts.map +1 -1
  7. package/dist/finance-installments.controller.js +38 -2
  8. package/dist/finance-installments.controller.js.map +1 -1
  9. package/dist/finance.service.d.ts +104 -2
  10. package/dist/finance.service.d.ts.map +1 -1
  11. package/dist/finance.service.js +366 -121
  12. package/dist/finance.service.js.map +1 -1
  13. package/hedhog/data/route.yaml +27 -0
  14. package/hedhog/frontend/app/_components/finance-entity-field-with-create.tsx.ejs +572 -0
  15. package/hedhog/frontend/app/_components/finance-title-actions-menu.tsx.ejs +244 -0
  16. package/hedhog/frontend/app/_components/person-field-with-create.tsx.ejs +143 -51
  17. package/hedhog/frontend/app/_lib/title-action-rules.ts.ejs +36 -0
  18. package/hedhog/frontend/app/accounts-payable/installments/[id]/page.tsx.ejs +449 -293
  19. package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +1189 -545
  20. package/hedhog/frontend/app/accounts-receivable/installments/[id]/page.tsx.ejs +176 -133
  21. package/hedhog/frontend/app/accounts-receivable/installments/page.tsx.ejs +1459 -312
  22. package/hedhog/frontend/app/page.tsx.ejs +15 -4
  23. package/hedhog/frontend/messages/en.json +294 -5
  24. package/hedhog/frontend/messages/pt.json +294 -5
  25. package/hedhog/query/settlement-auditability.sql +175 -0
  26. package/hedhog/table/bank_reconciliation.yaml +11 -0
  27. package/hedhog/table/settlement.yaml +17 -1
  28. package/hedhog/table/settlement_allocation.yaml +3 -0
  29. package/package.json +7 -7
  30. package/src/dto/reverse-settlement.dto.ts +4 -0
  31. package/src/finance-installments.controller.ts +45 -12
  32. package/src/finance.service.ts +521 -146
@@ -99,11 +99,114 @@
99
99
  "description": "Não há aprovações pendentes."
100
100
  }
101
101
  },
102
+ "PersonFieldWithCreate": {
103
+ "sheet": {
104
+ "title": "Novo {entityLabel}",
105
+ "description": "Apenas nome e tipo são obrigatórios. Você pode completar os demais dados agora ou depois."
106
+ },
107
+ "common": {
108
+ "select": "Selecione"
109
+ },
110
+ "types": {
111
+ "individual": "Pessoa Física",
112
+ "company": "Pessoa Jurídica"
113
+ },
114
+ "fields": {
115
+ "name": "Nome",
116
+ "type": "Tipo",
117
+ "documentIndividualOptional": "CPF (opcional)",
118
+ "documentCompanyOptional": "CNPJ (opcional)",
119
+ "emailOptional": "E-mail (opcional)",
120
+ "phoneOptional": "Telefone (opcional)",
121
+ "addressOptional": "Endereço (opcional)"
122
+ },
123
+ "placeholders": {
124
+ "name": "Nome do {entityLabel}",
125
+ "email": "{entityLabel}@empresa.com",
126
+ "phone": "(11) 99999-9999",
127
+ "addressLine1": "Rua, número, complemento",
128
+ "city": "Cidade",
129
+ "state": "UF"
130
+ },
131
+ "search": {
132
+ "placeholder": "Digite para buscar pessoa...",
133
+ "loading": "Buscando pessoas...",
134
+ "noResults": "Nenhuma pessoa encontrada"
135
+ },
136
+ "actions": {
137
+ "cancel": "Cancelar",
138
+ "saveEntity": "Salvar {entityLabel}",
139
+ "createNew": "Criar novo cadastro",
140
+ "clearSelection": "Limpar seleção",
141
+ "createEntityAria": "Criar novo {entityLabel}"
142
+ },
143
+ "messages": {
144
+ "createdSuccess": "{entityLabel} criado com sucesso",
145
+ "createdError": "Não foi possível criar {entityLabel}"
146
+ }
147
+ },
148
+ "FinanceEntityFieldWithCreate": {
149
+ "validation": {
150
+ "nameRequired": "Nome é obrigatório"
151
+ },
152
+ "fields": {
153
+ "name": "Nome"
154
+ },
155
+ "actions": {
156
+ "cancel": "Cancelar",
157
+ "save": "Salvar",
158
+ "createCategoryAria": "Criar nova categoria",
159
+ "createCostCenterAria": "Criar novo centro de custo"
160
+ },
161
+ "categorySheet": {
162
+ "title": "Nova categoria",
163
+ "description": "Cadastre uma nova categoria para usar neste lançamento.",
164
+ "namePlaceholder": "Nome da categoria"
165
+ },
166
+ "costCenterSheet": {
167
+ "title": "Novo centro de custo",
168
+ "description": "Cadastre um novo centro de custo para usar neste lançamento.",
169
+ "namePlaceholder": "Nome do centro de custo"
170
+ },
171
+ "messages": {
172
+ "categoryCreateSuccess": "Categoria criada com sucesso",
173
+ "categoryCreateError": "Não foi possível criar categoria",
174
+ "costCenterCreateSuccess": "Centro de custo criado com sucesso",
175
+ "costCenterCreateError": "Não foi possível criar centro de custo"
176
+ }
177
+ },
102
178
  "PayableInstallmentsPage": {
103
179
  "common": {
104
180
  "select": "Selecione...",
105
181
  "cancel": "Cancelar",
106
- "save": "Salvar"
182
+ "save": "Salvar",
183
+ "upload": {
184
+ "label": "Arquivo da fatura (opcional)",
185
+ "upload": "Upload",
186
+ "change": "Trocar arquivo",
187
+ "remove": "Remover",
188
+ "selectedPrefix": "Arquivo:",
189
+ "uploadingProgress": "Upload em andamento: {progress}%",
190
+ "processingAi": "Analisando documento com IA",
191
+ "lowConfidence": "Confiança da extração: {confidence}%",
192
+ "fillingWithAi": "Preenchendo com IA...",
193
+ "uploadingFile": "Enviando arquivo..."
194
+ }
195
+ },
196
+ "validation": {
197
+ "documentRequired": "Documento é obrigatório",
198
+ "supplierRequired": "Fornecedor é obrigatório",
199
+ "dueDateRequired": "Vencimento é obrigatório",
200
+ "amountGreaterThanZero": "Valor deve ser maior que zero",
201
+ "invalidInstallmentCount": "Quantidade de parcelas inválida",
202
+ "installmentsMin": "Mínimo de 1 parcela",
203
+ "installmentsMax": "Máximo de 120 parcelas",
204
+ "installmentDueDateRequired": "Vencimento da parcela é obrigatório",
205
+ "installmentAmountGreaterThanZero": "Valor da parcela deve ser maior que zero",
206
+ "installmentsRequired": "Informe ao menos uma parcela",
207
+ "installmentsCountMismatch": "Quantidade de parcelas não confere com o detalhamento",
208
+ "installmentsSumMismatch": "A soma das parcelas deve ser igual ao valor total",
209
+ "installmentRequired": "Parcela obrigatória"
107
210
  },
108
211
  "newTitle": {
109
212
  "action": "Novo Título",
@@ -169,9 +272,75 @@
169
272
  "approve": "Aprovar",
170
273
  "settle": "Baixar",
171
274
  "reverse": "Estornar",
172
- "cancel": "Cancelar"
275
+ "cancel": "Cancelar",
276
+ "openAttachment": "Abrir anexo"
277
+ }
278
+ },
279
+ "dialogs": {
280
+ "cancel": {
281
+ "title": "Confirmar cancelamento",
282
+ "description": "Esta ação cancela o título e não pode ser desfeita automaticamente.",
283
+ "cancel": "Voltar",
284
+ "confirm": "Confirmar cancelamento"
285
+ },
286
+ "reverse": {
287
+ "title": "Confirmar estorno",
288
+ "description": "Informe o motivo do estorno. O sistema recalculará os saldos e status automaticamente.",
289
+ "reasonLabel": "Motivo do estorno",
290
+ "reasonPlaceholder": "Ex.: baixa duplicada",
291
+ "cancel": "Voltar",
292
+ "confirm": "Confirmar estorno"
173
293
  }
174
294
  },
295
+ "settleSheet": {
296
+ "title": "Registrar baixa",
297
+ "description": "Informe a parcela e o valor da baixa para o título {document}.",
298
+ "installmentLabel": "Parcela",
299
+ "installmentPlaceholder": "Selecione",
300
+ "installmentOption": "Parcela {number} - em aberto: {amount}",
301
+ "amountLabel": "Valor",
302
+ "descriptionLabel": "Descrição (opcional)",
303
+ "confirm": "Confirmar baixa"
304
+ },
305
+ "installmentsEditor": {
306
+ "title": "Parcelas",
307
+ "countLabel": "Quantidade de Parcelas",
308
+ "recalculate": "Recalcular automaticamente",
309
+ "autoRedistributeLabel": "Redistribuir automaticamente o restante ao editar parcela",
310
+ "autoRedistributeHint": "A redistribuição ocorre ao parar de digitar e ao sair do campo.",
311
+ "dueDateLabel": "Vencimento",
312
+ "amountLabel": "Valor",
313
+ "totalPrefix": "Soma das parcelas: {total}",
314
+ "adjustmentNeeded": "(ajuste necessário)"
315
+ },
316
+ "editTitle": {
317
+ "description": "Edite os dados do título enquanto estiver em rascunho."
318
+ },
319
+ "messages": {
320
+ "createSuccess": "Título criado com sucesso",
321
+ "createError": "Erro ao criar título",
322
+ "invalidFile": "Arquivo inválido",
323
+ "attachSuccess": "Arquivo relacionado com sucesso",
324
+ "aiExtractSuccess": "Dados da fatura extraídos e preenchidos automaticamente",
325
+ "aiExtractError": "Não foi possível extrair os dados automaticamente",
326
+ "uploadError": "Não foi possível enviar o arquivo",
327
+ "invalidTitleForEdit": "Título inválido para edição",
328
+ "updateSuccess": "Título atualizado com sucesso",
329
+ "updateError": "Não foi possível atualizar o título",
330
+ "editDraftOnly": "Apenas títulos em rascunho podem ser editados",
331
+ "cancelSuccess": "Título cancelado com sucesso",
332
+ "cancelError": "Não foi possível cancelar o título",
333
+ "approveSuccess": "Título aprovado com sucesso",
334
+ "approveError": "Não foi possível aprovar o título",
335
+ "noInstallmentForSettle": "Nenhuma parcela disponível para baixa",
336
+ "settleSuccess": "Baixa registrada com sucesso",
337
+ "settleError": "Não foi possível registrar a baixa",
338
+ "noActiveSettlementToReverse": "Nenhuma liquidação ativa para estornar",
339
+ "reverseDefaultReason": "Estorno realizado pela listagem de títulos",
340
+ "reverseSuccess": "Estorno realizado com sucesso",
341
+ "reverseError": "Não foi possível estornar a liquidação",
342
+ "openAttachmentError": "Não foi possível abrir o anexo"
343
+ },
175
344
  "footer": {
176
345
  "showing": "Mostrando {filtered} de {total} registros",
177
346
  "previous": "Anterior",
@@ -212,6 +381,8 @@
212
381
  "reverse": {
213
382
  "title": "Confirmar estorno",
214
383
  "description": "Esta ação estorna a liquidação selecionada, recalcula os saldos das parcelas e atualiza o status do título.",
384
+ "reasonLabel": "Motivo do estorno",
385
+ "reasonPlaceholder": "Ex.: baixa duplicada",
215
386
  "cancel": "Cancelar",
216
387
  "confirm": "Confirmar estorno"
217
388
  }
@@ -234,7 +405,9 @@
234
405
  "reverseSuccess": "Estorno realizado com sucesso",
235
406
  "reverseError": "Não foi possível estornar a liquidação",
236
407
  "cancelSuccess": "Título cancelado com sucesso",
237
- "cancelError": "Não foi possível cancelar o título"
408
+ "cancelError": "Não foi possível cancelar o título",
409
+ "unreconcileSuccess": "Conciliação removida com sucesso",
410
+ "unreconcileError": "Não foi possível desconciliar a liquidação"
238
411
  },
239
412
  "documentData": {
240
413
  "title": "Dados do Documento",
@@ -300,6 +473,32 @@
300
473
  "reverseDialogConfirm": "Confirmar estorno",
301
474
  "none": "Nenhuma liquidação registrada"
302
475
  },
476
+ "settlementsHistory": {
477
+ "loading": "Carregando histórico de liquidações...",
478
+ "allocationsTitle": "Rateios",
479
+ "allocationInstallment": "Parcela",
480
+ "headers": {
481
+ "date": "Data",
482
+ "type": "Tipo",
483
+ "status": "Status",
484
+ "netAmount": "Valor Líquido",
485
+ "method": "Método",
486
+ "account": "Conta",
487
+ "reconciled": "Conciliação",
488
+ "actions": "Ações"
489
+ },
490
+ "normalType": "NORMAL",
491
+ "reversalType": "REVERSAL",
492
+ "reversedStatus": "ESTORNO",
493
+ "reconciled": {
494
+ "yes": "Conciliado",
495
+ "no": "Pendente"
496
+ },
497
+ "unreconcile": "Desconciliar",
498
+ "unreconcileLoading": "Desconciliando...",
499
+ "unreconcileFirst": "Desconciliar primeiro",
500
+ "reverse": "Estornar"
501
+ },
303
502
  "audit": { "none": "Nenhum evento de auditoria" }
304
503
  },
305
504
  "CollectionsDefaultPage": {
@@ -376,7 +575,33 @@
376
575
  "common": {
377
576
  "select": "Selecione...",
378
577
  "cancel": "Cancelar",
379
- "save": "Salvar"
578
+ "save": "Salvar",
579
+ "upload": {
580
+ "label": "Arquivo da fatura (opcional)",
581
+ "upload": "Upload",
582
+ "change": "Trocar arquivo",
583
+ "remove": "Remover",
584
+ "selectedPrefix": "Arquivo:",
585
+ "uploadingProgress": "Upload em andamento: {progress}%",
586
+ "processingAi": "Analisando documento com IA",
587
+ "lowConfidence": "Confiança da extração: {confidence}%",
588
+ "fillingWithAi": "Preenchendo com IA...",
589
+ "uploadingFile": "Enviando arquivo..."
590
+ }
591
+ },
592
+ "validation": {
593
+ "documentRequired": "Documento é obrigatório",
594
+ "clientRequired": "Cliente é obrigatório",
595
+ "dueDateRequired": "Vencimento é obrigatório",
596
+ "amountGreaterThanZero": "Valor deve ser maior que zero",
597
+ "invalidInstallmentCount": "Quantidade de parcelas inválida",
598
+ "installmentsMin": "Mínimo de 1 parcela",
599
+ "installmentsMax": "Máximo de 120 parcelas",
600
+ "installmentDueDateRequired": "Vencimento da parcela é obrigatório",
601
+ "installmentAmountGreaterThanZero": "Valor da parcela deve ser maior que zero",
602
+ "installmentsRequired": "Informe ao menos uma parcela",
603
+ "installmentsCountMismatch": "Quantidade de parcelas não confere com o detalhamento",
604
+ "installmentsSumMismatch": "A soma das parcelas deve ser igual ao valor total"
380
605
  },
381
606
  "newTitle": {
382
607
  "action": "Novo Título",
@@ -436,9 +661,37 @@
436
661
  "viewDetails": "Ver Detalhes",
437
662
  "edit": "Editar",
438
663
  "registerReceipt": "Registrar Recebimento",
439
- "sendCollection": "Enviar Cobrança"
664
+ "sendCollection": "Enviar Cobrança",
665
+ "openAttachment": "Abrir anexo"
440
666
  }
441
667
  },
668
+ "installmentsEditor": {
669
+ "title": "Parcelas",
670
+ "countLabel": "Quantidade de Parcelas",
671
+ "recalculate": "Recalcular automaticamente",
672
+ "autoRedistributeLabel": "Redistribuir automaticamente o restante ao editar parcela",
673
+ "dueDateLabel": "Vencimento",
674
+ "amountLabel": "Valor",
675
+ "totalPrefix": "Soma das parcelas: {total}",
676
+ "adjustmentNeeded": "(ajuste necessário)"
677
+ },
678
+ "editTitle": {
679
+ "description": "Edite os dados do título enquanto estiver em rascunho."
680
+ },
681
+ "messages": {
682
+ "createSuccess": "Título criado com sucesso",
683
+ "createError": "Erro ao criar título",
684
+ "invalidFile": "Arquivo inválido",
685
+ "attachSuccess": "Arquivo relacionado com sucesso",
686
+ "aiExtractSuccess": "Dados da fatura extraídos e preenchidos automaticamente",
687
+ "aiExtractError": "Não foi possível extrair os dados automaticamente",
688
+ "uploadError": "Não foi possível enviar o arquivo",
689
+ "invalidTitleForEdit": "Título inválido para edição",
690
+ "updateSuccess": "Título atualizado com sucesso",
691
+ "updateError": "Não foi possível atualizar o título",
692
+ "editDraftOnly": "Apenas títulos em rascunho podem ser editados",
693
+ "openAttachmentError": "Não foi possível abrir o anexo"
694
+ },
442
695
  "footer": {
443
696
  "showing": "Mostrando {filtered} de {total} registros",
444
697
  "previous": "Anterior",
@@ -465,9 +718,43 @@
465
718
  "actions": {
466
719
  "title": "Ações",
467
720
  "edit": "Editar",
721
+ "approve": "Aprovar",
468
722
  "registerReceipt": "Registrar Recebimento",
469
723
  "sendCollection": "Enviar Cobrança"
470
724
  },
725
+ "common": {
726
+ "cancel": "Cancelar"
727
+ },
728
+ "validation": {
729
+ "installmentRequired": "Parcela obrigatória",
730
+ "amountGreaterThanZero": "Valor deve ser maior que zero"
731
+ },
732
+ "settleSheet": {
733
+ "title": "Registrar recebimento",
734
+ "description": "Informe a parcela e o valor para baixa parcial ou total.",
735
+ "installmentLabel": "Parcela",
736
+ "installmentPlaceholder": "Selecione",
737
+ "installmentOption": "Parcela {number} - em aberto: {amount}",
738
+ "amountLabel": "Valor",
739
+ "descriptionLabel": "Descrição (opcional)",
740
+ "confirm": "Confirmar recebimento"
741
+ },
742
+ "reverseSheet": {
743
+ "title": "Confirmar estorno",
744
+ "description": "Esta ação estorna o recebimento e recalcula saldos e status.",
745
+ "reasonLabel": "Motivo do estorno",
746
+ "reasonPlaceholder": "Ex.: baixa duplicada",
747
+ "confirm": "Confirmar estorno"
748
+ },
749
+ "messages": {
750
+ "approveSuccess": "Título aprovado com sucesso",
751
+ "approveError": "Não foi possível aprovar o título",
752
+ "settleSuccess": "Recebimento registrado com sucesso",
753
+ "settleError": "Não foi possível registrar o recebimento",
754
+ "reverseSuccess": "Estorno realizado com sucesso",
755
+ "reverseError": "Não foi possível estornar o recebimento",
756
+ "reverseDefaultReason": "Estorno realizado no detalhe do título"
757
+ },
471
758
  "documentData": {
472
759
  "title": "Dados do Documento",
473
760
  "client": "Cliente",
@@ -521,6 +808,8 @@
521
808
  "discount": "Desconto",
522
809
  "account": "Conta",
523
810
  "method": "Método",
811
+ "actions": "Ações",
812
+ "reverseButton": "Estornar",
524
813
  "none": "Nenhum recebimento registrado"
525
814
  },
526
815
  "audit": { "none": "Nenhum evento de auditoria" }
@@ -0,0 +1,175 @@
1
+ -- Settlement auditability, reversal linkage and reconciliation helpers
2
+ -- Idempotent script for PostgreSQL
3
+
4
+ DO $$
5
+ BEGIN
6
+ IF NOT EXISTS (
7
+ SELECT 1
8
+ FROM pg_type
9
+ WHERE typname = 'settlement_entry_type_enum'
10
+ ) THEN
11
+ CREATE TYPE settlement_entry_type_enum AS ENUM ('normal', 'reversal');
12
+ END IF;
13
+ END $$;
14
+
15
+ ALTER TABLE settlement
16
+ ADD COLUMN IF NOT EXISTS entry_type settlement_entry_type_enum;
17
+
18
+ UPDATE settlement
19
+ SET entry_type = 'normal'
20
+ WHERE entry_type IS NULL;
21
+
22
+ ALTER TABLE settlement
23
+ ALTER COLUMN entry_type SET DEFAULT 'normal';
24
+
25
+ ALTER TABLE settlement
26
+ ALTER COLUMN entry_type SET NOT NULL;
27
+
28
+ ALTER TABLE settlement
29
+ ADD COLUMN IF NOT EXISTS reverses_settlement_id INTEGER;
30
+
31
+ DO $$
32
+ BEGIN
33
+ IF NOT EXISTS (
34
+ SELECT 1
35
+ FROM pg_constraint
36
+ WHERE conname = 'fk_settlement_reverses_settlement'
37
+ ) THEN
38
+ ALTER TABLE settlement
39
+ ADD CONSTRAINT fk_settlement_reverses_settlement
40
+ FOREIGN KEY (reverses_settlement_id)
41
+ REFERENCES settlement(id)
42
+ ON DELETE RESTRICT
43
+ ON UPDATE CASCADE;
44
+ END IF;
45
+ END $$;
46
+
47
+ CREATE UNIQUE INDEX IF NOT EXISTS uq_settlement_reverses_settlement_id
48
+ ON settlement(reverses_settlement_id)
49
+ WHERE reverses_settlement_id IS NOT NULL;
50
+
51
+ DO $$
52
+ BEGIN
53
+ BEGIN
54
+ ALTER TABLE settlement
55
+ ALTER COLUMN amount_cents TYPE BIGINT USING amount_cents::BIGINT;
56
+ EXCEPTION
57
+ WHEN undefined_column THEN
58
+ NULL;
59
+ END;
60
+ END $$;
61
+
62
+ ALTER TABLE settlement_allocation
63
+ ADD COLUMN IF NOT EXISTS amount_cents BIGINT;
64
+
65
+ UPDATE settlement_allocation
66
+ SET amount_cents = allocated_amount_cents
67
+ WHERE amount_cents IS NULL;
68
+
69
+ ALTER TABLE settlement_allocation
70
+ ALTER COLUMN amount_cents SET NOT NULL;
71
+
72
+ DO $$
73
+ BEGIN
74
+ BEGIN
75
+ ALTER TABLE settlement_allocation
76
+ ALTER COLUMN allocated_amount_cents TYPE BIGINT USING allocated_amount_cents::BIGINT;
77
+ EXCEPTION
78
+ WHEN undefined_column THEN
79
+ NULL;
80
+ END;
81
+ END $$;
82
+
83
+ DO $$
84
+ BEGIN
85
+ IF NOT EXISTS (
86
+ SELECT 1
87
+ FROM pg_constraint
88
+ WHERE conname = 'chk_settlement_allocation_amount_non_zero'
89
+ ) THEN
90
+ ALTER TABLE settlement_allocation
91
+ ADD CONSTRAINT chk_settlement_allocation_amount_non_zero
92
+ CHECK (amount_cents <> 0);
93
+ END IF;
94
+ END $$;
95
+
96
+ ALTER TABLE bank_reconciliation
97
+ ADD COLUMN IF NOT EXISTS reconciled_at TIMESTAMPTZ(6);
98
+
99
+ ALTER TABLE bank_reconciliation
100
+ ADD COLUMN IF NOT EXISTS reconciled_by_user_id INTEGER;
101
+
102
+ DO $$
103
+ BEGIN
104
+ IF NOT EXISTS (
105
+ SELECT 1
106
+ FROM pg_constraint
107
+ WHERE conname = 'fk_bank_reconciliation_reconciled_by_user'
108
+ ) THEN
109
+ ALTER TABLE bank_reconciliation
110
+ ADD CONSTRAINT fk_bank_reconciliation_reconciled_by_user
111
+ FOREIGN KEY (reconciled_by_user_id)
112
+ REFERENCES "user"(id)
113
+ ON DELETE SET NULL
114
+ ON UPDATE CASCADE;
115
+ END IF;
116
+ END $$;
117
+
118
+ CREATE INDEX IF NOT EXISTS idx_settlement_settled_at
119
+ ON settlement(settled_at);
120
+
121
+ CREATE INDEX IF NOT EXISTS idx_settlement_entry_type
122
+ ON settlement(entry_type);
123
+
124
+ CREATE OR REPLACE FUNCTION fn_prevent_settlement_updates()
125
+ RETURNS TRIGGER
126
+ LANGUAGE plpgsql
127
+ AS $$
128
+ BEGIN
129
+ RAISE EXCEPTION 'Settlement is immutable. Create a reversal instead of updating settlement %', NEW.id;
130
+ END;
131
+ $$;
132
+
133
+ DROP TRIGGER IF EXISTS trg_prevent_settlement_updates ON settlement;
134
+
135
+ CREATE TRIGGER trg_prevent_settlement_updates
136
+ BEFORE UPDATE ON settlement
137
+ FOR EACH ROW
138
+ EXECUTE FUNCTION fn_prevent_settlement_updates();
139
+
140
+ CREATE OR REPLACE VIEW v_settlement_history AS
141
+ SELECT
142
+ fi.title_id,
143
+ s.id AS settlement_id,
144
+ s.entry_type,
145
+ s.reverses_settlement_id,
146
+ CASE
147
+ WHEN s.entry_type = 'reversal' THEN s.reverses_settlement_id
148
+ ELSE s.id
149
+ END AS settlement_group_id,
150
+ s.settlement_type,
151
+ s.status,
152
+ s.settled_at,
153
+ s.amount_cents,
154
+ s.payment_method_id,
155
+ s.bank_account_id,
156
+ s.description,
157
+ s.external_reference,
158
+ s.created_by_user_id,
159
+ s.created_at,
160
+ s.updated_at,
161
+ sa.installment_id,
162
+ fi.installment_number,
163
+ sa.amount_cents AS allocation_amount_cents,
164
+ sa.discount_cents,
165
+ sa.interest_cents,
166
+ sa.penalty_cents,
167
+ br.id AS bank_reconciliation_id,
168
+ CASE
169
+ WHEN br.id IS NOT NULL THEN TRUE
170
+ ELSE FALSE
171
+ END AS reconciled
172
+ FROM settlement s
173
+ INNER JOIN settlement_allocation sa ON sa.settlement_id = s.id
174
+ INNER JOIN financial_installment fi ON fi.id = sa.installment_id
175
+ LEFT JOIN bank_reconciliation br ON br.settlement_id = s.id;
@@ -20,6 +20,9 @@ columns:
20
20
  - name: matched_at
21
21
  type: datetime
22
22
  isNullable: true
23
+ - name: reconciled_at
24
+ type: datetime
25
+ isNullable: true
23
26
  - name: matched_by_user_id
24
27
  type: fk
25
28
  isNullable: true
@@ -28,6 +31,14 @@ columns:
28
31
  column: id
29
32
  onDelete: SET NULL
30
33
  onUpdate: CASCADE
34
+ - name: reconciled_by_user_id
35
+ type: fk
36
+ isNullable: true
37
+ references:
38
+ table: user
39
+ column: id
40
+ onDelete: SET NULL
41
+ onUpdate: CASCADE
31
42
  - type: created_at
32
43
  - type: updated_at
33
44
 
@@ -27,6 +27,10 @@ columns:
27
27
  - name: settlement_type
28
28
  type: enum
29
29
  values: [payable, receivable, transfer, adjustment]
30
+ - name: entry_type
31
+ type: enum
32
+ values: [normal, reversal]
33
+ default: normal
30
34
  - name: status
31
35
  type: enum
32
36
  values: [pending, confirmed, reversed]
@@ -41,6 +45,14 @@ columns:
41
45
  type: varchar
42
46
  length: 120
43
47
  isNullable: true
48
+ - name: reverses_settlement_id
49
+ type: fk
50
+ isNullable: true
51
+ references:
52
+ table: settlement
53
+ column: id
54
+ onDelete: RESTRICT
55
+ onUpdate: CASCADE
44
56
  - name: created_by_user_id
45
57
  type: fk
46
58
  isNullable: true
@@ -55,4 +67,8 @@ columns:
55
67
  indices:
56
68
  - columns: [status]
57
69
  - columns: [settled_at]
58
- - columns: [bank_account_id]
70
+ - columns: [bank_account_id]
71
+ - columns: [entry_type]
72
+ - columns: [settlement_type, settled_at]
73
+ - columns: [reverses_settlement_id]
74
+ isUnique: true
@@ -16,6 +16,9 @@ columns:
16
16
  onUpdate: CASCADE
17
17
  - name: allocated_amount_cents
18
18
  type: int
19
+ - name: amount_cents
20
+ type: int
21
+ isNullable: true
19
22
  - name: discount_cents
20
23
  type: int
21
24
  default: 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hed-hog/finance",
3
- "version": "0.0.252",
3
+ "version": "0.0.256",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "dependencies": {
@@ -9,14 +9,14 @@
9
9
  "@nestjs/core": "^11",
10
10
  "@nestjs/jwt": "^11",
11
11
  "@nestjs/mapped-types": "*",
12
- "@hed-hog/api-prisma": "0.0.4",
13
- "@hed-hog/contact": "0.0.251",
14
12
  "@hed-hog/api-pagination": "0.0.5",
15
- "@hed-hog/tag": "0.0.251",
16
13
  "@hed-hog/api-locale": "0.0.11",
17
- "@hed-hog/api-types": "0.0.1",
18
- "@hed-hog/core": "0.0.251",
19
- "@hed-hog/api": "0.0.3"
14
+ "@hed-hog/contact": "0.0.254",
15
+ "@hed-hog/api-prisma": "0.0.4",
16
+ "@hed-hog/tag": "0.0.254",
17
+ "@hed-hog/api": "0.0.3",
18
+ "@hed-hog/core": "0.0.254",
19
+ "@hed-hog/api-types": "0.0.1"
20
20
  },
21
21
  "exports": {
22
22
  ".": {
@@ -4,4 +4,8 @@ export class ReverseSettlementDto {
4
4
  @IsOptional()
5
5
  @IsString()
6
6
  reason?: string;
7
+
8
+ @IsOptional()
9
+ @IsString()
10
+ memo?: string;
7
11
  }