@hed-hog/finance 0.0.226 → 0.0.228

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 (55) hide show
  1. package/dist/dto/create-bank-account.dto.d.ts +9 -0
  2. package/dist/dto/create-bank-account.dto.d.ts.map +1 -0
  3. package/dist/dto/create-bank-account.dto.js +61 -0
  4. package/dist/dto/create-bank-account.dto.js.map +1 -0
  5. package/dist/dto/update-bank-account.dto.d.ts +9 -0
  6. package/dist/dto/update-bank-account.dto.d.ts.map +1 -0
  7. package/dist/dto/update-bank-account.dto.js +60 -0
  8. package/dist/dto/update-bank-account.dto.js.map +1 -0
  9. package/dist/finance-bank-accounts.controller.d.ts +47 -0
  10. package/dist/finance-bank-accounts.controller.d.ts.map +1 -0
  11. package/dist/finance-bank-accounts.controller.js +73 -0
  12. package/dist/finance-bank-accounts.controller.js.map +1 -0
  13. package/dist/finance-data.controller.d.ts +136 -0
  14. package/dist/finance-data.controller.d.ts.map +1 -0
  15. package/dist/finance-data.controller.js +36 -0
  16. package/dist/finance-data.controller.js.map +1 -0
  17. package/dist/finance-installments.controller.d.ts +149 -0
  18. package/dist/finance-installments.controller.d.ts.map +1 -0
  19. package/dist/finance-installments.controller.js +101 -0
  20. package/dist/finance-installments.controller.js.map +1 -0
  21. package/dist/finance-statements.controller.d.ts +15 -0
  22. package/dist/finance-statements.controller.d.ts.map +1 -0
  23. package/dist/finance-statements.controller.js +43 -0
  24. package/dist/finance-statements.controller.js.map +1 -0
  25. package/dist/finance.module.d.ts.map +1 -1
  26. package/dist/finance.module.js +10 -2
  27. package/dist/finance.module.js.map +1 -1
  28. package/dist/finance.service.d.ts +63 -5
  29. package/dist/finance.service.d.ts.map +1 -1
  30. package/dist/finance.service.js +198 -9
  31. package/dist/finance.service.js.map +1 -1
  32. package/dist/index.d.ts +4 -1
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +4 -1
  35. package/dist/index.js.map +1 -1
  36. package/hedhog/data/menu.yaml +5 -1
  37. package/hedhog/data/route.yaml +45 -0
  38. package/hedhog/frontend/app/accounts-payable/approvals/page.tsx.ejs +4 -1
  39. package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +307 -112
  40. package/hedhog/frontend/app/accounts-receivable/collections-default/page.tsx.ejs +25 -8
  41. package/hedhog/frontend/app/accounts-receivable/installments/page.tsx.ejs +301 -100
  42. package/hedhog/frontend/app/cash-and-banks/bank-accounts/page.tsx.ejs +378 -71
  43. package/hedhog/frontend/app/cash-and-banks/bank-reconciliation/page.tsx.ejs +6 -3
  44. package/hedhog/frontend/app/cash-and-banks/statements/page.tsx.ejs +4 -2
  45. package/hedhog/frontend/app/cash-and-banks/transfers/page.tsx.ejs +6 -7
  46. package/package.json +4 -4
  47. package/src/dto/create-bank-account.dto.ts +49 -0
  48. package/src/dto/update-bank-account.dto.ts +45 -0
  49. package/src/finance-bank-accounts.controller.ts +43 -0
  50. package/src/finance-data.controller.ts +14 -0
  51. package/src/{finance.controller.ts → finance-installments.controller.ts} +8 -13
  52. package/src/finance-statements.controller.ts +20 -0
  53. package/src/finance.module.ts +10 -2
  54. package/src/finance.service.ts +239 -9
  55. package/src/index.ts +4 -1
@@ -0,0 +1,45 @@
1
+ import { getLocaleText } from '@hed-hog/api-locale';
2
+ import { IsOptional, IsString } from 'class-validator';
3
+
4
+ export class UpdateBankAccountDto {
5
+ @IsOptional()
6
+ @IsString({
7
+ message: (args) => getLocaleText('validation.bankMustBeString', args.value),
8
+ })
9
+ bank?: string;
10
+
11
+ @IsOptional()
12
+ @IsString({
13
+ message: (args) =>
14
+ getLocaleText('validation.branchMustBeString', args.value),
15
+ })
16
+ branch?: string;
17
+
18
+ @IsOptional()
19
+ @IsString({
20
+ message: (args) =>
21
+ getLocaleText('validation.accountMustBeString', args.value),
22
+ })
23
+ account?: string;
24
+
25
+ @IsOptional()
26
+ @IsString({
27
+ message: (args) =>
28
+ getLocaleText('validation.accountTypeMustBeString', args.value),
29
+ })
30
+ type?: string;
31
+
32
+ @IsOptional()
33
+ @IsString({
34
+ message: (args) =>
35
+ getLocaleText('validation.descriptionMustBeString', args.value),
36
+ })
37
+ description?: string;
38
+
39
+ @IsOptional()
40
+ @IsString({
41
+ message: (args) =>
42
+ getLocaleText('validation.statusMustBeString', args.value),
43
+ })
44
+ status?: 'active' | 'inactive';
45
+ }
@@ -0,0 +1,43 @@
1
+ import { Role, User } from '@hed-hog/api';
2
+ import {
3
+ Body,
4
+ Controller,
5
+ Delete,
6
+ Get,
7
+ Param,
8
+ ParseIntPipe,
9
+ Patch,
10
+ Post,
11
+ } from '@nestjs/common';
12
+ import { CreateBankAccountDto } from './dto/create-bank-account.dto';
13
+ import { UpdateBankAccountDto } from './dto/update-bank-account.dto';
14
+ import { FinanceService } from './finance.service';
15
+
16
+ @Role()
17
+ @Controller('finance')
18
+ export class FinanceBankAccountsController {
19
+ constructor(private readonly financeService: FinanceService) {}
20
+
21
+ @Get('bank-accounts')
22
+ async listBankAccounts() {
23
+ return this.financeService.listBankAccounts();
24
+ }
25
+
26
+ @Post('bank-accounts')
27
+ async createBankAccount(@Body() data: CreateBankAccountDto, @User() user) {
28
+ return this.financeService.createBankAccount(data, user?.id);
29
+ }
30
+
31
+ @Patch('bank-accounts/:id')
32
+ async updateBankAccount(
33
+ @Param('id', ParseIntPipe) id: number,
34
+ @Body() data: UpdateBankAccountDto,
35
+ ) {
36
+ return this.financeService.updateBankAccount(id, data);
37
+ }
38
+
39
+ @Delete('bank-accounts/:id')
40
+ async deleteBankAccount(@Param('id', ParseIntPipe) id: number) {
41
+ return this.financeService.deleteBankAccount(id);
42
+ }
43
+ }
@@ -0,0 +1,14 @@
1
+ import { Role } from '@hed-hog/api';
2
+ import { Controller, Get } from '@nestjs/common';
3
+ import { FinanceService } from './finance.service';
4
+
5
+ @Role()
6
+ @Controller('finance')
7
+ export class FinanceDataController {
8
+ constructor(private readonly financeService: FinanceService) {}
9
+
10
+ @Get('data')
11
+ async getData() {
12
+ return this.financeService.getData();
13
+ }
14
+ }
@@ -2,27 +2,22 @@ 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
- Post,
11
- Query,
5
+ Body,
6
+ Controller,
7
+ Get,
8
+ Param,
9
+ ParseIntPipe,
10
+ Post,
11
+ Query,
12
12
  } from '@nestjs/common';
13
13
  import { CreateFinancialTitleDto } from './dto/create-financial-title.dto';
14
14
  import { FinanceService } from './finance.service';
15
15
 
16
16
  @Role()
17
17
  @Controller('finance')
18
- export class FinanceController {
18
+ export class FinanceInstallmentsController {
19
19
  constructor(private readonly financeService: FinanceService) {}
20
20
 
21
- @Get('data')
22
- async getData() {
23
- return this.financeService.getData();
24
- }
25
-
26
21
  @Get('accounts-payable/installments')
27
22
  async listAccountsPayableInstallments(
28
23
  @Pagination() paginationParams,
@@ -0,0 +1,20 @@
1
+ import { Role } from '@hed-hog/api';
2
+ import { Controller, Get, Query } from '@nestjs/common';
3
+ import { FinanceService } from './finance.service';
4
+
5
+ @Role()
6
+ @Controller('finance')
7
+ export class FinanceStatementsController {
8
+ constructor(private readonly financeService: FinanceService) {}
9
+
10
+ @Get('statements')
11
+ async listBankStatements(@Query('bank_account_id') bankAccountId?: string) {
12
+ const parsedBankAccountId = bankAccountId
13
+ ? Number.parseInt(bankAccountId, 10)
14
+ : undefined;
15
+
16
+ return this.financeService.listBankStatements(
17
+ Number.isNaN(parsedBankAccountId) ? undefined : parsedBankAccountId,
18
+ );
19
+ }
20
+ }
@@ -3,7 +3,10 @@ import { PaginationModule } from '@hed-hog/api-pagination';
3
3
  import { PrismaModule } from '@hed-hog/api-prisma';
4
4
  import { forwardRef, Module } from '@nestjs/common';
5
5
  import { ConfigModule } from '@nestjs/config';
6
- import { FinanceController } from './finance.controller';
6
+ import { FinanceBankAccountsController } from './finance-bank-accounts.controller';
7
+ import { FinanceDataController } from './finance-data.controller';
8
+ import { FinanceInstallmentsController } from './finance-installments.controller';
9
+ import { FinanceStatementsController } from './finance-statements.controller';
7
10
  import { FinanceService } from './finance.service';
8
11
 
9
12
  @Module({
@@ -13,7 +16,12 @@ import { FinanceService } from './finance.service';
13
16
  forwardRef(() => PrismaModule),
14
17
  forwardRef(() => LocaleModule)
15
18
  ],
16
- controllers: [FinanceController],
19
+ controllers: [
20
+ FinanceDataController,
21
+ FinanceInstallmentsController,
22
+ FinanceBankAccountsController,
23
+ FinanceStatementsController,
24
+ ],
17
25
  providers: [FinanceService],
18
26
  exports: [FinanceService],
19
27
  })
@@ -6,7 +6,9 @@ import {
6
6
  Injectable,
7
7
  NotFoundException,
8
8
  } from '@nestjs/common';
9
+ import { CreateBankAccountDto } from './dto/create-bank-account.dto';
9
10
  import { CreateFinancialTitleDto } from './dto/create-financial-title.dto';
11
+ import { UpdateBankAccountDto } from './dto/update-bank-account.dto';
10
12
 
11
13
  type TitleType = 'payable' | 'receivable';
12
14
 
@@ -109,6 +111,157 @@ export class FinanceService {
109
111
  return this.createTitle(data, 'receivable', locale, userId);
110
112
  }
111
113
 
114
+ async listBankAccounts() {
115
+ const bankAccounts = await this.prisma.bank_account.findMany({
116
+ include: {
117
+ bank_statement_line: {
118
+ select: {
119
+ amount_cents: true,
120
+ status: true,
121
+ },
122
+ },
123
+ },
124
+ orderBy: [{ code: 'asc' }, { name: 'asc' }],
125
+ });
126
+
127
+ return bankAccounts.map((bankAccount) => this.mapBankAccountToFront(bankAccount));
128
+ }
129
+
130
+ async listBankStatements(bankAccountId?: number) {
131
+ const statements = await this.prisma.bank_statement_line.findMany({
132
+ where: {
133
+ ...(bankAccountId ? { bank_account_id: bankAccountId } : {}),
134
+ },
135
+ include: {
136
+ bank_account: {
137
+ select: {
138
+ id: true,
139
+ },
140
+ },
141
+ },
142
+ orderBy: [{ posted_date: 'desc' }, { id: 'desc' }],
143
+ });
144
+
145
+ return statements.map((statement) => ({
146
+ id: String(statement.id),
147
+ contaBancariaId: String(statement.bank_account_id),
148
+ data: statement.posted_date.toISOString(),
149
+ descricao: statement.description,
150
+ valor: this.fromCents(statement.amount_cents),
151
+ tipo: statement.amount_cents >= 0 ? 'entrada' : 'saida',
152
+ statusConciliacao: this.mapStatementStatusToPt(statement.status),
153
+ }));
154
+ }
155
+
156
+ async createBankAccount(data: CreateBankAccountDto, userId?: number) {
157
+ const accountType = this.mapAccountTypeFromPt(data.type);
158
+
159
+ const code = this.generateBankAccountCode(data.bank, data.account);
160
+ const name = data.description?.trim() || data.bank;
161
+
162
+ const createdAccount = await this.prisma.bank_account.create({
163
+ data: {
164
+ code,
165
+ name,
166
+ bank_name: data.bank,
167
+ agency: data.branch || null,
168
+ account_number: data.account || null,
169
+ account_type: accountType,
170
+ status: 'active',
171
+ },
172
+ });
173
+
174
+ if (data.initial_balance && data.initial_balance > 0) {
175
+ const statement = await this.prisma.bank_statement.create({
176
+ data: {
177
+ bank_account_id: createdAccount.id,
178
+ source_type: 'csv',
179
+ imported_at: new Date(),
180
+ imported_by_user_id: userId,
181
+ },
182
+ });
183
+
184
+ await this.prisma.bank_statement_line.create({
185
+ data: {
186
+ bank_statement_id: statement.id,
187
+ bank_account_id: createdAccount.id,
188
+ posted_date: new Date(),
189
+ amount_cents: this.toCents(data.initial_balance),
190
+ description: 'Saldo inicial',
191
+ status: 'reconciled',
192
+ dedupe_key: `initial-balance-${createdAccount.id}-${Date.now()}`,
193
+ },
194
+ });
195
+ }
196
+
197
+ const account = await this.prisma.bank_account.findUnique({
198
+ where: { id: createdAccount.id },
199
+ include: {
200
+ bank_statement_line: {
201
+ select: {
202
+ amount_cents: true,
203
+ status: true,
204
+ },
205
+ },
206
+ },
207
+ });
208
+
209
+ return this.mapBankAccountToFront(account);
210
+ }
211
+
212
+ async updateBankAccount(id: number, data: UpdateBankAccountDto) {
213
+ const current = await this.prisma.bank_account.findUnique({
214
+ where: { id },
215
+ select: { id: true },
216
+ });
217
+
218
+ if (!current) {
219
+ throw new NotFoundException('Bank account not found');
220
+ }
221
+
222
+ const updated = await this.prisma.bank_account.update({
223
+ where: { id },
224
+ data: {
225
+ bank_name: data.bank,
226
+ agency: data.branch,
227
+ account_number: data.account,
228
+ name: data.description,
229
+ account_type: data.type ? this.mapAccountTypeFromPt(data.type) : undefined,
230
+ status: data.status,
231
+ },
232
+ include: {
233
+ bank_statement_line: {
234
+ select: {
235
+ amount_cents: true,
236
+ status: true,
237
+ },
238
+ },
239
+ },
240
+ });
241
+
242
+ return this.mapBankAccountToFront(updated);
243
+ }
244
+
245
+ async deleteBankAccount(id: number) {
246
+ const current = await this.prisma.bank_account.findUnique({
247
+ where: { id },
248
+ select: { id: true },
249
+ });
250
+
251
+ if (!current) {
252
+ throw new NotFoundException('Bank account not found');
253
+ }
254
+
255
+ await this.prisma.bank_account.update({
256
+ where: { id },
257
+ data: {
258
+ status: 'inactive',
259
+ },
260
+ });
261
+
262
+ return { success: true };
263
+ }
264
+
112
265
  private async listTitles(
113
266
  titleType: TitleType,
114
267
  paginationParams: PaginationDTO,
@@ -326,18 +479,18 @@ export class FinanceService {
326
479
 
327
480
  private async loadBankAccounts() {
328
481
  const bankAccounts = await this.prisma.bank_account.findMany({
482
+ include: {
483
+ bank_statement_line: {
484
+ select: {
485
+ amount_cents: true,
486
+ status: true,
487
+ },
488
+ },
489
+ },
329
490
  orderBy: [{ code: 'asc' }, { name: 'asc' }],
330
491
  });
331
492
 
332
- return bankAccounts.map((bankAccount) => ({
333
- id: String(bankAccount.id),
334
- codigo: bankAccount.code,
335
- descricao: bankAccount.name,
336
- banco: bankAccount.bank_name,
337
- agencia: bankAccount.agency,
338
- conta: bankAccount.account_number,
339
- ativo: bankAccount.status === 'active',
340
- }));
493
+ return bankAccounts.map((bankAccount) => this.mapBankAccountToFront(bankAccount));
341
494
  }
342
495
 
343
496
  private async loadTags() {
@@ -519,6 +672,83 @@ export class FinanceService {
519
672
  return paymentMethodMap[paymentMethodType] || undefined;
520
673
  }
521
674
 
675
+ private mapAccountTypeToPt(accountType?: string | null) {
676
+ const accountTypeMap = {
677
+ checking: 'corrente',
678
+ savings: 'poupanca',
679
+ investment: 'investimento',
680
+ cash: 'caixa',
681
+ other: 'corrente',
682
+ };
683
+
684
+ return accountTypeMap[accountType] || 'corrente';
685
+ }
686
+
687
+ private mapAccountTypeFromPt(accountType?: string | null) {
688
+ const accountTypeMap = {
689
+ corrente: 'checking',
690
+ poupanca: 'savings',
691
+ investimento: 'investment',
692
+ caixa: 'cash',
693
+ other: 'other',
694
+ };
695
+
696
+ return accountTypeMap[accountType] || 'checking';
697
+ }
698
+
699
+ private mapBankAccountToFront(bankAccount: any) {
700
+ const currentCents = (bankAccount.bank_statement_line || []).reduce(
701
+ (acc, line) => acc + line.amount_cents,
702
+ 0,
703
+ );
704
+
705
+ const reconciledCents = (bankAccount.bank_statement_line || []).reduce(
706
+ (acc, line) =>
707
+ line.status === 'reconciled' || line.status === 'adjusted'
708
+ ? acc + line.amount_cents
709
+ : acc,
710
+ 0,
711
+ );
712
+
713
+ return {
714
+ id: String(bankAccount.id),
715
+ codigo: bankAccount.code,
716
+ descricao: bankAccount.name,
717
+ banco: bankAccount.bank_name || bankAccount.name,
718
+ agencia: bankAccount.agency || '-',
719
+ conta: bankAccount.account_number || '-',
720
+ tipo: this.mapAccountTypeToPt(bankAccount.account_type),
721
+ saldoAtual: this.fromCents(currentCents),
722
+ saldoConciliado: this.fromCents(reconciledCents),
723
+ ativo: bankAccount.status === 'active',
724
+ };
725
+ }
726
+
727
+ private mapStatementStatusToPt(status?: string | null) {
728
+ const statusMap = {
729
+ pending: 'pendente',
730
+ reconciled: 'conciliado',
731
+ adjusted: 'ajustado',
732
+ ignored: 'estornado',
733
+ };
734
+
735
+ return statusMap[status] || 'pendente';
736
+ }
737
+
738
+ private generateBankAccountCode(bankName?: string, accountNumber?: string) {
739
+ const bankPrefix = (bankName || 'ACC')
740
+ .replace(/[^A-Za-z0-9]/g, '')
741
+ .slice(0, 4)
742
+ .toUpperCase();
743
+
744
+ const accountSuffix = (accountNumber || '')
745
+ .replace(/\D/g, '')
746
+ .slice(-4)
747
+ .padStart(4, '0');
748
+
749
+ return `${bankPrefix || 'ACC'}-${accountSuffix}`;
750
+ }
751
+
522
752
  private toCents(value: number) {
523
753
  return Math.round(value * 100);
524
754
  }
package/src/index.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  export * from './dto/create-financial-title.dto';
2
- export * from './finance.controller';
2
+ export * from './finance-bank-accounts.controller';
3
+ export * from './finance-data.controller';
4
+ export * from './finance-installments.controller';
5
+ export * from './finance-statements.controller';
3
6
  export * from './finance.module';
4
7
  export * from './finance.service';
5
8