@hed-hog/finance 0.0.350 → 0.0.351

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 (112) hide show
  1. package/dist/dto/create-financial-title.dto.d.ts +6 -0
  2. package/dist/dto/create-financial-title.dto.d.ts.map +1 -1
  3. package/dist/dto/create-financial-title.dto.js +27 -1
  4. package/dist/dto/create-financial-title.dto.js.map +1 -1
  5. package/dist/finance-data.controller.d.ts +12 -0
  6. package/dist/finance-data.controller.d.ts.map +1 -1
  7. package/dist/finance-installments.controller.d.ts +120 -0
  8. package/dist/finance-installments.controller.d.ts.map +1 -1
  9. package/dist/finance-realtime.controller.d.ts +7 -0
  10. package/dist/finance-realtime.controller.d.ts.map +1 -0
  11. package/dist/finance-realtime.controller.js +34 -0
  12. package/dist/finance-realtime.controller.js.map +1 -0
  13. package/dist/finance-realtime.service.d.ts +36 -0
  14. package/dist/finance-realtime.service.d.ts.map +1 -0
  15. package/dist/finance-realtime.service.js +59 -0
  16. package/dist/finance-realtime.service.js.map +1 -0
  17. package/dist/finance-statements.controller.d.ts +6 -0
  18. package/dist/finance-statements.controller.d.ts.map +1 -1
  19. package/dist/finance.module.d.ts.map +1 -1
  20. package/dist/finance.module.js +28 -0
  21. package/dist/finance.module.js.map +1 -1
  22. package/dist/finance.service.d.ts +142 -1
  23. package/dist/finance.service.d.ts.map +1 -1
  24. package/dist/finance.service.js +134 -19
  25. package/dist/finance.service.js.map +1 -1
  26. package/dist/mcp-tools/finance-audit-logs.mcp-tools.d.ts +8 -0
  27. package/dist/mcp-tools/finance-audit-logs.mcp-tools.d.ts.map +1 -0
  28. package/dist/mcp-tools/finance-audit-logs.mcp-tools.js +60 -0
  29. package/dist/mcp-tools/finance-audit-logs.mcp-tools.js.map +1 -0
  30. package/dist/mcp-tools/finance-bank-accounts.mcp-tools.d.ts +16 -0
  31. package/dist/mcp-tools/finance-bank-accounts.mcp-tools.d.ts.map +1 -0
  32. package/dist/mcp-tools/finance-bank-accounts.mcp-tools.js +121 -0
  33. package/dist/mcp-tools/finance-bank-accounts.mcp-tools.js.map +1 -0
  34. package/dist/mcp-tools/finance-categories.mcp-tools.d.ts +20 -0
  35. package/dist/mcp-tools/finance-categories.mcp-tools.d.ts.map +1 -0
  36. package/dist/mcp-tools/finance-categories.mcp-tools.js +135 -0
  37. package/dist/mcp-tools/finance-categories.mcp-tools.js.map +1 -0
  38. package/dist/mcp-tools/finance-collections.mcp-tools.d.ts +16 -0
  39. package/dist/mcp-tools/finance-collections.mcp-tools.d.ts.map +1 -0
  40. package/dist/mcp-tools/finance-collections.mcp-tools.js +91 -0
  41. package/dist/mcp-tools/finance-collections.mcp-tools.js.map +1 -0
  42. package/dist/mcp-tools/finance-cost-centers.mcp-tools.d.ts +16 -0
  43. package/dist/mcp-tools/finance-cost-centers.mcp-tools.d.ts.map +1 -0
  44. package/dist/mcp-tools/finance-cost-centers.mcp-tools.js +114 -0
  45. package/dist/mcp-tools/finance-cost-centers.mcp-tools.js.map +1 -0
  46. package/dist/mcp-tools/finance-currencies.mcp-tools.d.ts +16 -0
  47. package/dist/mcp-tools/finance-currencies.mcp-tools.d.ts.map +1 -0
  48. package/dist/mcp-tools/finance-currencies.mcp-tools.js +120 -0
  49. package/dist/mcp-tools/finance-currencies.mcp-tools.js.map +1 -0
  50. package/dist/mcp-tools/finance-data.mcp-tools.d.ts +15 -0
  51. package/dist/mcp-tools/finance-data.mcp-tools.d.ts.map +1 -0
  52. package/dist/mcp-tools/finance-data.mcp-tools.js +80 -0
  53. package/dist/mcp-tools/finance-data.mcp-tools.js.map +1 -0
  54. package/dist/mcp-tools/finance-installments.mcp-tools.d.ts +93 -0
  55. package/dist/mcp-tools/finance-installments.mcp-tools.d.ts.map +1 -0
  56. package/dist/mcp-tools/finance-installments.mcp-tools.js +646 -0
  57. package/dist/mcp-tools/finance-installments.mcp-tools.js.map +1 -0
  58. package/dist/mcp-tools/finance-period-close.mcp-tools.d.ts +9 -0
  59. package/dist/mcp-tools/finance-period-close.mcp-tools.d.ts.map +1 -0
  60. package/dist/mcp-tools/finance-period-close.mcp-tools.js +79 -0
  61. package/dist/mcp-tools/finance-period-close.mcp-tools.js.map +1 -0
  62. package/dist/mcp-tools/finance-reports.mcp-tools.d.ts +10 -0
  63. package/dist/mcp-tools/finance-reports.mcp-tools.d.ts.map +1 -0
  64. package/dist/mcp-tools/finance-reports.mcp-tools.js +89 -0
  65. package/dist/mcp-tools/finance-reports.mcp-tools.js.map +1 -0
  66. package/dist/mcp-tools/finance-statements.mcp-tools.d.ts +34 -0
  67. package/dist/mcp-tools/finance-statements.mcp-tools.d.ts.map +1 -0
  68. package/dist/mcp-tools/finance-statements.mcp-tools.js +253 -0
  69. package/dist/mcp-tools/finance-statements.mcp-tools.js.map +1 -0
  70. package/dist/mcp-tools/finance-transfers.mcp-tools.d.ts +9 -0
  71. package/dist/mcp-tools/finance-transfers.mcp-tools.d.ts.map +1 -0
  72. package/dist/mcp-tools/finance-transfers.mcp-tools.js +79 -0
  73. package/dist/mcp-tools/finance-transfers.mcp-tools.js.map +1 -0
  74. package/hedhog/data/route.yaml +659 -1
  75. package/hedhog/frontend/app/_components/finance-title-actions-menu.tsx.ejs +9 -3
  76. package/hedhog/frontend/app/_lib/http-error.ts.ejs +105 -0
  77. package/hedhog/frontend/app/_lib/use-finance-realtime-refresh.ts.ejs +62 -0
  78. package/hedhog/frontend/app/accounts-payable/approvals/page.tsx.ejs +3 -0
  79. package/hedhog/frontend/app/accounts-payable/installments/[id]/page.tsx.ejs +5 -5
  80. package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +776 -344
  81. package/hedhog/frontend/app/accounts-receivable/collections-default/page.tsx.ejs +7 -13
  82. package/hedhog/frontend/app/accounts-receivable/installments/[id]/page.tsx.ejs +5 -5
  83. package/hedhog/frontend/app/accounts-receivable/installments/page.tsx.ejs +802 -355
  84. package/hedhog/frontend/app/administration/categories/page.tsx.ejs +9 -3
  85. package/hedhog/frontend/app/administration/cost-centers/page.tsx.ejs +9 -3
  86. package/hedhog/frontend/app/administration/currencies/page.tsx.ejs +9 -3
  87. package/hedhog/frontend/app/administration/period-close/page.tsx.ejs +9 -3
  88. package/hedhog/frontend/app/cash-and-banks/bank-accounts/page.tsx.ejs +7 -3
  89. package/hedhog/frontend/app/cash-and-banks/bank-reconciliation/page.tsx.ejs +3 -3
  90. package/hedhog/frontend/app/cash-and-banks/statements/page.tsx.ejs +28 -7
  91. package/hedhog/frontend/app/cash-and-banks/transfers/page.tsx.ejs +12 -3
  92. package/hedhog/frontend/messages/en.json +63 -3
  93. package/hedhog/frontend/messages/pt.json +63 -3
  94. package/hedhog/table/financial_title.yaml +10 -0
  95. package/package.json +5 -5
  96. package/src/dto/create-financial-title.dto.ts +23 -0
  97. package/src/finance-realtime.controller.ts +12 -0
  98. package/src/finance-realtime.service.ts +106 -0
  99. package/src/finance.module.ts +28 -0
  100. package/src/finance.service.ts +184 -45
  101. package/src/mcp-tools/finance-audit-logs.mcp-tools.ts +38 -0
  102. package/src/mcp-tools/finance-bank-accounts.mcp-tools.ts +76 -0
  103. package/src/mcp-tools/finance-categories.mcp-tools.ts +86 -0
  104. package/src/mcp-tools/finance-collections.mcp-tools.ts +50 -0
  105. package/src/mcp-tools/finance-cost-centers.mcp-tools.ts +69 -0
  106. package/src/mcp-tools/finance-currencies.mcp-tools.ts +75 -0
  107. package/src/mcp-tools/finance-data.mcp-tools.ts +43 -0
  108. package/src/mcp-tools/finance-installments.mcp-tools.ts +560 -0
  109. package/src/mcp-tools/finance-period-close.mcp-tools.ts +53 -0
  110. package/src/mcp-tools/finance-reports.mcp-tools.ts +59 -0
  111. package/src/mcp-tools/finance-statements.mcp-tools.ts +202 -0
  112. package/src/mcp-tools/finance-transfers.mcp-tools.ts +53 -0
@@ -6,6 +6,7 @@ import {
6
6
  } from '@hed-hog/api-pagination';
7
7
  import { PrismaService } from '@hed-hog/api-prisma';
8
8
  import { AiService, FileService, IntegrationDeveloperApiService, SettingService } from '@hed-hog/core';
9
+ import { FinanceRealtimeService } from './finance-realtime.service';
9
10
  import {
10
11
  BadRequestException,
11
12
  ConflictException,
@@ -89,6 +90,7 @@ export class FinanceService {
89
90
  private readonly fileService: FileService,
90
91
  @Inject(forwardRef(() => IntegrationDeveloperApiService))
91
92
  private readonly integrationApi: IntegrationDeveloperApiService,
93
+ private readonly financeRealtime: FinanceRealtimeService,
92
94
  ) {}
93
95
 
94
96
  async getAgentExtractInfoFromFile(
@@ -1501,6 +1503,7 @@ export class FinanceService {
1501
1503
  } as any);
1502
1504
 
1503
1505
  const settings = await this.loadFinancialScenarioSettings();
1506
+ this.financeRealtime.publish({ domain: 'installment', type: 'updated' });
1504
1507
  return {
1505
1508
  success: true,
1506
1509
  cenarios: settings.cenarios,
@@ -2140,6 +2143,7 @@ export class FinanceService {
2140
2143
  },
2141
2144
  });
2142
2145
 
2146
+ this.financeRealtime.publish({ domain: 'collection', type: 'created', entityId: personId });
2143
2147
  return {
2144
2148
  id: String(log.id),
2145
2149
  success: true,
@@ -2260,6 +2264,7 @@ export class FinanceService {
2260
2264
  };
2261
2265
  });
2262
2266
 
2267
+ this.financeRealtime.publish({ domain: 'collection', type: 'updated', entityId: personId });
2263
2268
  return {
2264
2269
  success: true,
2265
2270
  titleId: String(created.titleId),
@@ -2749,6 +2754,7 @@ export class FinanceService {
2749
2754
  });
2750
2755
  });
2751
2756
 
2757
+ this.financeRealtime.publish({ domain: 'bank_reconciliation', type: 'deleted', entityId: id });
2752
2758
  return { success: true };
2753
2759
  }
2754
2760
 
@@ -2791,6 +2797,7 @@ export class FinanceService {
2791
2797
  },
2792
2798
  });
2793
2799
 
2800
+ this.financeRealtime.publish({ domain: 'tag', type: 'created', entityId: createdTag.id });
2794
2801
  return {
2795
2802
  id: String(createdTag.id),
2796
2803
  nome: createdTag.slug,
@@ -3120,6 +3127,7 @@ export class FinanceService {
3120
3127
  payload: { id: transferResult.id, sourceAccountId, destinationAccountId, amount, date: postedDate.toISOString() },
3121
3128
  }).catch(() => null);
3122
3129
 
3130
+ this.financeRealtime.publish({ domain: 'transfer', type: 'created' });
3123
3131
  return transferResult;
3124
3132
  }
3125
3133
 
@@ -3729,6 +3737,7 @@ export class FinanceService {
3729
3737
  return line;
3730
3738
  });
3731
3739
 
3740
+ this.financeRealtime.publish({ domain: 'bank_statement', type: 'created' });
3732
3741
  return {
3733
3742
  id: String(created.id),
3734
3743
  contaBancariaId: String(created.bank_account_id),
@@ -3812,6 +3821,7 @@ export class FinanceService {
3812
3821
  });
3813
3822
  });
3814
3823
 
3824
+ this.financeRealtime.publish({ domain: 'bank_statement', type: 'created', entityId: created.bank_account_id });
3815
3825
  return {
3816
3826
  id: String(created.id),
3817
3827
  contaBancariaId: String(created.bank_account_id),
@@ -3915,6 +3925,7 @@ export class FinanceService {
3915
3925
  return result;
3916
3926
  });
3917
3927
 
3928
+ this.financeRealtime.publish({ domain: 'bank_statement', type: 'updated', entityId: id });
3918
3929
  return {
3919
3930
  id: String(updated.id),
3920
3931
  contaBancariaId: String(updated.bank_account_id),
@@ -3932,7 +3943,7 @@ export class FinanceService {
3932
3943
  }
3933
3944
 
3934
3945
  async deleteBankStatementEntry(id: number, userId?: number) {
3935
- return this.prisma.$transaction(async (tx) => {
3946
+ const result = await this.prisma.$transaction(async (tx) => {
3936
3947
  const statementLine = await tx.bank_statement_line.findUnique({
3937
3948
  where: { id },
3938
3949
  include: {
@@ -3996,6 +4007,8 @@ export class FinanceService {
3996
4007
 
3997
4008
  return { success: true };
3998
4009
  });
4010
+ this.financeRealtime.publish({ domain: 'bank_statement', type: 'deleted', entityId: id });
4011
+ return result;
3999
4012
  }
4000
4013
 
4001
4014
  async importBankStatements(
@@ -4144,6 +4157,7 @@ export class FinanceService {
4144
4157
  return createdStatement;
4145
4158
  });
4146
4159
 
4160
+ this.financeRealtime.publish({ domain: 'bank_statement', type: 'imported', entityId: bankAccountId });
4147
4161
  return {
4148
4162
  statementId: String(statement.id),
4149
4163
  fileId: String(uploadedFile.id),
@@ -4556,6 +4570,7 @@ export class FinanceService {
4556
4570
  payload: { id: createdAccount.id, name, bank: data.bank, accountType: this.mapAccountTypeFromPt(data.type) },
4557
4571
  }).catch(() => null);
4558
4572
 
4573
+ this.financeRealtime.publish({ domain: 'bank_account', type: 'created', entityId: createdAccount.id });
4559
4574
  return mapped;
4560
4575
  }
4561
4576
 
@@ -4578,6 +4593,7 @@ export class FinanceService {
4578
4593
  payload: { id: created.id, code, name: data.name.trim() },
4579
4594
  }).catch(() => null);
4580
4595
 
4596
+ this.financeRealtime.publish({ domain: 'cost_center', type: 'created', entityId: created.id });
4581
4597
  return this.mapCostCenterToFront(created);
4582
4598
  }
4583
4599
 
@@ -4596,6 +4612,7 @@ export class FinanceService {
4596
4612
  },
4597
4613
  });
4598
4614
 
4615
+ this.financeRealtime.publish({ domain: 'category', type: 'created', entityId: created.id });
4599
4616
  return this.mapFinanceCategoryToFront(created);
4600
4617
  }
4601
4618
 
@@ -4647,6 +4664,7 @@ export class FinanceService {
4647
4664
  },
4648
4665
  });
4649
4666
 
4667
+ this.financeRealtime.publish({ domain: 'period_close', type: 'created', entityId: created.id });
4650
4668
  return this.mapPeriodCloseToFront(created);
4651
4669
  }
4652
4670
 
@@ -4702,6 +4720,7 @@ export class FinanceService {
4702
4720
  payload: { id, name: data.description, status: data.status },
4703
4721
  }).catch(() => null);
4704
4722
 
4723
+ this.financeRealtime.publish({ domain: 'bank_account', type: 'updated', entityId: id });
4705
4724
  return this.mapBankAccountToFront(updatedBankAccount);
4706
4725
  }
4707
4726
 
@@ -4731,6 +4750,7 @@ export class FinanceService {
4731
4750
  payload: { id, name: data.name, status: data.status },
4732
4751
  }).catch(() => null);
4733
4752
 
4753
+ this.financeRealtime.publish({ domain: 'cost_center', type: 'updated', entityId: id });
4734
4754
  return this.mapCostCenterToFront(updatedCostCenter);
4735
4755
  }
4736
4756
 
@@ -4759,6 +4779,7 @@ export class FinanceService {
4759
4779
  },
4760
4780
  });
4761
4781
 
4782
+ this.financeRealtime.publish({ domain: 'category', type: 'updated', entityId: id });
4762
4783
  return this.mapFinanceCategoryToFront(updated);
4763
4784
  }
4764
4785
 
@@ -4808,6 +4829,7 @@ export class FinanceService {
4808
4829
  ),
4809
4830
  );
4810
4831
 
4832
+ this.financeRealtime.publish({ domain: 'category', type: 'moved', entityId: id });
4811
4833
  return { success: true };
4812
4834
  }
4813
4835
 
@@ -4834,6 +4856,7 @@ export class FinanceService {
4834
4856
  payload: { id },
4835
4857
  }).catch(() => null);
4836
4858
 
4859
+ this.financeRealtime.publish({ domain: 'bank_account', type: 'deleted', entityId: id });
4837
4860
  return { success: true };
4838
4861
  }
4839
4862
 
@@ -4860,6 +4883,7 @@ export class FinanceService {
4860
4883
  payload: { id },
4861
4884
  }).catch(() => null);
4862
4885
 
4886
+ this.financeRealtime.publish({ domain: 'cost_center', type: 'deleted', entityId: id });
4863
4887
  return { success: true };
4864
4888
  }
4865
4889
 
@@ -4899,6 +4923,7 @@ export class FinanceService {
4899
4923
  },
4900
4924
  });
4901
4925
 
4926
+ this.financeRealtime.publish({ domain: 'currency', type: 'created', entityId: created.id });
4902
4927
  return {
4903
4928
  id: String(created.id),
4904
4929
  code: created.code,
@@ -4929,6 +4954,7 @@ export class FinanceService {
4929
4954
  },
4930
4955
  });
4931
4956
 
4957
+ this.financeRealtime.publish({ domain: 'currency', type: 'updated', entityId: id });
4932
4958
  return {
4933
4959
  id: String(updated.id),
4934
4960
  code: updated.code,
@@ -4954,6 +4980,7 @@ export class FinanceService {
4954
4980
  data: { status: 'inactive' },
4955
4981
  });
4956
4982
 
4983
+ this.financeRealtime.publish({ domain: 'currency', type: 'deleted', entityId: id });
4957
4984
  return { success: true };
4958
4985
  }
4959
4986
 
@@ -4974,6 +5001,7 @@ export class FinanceService {
4974
5001
  },
4975
5002
  });
4976
5003
 
5004
+ this.financeRealtime.publish({ domain: 'category', type: 'deleted', entityId: id });
4977
5005
  return { success: true };
4978
5006
  }
4979
5007
 
@@ -5078,7 +5106,23 @@ export class FinanceService {
5078
5106
  locale: string,
5079
5107
  userId?: number,
5080
5108
  ) {
5081
- const installments = this.normalizeAndValidateInstallments(data, locale);
5109
+ const rule = data.recurrence_rule;
5110
+ if (rule && !rule.end_date && !rule.max_occurrences) {
5111
+ throw new BadRequestException(
5112
+ getLocaleText('recurrenceEndOrCountRequired', locale, 'Provide end_date or max_occurrences for recurring titles'),
5113
+ );
5114
+ }
5115
+
5116
+ const isRecurring = Boolean(rule);
5117
+ const installments = isRecurring
5118
+ ? this.buildRecurrenceInstallments(
5119
+ data.due_date,
5120
+ rule!.frequency,
5121
+ this.toCents(data.total_amount),
5122
+ rule!.end_date,
5123
+ rule!.max_occurrences,
5124
+ )
5125
+ : this.normalizeAndValidateInstallments(data, locale);
5082
5126
 
5083
5127
  const createdTitleId = await this.prisma.$transaction(async (tx) => {
5084
5128
  const person = await tx.person.findUnique({
@@ -5152,6 +5196,10 @@ export class FinanceService {
5152
5196
  'create title',
5153
5197
  );
5154
5198
 
5199
+ const totalAmountCents = isRecurring
5200
+ ? this.toCents(data.total_amount) * installments.length
5201
+ : this.toCents(data.total_amount);
5202
+
5155
5203
  const title = await tx.financial_title.create({
5156
5204
  data: {
5157
5205
  person_id: data.person_id,
@@ -5163,7 +5211,10 @@ export class FinanceService {
5163
5211
  ? this.parseLocalDate(data.competence_date)
5164
5212
  : null,
5165
5213
  issue_date: data.issue_date ? this.parseLocalDate(data.issue_date) : null,
5166
- total_amount_cents: this.toCents(data.total_amount),
5214
+ total_amount_cents: totalAmountCents,
5215
+ is_recurring: isRecurring,
5216
+ recurrence_frequency: rule?.frequency ?? null,
5217
+ recurrence_end_date: rule?.end_date ? this.parseLocalDate(rule.end_date) : null,
5167
5218
  finance_category_id: data.finance_category_id,
5168
5219
  created_by_user_id: userId,
5169
5220
  },
@@ -5229,6 +5280,7 @@ export class FinanceService {
5229
5280
  });
5230
5281
 
5231
5282
  const createdTitle = await this.getTitleById(createdTitleId, titleType, locale);
5283
+ this.financeRealtime.publish({ domain: 'installment', type: 'created', entityId: createdTitleId });
5232
5284
  return this.mapTitleToFront(createdTitle, data.payment_channel);
5233
5285
  }
5234
5286
 
@@ -5239,7 +5291,23 @@ export class FinanceService {
5239
5291
  locale: string,
5240
5292
  userId?: number,
5241
5293
  ) {
5242
- const installments = this.normalizeAndValidateInstallments(data, locale);
5294
+ const rule = data.recurrence_rule;
5295
+ if (rule && !rule.end_date && !rule.max_occurrences) {
5296
+ throw new BadRequestException(
5297
+ getLocaleText('recurrenceEndOrCountRequired', locale, 'Provide end_date or max_occurrences for recurring titles'),
5298
+ );
5299
+ }
5300
+
5301
+ const isRecurring = Boolean(rule);
5302
+ const installments = isRecurring
5303
+ ? this.buildRecurrenceInstallments(
5304
+ data.due_date,
5305
+ rule!.frequency,
5306
+ this.toCents(data.total_amount),
5307
+ rule!.end_date,
5308
+ rule!.max_occurrences,
5309
+ )
5310
+ : this.normalizeAndValidateInstallments(data, locale);
5243
5311
 
5244
5312
  const updatedTitle = await this.prisma.$transaction(async (tx) => {
5245
5313
  const title = await tx.financial_title.findFirst({
@@ -5362,6 +5430,10 @@ export class FinanceService {
5362
5430
  }
5363
5431
  }
5364
5432
 
5433
+ const totalAmountCents = isRecurring
5434
+ ? this.toCents(data.total_amount) * installments.length
5435
+ : this.toCents(data.total_amount);
5436
+
5365
5437
  await tx.financial_title.update({
5366
5438
  where: { id: title.id },
5367
5439
  data: {
@@ -5372,7 +5444,10 @@ export class FinanceService {
5372
5444
  ? this.parseLocalDate(data.competence_date)
5373
5445
  : null,
5374
5446
  issue_date: data.issue_date ? this.parseLocalDate(data.issue_date) : null,
5375
- total_amount_cents: this.toCents(data.total_amount),
5447
+ total_amount_cents: totalAmountCents,
5448
+ is_recurring: isRecurring,
5449
+ recurrence_frequency: rule?.frequency ?? null,
5450
+ recurrence_end_date: rule?.end_date ? this.parseLocalDate(rule.end_date) : null,
5376
5451
  finance_category_id: data.finance_category_id,
5377
5452
  },
5378
5453
  });
@@ -5478,9 +5553,49 @@ export class FinanceService {
5478
5553
  throw new NotFoundException('Financial title not found');
5479
5554
  }
5480
5555
 
5556
+ this.financeRealtime.publish({ domain: 'installment', type: 'updated', entityId: titleId });
5481
5557
  return this.mapTitleToFront(updatedTitle, data.payment_channel);
5482
5558
  }
5483
5559
 
5560
+ private buildRecurrenceInstallments(
5561
+ startDate: string,
5562
+ frequency: string,
5563
+ amountCents: number,
5564
+ endDate?: string,
5565
+ maxOccurrences?: number,
5566
+ ) {
5567
+ const advance = (date: Date, freq: string): Date => {
5568
+ const next = new Date(date);
5569
+ switch (freq) {
5570
+ case 'weekly': next.setDate(next.getDate() + 7); break;
5571
+ case 'biweekly': next.setDate(next.getDate() + 14); break;
5572
+ case 'bimonthly': next.setMonth(next.getMonth() + 2); break;
5573
+ case 'quarterly': next.setMonth(next.getMonth() + 3); break;
5574
+ case 'semiannual': next.setMonth(next.getMonth() + 6); break;
5575
+ case 'annual': next.setFullYear(next.getFullYear() + 1); break;
5576
+ default: next.setMonth(next.getMonth() + 1); // monthly
5577
+ }
5578
+ return next;
5579
+ };
5580
+
5581
+ const limit = maxOccurrences ?? 600;
5582
+ const endDateObj = endDate ? this.parseLocalDate(endDate) : null;
5583
+ const installments: { installment_number: number; due_date: string; amount_cents: number }[] = [];
5584
+ let current = this.parseLocalDate(startDate);
5585
+
5586
+ while (installments.length < limit) {
5587
+ if (endDateObj && current > endDateObj) break;
5588
+ installments.push({
5589
+ installment_number: installments.length + 1,
5590
+ due_date: current.toISOString().slice(0, 10),
5591
+ amount_cents: amountCents,
5592
+ });
5593
+ current = advance(current, frequency);
5594
+ }
5595
+
5596
+ return installments;
5597
+ }
5598
+
5484
5599
  private normalizeAndValidateInstallments(
5485
5600
  data: CreateFinancialTitleDto,
5486
5601
  locale: string,
@@ -5619,6 +5734,7 @@ export class FinanceService {
5619
5734
  throw new NotFoundException('Financial title not found');
5620
5735
  }
5621
5736
 
5737
+ this.financeRealtime.publish({ domain: 'installment', type: 'approved', entityId: titleId });
5622
5738
  return this.mapTitleToFront(updatedTitle);
5623
5739
  }
5624
5740
 
@@ -5695,6 +5811,7 @@ export class FinanceService {
5695
5811
  throw new NotFoundException('Financial title not found');
5696
5812
  }
5697
5813
 
5814
+ this.financeRealtime.publish({ domain: 'installment', type: 'rejected', entityId: titleId });
5698
5815
  return this.mapTitleToFront(updatedTitle);
5699
5816
  }
5700
5817
 
@@ -5804,6 +5921,7 @@ export class FinanceService {
5804
5921
  throw new NotFoundException('Financial title not found');
5805
5922
  }
5806
5923
 
5924
+ this.financeRealtime.publish({ domain: 'installment', type: 'canceled', entityId: titleId });
5807
5925
  return this.mapTitleToFront(updatedTitle);
5808
5926
  }
5809
5927
 
@@ -6107,6 +6225,7 @@ export class FinanceService {
6107
6225
  };
6108
6226
  });
6109
6227
 
6228
+ this.financeRealtime.publish({ domain: 'settlement', type: 'settled', entityId: result.settlementId });
6110
6229
  return {
6111
6230
  ...this.mapTitleToFront(result.title),
6112
6231
  settlementId: String(result.settlementId),
@@ -6428,6 +6547,7 @@ export class FinanceService {
6428
6547
  };
6429
6548
  });
6430
6549
 
6550
+ this.financeRealtime.publish({ domain: 'settlement', type: 'reversed', entityId: settlementId });
6431
6551
  return title;
6432
6552
  }
6433
6553
 
@@ -6517,6 +6637,7 @@ export class FinanceService {
6517
6637
  });
6518
6638
 
6519
6639
  const updatedTitle = await this.getTitleById(titleId, titleType, locale);
6640
+ this.financeRealtime.publish({ domain: 'tag', type: 'updated', entityId: titleId });
6520
6641
  return this.mapTitleToFront(updatedTitle);
6521
6642
  }
6522
6643
 
@@ -6868,54 +6989,67 @@ export class FinanceService {
6868
6989
  const tags = [
6869
6990
  ...new Set(
6870
6991
  title.financial_installment
6871
- .flatMap((installment) => installment.financial_installment_tag)
6872
- .map((tagRelation) => String(tagRelation.tag_id)),
6992
+ .map((installment) => installment.financial_installment_tag?.tag_id)
6993
+ .filter((tagId) => typeof tagId === 'number')
6994
+ .map((tagId) => String(tagId)),
6873
6995
  ),
6874
6996
  ];
6875
6997
 
6876
- const channelFromSettlement = title.financial_installment
6877
- .flatMap((installment) => installment.settlement_allocation)
6998
+ const settlementAllocations = title.financial_installment.flatMap(
6999
+ (installment) =>
7000
+ installment.settlement_allocation
7001
+ ? [installment.settlement_allocation]
7002
+ : [],
7003
+ );
7004
+
7005
+ const channelFromSettlement = settlementAllocations
6878
7006
  .map((allocation) => allocation.settlement?.payment_method?.type)
6879
7007
  .find(Boolean);
6880
7008
 
6881
- const mappedInstallments = title.financial_installment.map((installment) => ({
6882
- id: String(installment.id),
6883
- numero: installment.installment_number,
6884
- vencimento: installment.due_date.toISOString(),
6885
- valor: this.fromCents(installment.amount_cents),
6886
- valorAberto: this.fromCents(installment.open_amount_cents),
6887
- status: this.mapStatusToPt(
6888
- this.resolveInstallmentStatus(
6889
- installment.amount_cents,
6890
- installment.open_amount_cents,
6891
- installment.due_date,
6892
- installment.status,
7009
+ const mappedInstallments = title.financial_installment.map((installment) => {
7010
+ const installmentSettlementAllocations = installment.settlement_allocation
7011
+ ? [installment.settlement_allocation]
7012
+ : [];
7013
+
7014
+ return {
7015
+ id: String(installment.id),
7016
+ numero: installment.installment_number,
7017
+ vencimento: installment.due_date.toISOString(),
7018
+ valor: this.fromCents(installment.amount_cents),
7019
+ valorAberto: this.fromCents(installment.open_amount_cents),
7020
+ status: this.mapStatusToPt(
7021
+ this.resolveInstallmentStatus(
7022
+ installment.amount_cents,
7023
+ installment.open_amount_cents,
7024
+ installment.due_date,
7025
+ installment.status,
7026
+ ),
6893
7027
  ),
6894
- ),
6895
- metodoPagamento:
6896
- this.mapPaymentMethodToPt(
6897
- installment.settlement_allocation[0]?.settlement?.payment_method?.type,
6898
- ) || paymentChannelOverride || 'transferencia',
6899
- liquidacoes: installment.settlement_allocation.map((allocation) => ({
6900
- id: String(allocation.id),
6901
- settlementId: allocation.settlement?.id
6902
- ? String(allocation.settlement.id)
6903
- : null,
6904
- data: allocation.settlement?.settled_at?.toISOString(),
6905
- valor: this.fromCents(allocation.allocated_amount_cents),
6906
- juros: this.fromCents(allocation.interest_cents || 0),
6907
- desconto: this.fromCents(allocation.discount_cents || 0),
6908
- multa: this.fromCents(allocation.penalty_cents || 0),
6909
- contaBancariaId: allocation.settlement?.bank_account_id
6910
- ? String(allocation.settlement.bank_account_id)
6911
- : null,
6912
- status: allocation.settlement?.status || null,
6913
- metodo:
7028
+ metodoPagamento:
6914
7029
  this.mapPaymentMethodToPt(
6915
- allocation.settlement?.payment_method?.type,
6916
- ) || 'transferencia',
6917
- })),
6918
- }));
7030
+ installmentSettlementAllocations[0]?.settlement?.payment_method?.type,
7031
+ ) || paymentChannelOverride || 'transferencia',
7032
+ liquidacoes: installmentSettlementAllocations.map((allocation) => ({
7033
+ id: String(allocation.id),
7034
+ settlementId: allocation.settlement?.id
7035
+ ? String(allocation.settlement.id)
7036
+ : null,
7037
+ data: allocation.settlement?.settled_at?.toISOString(),
7038
+ valor: this.fromCents(allocation.allocated_amount_cents),
7039
+ juros: this.fromCents(allocation.interest_cents || 0),
7040
+ desconto: this.fromCents(allocation.discount_cents || 0),
7041
+ multa: this.fromCents(allocation.penalty_cents || 0),
7042
+ contaBancariaId: allocation.settlement?.bank_account_id
7043
+ ? String(allocation.settlement.bank_account_id)
7044
+ : null,
7045
+ status: allocation.settlement?.status || null,
7046
+ metodo:
7047
+ this.mapPaymentMethodToPt(
7048
+ allocation.settlement?.payment_method?.type,
7049
+ ) || 'transferencia',
7050
+ })),
7051
+ };
7052
+ });
6919
7053
 
6920
7054
  const attachmentDetails = title.financial_title_attachment.map((attachment) => ({
6921
7055
  id: String(attachment.file_id),
@@ -6942,6 +7076,11 @@ export class FinanceService {
6942
7076
  this.mapPaymentMethodToPt(channelFromSettlement) ||
6943
7077
  paymentChannelOverride ||
6944
7078
  'transferencia',
7079
+ isRecurring: title.is_recurring ?? false,
7080
+ recurrenceFrequency: title.recurrence_frequency ?? null,
7081
+ recurrenceEndDate: title.recurrence_end_date
7082
+ ? title.recurrence_end_date.toISOString().slice(0, 10)
7083
+ : null,
6945
7084
  ...(title.title_type === 'payable'
6946
7085
  ? {
6947
7086
  fornecedorId: String(title.person_id),
@@ -0,0 +1,38 @@
1
+ import { McpContext, McpTool } from '@hed-hog/core';
2
+ import { Injectable } from '@nestjs/common';
3
+ import { FinanceService } from '../finance.service';
4
+
5
+ @Injectable()
6
+ export class FinanceAuditLogsMcpTools {
7
+ constructor(private readonly financeService: FinanceService) {}
8
+
9
+ @McpTool({
10
+ name: 'finance.audit-logs.list',
11
+ description: 'Lists finance audit logs with filters and pagination.',
12
+ inputSchema: {
13
+ type: 'object',
14
+ properties: {
15
+ page: { type: 'number', description: 'Page number (default: 1)' },
16
+ pageSize: { type: 'number', description: 'Items per page (default: 20)' },
17
+ search: { type: 'string', description: 'Search term' },
18
+ action: { type: 'string', description: 'Action filter' },
19
+ entity_table: { type: 'string', description: 'Entity table filter' },
20
+ actor_user_id: { type: 'string', description: 'Actor user id filter' },
21
+ from: { type: 'string', description: 'Start date (YYYY-MM-DD)' },
22
+ to: { type: 'string', description: 'End date (YYYY-MM-DD)' },
23
+ },
24
+ },
25
+ readOnly: true,
26
+ })
27
+ async list(args: Record<string, any>, _context: McpContext): Promise<any> {
28
+ const { page, pageSize, search, action, entity_table, actor_user_id, from, to } = args;
29
+ return this.financeService.listAuditLogs({ page, pageSize } as any, {
30
+ search,
31
+ action,
32
+ entity_table,
33
+ actor_user_id,
34
+ from,
35
+ to,
36
+ });
37
+ }
38
+ }
@@ -0,0 +1,76 @@
1
+ import { McpContext, McpTool } from '@hed-hog/core';
2
+ import { Injectable } from '@nestjs/common';
3
+ import { FinanceService } from '../finance.service';
4
+
5
+ @Injectable()
6
+ export class FinanceBankAccountsMcpTools {
7
+ constructor(private readonly financeService: FinanceService) {}
8
+
9
+ @McpTool({
10
+ name: 'finance.bank-accounts.list',
11
+ description: 'Lists bank accounts with pagination.',
12
+ inputSchema: {
13
+ type: 'object',
14
+ properties: {
15
+ page: { type: 'number', description: 'Page number (default: 1)' },
16
+ pageSize: { type: 'number', description: 'Items per page (default: 20)' },
17
+ },
18
+ },
19
+ readOnly: true,
20
+ })
21
+ async list(args: Record<string, any>, _context: McpContext): Promise<any> {
22
+ return this.financeService.listBankAccounts(args as any);
23
+ }
24
+
25
+ @McpTool({
26
+ name: 'finance.bank-accounts.create',
27
+ description: 'Creates a bank account.',
28
+ inputSchema: {
29
+ type: 'object',
30
+ properties: {
31
+ bank: { type: 'string', description: 'Bank name' },
32
+ type: { type: 'string', description: 'Account type' },
33
+ currency_id: { type: 'number', description: 'Currency ID' },
34
+ opening_balance: { type: 'number', description: 'Opening balance' },
35
+ },
36
+ required: ['bank', 'type'],
37
+ },
38
+ })
39
+ async create(args: Record<string, any>, context: McpContext): Promise<any> {
40
+ return this.financeService.createBankAccount(args as any, context.userId);
41
+ }
42
+
43
+ @McpTool({
44
+ name: 'finance.bank-accounts.update',
45
+ description: 'Updates an existing bank account.',
46
+ inputSchema: {
47
+ type: 'object',
48
+ properties: {
49
+ id: { type: 'number', description: 'Bank account ID' },
50
+ bank: { type: 'string', description: 'Bank name' },
51
+ type: { type: 'string', description: 'Account type' },
52
+ currency_id: { type: 'number', description: 'Currency ID' },
53
+ },
54
+ required: ['id'],
55
+ },
56
+ })
57
+ async update(args: { id: number; [key: string]: any }, _context: McpContext): Promise<any> {
58
+ const { id, ...data } = args;
59
+ return this.financeService.updateBankAccount(id, data as any);
60
+ }
61
+
62
+ @McpTool({
63
+ name: 'finance.bank-accounts.delete',
64
+ description: 'Deletes a bank account.',
65
+ inputSchema: {
66
+ type: 'object',
67
+ properties: {
68
+ id: { type: 'number', description: 'Bank account ID' },
69
+ },
70
+ required: ['id'],
71
+ },
72
+ })
73
+ async delete(args: { id: number }, _context: McpContext): Promise<any> {
74
+ return this.financeService.deleteBankAccount(args.id);
75
+ }
76
+ }