@hed-hog/finance 0.0.318 → 0.0.321

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 (92) hide show
  1. package/dist/dto/create-bank-account.dto.d.ts +1 -0
  2. package/dist/dto/create-bank-account.dto.d.ts.map +1 -1
  3. package/dist/dto/create-bank-account.dto.js +7 -0
  4. package/dist/dto/create-bank-account.dto.js.map +1 -1
  5. package/dist/dto/create-bank-statement-entry.dto.d.ts +8 -0
  6. package/dist/dto/create-bank-statement-entry.dto.d.ts.map +1 -0
  7. package/dist/dto/create-bank-statement-entry.dto.js +54 -0
  8. package/dist/dto/create-bank-statement-entry.dto.js.map +1 -0
  9. package/dist/dto/create-currency.dto.d.ts +6 -0
  10. package/dist/dto/create-currency.dto.d.ts.map +1 -0
  11. package/dist/dto/create-currency.dto.js +37 -0
  12. package/dist/dto/create-currency.dto.js.map +1 -0
  13. package/dist/dto/update-bank-account.dto.d.ts +1 -0
  14. package/dist/dto/update-bank-account.dto.d.ts.map +1 -1
  15. package/dist/dto/update-bank-account.dto.js +7 -0
  16. package/dist/dto/update-bank-account.dto.js.map +1 -1
  17. package/dist/dto/update-bank-statement-entry.dto.d.ts +6 -0
  18. package/dist/dto/update-bank-statement-entry.dto.d.ts.map +1 -0
  19. package/dist/dto/update-bank-statement-entry.dto.js +42 -0
  20. package/dist/dto/update-bank-statement-entry.dto.js.map +1 -0
  21. package/dist/dto/update-currency.dto.d.ts +7 -0
  22. package/dist/dto/update-currency.dto.d.ts.map +1 -0
  23. package/dist/dto/update-currency.dto.js +47 -0
  24. package/dist/dto/update-currency.dto.js.map +1 -0
  25. package/dist/finance-bank-accounts.controller.d.ts +25 -13
  26. package/dist/finance-bank-accounts.controller.d.ts.map +1 -1
  27. package/dist/finance-bank-accounts.controller.js +5 -3
  28. package/dist/finance-bank-accounts.controller.js.map +1 -1
  29. package/dist/finance-currencies.controller.d.ts +36 -0
  30. package/dist/finance-currencies.controller.d.ts.map +1 -0
  31. package/dist/finance-currencies.controller.js +74 -0
  32. package/dist/finance-currencies.controller.js.map +1 -0
  33. package/dist/finance-data.controller.d.ts +4 -0
  34. package/dist/finance-data.controller.d.ts.map +1 -1
  35. package/dist/finance-installments.controller.d.ts +3 -2
  36. package/dist/finance-installments.controller.d.ts.map +1 -1
  37. package/dist/finance-installments.controller.js +10 -6
  38. package/dist/finance-installments.controller.js.map +1 -1
  39. package/dist/finance-statements.controller.d.ts +61 -12
  40. package/dist/finance-statements.controller.d.ts.map +1 -1
  41. package/dist/finance-statements.controller.js +50 -8
  42. package/dist/finance-statements.controller.js.map +1 -1
  43. package/dist/finance-transfers.controller.d.ts +13 -8
  44. package/dist/finance-transfers.controller.d.ts.map +1 -1
  45. package/dist/finance-transfers.controller.js +11 -5
  46. package/dist/finance-transfers.controller.js.map +1 -1
  47. package/dist/finance.module.d.ts.map +1 -1
  48. package/dist/finance.module.js +2 -0
  49. package/dist/finance.module.js.map +1 -1
  50. package/dist/finance.service.d.ts +153 -35
  51. package/dist/finance.service.d.ts.map +1 -1
  52. package/dist/finance.service.js +468 -55
  53. package/dist/finance.service.js.map +1 -1
  54. package/hedhog/data/currency.yaml +14 -0
  55. package/hedhog/data/menu.yaml +16 -0
  56. package/hedhog/data/role.yaml +9 -1
  57. package/hedhog/data/route.yaml +78 -0
  58. package/hedhog/frontend/app/accounts-payable/approvals/page.tsx.ejs +87 -72
  59. package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +53 -25
  60. package/hedhog/frontend/app/accounts-receivable/installments/[id]/page.tsx.ejs +8 -0
  61. package/hedhog/frontend/app/accounts-receivable/installments/page.tsx.ejs +60 -24
  62. package/hedhog/frontend/app/administration/currencies/page.tsx.ejs +490 -0
  63. package/hedhog/frontend/app/cash-and-banks/bank-accounts/page.tsx.ejs +243 -65
  64. package/hedhog/frontend/app/cash-and-banks/bank-reconciliation/page.tsx.ejs +25 -3
  65. package/hedhog/frontend/app/cash-and-banks/statements/page.tsx.ejs +732 -61
  66. package/hedhog/frontend/app/cash-and-banks/transfers/page.tsx.ejs +101 -15
  67. package/hedhog/frontend/messages/en.json +58 -0
  68. package/hedhog/frontend/messages/pt.json +58 -0
  69. package/hedhog/table/bank_account.yaml +8 -0
  70. package/hedhog/table/bank_statement_line.yaml +1 -1
  71. package/hedhog/table/cashflow_projection.yaml +1 -1
  72. package/hedhog/table/currency.yaml +21 -0
  73. package/hedhog/table/financial_installment.yaml +2 -2
  74. package/hedhog/table/financial_title.yaml +1 -1
  75. package/hedhog/table/installment_allocation.yaml +1 -1
  76. package/hedhog/table/receivable_schedule.yaml +1 -1
  77. package/hedhog/table/settlement.yaml +1 -1
  78. package/hedhog/table/settlement_allocation.yaml +5 -5
  79. package/package.json +6 -6
  80. package/src/dto/create-bank-account.dto.ts +18 -1
  81. package/src/dto/create-bank-statement-entry.dto.ts +50 -0
  82. package/src/dto/create-currency.dto.ts +21 -0
  83. package/src/dto/update-bank-account.dto.ts +11 -1
  84. package/src/dto/update-bank-statement-entry.dto.ts +31 -0
  85. package/src/dto/update-currency.dto.ts +31 -0
  86. package/src/finance-bank-accounts.controller.ts +3 -2
  87. package/src/finance-currencies.controller.ts +44 -0
  88. package/src/finance-installments.controller.ts +9 -3
  89. package/src/finance-statements.controller.ts +40 -0
  90. package/src/finance-transfers.controller.ts +7 -1
  91. package/src/finance.module.ts +2 -0
  92. package/src/finance.service.ts +633 -55
@@ -1,6 +1,11 @@
1
1
  'use client';
2
2
 
3
- import { EmptyState, Page, PageHeader } from '@/components/entity-list';
3
+ import {
4
+ EmptyState,
5
+ Page,
6
+ PageHeader,
7
+ PaginationFooter,
8
+ } from '@/components/entity-list';
4
9
  import {
5
10
  AlertDialog,
6
11
  AlertDialogAction,
@@ -20,6 +25,7 @@ import {
20
25
  CardHeader,
21
26
  CardTitle,
22
27
  } from '@/components/ui/card';
28
+ import { EntityPicker } from '@/components/ui/entity-picker';
23
29
  import {
24
30
  Form,
25
31
  FormControl,
@@ -84,11 +90,21 @@ const bankAccountFormSchema = z.object({
84
90
  tipo: z.string().min(1, 'Tipo é obrigatório'),
85
91
  descricao: z.string().optional(),
86
92
  logoFileId: z.number().int().nullable().optional(),
93
+ currencyId: z.number().int().nullable().optional(),
94
+ dataInicial: z.string().optional(),
87
95
  saldoInicial: z.number().min(0, 'Saldo inicial inválido'),
88
96
  });
89
97
 
90
98
  type BankAccountFormValues = z.infer<typeof bankAccountFormSchema>;
91
99
 
100
+ type Currency = {
101
+ id: string;
102
+ code: string;
103
+ name: string;
104
+ symbol: string;
105
+ ativo: boolean;
106
+ };
107
+
92
108
  type BankAccount = {
93
109
  id: string;
94
110
  codigo: string;
@@ -98,9 +114,22 @@ type BankAccount = {
98
114
  conta: string;
99
115
  tipo: 'corrente' | 'poupanca' | 'investimento' | 'caixa';
100
116
  logoFileId: number | null;
117
+ currencyId: number | null;
118
+ currency: Currency | null;
101
119
  saldoAtual: number;
102
120
  saldoConciliado: number;
103
121
  ativo: boolean;
122
+ dataInicial: string | null;
123
+ };
124
+
125
+ type PaginatedBankAccountsResponse = {
126
+ data: BankAccount[];
127
+ total: number;
128
+ page: number;
129
+ pageSize: number;
130
+ prev: number | null;
131
+ next: number | null;
132
+ lastPage: number;
104
133
  };
105
134
 
106
135
  type UploadedFilePayload = {
@@ -113,6 +142,7 @@ type BankAccountDraftPayload = {
113
142
  values: BankAccountFormValues;
114
143
  logoFileId: number | null;
115
144
  logoPreviewUrl: string | null;
145
+ currencyId: number | null;
116
146
  };
117
147
 
118
148
  const BANK_ACCOUNT_FORM_DRAFT_STORAGE_KEY = 'finance-bank-account-form-draft';
@@ -167,6 +197,21 @@ function NovaContaSheet({
167
197
  const { request, showToastHandler, currentLocaleCode, getSettingValue } =
168
198
  useApp();
169
199
 
200
+ const { data: currenciesData, refetch: refetchCurrencies } = useQuery<
201
+ Currency[]
202
+ >({
203
+ queryKey: ['finance-currencies-for-select'],
204
+ queryFn: async () => {
205
+ const response = await request({
206
+ url: '/finance/currencies',
207
+ method: 'GET',
208
+ });
209
+ return (response.data || []) as Currency[];
210
+ },
211
+ placeholderData: [],
212
+ });
213
+ const currencies = currenciesData || [];
214
+
170
215
  const createSuccessMessage = t.has('messages.createSuccess')
171
216
  ? t('messages.createSuccess')
172
217
  : 'Conta bancária cadastrada com sucesso';
@@ -204,6 +249,8 @@ function NovaContaSheet({
204
249
  tipo: '',
205
250
  descricao: '',
206
251
  logoFileId: null,
252
+ currencyId: null,
253
+ dataInicial: new Date().toISOString().slice(0, 10),
207
254
  saldoInicial: 0,
208
255
  },
209
256
  });
@@ -228,6 +275,8 @@ function NovaContaSheet({
228
275
  (watchedFormValues.conta ?? '').trim() ||
229
276
  (watchedFormValues.tipo ?? '').trim() ||
230
277
  (watchedFormValues.descricao ?? '').trim() ||
278
+ (watchedFormValues.dataInicial ?? '').trim() !==
279
+ new Date().toISOString().slice(0, 10) ||
231
280
  (watchedFormValues.saldoInicial ?? 0) > 0 ||
232
281
  logoFileId != null
233
282
  ),
@@ -245,10 +294,15 @@ function NovaContaSheet({
245
294
  tipo: watchedFormValues.tipo ?? '',
246
295
  descricao: watchedFormValues.descricao ?? '',
247
296
  logoFileId: logoFileId ?? null,
297
+ dataInicial:
298
+ watchedFormValues.dataInicial ||
299
+ new Date().toISOString().slice(0, 10),
248
300
  saldoInicial: watchedFormValues.saldoInicial ?? 0,
301
+ currencyId: watchedFormValues.currencyId ?? null,
249
302
  },
250
303
  logoFileId: logoFileId ?? null,
251
304
  logoPreviewUrl,
305
+ currencyId: watchedFormValues.currencyId ?? null,
252
306
  }),
253
307
  [editingAccount, logoFileId, logoPreviewUrl, watchedFormValues]
254
308
  );
@@ -426,6 +480,15 @@ function NovaContaSheet({
426
480
  showToastHandler?.('success', logoRemoveSuccessMessage);
427
481
  };
428
482
 
483
+ useEffect(() => {
484
+ if (!open || !editingAccount) {
485
+ const brlCurrency = currencies.find((c) => c.code === 'BRL');
486
+ if (brlCurrency && open) {
487
+ form.setValue('currencyId', Number(brlCurrency.id));
488
+ }
489
+ }
490
+ }, [currencies, open, editingAccount, form]);
491
+
429
492
  useEffect(() => {
430
493
  if (!open) {
431
494
  return;
@@ -463,6 +526,10 @@ function NovaContaSheet({
463
526
  tipo: editingAccount.tipo,
464
527
  descricao: editingAccount.descricao,
465
528
  logoFileId: currentLogoFileId,
529
+ currencyId: editingAccount.currencyId ?? null,
530
+ dataInicial:
531
+ editingAccount.dataInicial ??
532
+ new Date().toISOString().slice(0, 10),
466
533
  saldoInicial: editingAccount.saldoAtual,
467
534
  }
468
535
  );
@@ -488,6 +555,8 @@ function NovaContaSheet({
488
555
  tipo: '',
489
556
  descricao: '',
490
557
  logoFileId: null,
558
+ currencyId: null,
559
+ dataInicial: new Date().toISOString().slice(0, 10),
491
560
  saldoInicial: 0,
492
561
  }
493
562
  );
@@ -509,6 +578,8 @@ function NovaContaSheet({
509
578
  type: values.tipo,
510
579
  description: values.descricao?.trim() || undefined,
511
580
  logo_file_id: nextLogoFileId,
581
+ currency_id: values.currencyId ?? null,
582
+ start_date: values.dataInicial || undefined,
512
583
  },
513
584
  });
514
585
  } else {
@@ -522,6 +593,8 @@ function NovaContaSheet({
522
593
  type: values.tipo,
523
594
  description: values.descricao?.trim() || undefined,
524
595
  logo_file_id: nextLogoFileId,
596
+ currency_id: values.currencyId ?? null,
597
+ start_date: values.dataInicial || undefined,
525
598
  initial_balance: values.saldoInicial,
526
599
  },
527
600
  });
@@ -550,6 +623,8 @@ function NovaContaSheet({
550
623
  tipo: '',
551
624
  descricao: '',
552
625
  logoFileId: null,
626
+ currencyId: null,
627
+ dataInicial: new Date().toISOString().slice(0, 10),
553
628
  saldoInicial: 0,
554
629
  });
555
630
  onOpenChange(false);
@@ -661,42 +736,42 @@ function NovaContaSheet({
661
736
  />
662
737
  </div>
663
738
 
664
- <div className="grid gap-4 md:grid-cols-2">
665
- <FormField
666
- control={form.control}
667
- name="tipo"
668
- render={({ field }) => (
669
- <FormItem>
670
- <FormLabel>{t('fields.type')}</FormLabel>
671
- <Select
672
- value={field.value}
673
- onValueChange={field.onChange}
674
- >
675
- <FormControl>
676
- <SelectTrigger className="w-full">
677
- <SelectValue placeholder={t('common.select')} />
678
- </SelectTrigger>
679
- </FormControl>
680
- <SelectContent>
681
- <SelectItem value="corrente">
682
- {t('types.corrente')}
683
- </SelectItem>
684
- <SelectItem value="poupanca">
685
- {t('types.poupanca')}
686
- </SelectItem>
687
- <SelectItem value="investimento">
688
- {t('types.investimento')}
689
- </SelectItem>
690
- <SelectItem value="caixa">
691
- {t('types.caixa')}
692
- </SelectItem>
693
- </SelectContent>
694
- </Select>
695
- <FormMessage />
696
- </FormItem>
697
- )}
698
- />
739
+ <FormField
740
+ control={form.control}
741
+ name="tipo"
742
+ render={({ field }) => (
743
+ <FormItem>
744
+ <FormLabel>{t('fields.type')}</FormLabel>
745
+ <Select
746
+ value={field.value}
747
+ onValueChange={field.onChange}
748
+ >
749
+ <FormControl>
750
+ <SelectTrigger className="w-full">
751
+ <SelectValue placeholder={t('common.select')} />
752
+ </SelectTrigger>
753
+ </FormControl>
754
+ <SelectContent>
755
+ <SelectItem value="corrente">
756
+ {t('types.corrente')}
757
+ </SelectItem>
758
+ <SelectItem value="poupanca">
759
+ {t('types.poupanca')}
760
+ </SelectItem>
761
+ <SelectItem value="investimento">
762
+ {t('types.investimento')}
763
+ </SelectItem>
764
+ <SelectItem value="caixa">
765
+ {t('types.caixa')}
766
+ </SelectItem>
767
+ </SelectContent>
768
+ </Select>
769
+ <FormMessage />
770
+ </FormItem>
771
+ )}
772
+ />
699
773
 
774
+ <div className="grid gap-4 md:grid-cols-2">
700
775
  <FormField
701
776
  control={form.control}
702
777
  name="saldoInicial"
@@ -720,25 +795,101 @@ function NovaContaSheet({
720
795
  </FormItem>
721
796
  )}
722
797
  />
798
+
799
+ <FormField
800
+ control={form.control}
801
+ name="dataInicial"
802
+ render={({ field }) => (
803
+ <FormItem>
804
+ <FormLabel>Data inicial</FormLabel>
805
+ <FormControl>
806
+ <Input
807
+ type="date"
808
+ {...field}
809
+ onChange={(e) =>
810
+ field.onChange(
811
+ e.target.value ||
812
+ new Date().toISOString().slice(0, 10)
813
+ )
814
+ }
815
+ disabled={!!editingAccount}
816
+ />
817
+ </FormControl>
818
+ <FormMessage />
819
+ </FormItem>
820
+ )}
821
+ />
723
822
  </div>
724
823
 
725
- <FormField
726
- control={form.control}
727
- name="descricao"
728
- render={({ field }) => (
729
- <FormItem>
730
- <FormLabel>{t('fields.description')}</FormLabel>
731
- <FormControl>
732
- <Input
733
- placeholder={t('fields.descriptionPlaceholder')}
734
- {...field}
735
- value={field.value || ''}
736
- />
737
- </FormControl>
738
- <FormMessage />
739
- </FormItem>
740
- )}
741
- />
824
+ <div className="grid gap-4 md:grid-cols-2">
825
+ <EntityPicker<Currency>
826
+ form={form as never}
827
+ name="currencyId"
828
+ label="Moeda"
829
+ placeholder="Selecione uma moeda"
830
+ searchPlaceholder="Buscar moeda..."
831
+ entityLabel="moeda"
832
+ valueType="number"
833
+ clearable
834
+ options={currencies}
835
+ getOptionValue={(c) => c.id}
836
+ getOptionLabel={(c) => `${c.symbol} ${c.code} — ${c.name}`}
837
+ createTitle="Nova moeda"
838
+ createDescription="Cadastre uma nova moeda para usar nas contas bancárias."
839
+ mapSearchToCreateValues={(s) => ({ code: s.toUpperCase() })}
840
+ createFields={[
841
+ {
842
+ name: 'code',
843
+ label: 'Código ISO',
844
+ placeholder: 'Ex.: BRL',
845
+ required: true,
846
+ },
847
+ {
848
+ name: 'name',
849
+ label: 'Nome',
850
+ placeholder: 'Ex.: Real Brasileiro',
851
+ required: true,
852
+ },
853
+ {
854
+ name: 'symbol',
855
+ label: 'Símbolo',
856
+ placeholder: 'Ex.: R$',
857
+ required: true,
858
+ },
859
+ ]}
860
+ onCreate={async (values) => {
861
+ const response = await request({
862
+ url: '/finance/currencies',
863
+ method: 'POST',
864
+ data: {
865
+ code: (values.code ?? '').toUpperCase(),
866
+ name: values.name,
867
+ symbol: values.symbol,
868
+ },
869
+ });
870
+ void refetchCurrencies();
871
+ return response.data as Currency;
872
+ }}
873
+ />
874
+
875
+ <FormField
876
+ control={form.control}
877
+ name="descricao"
878
+ render={({ field }) => (
879
+ <FormItem>
880
+ <FormLabel>{t('fields.description')}</FormLabel>
881
+ <FormControl>
882
+ <Input
883
+ placeholder={t('fields.descriptionPlaceholder')}
884
+ {...field}
885
+ value={field.value || ''}
886
+ />
887
+ </FormControl>
888
+ <FormMessage />
889
+ </FormItem>
890
+ )}
891
+ />
892
+ </div>
742
893
  </FinanceSheetSection>
743
894
 
744
895
  <FinanceSheetSection
@@ -862,19 +1013,34 @@ export default function ContasBancariasPage() {
862
1013
  const [accountIdToDelete, setAccountIdToDelete] = useState<string | null>(
863
1014
  null
864
1015
  );
865
- const { data: contasBancarias, refetch } = useQuery<BankAccount[]>({
866
- queryKey: ['finance-bank-accounts', currentLocaleCode],
867
- queryFn: async () => {
868
- const response = await request({
869
- url: '/finance/bank-accounts',
870
- method: 'GET',
871
- });
1016
+ const [page, setPage] = useState(1);
1017
+ const pageSize = 9;
1018
+ const { data: contasBancarias, refetch } =
1019
+ useQuery<PaginatedBankAccountsResponse>({
1020
+ queryKey: ['finance-bank-accounts', currentLocaleCode, page, pageSize],
1021
+ queryFn: async () => {
1022
+ const response = await request({
1023
+ url: '/finance/bank-accounts',
1024
+ method: 'GET',
1025
+ params: {
1026
+ page,
1027
+ pageSize,
1028
+ },
1029
+ });
872
1030
 
873
- return (response.data || []) as BankAccount[];
874
- },
875
- placeholderData: [],
876
- });
877
- const accounts = contasBancarias ?? [];
1031
+ return (response.data || {
1032
+ data: [],
1033
+ total: 0,
1034
+ page,
1035
+ pageSize,
1036
+ prev: null,
1037
+ next: null,
1038
+ lastPage: 1,
1039
+ }) as PaginatedBankAccountsResponse;
1040
+ },
1041
+ placeholderData: (old) => old,
1042
+ });
1043
+ const accounts = contasBancarias?.data ?? [];
878
1044
 
879
1045
  const tipoConfig = {
880
1046
  corrente: { label: t('types.corrente'), icon: Building2 },
@@ -1159,6 +1325,18 @@ export default function ContasBancariasPage() {
1159
1325
  onAction={handleCreate}
1160
1326
  />
1161
1327
  )}
1328
+ {accounts.length > 0 ? (
1329
+ <div className="border-t border-border/50 px-4 py-4 sm:px-6">
1330
+ <PaginationFooter
1331
+ currentPage={page}
1332
+ pageSize={pageSize}
1333
+ totalItems={contasBancarias?.total || 0}
1334
+ onPageChange={setPage}
1335
+ onPageSizeChange={() => undefined}
1336
+ pageSizeOptions={[9]}
1337
+ />
1338
+ </div>
1339
+ ) : null}
1162
1340
  </FinancePageSection>
1163
1341
  </Page>
1164
1342
  );
@@ -388,9 +388,20 @@ export default function ConciliacaoPage() {
388
388
  const response = await request({
389
389
  url: '/finance/bank-accounts',
390
390
  method: 'GET',
391
+ params: {
392
+ page: 1,
393
+ pageSize: 100,
394
+ },
391
395
  });
392
396
 
393
- return (response?.data || []) as BankAccount[];
397
+ const payload = response?.data as
398
+ | { data?: BankAccount[] }
399
+ | BankAccount[]
400
+ | undefined;
401
+
402
+ return (
403
+ Array.isArray(payload) ? payload : payload?.data || []
404
+ ) as BankAccount[];
394
405
  },
395
406
  });
396
407
 
@@ -437,11 +448,22 @@ export default function ConciliacaoPage() {
437
448
  }
438
449
 
439
450
  const response = await request({
440
- url: `/finance/statements?bank_account_id=${activeContaFilter}`,
451
+ url: '/finance/statements',
441
452
  method: 'GET',
453
+ params: {
454
+ bank_account_id: activeContaFilter,
455
+ page: 1,
456
+ pageSize: 500,
457
+ },
442
458
  });
443
459
 
444
- return (response?.data || []) as Statement[];
460
+ const payload = response?.data as
461
+ | { data?: Statement[] }
462
+ | Statement[]
463
+ | undefined;
464
+ return (
465
+ Array.isArray(payload) ? payload : payload?.data || []
466
+ ) as Statement[];
445
467
  },
446
468
  });
447
469