@hed-hog/finance 0.0.239 → 0.0.240

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 (35) hide show
  1. package/README.md +1 -22
  2. package/dist/finance-installments.controller.d.ts +132 -0
  3. package/dist/finance-installments.controller.d.ts.map +1 -1
  4. package/dist/finance-installments.controller.js +52 -0
  5. package/dist/finance-installments.controller.js.map +1 -1
  6. package/dist/finance-statements.controller.d.ts +8 -0
  7. package/dist/finance-statements.controller.d.ts.map +1 -1
  8. package/dist/finance-statements.controller.js +40 -0
  9. package/dist/finance-statements.controller.js.map +1 -1
  10. package/dist/finance.module.d.ts.map +1 -1
  11. package/dist/finance.module.js +1 -0
  12. package/dist/finance.module.js.map +1 -1
  13. package/dist/finance.service.d.ts +160 -2
  14. package/dist/finance.service.d.ts.map +1 -1
  15. package/dist/finance.service.js +626 -8
  16. package/dist/finance.service.js.map +1 -1
  17. package/hedhog/data/route.yaml +54 -0
  18. package/hedhog/frontend/app/accounts-payable/installments/[id]/page.tsx.ejs +80 -4
  19. package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +736 -13
  20. package/hedhog/frontend/app/accounts-receivable/installments/[id]/page.tsx.ejs +1 -1
  21. package/hedhog/frontend/app/accounts-receivable/installments/page.tsx.ejs +1 -3
  22. package/hedhog/frontend/app/cash-and-banks/statements/page.tsx.ejs +212 -60
  23. package/hedhog/frontend/messages/en.json +1 -0
  24. package/hedhog/frontend/messages/pt.json +1 -0
  25. package/hedhog/query/0_constraints.sql +2 -0
  26. package/hedhog/query/constraints.sql +86 -0
  27. package/hedhog/table/bank_account.yaml +0 -8
  28. package/hedhog/table/financial_title.yaml +1 -9
  29. package/hedhog/table/settlement.yaml +0 -8
  30. package/package.json +6 -6
  31. package/src/finance-installments.controller.ts +70 -10
  32. package/src/finance-statements.controller.ts +61 -2
  33. package/src/finance.module.ts +2 -1
  34. package/src/finance.service.ts +868 -12
  35. package/hedhog/table/branch.yaml +0 -18
@@ -378,7 +378,7 @@ export default function TituloReceberDetalhePage() {
378
378
  };
379
379
 
380
380
  const canApprove = titulo.status === 'rascunho';
381
- const canSettle = ['aprovado', 'aberto', 'parcial'].includes(titulo.status);
381
+ const canSettle = ['aberto', 'parcial'].includes(titulo.status);
382
382
 
383
383
  const getErrorMessage = (error: any, fallback: string) => {
384
384
  const message = error?.response?.data?.message;
@@ -1252,9 +1252,7 @@ export default function TitulosReceberPage() {
1252
1252
  <DropdownMenuSeparator />
1253
1253
  <DropdownMenuItem
1254
1254
  disabled={
1255
- !['aprovado', 'aberto', 'parcial'].includes(
1256
- titulo.status
1257
- )
1255
+ !['aberto', 'parcial'].includes(titulo.status)
1258
1256
  }
1259
1257
  >
1260
1258
  <Download className="mr-2 h-4 w-4" />
@@ -9,18 +9,16 @@ import {
9
9
  CardHeader,
10
10
  CardTitle,
11
11
  } from '@/components/ui/card';
12
- import {
13
- Dialog,
14
- DialogContent,
15
- DialogDescription,
16
- DialogFooter,
17
- DialogHeader,
18
- DialogTitle,
19
- DialogTrigger,
20
- } from '@/components/ui/dialog';
21
12
  import { FilterBar } from '@/components/ui/filter-bar';
13
+ import {
14
+ Form,
15
+ FormControl,
16
+ FormField,
17
+ FormItem,
18
+ FormLabel,
19
+ FormMessage,
20
+ } from '@/components/ui/form';
22
21
  import { Input } from '@/components/ui/input';
23
- import { Label } from '@/components/ui/label';
24
22
  import { Money } from '@/components/ui/money';
25
23
  import {
26
24
  Select,
@@ -29,6 +27,13 @@ import {
29
27
  SelectTrigger,
30
28
  SelectValue,
31
29
  } from '@/components/ui/select';
30
+ import {
31
+ Sheet,
32
+ SheetContent,
33
+ SheetDescription,
34
+ SheetHeader,
35
+ SheetTitle,
36
+ } from '@/components/ui/sheet';
32
37
  import { StatusBadge } from '@/components/ui/status-badge';
33
38
  import {
34
39
  Table,
@@ -39,10 +44,13 @@ import {
39
44
  TableRow,
40
45
  } from '@/components/ui/table';
41
46
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
47
+ import { zodResolver } from '@hookform/resolvers/zod';
42
48
  import { ArrowDownRight, ArrowUpRight, Download, Upload } from 'lucide-react';
43
49
  import { useTranslations } from 'next-intl';
44
50
  import { usePathname, useRouter, useSearchParams } from 'next/navigation';
45
51
  import { useEffect, useMemo, useState } from 'react';
52
+ import { useForm } from 'react-hook-form';
53
+ import { z } from 'zod';
46
54
  import { formatarData } from '../../_lib/formatters';
47
55
 
48
56
  type BankAccount = {
@@ -67,64 +75,162 @@ type Statement = {
67
75
  | 'ajustado';
68
76
  };
69
77
 
70
- function ImportarExtratoDialog({
78
+ const importStatementSchema = z.object({
79
+ bankAccountId: z.string().trim().min(1, 'Conta bancária é obrigatória'),
80
+ file: z.instanceof(File, { message: 'Arquivo é obrigatório' }).refine(
81
+ (value) => {
82
+ const fileName = value.name.toLowerCase();
83
+ return fileName.endsWith('.csv') || fileName.endsWith('.ofx');
84
+ },
85
+ { message: 'Apenas arquivos CSV ou OFX são permitidos' }
86
+ ),
87
+ });
88
+
89
+ type ImportStatementFormValues = z.infer<typeof importStatementSchema>;
90
+
91
+ function ImportarExtratoSheet({
71
92
  contasBancarias,
72
93
  t,
94
+ defaultBankAccountId,
95
+ onImported,
73
96
  }: {
74
- contasBancarias: any[];
97
+ contasBancarias: BankAccount[];
75
98
  t: ReturnType<typeof useTranslations>;
99
+ defaultBankAccountId?: string;
100
+ onImported: () => Promise<any> | void;
76
101
  }) {
102
+ const { request, showToastHandler } = useApp();
103
+ const [open, setOpen] = useState(false);
104
+
105
+ const form = useForm<ImportStatementFormValues>({
106
+ resolver: zodResolver(importStatementSchema),
107
+ defaultValues: {
108
+ bankAccountId: defaultBankAccountId || '',
109
+ },
110
+ });
111
+
112
+ useEffect(() => {
113
+ if (!open) {
114
+ return;
115
+ }
116
+
117
+ form.reset({
118
+ bankAccountId: defaultBankAccountId || '',
119
+ file: undefined as unknown as File,
120
+ });
121
+ }, [defaultBankAccountId, form, open]);
122
+
123
+ const handleSubmit = async (values: ImportStatementFormValues) => {
124
+ const formData = new FormData();
125
+ formData.append('bank_account_id', values.bankAccountId);
126
+ formData.append('file', values.file);
127
+
128
+ try {
129
+ await request({
130
+ url: '/finance/statements/import',
131
+ method: 'POST',
132
+ data: formData,
133
+ });
134
+
135
+ await onImported();
136
+ showToastHandler?.('success', 'Extrato importado com sucesso');
137
+ setOpen(false);
138
+ } catch {
139
+ showToastHandler?.('error', 'Não foi possível importar o extrato');
140
+ }
141
+ };
142
+
77
143
  return (
78
- <Dialog>
79
- <DialogTrigger asChild>
80
- <Button>
81
- <Upload className="mr-2 h-4 w-4" />
82
- {t('importDialog.action')}
83
- </Button>
84
- </DialogTrigger>
85
- <DialogContent>
86
- <DialogHeader>
87
- <DialogTitle>{t('importDialog.title')}</DialogTitle>
88
- <DialogDescription>{t('importDialog.description')}</DialogDescription>
89
- </DialogHeader>
90
- <div className="space-y-4">
91
- <div className="space-y-2">
92
- <Label htmlFor="conta">{t('importDialog.bankAccount')}</Label>
93
- <Select>
94
- <SelectTrigger>
95
- <SelectValue placeholder={t('importDialog.selectAccount')} />
96
- </SelectTrigger>
97
- <SelectContent>
98
- {contasBancarias.map((conta: any) => (
99
- <SelectItem key={conta.id} value={conta.id}>
100
- {conta.banco} - {conta.descricao}
101
- </SelectItem>
102
- ))}
103
- </SelectContent>
104
- </Select>
105
- </div>
106
- <div className="space-y-2">
107
- <Label htmlFor="arquivo">{t('importDialog.file')}</Label>
108
- <Input id="arquivo" type="file" accept=".ofx,.csv" />
109
- <p className="text-xs text-muted-foreground">
110
- {t('importDialog.acceptedFormats')}
111
- </p>
112
- </div>
113
- </div>
114
- <DialogFooter>
115
- <Button type="button" variant="outline">
116
- {t('common.cancel')}
117
- </Button>
118
- <Button type="button">{t('importDialog.submit')}</Button>
119
- </DialogFooter>
120
- </DialogContent>
121
- </Dialog>
144
+ <Sheet open={open} onOpenChange={setOpen}>
145
+ <Button onClick={() => setOpen(true)}>
146
+ <Upload className="mr-2 h-4 w-4" />
147
+ {t('importDialog.action')}
148
+ </Button>
149
+
150
+ <SheetContent className="w-full sm:max-w-lg overflow-y-auto">
151
+ <SheetHeader>
152
+ <SheetTitle>{t('importDialog.title')}</SheetTitle>
153
+ <SheetDescription>{t('importDialog.description')}</SheetDescription>
154
+ </SheetHeader>
155
+
156
+ <Form {...form}>
157
+ <form
158
+ className="space-y-4 px-4"
159
+ onSubmit={form.handleSubmit(handleSubmit)}
160
+ >
161
+ <FormField
162
+ control={form.control}
163
+ name="bankAccountId"
164
+ render={({ field }) => (
165
+ <FormItem>
166
+ <FormLabel>{t('importDialog.bankAccount')}</FormLabel>
167
+ <Select value={field.value} onValueChange={field.onChange}>
168
+ <FormControl>
169
+ <SelectTrigger>
170
+ <SelectValue
171
+ placeholder={t('importDialog.selectAccount')}
172
+ />
173
+ </SelectTrigger>
174
+ </FormControl>
175
+ <SelectContent>
176
+ {contasBancarias.map((conta) => (
177
+ <SelectItem key={conta.id} value={conta.id}>
178
+ {conta.banco} - {conta.descricao}
179
+ </SelectItem>
180
+ ))}
181
+ </SelectContent>
182
+ </Select>
183
+ <FormMessage />
184
+ </FormItem>
185
+ )}
186
+ />
187
+
188
+ <FormField
189
+ control={form.control}
190
+ name="file"
191
+ render={({ field }) => (
192
+ <FormItem>
193
+ <FormLabel>{t('importDialog.file')}</FormLabel>
194
+ <FormControl>
195
+ <Input
196
+ type="file"
197
+ accept=".ofx,.csv"
198
+ onChange={(event) => {
199
+ const selectedFile = event.target.files?.[0];
200
+ field.onChange(selectedFile);
201
+ }}
202
+ />
203
+ </FormControl>
204
+ <p className="text-xs text-muted-foreground">
205
+ {t('importDialog.acceptedFormats')}
206
+ </p>
207
+ <FormMessage />
208
+ </FormItem>
209
+ )}
210
+ />
211
+
212
+ <div className="flex justify-end gap-2 pt-2">
213
+ <Button
214
+ type="button"
215
+ variant="outline"
216
+ onClick={() => setOpen(false)}
217
+ >
218
+ {t('common.cancel')}
219
+ </Button>
220
+ <Button type="submit" disabled={form.formState.isSubmitting}>
221
+ {t('importDialog.submit')}
222
+ </Button>
223
+ </div>
224
+ </form>
225
+ </Form>
226
+ </SheetContent>
227
+ </Sheet>
122
228
  );
123
229
  }
124
230
 
125
231
  export default function ExtratosPage() {
126
232
  const t = useTranslations('finance.StatementsPage');
127
- const { request } = useApp();
233
+ const { request, showToastHandler } = useApp();
128
234
  const pathname = usePathname();
129
235
  const router = useRouter();
130
236
  const searchParams = useSearchParams();
@@ -179,7 +285,7 @@ export default function ExtratosPage() {
179
285
  searchParams,
180
286
  ]);
181
287
 
182
- const { data: extratos } = useQuery<Statement[]>({
288
+ const { data: extratos, refetch: refetchExtratos } = useQuery<Statement[]>({
183
289
  queryKey: ['finance-statements', contaFilter],
184
290
  queryFn: async () => {
185
291
  if (!contaFilter) {
@@ -212,6 +318,47 @@ export default function ExtratosPage() {
212
318
  .filter((e) => e.tipo === 'saida')
213
319
  .reduce((acc, e) => acc + e.valor, 0);
214
320
 
321
+ const handleExport = async () => {
322
+ if (!contaFilter) {
323
+ showToastHandler?.('error', 'Selecione uma conta bancária para exportar');
324
+ return;
325
+ }
326
+
327
+ try {
328
+ const response = await request<Blob>({
329
+ url: `/finance/statements/export?bank_account_id=${contaFilter}`,
330
+ method: 'GET',
331
+ responseType: 'blob',
332
+ });
333
+
334
+ const contentDisposition =
335
+ response.headers?.['content-disposition'] ||
336
+ response.headers?.['Content-Disposition'];
337
+ const fileNameMatch =
338
+ typeof contentDisposition === 'string'
339
+ ? contentDisposition.match(/filename\*?=(?:UTF-8'')?"?([^";]+)"?/i)
340
+ : null;
341
+ const fileName = fileNameMatch?.[1]
342
+ ? decodeURIComponent(fileNameMatch[1])
343
+ : `extrato-bancario-${contaFilter}.csv`;
344
+
345
+ const blob = new Blob([response.data], {
346
+ type: 'text/csv;charset=utf-8;',
347
+ });
348
+ const url = window.URL.createObjectURL(blob);
349
+ const link = document.createElement('a');
350
+
351
+ link.href = url;
352
+ link.setAttribute('download', fileName);
353
+ document.body.appendChild(link);
354
+ link.click();
355
+ link.remove();
356
+ window.URL.revokeObjectURL(url);
357
+ } catch {
358
+ showToastHandler?.('error', 'Não foi possível exportar o extrato');
359
+ }
360
+ };
361
+
215
362
  return (
216
363
  <Page>
217
364
  <PageHeader
@@ -224,11 +371,16 @@ export default function ExtratosPage() {
224
371
  ]}
225
372
  actions={
226
373
  <div className="flex gap-2">
227
- <Button variant="outline">
374
+ <Button variant="outline" onClick={() => void handleExport()}>
228
375
  <Download className="mr-2 h-4 w-4" />
229
376
  {t('actions.export')}
230
377
  </Button>
231
- <ImportarExtratoDialog contasBancarias={contasBancarias} t={t} />
378
+ <ImportarExtratoSheet
379
+ contasBancarias={contasBancarias}
380
+ t={t}
381
+ defaultBankAccountId={contaFilter}
382
+ onImported={refetchExtratos}
383
+ />
232
384
  </div>
233
385
  }
234
386
  />
@@ -204,6 +204,7 @@
204
204
  "cnpjCpf": "Tax ID",
205
205
  "competency": "Competency",
206
206
  "totalValue": "Total Value",
207
+ "status": "Status",
207
208
  "category": "Category",
208
209
  "costCenter": "Cost Center",
209
210
  "createdAt": "Created At",
@@ -204,6 +204,7 @@
204
204
  "cnpjCpf": "CNPJ/CPF",
205
205
  "competency": "Competência",
206
206
  "totalValue": "Valor Total",
207
+ "status": "Status",
207
208
  "category": "Categoria",
208
209
  "costCenter": "Centro de Custo",
209
210
  "createdAt": "Data de Criação",
@@ -0,0 +1,2 @@
1
+ -- Conteúdo consolidado em constraints.sql.
2
+ -- Este arquivo é mantido apenas por compatibilidade histórica de nomenclatura.
@@ -1,6 +1,13 @@
1
1
  -- Regras de integridade do módulo financeiro (PostgreSQL)
2
2
  -- Script idempotente: pode ser executado mais de uma vez.
3
3
 
4
+ -- 0) Normalização de dados legados
5
+ -- Se existir título em status aprovado, converte para aberto.
6
+ UPDATE financial_title
7
+ SET status = 'open',
8
+ updated_at = NOW()
9
+ WHERE status::text = 'approved';
10
+
4
11
  -- 1) CHECKs simples de domínio
5
12
  DO $$
6
13
  BEGIN
@@ -167,3 +174,82 @@ ON installment_allocation
167
174
  DEFERRABLE INITIALLY DEFERRED
168
175
  FOR EACH ROW
169
176
  EXECUTE FUNCTION fn_check_installment_allocation_total_per_installment();
177
+
178
+ -- 4) Sincronização automática de status do título com base nas parcelas
179
+ -- Regras:
180
+ -- - Todas parcelas com open_amount_cents = 0 => settled
181
+ -- - Todas parcelas com open_amount_cents > 0 => open
182
+ -- - Mistura => partial
183
+ -- - Preserva títulos em draft e canceled
184
+
185
+ CREATE OR REPLACE FUNCTION finance_sync_title_status_from_installments()
186
+ RETURNS trigger
187
+ LANGUAGE plpgsql
188
+ AS $$
189
+ DECLARE
190
+ v_title_id BIGINT;
191
+ v_current_status financial_title_status;
192
+ v_total_installments INTEGER;
193
+ v_open_installments INTEGER;
194
+ v_settled_installments INTEGER;
195
+ v_next_status financial_title_status;
196
+ BEGIN
197
+ v_title_id := COALESCE(NEW.title_id, OLD.title_id);
198
+
199
+ IF v_title_id IS NULL THEN
200
+ RETURN COALESCE(NEW, OLD);
201
+ END IF;
202
+
203
+ SELECT status
204
+ INTO v_current_status
205
+ FROM financial_title
206
+ WHERE id = v_title_id;
207
+
208
+ IF v_current_status IS NULL THEN
209
+ RETURN COALESCE(NEW, OLD);
210
+ END IF;
211
+
212
+ IF v_current_status IN ('draft', 'canceled') THEN
213
+ RETURN COALESCE(NEW, OLD);
214
+ END IF;
215
+
216
+ SELECT COUNT(*)::INTEGER,
217
+ COUNT(*) FILTER (WHERE open_amount_cents > 0)::INTEGER,
218
+ COUNT(*) FILTER (WHERE open_amount_cents = 0)::INTEGER
219
+ INTO v_total_installments,
220
+ v_open_installments,
221
+ v_settled_installments
222
+ FROM financial_installment
223
+ WHERE title_id = v_title_id;
224
+
225
+ IF v_total_installments = 0 THEN
226
+ RETURN COALESCE(NEW, OLD);
227
+ END IF;
228
+
229
+ IF v_settled_installments = v_total_installments THEN
230
+ v_next_status := 'settled';
231
+ ELSIF v_open_installments = v_total_installments THEN
232
+ v_next_status := 'open';
233
+ ELSE
234
+ v_next_status := 'partial';
235
+ END IF;
236
+
237
+ IF v_current_status <> v_next_status THEN
238
+ UPDATE financial_title
239
+ SET status = v_next_status,
240
+ updated_at = NOW()
241
+ WHERE id = v_title_id;
242
+ END IF;
243
+
244
+ RETURN COALESCE(NEW, OLD);
245
+ END;
246
+ $$;
247
+
248
+ DROP TRIGGER IF EXISTS trg_financial_installment_sync_title_status
249
+ ON financial_installment;
250
+
251
+ CREATE TRIGGER trg_financial_installment_sync_title_status
252
+ AFTER INSERT OR UPDATE OF open_amount_cents, title_id OR DELETE
253
+ ON financial_installment
254
+ FOR EACH ROW
255
+ EXECUTE FUNCTION finance_sync_title_status_from_installments();
@@ -1,13 +1,5 @@
1
1
  columns:
2
2
  - type: pk
3
- - name: branch_id
4
- type: fk
5
- isNullable: true
6
- references:
7
- table: branch
8
- column: id
9
- onDelete: SET NULL
10
- onUpdate: CASCADE
11
3
  - name: code
12
4
  type: varchar
13
5
  length: 32
@@ -1,13 +1,5 @@
1
1
  columns:
2
2
  - type: pk
3
- - name: branch_id
4
- type: fk
5
- isNullable: true
6
- references:
7
- table: branch
8
- column: id
9
- onDelete: SET NULL
10
- onUpdate: CASCADE
11
3
  - name: person_id
12
4
  type: fk
13
5
  references:
@@ -20,7 +12,7 @@ columns:
20
12
  values: [payable, receivable]
21
13
  - name: status
22
14
  type: enum
23
- values: [draft, approved, open, partial, settled, canceled, overdue]
15
+ values: [draft, open, partial, settled, canceled, overdue]
24
16
  - name: document_number
25
17
  type: varchar
26
18
  length: 80
@@ -1,13 +1,5 @@
1
1
  columns:
2
2
  - type: pk
3
- - name: branch_id
4
- type: fk
5
- isNullable: true
6
- references:
7
- table: branch
8
- column: id
9
- onDelete: SET NULL
10
- onUpdate: CASCADE
11
3
  - name: person_id
12
4
  type: fk
13
5
  isNullable: true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hed-hog/finance",
3
- "version": "0.0.239",
3
+ "version": "0.0.240",
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",
12
+ "@hed-hog/tag": "0.0.240",
13
13
  "@hed-hog/api-pagination": "0.0.5",
14
+ "@hed-hog/api-prisma": "0.0.4",
14
15
  "@hed-hog/api-locale": "0.0.11",
15
- "@hed-hog/api-types": "0.0.1",
16
- "@hed-hog/tag": "0.0.239",
17
16
  "@hed-hog/api": "0.0.3",
18
- "@hed-hog/contact": "0.0.239",
19
- "@hed-hog/core": "0.0.239"
17
+ "@hed-hog/contact": "0.0.240",
18
+ "@hed-hog/api-types": "0.0.1",
19
+ "@hed-hog/core": "0.0.240"
20
20
  },
21
21
  "exports": {
22
22
  ".": {
@@ -2,16 +2,16 @@ import { Role, User } from '@hed-hog/api';
2
2
  import { Locale } from '@hed-hog/api-locale';
3
3
  import { Pagination } from '@hed-hog/api-pagination';
4
4
  import {
5
- Body,
6
- Controller,
7
- Get,
8
- Param,
9
- ParseIntPipe,
10
- Patch,
11
- Post,
12
- Query,
13
- UploadedFile,
14
- UseInterceptors,
5
+ Body,
6
+ Controller,
7
+ Get,
8
+ Param,
9
+ ParseIntPipe,
10
+ Patch,
11
+ Post,
12
+ Query,
13
+ UploadedFile,
14
+ UseInterceptors,
15
15
  } from '@nestjs/common';
16
16
  import { FileInterceptor } from '@nestjs/platform-express';
17
17
  import { CreateFinanceTagDto } from './dto/create-finance-tag.dto';
@@ -58,6 +58,21 @@ export class FinanceInstallmentsController {
58
58
  );
59
59
  }
60
60
 
61
+ @Patch('accounts-payable/installments/:id')
62
+ async updateAccountsPayableTitle(
63
+ @Param('id', ParseIntPipe) id: number,
64
+ @Body() data: CreateFinancialTitleDto,
65
+ @Locale() locale: string,
66
+ @User() user,
67
+ ) {
68
+ return this.financeService.updateAccountsPayableTitle(
69
+ id,
70
+ data,
71
+ locale,
72
+ user?.id,
73
+ );
74
+ }
75
+
61
76
  @Patch('accounts-payable/installments/:id/approve')
62
77
  async approveAccountsPayableTitle(
63
78
  @Param('id', ParseIntPipe) id: number,
@@ -82,6 +97,21 @@ export class FinanceInstallmentsController {
82
97
  );
83
98
  }
84
99
 
100
+ @Patch('accounts-payable/installments/:id/cancel')
101
+ async cancelAccountsPayableTitle(
102
+ @Param('id', ParseIntPipe) id: number,
103
+ @Body() data: RejectTitleDto,
104
+ @Locale() locale: string,
105
+ @User() user,
106
+ ) {
107
+ return this.financeService.cancelAccountsPayableTitle(
108
+ id,
109
+ data,
110
+ locale,
111
+ user?.id,
112
+ );
113
+ }
114
+
85
115
  @Post('accounts-payable/installments/:id/settlements')
86
116
  async settleAccountsPayableInstallment(
87
117
  @Param('id', ParseIntPipe) id: number,
@@ -168,6 +198,21 @@ export class FinanceInstallmentsController {
168
198
  );
169
199
  }
170
200
 
201
+ @Patch('accounts-receivable/installments/:id')
202
+ async updateAccountsReceivableTitle(
203
+ @Param('id', ParseIntPipe) id: number,
204
+ @Body() data: CreateFinancialTitleDto,
205
+ @Locale() locale: string,
206
+ @User() user,
207
+ ) {
208
+ return this.financeService.updateAccountsReceivableTitle(
209
+ id,
210
+ data,
211
+ locale,
212
+ user?.id,
213
+ );
214
+ }
215
+
171
216
  @Patch('accounts-receivable/installments/:id/approve')
172
217
  async approveAccountsReceivableTitle(
173
218
  @Param('id', ParseIntPipe) id: number,
@@ -181,6 +226,21 @@ export class FinanceInstallmentsController {
181
226
  );
182
227
  }
183
228
 
229
+ @Patch('accounts-receivable/installments/:id/cancel')
230
+ async cancelAccountsReceivableTitle(
231
+ @Param('id', ParseIntPipe) id: number,
232
+ @Body() data: RejectTitleDto,
233
+ @Locale() locale: string,
234
+ @User() user,
235
+ ) {
236
+ return this.financeService.cancelAccountsReceivableTitle(
237
+ id,
238
+ data,
239
+ locale,
240
+ user?.id,
241
+ );
242
+ }
243
+
184
244
  @Post('accounts-receivable/installments/:id/settlements')
185
245
  async settleAccountsReceivableInstallment(
186
246
  @Param('id', ParseIntPipe) id: number,