@hed-hog/finance 0.0.317 → 0.0.319

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 (66) 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/update-bank-account.dto.d.ts +1 -0
  10. package/dist/dto/update-bank-account.dto.d.ts.map +1 -1
  11. package/dist/dto/update-bank-account.dto.js +7 -0
  12. package/dist/dto/update-bank-account.dto.js.map +1 -1
  13. package/dist/dto/update-bank-statement-entry.dto.d.ts +6 -0
  14. package/dist/dto/update-bank-statement-entry.dto.d.ts.map +1 -0
  15. package/dist/dto/update-bank-statement-entry.dto.js +42 -0
  16. package/dist/dto/update-bank-statement-entry.dto.js.map +1 -0
  17. package/dist/finance-bank-accounts.controller.d.ts +25 -13
  18. package/dist/finance-bank-accounts.controller.d.ts.map +1 -1
  19. package/dist/finance-bank-accounts.controller.js +5 -3
  20. package/dist/finance-bank-accounts.controller.js.map +1 -1
  21. package/dist/finance-data.controller.d.ts +4 -0
  22. package/dist/finance-data.controller.d.ts.map +1 -1
  23. package/dist/finance-installments.controller.d.ts +3 -2
  24. package/dist/finance-installments.controller.d.ts.map +1 -1
  25. package/dist/finance-installments.controller.js +10 -6
  26. package/dist/finance-installments.controller.js.map +1 -1
  27. package/dist/finance-statements.controller.d.ts +61 -12
  28. package/dist/finance-statements.controller.d.ts.map +1 -1
  29. package/dist/finance-statements.controller.js +50 -8
  30. package/dist/finance-statements.controller.js.map +1 -1
  31. package/dist/finance-transfers.controller.d.ts +13 -8
  32. package/dist/finance-transfers.controller.d.ts.map +1 -1
  33. package/dist/finance-transfers.controller.js +11 -5
  34. package/dist/finance-transfers.controller.js.map +1 -1
  35. package/dist/finance.service.d.ts +124 -35
  36. package/dist/finance.service.d.ts.map +1 -1
  37. package/dist/finance.service.js +389 -55
  38. package/dist/finance.service.js.map +1 -1
  39. package/hedhog/data/role.yaml +9 -1
  40. package/hedhog/data/route.yaml +42 -0
  41. package/hedhog/frontend/app/accounts-payable/approvals/page.tsx.ejs +87 -72
  42. package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +53 -25
  43. package/hedhog/frontend/app/accounts-receivable/installments/[id]/page.tsx.ejs +8 -0
  44. package/hedhog/frontend/app/accounts-receivable/installments/page.tsx.ejs +60 -24
  45. package/hedhog/frontend/app/cash-and-banks/bank-accounts/page.tsx.ejs +114 -31
  46. package/hedhog/frontend/app/cash-and-banks/bank-reconciliation/page.tsx.ejs +25 -3
  47. package/hedhog/frontend/app/cash-and-banks/statements/page.tsx.ejs +732 -61
  48. package/hedhog/frontend/app/cash-and-banks/transfers/page.tsx.ejs +101 -15
  49. package/hedhog/table/bank_statement_line.yaml +1 -1
  50. package/hedhog/table/cashflow_projection.yaml +1 -1
  51. package/hedhog/table/financial_installment.yaml +2 -2
  52. package/hedhog/table/financial_title.yaml +1 -1
  53. package/hedhog/table/installment_allocation.yaml +1 -1
  54. package/hedhog/table/receivable_schedule.yaml +1 -1
  55. package/hedhog/table/settlement.yaml +1 -1
  56. package/hedhog/table/settlement_allocation.yaml +5 -5
  57. package/package.json +7 -7
  58. package/src/dto/create-bank-account.dto.ts +18 -1
  59. package/src/dto/create-bank-statement-entry.dto.ts +50 -0
  60. package/src/dto/update-bank-account.dto.ts +11 -1
  61. package/src/dto/update-bank-statement-entry.dto.ts +31 -0
  62. package/src/finance-bank-accounts.controller.ts +3 -2
  63. package/src/finance-installments.controller.ts +9 -3
  64. package/src/finance-statements.controller.ts +40 -0
  65. package/src/finance-transfers.controller.ts +7 -1
  66. package/src/finance.service.ts +543 -61
@@ -419,6 +419,23 @@ let FinanceService = FinanceService_1 = class FinanceService {
419
419
  }
420
420
  return dt.toISOString().slice(0, 10);
421
421
  }
422
+ parseFilterDate(value, endOfDay = false) {
423
+ if (!value) {
424
+ return undefined;
425
+ }
426
+ const raw = String(value).trim();
427
+ if (!raw) {
428
+ return undefined;
429
+ }
430
+ const normalized = /^\d{4}-\d{2}-\d{2}$/.test(raw)
431
+ ? `${raw}T${endOfDay ? '23:59:59.999' : '00:00:00.000'}`
432
+ : raw;
433
+ const parsed = new Date(normalized);
434
+ if (Number.isNaN(parsed.getTime())) {
435
+ return undefined;
436
+ }
437
+ return parsed;
438
+ }
422
439
  normalizeMonth(value) {
423
440
  if (!value)
424
441
  return '';
@@ -454,6 +471,14 @@ let FinanceService = FinanceService_1 = class FinanceService {
454
471
  card: 'cartao',
455
472
  credito: 'cartao',
456
473
  debito: 'cartao',
474
+ 'debito automatico': 'debito_automatico',
475
+ debito_automatico: 'debito_automatico',
476
+ 'debito-automatico': 'debito_automatico',
477
+ 'débito automático': 'debito_automatico',
478
+ 'debito em conta': 'debito_em_conta',
479
+ debito_em_conta: 'debito_em_conta',
480
+ 'debito-em-conta': 'debito_em_conta',
481
+ 'débito em conta': 'debito_em_conta',
457
482
  dinheiro: 'dinheiro',
458
483
  cash: 'dinheiro',
459
484
  cheque: 'cheque',
@@ -513,7 +538,14 @@ let FinanceService = FinanceService_1 = class FinanceService {
513
538
  this.loadCategories(),
514
539
  this.loadCostCenters(),
515
540
  this.loadBankAccounts(),
516
- this.listBankStatements(),
541
+ this.listBankStatements({
542
+ page: 1,
543
+ pageSize: 1000,
544
+ search: undefined,
545
+ sortField: undefined,
546
+ sortOrder: undefined,
547
+ fields: undefined,
548
+ }),
517
549
  this.getAccountsReceivableCollectionsDefault(),
518
550
  this.loadTags(),
519
551
  this.loadAuditLogs(),
@@ -528,7 +560,7 @@ let FinanceService = FinanceService_1 = class FinanceService {
528
560
  const costCenters = costCentersResult.status === 'fulfilled' ? costCentersResult.value : [];
529
561
  const bankAccounts = bankAccountsResult.status === 'fulfilled' ? bankAccountsResult.value : [];
530
562
  const bankStatements = bankStatementsResult.status === 'fulfilled'
531
- ? bankStatementsResult.value
563
+ ? bankStatementsResult.value.data
532
564
  : [];
533
565
  const collectionsDefault = collectionsDefaultResult.status === 'fulfilled'
534
566
  ? collectionsDefaultResult.value
@@ -1446,7 +1478,7 @@ let FinanceService = FinanceService_1 = class FinanceService {
1446
1478
  if (!person) {
1447
1479
  throw new common_1.NotFoundException('Person not found');
1448
1480
  }
1449
- const firstDueDate = new Date(data.first_due_date);
1481
+ const firstDueDate = this.parseLocalDate(data.first_due_date);
1450
1482
  if (Number.isNaN(firstDueDate.getTime())) {
1451
1483
  throw new common_1.BadRequestException('Invalid first due date');
1452
1484
  }
@@ -1591,11 +1623,11 @@ let FinanceService = FinanceService_1 = class FinanceService {
1591
1623
  next.setMonth(next.getMonth() + months);
1592
1624
  return next;
1593
1625
  }
1594
- async listAccountsPayableInstallments(paginationParams, status) {
1595
- return this.listTitles('payable', paginationParams, status);
1626
+ async listAccountsPayableInstallments(paginationParams, status, filters) {
1627
+ return this.listTitles('payable', paginationParams, status, filters);
1596
1628
  }
1597
- async listAccountsReceivableInstallments(paginationParams, status) {
1598
- return this.listTitles('receivable', paginationParams, status);
1629
+ async listAccountsReceivableInstallments(paginationParams, status, filters) {
1630
+ return this.listTitles('receivable', paginationParams, status, filters);
1599
1631
  }
1600
1632
  async getAccountsPayableInstallment(id, locale) {
1601
1633
  const title = await this.getTitleById(id, 'payable', locale);
@@ -1822,23 +1854,27 @@ let FinanceService = FinanceService_1 = class FinanceService {
1822
1854
  async updateAccountsReceivableInstallmentTags(id, tagIds, locale) {
1823
1855
  return this.updateTitleTags(id, 'receivable', tagIds, locale);
1824
1856
  }
1825
- async listBankAccounts() {
1826
- const bankAccounts = await this.prisma.bank_account.findMany({
1857
+ async listBankAccounts(paginationParams) {
1858
+ const paginated = await this.paginationService.paginate(this.prisma.bank_account, Object.assign(Object.assign({}, paginationParams), { sortField: (paginationParams === null || paginationParams === void 0 ? void 0 : paginationParams.sortField) || 'code', sortOrder: (paginationParams === null || paginationParams === void 0 ? void 0 : paginationParams.sortOrder) || api_pagination_1.PageOrderDirection.Asc }), {
1827
1859
  include: {
1828
1860
  bank_statement_line: {
1829
1861
  select: {
1830
1862
  amount_cents: true,
1831
1863
  status: true,
1864
+ posted_date: true,
1865
+ description: true,
1832
1866
  },
1833
1867
  },
1834
1868
  },
1835
1869
  orderBy: [{ code: 'asc' }, { name: 'asc' }],
1836
1870
  });
1837
- return bankAccounts.map((bankAccount) => this.mapBankAccountToFront(bankAccount));
1871
+ return Object.assign(Object.assign({}, paginated), { data: (paginated.data || []).map((bankAccount) => this.mapBankAccountToFront(bankAccount)) });
1838
1872
  }
1839
- async listTransfers(filters) {
1873
+ async listTransfers(paginationParams, filters) {
1840
1874
  var _a;
1841
1875
  const search = (_a = filters === null || filters === void 0 ? void 0 : filters.search) === null || _a === void 0 ? void 0 : _a.trim();
1876
+ const fromDate = this.parseFilterDate(filters === null || filters === void 0 ? void 0 : filters.from);
1877
+ const toDate = this.parseFilterDate(filters === null || filters === void 0 ? void 0 : filters.to, true);
1842
1878
  const parsedBankAccountId = (filters === null || filters === void 0 ? void 0 : filters.bank_account_id)
1843
1879
  ? Number.parseInt(filters.bank_account_id, 10)
1844
1880
  : undefined;
@@ -1848,7 +1884,7 @@ let FinanceService = FinanceService_1 = class FinanceService {
1848
1884
  let transferKeys;
1849
1885
  if (search || bankAccountId) {
1850
1886
  const filteredLines = await this.prisma.bank_statement_line.findMany({
1851
- where: Object.assign(Object.assign({ external_id: {
1887
+ where: Object.assign(Object.assign(Object.assign({ external_id: {
1852
1888
  startsWith: 'transfer:',
1853
1889
  } }, (search
1854
1890
  ? {
@@ -1857,7 +1893,11 @@ let FinanceService = FinanceService_1 = class FinanceService {
1857
1893
  mode: 'insensitive',
1858
1894
  },
1859
1895
  }
1860
- : {})), (bankAccountId ? { bank_account_id: bankAccountId } : {})),
1896
+ : {})), (bankAccountId ? { bank_account_id: bankAccountId } : {})), ((fromDate || toDate)
1897
+ ? {
1898
+ posted_date: Object.assign(Object.assign({}, (fromDate ? { gte: fromDate } : {})), (toDate ? { lte: toDate } : {})),
1899
+ }
1900
+ : {})),
1861
1901
  select: {
1862
1902
  external_id: true,
1863
1903
  },
@@ -1866,11 +1906,13 @@ let FinanceService = FinanceService_1 = class FinanceService {
1866
1906
  .map((line) => line.external_id)
1867
1907
  .filter((externalId) => !!externalId)));
1868
1908
  if (transferKeys.length === 0) {
1869
- return [];
1909
+ return Object.assign(Object.assign({}, this.paginateCollection([], paginationParams)), { summary: {
1910
+ totalTransferido: 0,
1911
+ } });
1870
1912
  }
1871
1913
  }
1872
1914
  const transferLines = await this.prisma.bank_statement_line.findMany({
1873
- where: Object.assign({}, (transferKeys
1915
+ where: Object.assign(Object.assign({}, (transferKeys
1874
1916
  ? {
1875
1917
  external_id: {
1876
1918
  in: transferKeys,
@@ -1880,7 +1922,11 @@ let FinanceService = FinanceService_1 = class FinanceService {
1880
1922
  external_id: {
1881
1923
  startsWith: 'transfer:',
1882
1924
  },
1883
- })),
1925
+ })), ((fromDate || toDate)
1926
+ ? {
1927
+ posted_date: Object.assign(Object.assign({}, (fromDate ? { gte: fromDate } : {})), (toDate ? { lte: toDate } : {})),
1928
+ }
1929
+ : {})),
1884
1930
  select: {
1885
1931
  id: true,
1886
1932
  external_id: true,
@@ -1913,19 +1959,21 @@ let FinanceService = FinanceService_1 = class FinanceService {
1913
1959
  contaOrigemId: String(sourceLine.bank_account_id),
1914
1960
  contaDestinoId: String(destinationLine.bank_account_id),
1915
1961
  data: sourceLine.posted_date.toISOString(),
1916
- valor: this.fromCents(Math.abs(sourceLine.amount_cents)),
1962
+ valor: Math.abs(this.fromCents(sourceLine.amount_cents)),
1917
1963
  descricao: sourceLine.description || destinationLine.description || '',
1918
1964
  };
1919
1965
  })
1920
1966
  .filter(Boolean);
1921
- return transfers;
1967
+ return Object.assign(Object.assign({}, this.paginateCollection(transfers, paginationParams)), { summary: {
1968
+ totalTransferido: transfers.reduce((acc, transfer) => acc + Number((transfer === null || transfer === void 0 ? void 0 : transfer.valor) || 0), 0),
1969
+ } });
1922
1970
  }
1923
1971
  async createTransfer(data, userId) {
1924
1972
  var _a;
1925
1973
  const sourceAccountId = Number(data.source_account_id);
1926
1974
  const destinationAccountId = Number(data.destination_account_id);
1927
1975
  const amount = Number(data.amount);
1928
- const postedDate = new Date(data.date);
1976
+ const postedDate = this.parseLocalDate(data.date);
1929
1977
  if (Number.isNaN(sourceAccountId) ||
1930
1978
  Number.isNaN(destinationAccountId) ||
1931
1979
  sourceAccountId <= 0 ||
@@ -2168,16 +2216,22 @@ let FinanceService = FinanceService_1 = class FinanceService {
2168
2216
  });
2169
2217
  return Object.assign(Object.assign({}, paginated), { data: (paginated.data || []).map((period) => this.mapPeriodCloseToFront(period)) });
2170
2218
  }
2171
- async listBankStatements(bankAccountId, search) {
2219
+ async listBankStatements(paginationParams, bankAccountId, search, filters) {
2172
2220
  const trimmedSearch = search === null || search === void 0 ? void 0 : search.trim();
2221
+ const fromDate = this.parseFilterDate(filters === null || filters === void 0 ? void 0 : filters.from);
2222
+ const toDate = this.parseFilterDate(filters === null || filters === void 0 ? void 0 : filters.to, true);
2173
2223
  const statements = await this.prisma.bank_statement_line.findMany({
2174
- where: Object.assign(Object.assign({}, (bankAccountId ? { bank_account_id: bankAccountId } : {})), (trimmedSearch
2224
+ where: Object.assign(Object.assign(Object.assign({}, (bankAccountId ? { bank_account_id: bankAccountId } : {})), (trimmedSearch
2175
2225
  ? {
2176
2226
  description: {
2177
2227
  contains: trimmedSearch,
2178
2228
  mode: 'insensitive',
2179
2229
  },
2180
2230
  }
2231
+ : {})), ((fromDate || toDate)
2232
+ ? {
2233
+ posted_date: Object.assign(Object.assign({}, (fromDate ? { gte: fromDate } : {})), (toDate ? { lte: toDate } : {})),
2234
+ }
2181
2235
  : {})),
2182
2236
  include: {
2183
2237
  bank_account: {
@@ -2203,8 +2257,8 @@ let FinanceService = FinanceService_1 = class FinanceService {
2203
2257
  },
2204
2258
  orderBy: [{ posted_date: 'desc' }, { id: 'desc' }],
2205
2259
  });
2206
- return statements.map((statement) => {
2207
- var _a, _b;
2260
+ const mappedStatements = statements.map((statement) => {
2261
+ var _a, _b, _c, _d, _e;
2208
2262
  return ({
2209
2263
  id: String(statement.id),
2210
2264
  contaBancariaId: String(statement.bank_account_id),
@@ -2213,14 +2267,27 @@ let FinanceService = FinanceService_1 = class FinanceService {
2213
2267
  valor: this.fromCents(statement.amount_cents),
2214
2268
  tipo: statement.amount_cents >= 0 ? 'entrada' : 'saida',
2215
2269
  statusConciliacao: this.mapStatementStatusToPt(statement.status),
2216
- reconciliationId: ((_a = statement.bank_reconciliation[0]) === null || _a === void 0 ? void 0 : _a.id)
2270
+ isTransfer: ((_a = statement.external_id) === null || _a === void 0 ? void 0 : _a.startsWith('transfer:')) || false,
2271
+ canEdit: !((_b = statement.external_id) === null || _b === void 0 ? void 0 : _b.startsWith('transfer:')) &&
2272
+ statement.bank_reconciliation.length === 0,
2273
+ canDelete: !((_c = statement.external_id) === null || _c === void 0 ? void 0 : _c.startsWith('transfer:')) &&
2274
+ statement.bank_reconciliation.length === 0,
2275
+ reconciliationId: ((_d = statement.bank_reconciliation[0]) === null || _d === void 0 ? void 0 : _d.id)
2217
2276
  ? String(statement.bank_reconciliation[0].id)
2218
2277
  : null,
2219
- settlementId: ((_b = statement.bank_reconciliation[0]) === null || _b === void 0 ? void 0 : _b.settlement_id)
2278
+ settlementId: ((_e = statement.bank_reconciliation[0]) === null || _e === void 0 ? void 0 : _e.settlement_id)
2220
2279
  ? String(statement.bank_reconciliation[0].settlement_id)
2221
2280
  : null,
2222
2281
  });
2223
2282
  });
2283
+ return Object.assign(Object.assign({}, this.paginateCollection(mappedStatements, paginationParams)), { summary: {
2284
+ totalEntradas: mappedStatements
2285
+ .filter((statement) => statement.tipo === 'entrada')
2286
+ .reduce((acc, statement) => acc + Number(statement.valor || 0), 0),
2287
+ totalSaidas: mappedStatements
2288
+ .filter((statement) => statement.tipo === 'saida')
2289
+ .reduce((acc, statement) => acc + Number(statement.valor || 0), 0),
2290
+ } });
2224
2291
  }
2225
2292
  async createBankReconciliation(data, locale, userId) {
2226
2293
  var _a;
@@ -2279,7 +2346,7 @@ let FinanceService = FinanceService_1 = class FinanceService {
2279
2346
  }
2280
2347
  return this.settleTitleInstallment(title.id, {
2281
2348
  installment_id: installment.id,
2282
- amount: this.fromCents(Math.abs(statementLine.amount_cents)),
2349
+ amount: Math.abs(this.fromCents(statementLine.amount_cents)),
2283
2350
  settled_at: statementLine.posted_date.toISOString(),
2284
2351
  bank_account_id: statementLine.bank_account_id,
2285
2352
  bank_statement_line_id: statementLine.id,
@@ -2328,7 +2395,7 @@ let FinanceService = FinanceService_1 = class FinanceService {
2328
2395
  const payableAmounts = new Set();
2329
2396
  const receivableAmounts = new Set();
2330
2397
  for (const installment of openInstallments) {
2331
- const amount = Math.abs(installment.open_amount_cents);
2398
+ const amount = Number(installment.open_amount_cents);
2332
2399
  if (installment.financial_title.title_type === 'payable') {
2333
2400
  payableAmounts.add(amount);
2334
2401
  }
@@ -2339,8 +2406,8 @@ let FinanceService = FinanceService_1 = class FinanceService {
2339
2406
  let discrepancyCount = 0;
2340
2407
  let differenceCents = 0;
2341
2408
  for (const statement of pendingStatements) {
2342
- const normalizedAmount = Math.abs(statement.amount_cents);
2343
- differenceCents += statement.amount_cents;
2409
+ const normalizedAmount = Math.abs(Number(statement.amount_cents));
2410
+ differenceCents += Number(statement.amount_cents);
2344
2411
  const hasPossibleMatch = statement.amount_cents < 0
2345
2412
  ? payableAmounts.has(normalizedAmount)
2346
2413
  : receivableAmounts.has(normalizedAmount);
@@ -2353,8 +2420,16 @@ let FinanceService = FinanceService_1 = class FinanceService {
2353
2420
  difference: this.fromCents(differenceCents),
2354
2421
  };
2355
2422
  }
2356
- async exportBankStatementsCsv(bankAccountId, search) {
2357
- const statements = await this.listBankStatements(bankAccountId, search);
2423
+ async exportBankStatementsCsv(bankAccountId, search, filters) {
2424
+ const statementsResult = await this.listBankStatements({
2425
+ page: 1,
2426
+ pageSize: 100000,
2427
+ search: undefined,
2428
+ sortField: undefined,
2429
+ sortOrder: undefined,
2430
+ fields: undefined,
2431
+ }, bankAccountId, search, filters);
2432
+ const statements = statementsResult.data;
2358
2433
  const headers = [
2359
2434
  'id',
2360
2435
  'data',
@@ -2389,7 +2464,7 @@ let FinanceService = FinanceService_1 = class FinanceService {
2389
2464
  var _a;
2390
2465
  const bankAccountId = Number(data.bank_account_id);
2391
2466
  const amount = Number(data.amount);
2392
- const postedAt = data.date ? new Date(data.date) : new Date();
2467
+ const postedAt = data.date ? this.parseLocalDate(data.date) : new Date();
2393
2468
  if (Number.isNaN(bankAccountId) || bankAccountId <= 0) {
2394
2469
  throw new common_1.BadRequestException('bank_account_id is required');
2395
2470
  }
@@ -2446,6 +2521,210 @@ let FinanceService = FinanceService_1 = class FinanceService {
2446
2521
  statusConciliacao: this.mapStatementStatusToPt(created.status),
2447
2522
  };
2448
2523
  }
2524
+ async createBankStatementEntry(data, userId) {
2525
+ var _a;
2526
+ const bankAccountId = Number(data.bank_account_id);
2527
+ const amount = Number(data.amount);
2528
+ const postedDate = new Date(`${data.date}T00:00:00`);
2529
+ if (Number.isNaN(bankAccountId) || bankAccountId <= 0) {
2530
+ throw new common_1.BadRequestException('bank_account_id is required');
2531
+ }
2532
+ if (Number.isNaN(amount) || amount <= 0) {
2533
+ throw new common_1.BadRequestException('amount must be greater than zero');
2534
+ }
2535
+ if (Number.isNaN(postedDate.getTime())) {
2536
+ throw new common_1.BadRequestException('Invalid statement date');
2537
+ }
2538
+ const description = (_a = data.description) === null || _a === void 0 ? void 0 : _a.trim();
2539
+ if (!description) {
2540
+ throw new common_1.BadRequestException('description is required');
2541
+ }
2542
+ const bankAccount = await this.prisma.bank_account.findUnique({
2543
+ where: { id: bankAccountId },
2544
+ select: { id: true },
2545
+ });
2546
+ if (!bankAccount) {
2547
+ throw new common_1.NotFoundException('Bank account not found');
2548
+ }
2549
+ const signedAmountCents = data.type === 'saida'
2550
+ ? -Math.abs(this.toCents(amount))
2551
+ : Math.abs(this.toCents(amount));
2552
+ const created = await this.prisma.$transaction(async (tx) => {
2553
+ await this.assertDateNotInClosedPeriod(tx, postedDate, 'create bank statement entry');
2554
+ const reference = `manual-entry:${bankAccountId}:${Date.now()}`;
2555
+ const statement = await tx.bank_statement.create({
2556
+ data: {
2557
+ bank_account_id: bankAccountId,
2558
+ source_type: 'manual',
2559
+ imported_at: postedDate,
2560
+ imported_by_user_id: userId,
2561
+ idempotency_key: reference,
2562
+ period_start: postedDate,
2563
+ period_end: postedDate,
2564
+ },
2565
+ });
2566
+ return tx.bank_statement_line.create({
2567
+ data: {
2568
+ bank_statement_id: statement.id,
2569
+ bank_account_id: bankAccountId,
2570
+ posted_date: postedDate,
2571
+ amount_cents: signedAmountCents,
2572
+ description,
2573
+ status: 'pending',
2574
+ dedupe_key: `${reference}:${Math.round(Math.random() * 1000000)}`,
2575
+ },
2576
+ });
2577
+ });
2578
+ return {
2579
+ id: String(created.id),
2580
+ contaBancariaId: String(created.bank_account_id),
2581
+ data: created.posted_date.toISOString(),
2582
+ descricao: created.description,
2583
+ valor: this.fromCents(created.amount_cents),
2584
+ tipo: created.amount_cents >= 0 ? 'entrada' : 'saida',
2585
+ statusConciliacao: this.mapStatementStatusToPt(created.status),
2586
+ isTransfer: false,
2587
+ canEdit: true,
2588
+ canDelete: true,
2589
+ reconciliationId: null,
2590
+ settlementId: null,
2591
+ };
2592
+ }
2593
+ async updateBankStatementEntry(id, data, userId) {
2594
+ if (data.amount !== undefined) {
2595
+ const amount = Number(data.amount);
2596
+ if (Number.isNaN(amount) || amount <= 0) {
2597
+ throw new common_1.BadRequestException('amount must be greater than zero');
2598
+ }
2599
+ }
2600
+ if (data.amount === undefined && data.description === undefined && data.date === undefined) {
2601
+ throw new common_1.BadRequestException('At least one field must be provided for update');
2602
+ }
2603
+ const updated = await this.prisma.$transaction(async (tx) => {
2604
+ var _a;
2605
+ const statementLine = await tx.bank_statement_line.findUnique({
2606
+ where: { id },
2607
+ include: {
2608
+ bank_reconciliation: {
2609
+ where: {
2610
+ status: {
2611
+ in: ['pending', 'reconciled', 'adjusted'],
2612
+ },
2613
+ },
2614
+ select: {
2615
+ id: true,
2616
+ },
2617
+ take: 1,
2618
+ },
2619
+ },
2620
+ });
2621
+ if (!statementLine) {
2622
+ throw new common_1.NotFoundException('Bank statement line not found');
2623
+ }
2624
+ if ((_a = statementLine.external_id) === null || _a === void 0 ? void 0 : _a.startsWith('transfer:')) {
2625
+ throw new common_1.ConflictException('Transfer movements cannot be edited');
2626
+ }
2627
+ if (statementLine.bank_reconciliation.length > 0) {
2628
+ throw new common_1.ConflictException('Reconciled movements cannot be edited');
2629
+ }
2630
+ const targetDate = data.date ? this.parseLocalDate(data.date) : statementLine.posted_date;
2631
+ await this.assertDateNotInClosedPeriod(tx, targetDate, 'update bank statement entry');
2632
+ const updateData = {};
2633
+ if (data.amount !== undefined) {
2634
+ const amount = Number(data.amount);
2635
+ updateData.amount_cents =
2636
+ statementLine.amount_cents < 0
2637
+ ? -Math.abs(this.toCents(amount))
2638
+ : Math.abs(this.toCents(amount));
2639
+ }
2640
+ if (data.description !== undefined) {
2641
+ updateData.description = data.description.trim();
2642
+ }
2643
+ if (data.date !== undefined) {
2644
+ updateData.posted_date = this.parseLocalDate(data.date);
2645
+ }
2646
+ const result = await tx.bank_statement_line.update({
2647
+ where: { id },
2648
+ data: updateData,
2649
+ });
2650
+ await this.createAuditLog(tx, {
2651
+ action: 'UPDATE_BANK_STATEMENT_LINE',
2652
+ entityTable: 'bank_statement_line',
2653
+ entityId: String(result.id),
2654
+ actorUserId: userId,
2655
+ summary: `Updated bank statement line ${result.id}`,
2656
+ });
2657
+ return result;
2658
+ });
2659
+ return {
2660
+ id: String(updated.id),
2661
+ contaBancariaId: String(updated.bank_account_id),
2662
+ data: updated.posted_date.toISOString(),
2663
+ descricao: updated.description,
2664
+ valor: this.fromCents(updated.amount_cents),
2665
+ tipo: updated.amount_cents >= 0 ? 'entrada' : 'saida',
2666
+ statusConciliacao: this.mapStatementStatusToPt(updated.status),
2667
+ isTransfer: false,
2668
+ canEdit: true,
2669
+ canDelete: true,
2670
+ reconciliationId: null,
2671
+ settlementId: null,
2672
+ };
2673
+ }
2674
+ async deleteBankStatementEntry(id, userId) {
2675
+ return this.prisma.$transaction(async (tx) => {
2676
+ var _a;
2677
+ const statementLine = await tx.bank_statement_line.findUnique({
2678
+ where: { id },
2679
+ include: {
2680
+ bank_reconciliation: {
2681
+ where: {
2682
+ status: {
2683
+ in: ['pending', 'reconciled', 'adjusted'],
2684
+ },
2685
+ },
2686
+ select: {
2687
+ id: true,
2688
+ },
2689
+ take: 1,
2690
+ },
2691
+ },
2692
+ });
2693
+ if (!statementLine) {
2694
+ throw new common_1.NotFoundException('Bank statement line not found');
2695
+ }
2696
+ if ((_a = statementLine.external_id) === null || _a === void 0 ? void 0 : _a.startsWith('transfer:')) {
2697
+ throw new common_1.ConflictException('Transfer movements cannot be deleted');
2698
+ }
2699
+ if (statementLine.bank_reconciliation.length > 0) {
2700
+ throw new common_1.ConflictException('Reconciled movements cannot be deleted');
2701
+ }
2702
+ await this.assertDateNotInClosedPeriod(tx, statementLine.posted_date, 'delete bank statement entry');
2703
+ await tx.bank_statement_line.delete({
2704
+ where: { id },
2705
+ });
2706
+ const remainingLines = await tx.bank_statement_line.count({
2707
+ where: {
2708
+ bank_statement_id: statementLine.bank_statement_id,
2709
+ },
2710
+ });
2711
+ if (remainingLines === 0) {
2712
+ await tx.bank_statement.delete({
2713
+ where: {
2714
+ id: statementLine.bank_statement_id,
2715
+ },
2716
+ });
2717
+ }
2718
+ await this.createAuditLog(tx, {
2719
+ action: 'DELETE_BANK_STATEMENT_LINE',
2720
+ entityTable: 'bank_statement_line',
2721
+ entityId: String(statementLine.id),
2722
+ actorUserId: userId,
2723
+ summary: `Deleted bank statement line ${statementLine.id}`,
2724
+ });
2725
+ return { success: true };
2726
+ });
2727
+ }
2449
2728
  async importBankStatements(bankAccountId, file, locale, userId) {
2450
2729
  if (!file) {
2451
2730
  throw new common_1.BadRequestException('File is required');
@@ -2790,6 +3069,7 @@ let FinanceService = FinanceService_1 = class FinanceService {
2790
3069
  const accountType = this.mapAccountTypeFromPt(data.type);
2791
3070
  const code = this.generateBankAccountCode(data.bank, data.account);
2792
3071
  const name = ((_a = data.description) === null || _a === void 0 ? void 0 : _a.trim()) || data.bank;
3072
+ const startDate = this.parseFilterDate(data.start_date) || new Date();
2793
3073
  const createdAccount = await this.prisma.$transaction(async (tx) => {
2794
3074
  var _a;
2795
3075
  const account = await tx.bank_account.create({
@@ -2805,7 +3085,7 @@ let FinanceService = FinanceService_1 = class FinanceService {
2805
3085
  },
2806
3086
  });
2807
3087
  if (data.initial_balance && data.initial_balance > 0) {
2808
- const postedDate = new Date();
3088
+ const postedDate = startDate;
2809
3089
  await this.assertDateNotInClosedPeriod(tx, postedDate, 'create bank account initial balance');
2810
3090
  const statement = await tx.bank_statement.create({
2811
3091
  data: {
@@ -2836,6 +3116,8 @@ let FinanceService = FinanceService_1 = class FinanceService {
2836
3116
  select: {
2837
3117
  amount_cents: true,
2838
3118
  status: true,
3119
+ posted_date: true,
3120
+ description: true,
2839
3121
  },
2840
3122
  },
2841
3123
  },
@@ -2870,8 +3152,8 @@ let FinanceService = FinanceService_1 = class FinanceService {
2870
3152
  }
2871
3153
  async createPeriodClose(data, userId) {
2872
3154
  var _a;
2873
- const periodStart = new Date(data.period_start);
2874
- const periodEnd = new Date(data.period_end);
3155
+ const periodStart = this.parseLocalDate(data.period_start);
3156
+ const periodEnd = this.parseLocalDate(data.period_end);
2875
3157
  if (Number.isNaN(periodStart.getTime()) ||
2876
3158
  Number.isNaN(periodEnd.getTime())) {
2877
3159
  throw new common_1.BadRequestException('Invalid period dates');
@@ -2934,6 +3216,8 @@ let FinanceService = FinanceService_1 = class FinanceService {
2934
3216
  select: {
2935
3217
  amount_cents: true,
2936
3218
  status: true,
3219
+ posted_date: true,
3220
+ description: true,
2937
3221
  },
2938
3222
  },
2939
3223
  },
@@ -3064,10 +3348,12 @@ let FinanceService = FinanceService_1 = class FinanceService {
3064
3348
  });
3065
3349
  return { success: true };
3066
3350
  }
3067
- async listTitles(titleType, paginationParams, status) {
3351
+ async listTitles(titleType, paginationParams, status, filters) {
3068
3352
  var _a;
3069
3353
  const prismaStatus = this.mapStatusFromPt(status);
3070
3354
  const search = (_a = paginationParams === null || paginationParams === void 0 ? void 0 : paginationParams.search) === null || _a === void 0 ? void 0 : _a.trim();
3355
+ const fromDate = this.parseFilterDate(filters === null || filters === void 0 ? void 0 : filters.from);
3356
+ const toDate = this.parseFilterDate(filters === null || filters === void 0 ? void 0 : filters.to, true);
3071
3357
  const where = {
3072
3358
  title_type: titleType,
3073
3359
  };
@@ -3092,6 +3378,13 @@ let FinanceService = FinanceService_1 = class FinanceService {
3092
3378
  },
3093
3379
  ];
3094
3380
  }
3381
+ if (fromDate || toDate) {
3382
+ where.financial_installment = {
3383
+ some: {
3384
+ due_date: Object.assign(Object.assign({}, (fromDate ? { gte: fromDate } : {})), (toDate ? { lte: toDate } : {})),
3385
+ },
3386
+ };
3387
+ }
3095
3388
  const normalizedPaginationParams = Object.assign(Object.assign({}, paginationParams), { sortField: (paginationParams === null || paginationParams === void 0 ? void 0 : paginationParams.sortField) || 'created_at', sortOrder: (paginationParams === null || paginationParams === void 0 ? void 0 : paginationParams.sortOrder) || api_pagination_1.PageOrderDirection.Desc });
3096
3389
  const paginated = await this.paginationService.paginate(this.prisma.financial_title, normalizedPaginationParams, {
3097
3390
  where,
@@ -3160,8 +3453,8 @@ let FinanceService = FinanceService_1 = class FinanceService {
3160
3453
  }
3161
3454
  }
3162
3455
  await this.assertDateNotInClosedPeriod(tx, data.competence_date
3163
- ? new Date(data.competence_date)
3164
- : new Date(installments[0].due_date), 'create title');
3456
+ ? this.parseLocalDate(data.competence_date)
3457
+ : this.parseLocalDate(installments[0].due_date), 'create title');
3165
3458
  const title = await tx.financial_title.create({
3166
3459
  data: {
3167
3460
  person_id: data.person_id,
@@ -3170,9 +3463,9 @@ let FinanceService = FinanceService_1 = class FinanceService {
3170
3463
  document_number: data.document_number,
3171
3464
  description: data.description,
3172
3465
  competence_date: data.competence_date
3173
- ? new Date(data.competence_date)
3466
+ ? this.parseLocalDate(data.competence_date)
3174
3467
  : null,
3175
- issue_date: data.issue_date ? new Date(data.issue_date) : null,
3468
+ issue_date: data.issue_date ? this.parseLocalDate(data.issue_date) : null,
3176
3469
  total_amount_cents: this.toCents(data.total_amount),
3177
3470
  finance_category_id: data.finance_category_id,
3178
3471
  created_by_user_id: userId,
@@ -3195,12 +3488,12 @@ let FinanceService = FinanceService_1 = class FinanceService {
3195
3488
  title_id: title.id,
3196
3489
  installment_number: installment.installment_number,
3197
3490
  competence_date: data.competence_date
3198
- ? new Date(data.competence_date)
3199
- : new Date(installment.due_date),
3200
- due_date: new Date(installment.due_date),
3491
+ ? this.parseLocalDate(data.competence_date)
3492
+ : this.parseLocalDate(installment.due_date),
3493
+ due_date: this.parseLocalDate(installment.due_date),
3201
3494
  amount_cents: amountCents,
3202
3495
  open_amount_cents: amountCents,
3203
- status: this.resolveInstallmentStatus(amountCents, amountCents, new Date(installment.due_date)),
3496
+ status: this.resolveInstallmentStatus(amountCents, amountCents, this.parseLocalDate(installment.due_date)),
3204
3497
  notes: data.description,
3205
3498
  },
3206
3499
  });
@@ -3296,7 +3589,7 @@ let FinanceService = FinanceService_1 = class FinanceService {
3296
3589
  }
3297
3590
  }
3298
3591
  await this.assertDateNotInClosedPeriod(tx, data.competence_date
3299
- ? new Date(data.competence_date)
3592
+ ? this.parseLocalDate(data.competence_date)
3300
3593
  : new Date(installments[0].due_date), 'update title');
3301
3594
  const installmentIds = title.financial_installment.map((item) => item.id);
3302
3595
  if (installmentIds.length > 0) {
@@ -3321,9 +3614,9 @@ let FinanceService = FinanceService_1 = class FinanceService {
3321
3614
  document_number: data.document_number,
3322
3615
  description: data.description,
3323
3616
  competence_date: data.competence_date
3324
- ? new Date(data.competence_date)
3617
+ ? this.parseLocalDate(data.competence_date)
3325
3618
  : null,
3326
- issue_date: data.issue_date ? new Date(data.issue_date) : null,
3619
+ issue_date: data.issue_date ? this.parseLocalDate(data.issue_date) : null,
3327
3620
  total_amount_cents: this.toCents(data.total_amount),
3328
3621
  finance_category_id: data.finance_category_id,
3329
3622
  },
@@ -3356,12 +3649,12 @@ let FinanceService = FinanceService_1 = class FinanceService {
3356
3649
  title_id: title.id,
3357
3650
  installment_number: installment.installment_number,
3358
3651
  competence_date: data.competence_date
3359
- ? new Date(data.competence_date)
3360
- : new Date(installment.due_date),
3361
- due_date: new Date(installment.due_date),
3652
+ ? this.parseLocalDate(data.competence_date)
3653
+ : this.parseLocalDate(installment.due_date),
3654
+ due_date: this.parseLocalDate(installment.due_date),
3362
3655
  amount_cents: amountCents,
3363
3656
  open_amount_cents: amountCents,
3364
- status: this.resolveInstallmentStatus(amountCents, amountCents, new Date(installment.due_date)),
3657
+ status: this.resolveInstallmentStatus(amountCents, amountCents, this.parseLocalDate(installment.due_date)),
3365
3658
  notes: data.description,
3366
3659
  },
3367
3660
  });
@@ -3721,7 +4014,7 @@ let FinanceService = FinanceService_1 = class FinanceService {
3721
4014
  statementLine.bank_account_id !== data.bank_account_id) {
3722
4015
  throw new common_1.ConflictException('Bank statement line does not belong to informed bank account');
3723
4016
  }
3724
- if (Math.abs(statementLine.amount_cents) !== amountCents) {
4017
+ if (Math.abs(Number(statementLine.amount_cents)) !== amountCents) {
3725
4018
  throw new common_1.ConflictException('Bank statement amount and settlement amount must match');
3726
4019
  }
3727
4020
  const hasReconciliation = await tx.bank_reconciliation.findFirst({
@@ -3812,7 +4105,7 @@ let FinanceService = FinanceService_1 = class FinanceService {
3812
4105
  if (!updatedInstallment) {
3813
4106
  throw new common_1.NotFoundException('Installment not found');
3814
4107
  }
3815
- const nextInstallmentStatus = this.resolveInstallmentStatus(updatedInstallment.amount_cents, updatedInstallment.open_amount_cents, updatedInstallment.due_date);
4108
+ const nextInstallmentStatus = this.resolveInstallmentStatus(Number(updatedInstallment.amount_cents), Number(updatedInstallment.open_amount_cents), updatedInstallment.due_date);
3816
4109
  if (updatedInstallment.status !== nextInstallmentStatus) {
3817
4110
  await tx.financial_installment.update({
3818
4111
  where: {
@@ -4187,6 +4480,8 @@ let FinanceService = FinanceService_1 = class FinanceService {
4187
4480
  select: {
4188
4481
  amount_cents: true,
4189
4482
  status: true,
4483
+ posted_date: true,
4484
+ description: true,
4190
4485
  },
4191
4486
  },
4192
4487
  },
@@ -4484,6 +4779,29 @@ let FinanceService = FinanceService_1 = class FinanceService {
4484
4779
  };
4485
4780
  return statusMap[status] || 'aberto';
4486
4781
  }
4782
+ paginateCollection(items, paginationParams) {
4783
+ const requestedPage = Number((paginationParams === null || paginationParams === void 0 ? void 0 : paginationParams.page) || 1);
4784
+ const requestedPageSize = Number((paginationParams === null || paginationParams === void 0 ? void 0 : paginationParams.pageSize) || 10);
4785
+ const page = Number.isNaN(requestedPage) || requestedPage < 1
4786
+ ? 1
4787
+ : requestedPage;
4788
+ const pageSize = Number.isNaN(requestedPageSize) || requestedPageSize < 1
4789
+ ? 10
4790
+ : requestedPageSize;
4791
+ const total = items.length;
4792
+ const lastPage = Math.max(1, Math.ceil(total / pageSize));
4793
+ const currentPage = Math.min(page, lastPage);
4794
+ const start = (currentPage - 1) * pageSize;
4795
+ return {
4796
+ data: items.slice(start, start + pageSize),
4797
+ total,
4798
+ page: currentPage,
4799
+ pageSize,
4800
+ prev: currentPage > 1 ? currentPage - 1 : null,
4801
+ next: currentPage < lastPage ? currentPage + 1 : null,
4802
+ lastPage,
4803
+ };
4804
+ }
4487
4805
  normalizeTagSlug(value) {
4488
4806
  if (!value) {
4489
4807
  return '';
@@ -4566,10 +4884,14 @@ let FinanceService = FinanceService_1 = class FinanceService {
4566
4884
  }
4567
4885
  mapBankAccountToFront(bankAccount) {
4568
4886
  var _a;
4569
- const currentCents = (bankAccount.bank_statement_line || []).reduce((acc, line) => acc + line.amount_cents, 0);
4887
+ const currentCents = (bankAccount.bank_statement_line || []).reduce((acc, line) => acc + Number(line.amount_cents), 0);
4570
4888
  const reconciledCents = (bankAccount.bank_statement_line || []).reduce((acc, line) => line.status === 'reconciled' || line.status === 'adjusted'
4571
- ? acc + line.amount_cents
4889
+ ? acc + Number(line.amount_cents)
4572
4890
  : acc, 0);
4891
+ const initialLine = (bankAccount.bank_statement_line || []).find((line) => line.description === 'Saldo inicial');
4892
+ const dataInicial = (initialLine === null || initialLine === void 0 ? void 0 : initialLine.posted_date)
4893
+ ? new Date(initialLine.posted_date).toISOString().slice(0, 10)
4894
+ : null;
4573
4895
  return {
4574
4896
  id: String(bankAccount.id),
4575
4897
  codigo: bankAccount.code,
@@ -4582,6 +4904,7 @@ let FinanceService = FinanceService_1 = class FinanceService {
4582
4904
  saldoAtual: this.fromCents(currentCents),
4583
4905
  saldoConciliado: this.fromCents(reconciledCents),
4584
4906
  ativo: bankAccount.status === 'active',
4907
+ dataInicial,
4585
4908
  };
4586
4909
  }
4587
4910
  mapCostCenterToFront(costCenter) {
@@ -4825,6 +5148,17 @@ let FinanceService = FinanceService_1 = class FinanceService {
4825
5148
  toCents(value) {
4826
5149
  return Math.round(value * 100);
4827
5150
  }
5151
+ /**
5152
+ * Parses a date-only string (YYYY-MM-DD) as local noon to avoid
5153
+ * timezone shifts that would cause the date to appear as the previous day
5154
+ * in UTC-negative timezones.
5155
+ */
5156
+ parseLocalDate(dateString) {
5157
+ if (/^\d{4}-\d{2}-\d{2}$/.test(dateString)) {
5158
+ return new Date(dateString + 'T12:00:00.000Z');
5159
+ }
5160
+ return new Date(dateString);
5161
+ }
4828
5162
  fromCents(value) {
4829
5163
  if (value === null || value === undefined) {
4830
5164
  return 0;