@hed-hog/finance 0.0.251 → 0.0.253

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.
@@ -1729,6 +1729,8 @@ export default function TitulosPagarPage() {
1729
1729
  const [search, setSearch] = useState('');
1730
1730
  const [statusFilter, setStatusFilter] = useState<string>('');
1731
1731
  const [editingTitleId, setEditingTitleId] = useState<string | null>(null);
1732
+ const [approvingTitleId, setApprovingTitleId] = useState<string | null>(null);
1733
+ const [reversingTitleId, setReversingTitleId] = useState<string | null>(null);
1732
1734
  const [cancelingTitleId, setCancelingTitleId] = useState<string | null>(null);
1733
1735
 
1734
1736
  const editingTitle = useMemo(
@@ -1792,13 +1794,97 @@ export default function TitulosPagarPage() {
1792
1794
 
1793
1795
  await refetch();
1794
1796
  showToastHandler?.('success', 'Título cancelado com sucesso');
1795
- } catch {
1796
- showToastHandler?.('error', 'Não foi possível cancelar o título');
1797
+ } catch (error: any) {
1798
+ const message = error?.response?.data?.message;
1799
+ showToastHandler?.(
1800
+ 'error',
1801
+ typeof message === 'string' && message.trim()
1802
+ ? message
1803
+ : 'Não foi possível cancelar o título'
1804
+ );
1797
1805
  } finally {
1798
1806
  setCancelingTitleId(null);
1799
1807
  }
1800
1808
  };
1801
1809
 
1810
+ const handleApproveTitle = async (titleId: string) => {
1811
+ if (!titleId || approvingTitleId) {
1812
+ return;
1813
+ }
1814
+
1815
+ setApprovingTitleId(titleId);
1816
+ try {
1817
+ await request({
1818
+ url: `/finance/accounts-payable/installments/${titleId}/approve`,
1819
+ method: 'PATCH',
1820
+ });
1821
+
1822
+ await refetch();
1823
+ showToastHandler?.('success', 'Título aprovado com sucesso');
1824
+ } catch (error: any) {
1825
+ const message = error?.response?.data?.message;
1826
+ showToastHandler?.(
1827
+ 'error',
1828
+ typeof message === 'string' && message.trim()
1829
+ ? message
1830
+ : 'Não foi possível aprovar o título'
1831
+ );
1832
+ } finally {
1833
+ setApprovingTitleId(null);
1834
+ }
1835
+ };
1836
+
1837
+ const handleSettleTitle = (titleId: string) => {
1838
+ router.push(
1839
+ `/finance/accounts-payable/installments/${titleId}?action=settle`
1840
+ );
1841
+ };
1842
+
1843
+ const handleReverseTitle = async (title: any) => {
1844
+ const titleId = title?.id;
1845
+
1846
+ if (!titleId || reversingTitleId) {
1847
+ return;
1848
+ }
1849
+
1850
+ const settlementToReverse = (title?.parcelas || [])
1851
+ .flatMap((parcela: any) => parcela?.liquidacoes || [])
1852
+ .find((liquidacao: any) => {
1853
+ return (
1854
+ !!liquidacao?.settlementId &&
1855
+ liquidacao?.status !== 'reversed' &&
1856
+ liquidacao?.status !== 'estornado'
1857
+ );
1858
+ });
1859
+
1860
+ if (!settlementToReverse?.settlementId) {
1861
+ showToastHandler?.('error', 'Nenhuma liquidação ativa para estornar');
1862
+ return;
1863
+ }
1864
+
1865
+ setReversingTitleId(titleId);
1866
+ try {
1867
+ await request({
1868
+ url: `/finance/accounts-payable/installments/${titleId}/settlements/${settlementToReverse.settlementId}/reverse`,
1869
+ method: 'PATCH',
1870
+ data: {},
1871
+ });
1872
+
1873
+ await refetch();
1874
+ showToastHandler?.('success', 'Estorno realizado com sucesso');
1875
+ } catch (error: any) {
1876
+ const message = error?.response?.data?.message;
1877
+ showToastHandler?.(
1878
+ 'error',
1879
+ typeof message === 'string' && message.trim()
1880
+ ? message
1881
+ : 'Não foi possível estornar a liquidação'
1882
+ );
1883
+ } finally {
1884
+ setReversingTitleId(null);
1885
+ }
1886
+ };
1887
+
1802
1888
  const filteredTitulos = titulosPagar.filter((titulo) => {
1803
1889
  const matchesSearch =
1804
1890
  titulo.documento.toLowerCase().includes(search.toLowerCase()) ||
@@ -1985,23 +2071,32 @@ export default function TitulosPagarPage() {
1985
2071
  </DropdownMenuItem>
1986
2072
  <DropdownMenuSeparator />
1987
2073
  <DropdownMenuItem
1988
- disabled={titulo.status !== 'rascunho'}
2074
+ disabled={
2075
+ titulo.status !== 'rascunho' ||
2076
+ approvingTitleId === titulo.id
2077
+ }
2078
+ onClick={() => void handleApproveTitle(titulo.id)}
1989
2079
  >
1990
2080
  <CheckCircle className="mr-2 h-4 w-4" />
1991
2081
  {t('table.actions.approve')}
1992
2082
  </DropdownMenuItem>
1993
2083
  <DropdownMenuItem
1994
2084
  disabled={
1995
- !['aberto', 'parcial'].includes(titulo.status)
2085
+ !['aberto', 'parcial', 'vencido'].includes(
2086
+ titulo.status
2087
+ )
1996
2088
  }
2089
+ onClick={() => handleSettleTitle(titulo.id)}
1997
2090
  >
1998
2091
  <Download className="mr-2 h-4 w-4" />
1999
2092
  {t('table.actions.settle')}
2000
2093
  </DropdownMenuItem>
2001
2094
  <DropdownMenuItem
2002
2095
  disabled={
2003
- !['parcial', 'liquidado'].includes(titulo.status)
2096
+ !['parcial', 'liquidado'].includes(titulo.status) ||
2097
+ reversingTitleId === titulo.id
2004
2098
  }
2099
+ onClick={() => void handleReverseTitle(titulo)}
2005
2100
  >
2006
2101
  <Undo className="mr-2 h-4 w-4" />
2007
2102
  {t('table.actions.reverse')}
@@ -22,14 +22,6 @@ import {
22
22
  CardHeader,
23
23
  CardTitle,
24
24
  } from '@/components/ui/card';
25
- import {
26
- Dialog,
27
- DialogContent,
28
- DialogDescription,
29
- DialogFooter,
30
- DialogHeader,
31
- DialogTitle,
32
- } from '@/components/ui/dialog';
33
25
  import {
34
26
  DropdownMenu,
35
27
  DropdownMenuContent,
@@ -54,6 +46,13 @@ import {
54
46
  SelectTrigger,
55
47
  SelectValue,
56
48
  } from '@/components/ui/select';
49
+ import {
50
+ Sheet,
51
+ SheetContent,
52
+ SheetDescription,
53
+ SheetHeader,
54
+ SheetTitle,
55
+ } from '@/components/ui/sheet';
57
56
  import { StatusBadge } from '@/components/ui/status-badge';
58
57
  import {
59
58
  Table,
@@ -522,21 +521,21 @@ export default function TituloReceberDetalhePage() {
522
521
  </DropdownMenuContent>
523
522
  </DropdownMenu>
524
523
 
525
- <Dialog
524
+ <Sheet
526
525
  open={isSettleDialogOpen}
527
526
  onOpenChange={setIsSettleDialogOpen}
528
527
  >
529
- <DialogContent>
530
- <DialogHeader>
531
- <DialogTitle>Registrar recebimento</DialogTitle>
532
- <DialogDescription>
528
+ <SheetContent className="w-full sm:max-w-lg overflow-y-auto">
529
+ <SheetHeader>
530
+ <SheetTitle>Registrar recebimento</SheetTitle>
531
+ <SheetDescription>
533
532
  Informe a parcela e o valor para baixa parcial ou total.
534
- </DialogDescription>
535
- </DialogHeader>
533
+ </SheetDescription>
534
+ </SheetHeader>
536
535
 
537
536
  <Form {...settleForm}>
538
537
  <form
539
- className="space-y-4"
538
+ className="space-y-4 px-1"
540
539
  onSubmit={settleForm.handleSubmit(handleSettle)}
541
540
  >
542
541
  <FormField
@@ -618,7 +617,7 @@ export default function TituloReceberDetalhePage() {
618
617
  )}
619
618
  />
620
619
 
621
- <DialogFooter>
620
+ <div className="flex justify-end gap-2 pt-2">
622
621
  <Button
623
622
  type="button"
624
623
  variant="outline"
@@ -630,11 +629,11 @@ export default function TituloReceberDetalhePage() {
630
629
  <Button type="submit" disabled={isSettling}>
631
630
  Confirmar recebimento
632
631
  </Button>
633
- </DialogFooter>
632
+ </div>
634
633
  </form>
635
634
  </Form>
636
- </DialogContent>
637
- </Dialog>
635
+ </SheetContent>
636
+ </Sheet>
638
637
  </div>
639
638
  }
640
639
  />
@@ -221,8 +221,12 @@ function Alertas({
221
221
  (e) => e.statusConciliacao === 'pendente'
222
222
  ).length;
223
223
 
224
- const periodoBase = periodoAberto?.inicio ? new Date(periodoAberto.inicio) : new Date();
225
- const mes = new Intl.DateTimeFormat(locale, { month: 'long' }).format(periodoBase);
224
+ const periodoBase = periodoAberto?.inicio
225
+ ? new Date(periodoAberto.inicio)
226
+ : new Date();
227
+ const mes = new Intl.DateTimeFormat(locale, { month: 'long' }).format(
228
+ periodoBase
229
+ );
226
230
  const periodoAtual = `${mes.charAt(0).toUpperCase()}${mes.slice(1)}/${periodoBase.getFullYear()}`;
227
231
 
228
232
  return (
@@ -281,6 +285,9 @@ export default function DashboardPage() {
281
285
  } = data;
282
286
 
283
287
  const getPessoaById = (id?: string) => pessoas.find((p) => p.id === id);
288
+ const titulosPagarAprovados = titulosPagar.filter(
289
+ (titulo) => titulo.status !== 'rascunho' && titulo.status !== 'cancelado'
290
+ );
284
291
 
285
292
  return (
286
293
  <Page>
@@ -335,7 +342,7 @@ export default function DashboardPage() {
335
342
  </CardContent>
336
343
  </Card>
337
344
  <Alertas
338
- titulosPagar={titulosPagar}
345
+ titulosPagar={titulosPagarAprovados}
339
346
  extratos={extratos}
340
347
  periodoAberto={periodoAberto}
341
348
  locale={locale}
@@ -344,7 +351,7 @@ export default function DashboardPage() {
344
351
  </div>
345
352
 
346
353
  <ProximosVencimentos
347
- titulosPagar={titulosPagar}
354
+ titulosPagar={titulosPagarAprovados}
348
355
  titulosReceber={titulosReceber}
349
356
  getPessoaById={getPessoaById}
350
357
  t={t}
@@ -198,6 +198,44 @@
198
198
  "reverse": "Reverse",
199
199
  "cancel": "Cancel"
200
200
  },
201
+ "validation": {
202
+ "installmentRequired": "Installment is required",
203
+ "amountGreaterThanZero": "Amount must be greater than zero"
204
+ },
205
+ "dialogs": {
206
+ "cancel": {
207
+ "title": "Confirm cancellation",
208
+ "description": "This action changes the title status to canceled and does not remove audit records.",
209
+ "cancel": "Cancel",
210
+ "confirm": "Confirm cancellation"
211
+ },
212
+ "reverse": {
213
+ "title": "Confirm reversal",
214
+ "description": "This action reverses the selected settlement, recalculates installment balances, and updates the title status.",
215
+ "cancel": "Cancel",
216
+ "confirm": "Confirm reversal"
217
+ }
218
+ },
219
+ "settleSheet": {
220
+ "title": "Register settlement",
221
+ "description": "Provide the installment and settlement amount. The backend validates status and amount limits automatically.",
222
+ "installmentLabel": "Installment",
223
+ "installmentPlaceholder": "Select",
224
+ "installmentOption": "Installment {number} - open amount: {amount}",
225
+ "amountLabel": "Amount",
226
+ "descriptionLabel": "Description (optional)",
227
+ "confirm": "Confirm settlement"
228
+ },
229
+ "messages": {
230
+ "approveSuccess": "Title approved successfully",
231
+ "approveError": "Could not approve the title",
232
+ "settleSuccess": "Settlement registered successfully",
233
+ "settleError": "Could not register settlement",
234
+ "reverseSuccess": "Reversal completed successfully",
235
+ "reverseError": "Could not reverse the settlement",
236
+ "cancelSuccess": "Title canceled successfully",
237
+ "cancelError": "Could not cancel the title"
238
+ },
201
239
  "documentData": {
202
240
  "title": "Document Data",
203
241
  "supplier": "Supplier",
@@ -254,6 +292,12 @@
254
292
  "fine": "Fine",
255
293
  "account": "Account",
256
294
  "method": "Method",
295
+ "actions": "Actions",
296
+ "reverseButton": "Reverse",
297
+ "reverseDialogTitle": "Confirm reversal",
298
+ "reverseDialogDescription": "This action creates a reversal for the settlement and recalculates balances and status.",
299
+ "reverseDialogCancel": "Cancel",
300
+ "reverseDialogConfirm": "Confirm reversal",
257
301
  "none": "No settlement recorded"
258
302
  },
259
303
  "audit": { "none": "No audit event" }
@@ -198,6 +198,44 @@
198
198
  "reverse": "Estornar",
199
199
  "cancel": "Cancelar"
200
200
  },
201
+ "validation": {
202
+ "installmentRequired": "Parcela obrigatória",
203
+ "amountGreaterThanZero": "Valor deve ser maior que zero"
204
+ },
205
+ "dialogs": {
206
+ "cancel": {
207
+ "title": "Confirmar cancelamento",
208
+ "description": "Essa ação altera o título para cancelado e não remove os registros de auditoria.",
209
+ "cancel": "Cancelar",
210
+ "confirm": "Confirmar cancelamento"
211
+ },
212
+ "reverse": {
213
+ "title": "Confirmar estorno",
214
+ "description": "Esta ação estorna a liquidação selecionada, recalcula os saldos das parcelas e atualiza o status do título.",
215
+ "cancel": "Cancelar",
216
+ "confirm": "Confirmar estorno"
217
+ }
218
+ },
219
+ "settleSheet": {
220
+ "title": "Registrar baixa",
221
+ "description": "Informe a parcela e o valor da baixa. O backend valida os estados e limites de valor automaticamente.",
222
+ "installmentLabel": "Parcela",
223
+ "installmentPlaceholder": "Selecione",
224
+ "installmentOption": "Parcela {number} - em aberto: {amount}",
225
+ "amountLabel": "Valor",
226
+ "descriptionLabel": "Descrição (opcional)",
227
+ "confirm": "Confirmar baixa"
228
+ },
229
+ "messages": {
230
+ "approveSuccess": "Título aprovado com sucesso",
231
+ "approveError": "Não foi possível aprovar o título",
232
+ "settleSuccess": "Baixa registrada com sucesso",
233
+ "settleError": "Não foi possível registrar a baixa",
234
+ "reverseSuccess": "Estorno realizado com sucesso",
235
+ "reverseError": "Não foi possível estornar a liquidação",
236
+ "cancelSuccess": "Título cancelado com sucesso",
237
+ "cancelError": "Não foi possível cancelar o título"
238
+ },
201
239
  "documentData": {
202
240
  "title": "Dados do Documento",
203
241
  "supplier": "Fornecedor",
@@ -254,6 +292,12 @@
254
292
  "fine": "Multa",
255
293
  "account": "Conta",
256
294
  "method": "Método",
295
+ "actions": "Ações",
296
+ "reverseButton": "Estornar",
297
+ "reverseDialogTitle": "Confirmar estorno",
298
+ "reverseDialogDescription": "Esta ação cria o estorno da liquidação e recalcula saldos e status.",
299
+ "reverseDialogCancel": "Cancelar",
300
+ "reverseDialogConfirm": "Confirmar estorno",
257
301
  "none": "Nenhuma liquidação registrada"
258
302
  },
259
303
  "audit": { "none": "Nenhum evento de auditoria" }
@@ -102,7 +102,9 @@ BEGIN
102
102
  SELECT COALESCE(SUM(sa.allocated_amount_cents), 0)
103
103
  INTO allocated_total_cents
104
104
  FROM settlement_allocation sa
105
- WHERE sa.installment_id = target_installment_id;
105
+ INNER JOIN settlement s ON s.id = sa.settlement_id
106
+ WHERE sa.installment_id = target_installment_id
107
+ AND s.status <> 'reversed';
106
108
 
107
109
  IF allocated_total_cents > installment_amount_cents THEN
108
110
  RAISE EXCEPTION
@@ -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.251",
3
+ "version": "0.0.253",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "dependencies": {
@@ -11,9 +11,9 @@
11
11
  "@nestjs/mapped-types": "*",
12
12
  "@hed-hog/api-pagination": "0.0.5",
13
13
  "@hed-hog/contact": "0.0.251",
14
- "@hed-hog/api-prisma": "0.0.4",
15
14
  "@hed-hog/tag": "0.0.251",
16
15
  "@hed-hog/api-locale": "0.0.11",
16
+ "@hed-hog/api-prisma": "0.0.4",
17
17
  "@hed-hog/api-types": "0.0.1",
18
18
  "@hed-hog/api": "0.0.3",
19
19
  "@hed-hog/core": "0.0.251"
@@ -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
  }