@hed-hog/finance 0.0.252 → 0.0.256

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 (32) hide show
  1. package/dist/dto/reverse-settlement.dto.d.ts +1 -0
  2. package/dist/dto/reverse-settlement.dto.d.ts.map +1 -1
  3. package/dist/dto/reverse-settlement.dto.js +5 -0
  4. package/dist/dto/reverse-settlement.dto.js.map +1 -1
  5. package/dist/finance-installments.controller.d.ts +106 -4
  6. package/dist/finance-installments.controller.d.ts.map +1 -1
  7. package/dist/finance-installments.controller.js +38 -2
  8. package/dist/finance-installments.controller.js.map +1 -1
  9. package/dist/finance.service.d.ts +104 -2
  10. package/dist/finance.service.d.ts.map +1 -1
  11. package/dist/finance.service.js +366 -121
  12. package/dist/finance.service.js.map +1 -1
  13. package/hedhog/data/route.yaml +27 -0
  14. package/hedhog/frontend/app/_components/finance-entity-field-with-create.tsx.ejs +572 -0
  15. package/hedhog/frontend/app/_components/finance-title-actions-menu.tsx.ejs +244 -0
  16. package/hedhog/frontend/app/_components/person-field-with-create.tsx.ejs +143 -51
  17. package/hedhog/frontend/app/_lib/title-action-rules.ts.ejs +36 -0
  18. package/hedhog/frontend/app/accounts-payable/installments/[id]/page.tsx.ejs +449 -293
  19. package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +1189 -545
  20. package/hedhog/frontend/app/accounts-receivable/installments/[id]/page.tsx.ejs +176 -133
  21. package/hedhog/frontend/app/accounts-receivable/installments/page.tsx.ejs +1459 -312
  22. package/hedhog/frontend/app/page.tsx.ejs +15 -4
  23. package/hedhog/frontend/messages/en.json +294 -5
  24. package/hedhog/frontend/messages/pt.json +294 -5
  25. package/hedhog/query/settlement-auditability.sql +175 -0
  26. package/hedhog/table/bank_reconciliation.yaml +11 -0
  27. package/hedhog/table/settlement.yaml +17 -1
  28. package/hedhog/table/settlement_allocation.yaml +3 -0
  29. package/package.json +7 -7
  30. package/src/dto/reverse-settlement.dto.ts +4 -0
  31. package/src/finance-installments.controller.ts +45 -12
  32. package/src/finance.service.ts +521 -146
@@ -690,6 +690,138 @@ let FinanceService = FinanceService_1 = class FinanceService {
690
690
  async reverseAccountsReceivableSettlement(id, settlementId, data, locale, userId) {
691
691
  return this.reverseTitleSettlement(id, settlementId, data, 'receivable', locale, userId);
692
692
  }
693
+ async getTitleSettlementsHistory(titleId, locale) {
694
+ var _a, _b, _c, _d, _e, _f, _g, _h;
695
+ const title = await this.prisma.financial_title.findUnique({
696
+ where: { id: titleId },
697
+ select: { id: true },
698
+ });
699
+ if (!title) {
700
+ throw new common_1.NotFoundException((0, api_locale_1.getLocaleText)('itemNotFound', locale, `Financial title with ID ${titleId} not found`).replace('{{item}}', 'Financial title'));
701
+ }
702
+ const rows = await this.prisma.$queryRaw `
703
+ SELECT
704
+ s.id AS normal_id,
705
+ s.settled_at AS normal_paid_at,
706
+ s.amount_cents AS normal_amount_cents,
707
+ pm.type::text AS normal_method,
708
+ s.bank_account_id AS normal_account_id,
709
+ ba.name AS normal_account_name,
710
+ s.created_at AS normal_created_at,
711
+ u.name AS normal_created_by,
712
+ s.description AS normal_memo,
713
+ br.id AS reconciliation_id,
714
+ br.status::text AS reconciliation_status,
715
+ fi.id AS installment_id,
716
+ fi.installment_number AS installment_seq,
717
+ COALESCE(sa.amount_cents, sa.allocated_amount_cents) AS allocation_amount_cents,
718
+ r.id AS reversal_id,
719
+ r.settled_at AS reversal_paid_at,
720
+ r.amount_cents AS reversal_amount_cents,
721
+ r.created_at AS reversal_created_at,
722
+ ur.name AS reversal_created_by,
723
+ r.description AS reversal_memo
724
+ FROM settlement s
725
+ INNER JOIN settlement_allocation sa ON sa.settlement_id = s.id
726
+ INNER JOIN financial_installment fi ON fi.id = sa.installment_id
727
+ LEFT JOIN payment_method pm ON pm.id = s.payment_method_id
728
+ LEFT JOIN bank_account ba ON ba.id = s.bank_account_id
729
+ LEFT JOIN "user" u ON u.id = s.created_by_user_id
730
+ LEFT JOIN bank_reconciliation br ON br.settlement_id = s.id
731
+ LEFT JOIN settlement r ON r.reverses_settlement_id = s.id
732
+ LEFT JOIN "user" ur ON ur.id = r.created_by_user_id
733
+ WHERE fi.title_id = ${titleId}
734
+ AND COALESCE(s.entry_type::text, 'normal') = 'normal'
735
+ ORDER BY s.settled_at DESC, s.id DESC, fi.installment_number ASC
736
+ `;
737
+ const groups = new Map();
738
+ for (const row of rows) {
739
+ const key = String(row.normal_id);
740
+ const existing = groups.get(key);
741
+ if (!existing) {
742
+ groups.set(key, {
743
+ normal: {
744
+ id: key,
745
+ paidAt: ((_b = (_a = row.normal_paid_at) === null || _a === void 0 ? void 0 : _a.toISOString) === null || _b === void 0 ? void 0 : _b.call(_a)) || null,
746
+ amountCents: Number(row.normal_amount_cents || 0),
747
+ type: 'NORMAL',
748
+ method: this.mapPaymentMethodToPt(row.normal_method) || row.normal_method,
749
+ account: row.normal_account_name || null,
750
+ accountId: row.normal_account_id
751
+ ? String(row.normal_account_id)
752
+ : null,
753
+ createdAt: ((_d = (_c = row.normal_created_at) === null || _c === void 0 ? void 0 : _c.toISOString) === null || _d === void 0 ? void 0 : _d.call(_c)) || null,
754
+ createdBy: row.normal_created_by || null,
755
+ memo: row.normal_memo || null,
756
+ reconciled: row.reconciliation_status === 'reconciled',
757
+ reconciliationId: row.reconciliation_id
758
+ ? String(row.reconciliation_id)
759
+ : null,
760
+ },
761
+ reversal: row.reversal_id
762
+ ? {
763
+ id: String(row.reversal_id),
764
+ paidAt: ((_f = (_e = row.reversal_paid_at) === null || _e === void 0 ? void 0 : _e.toISOString) === null || _f === void 0 ? void 0 : _f.call(_e)) || null,
765
+ amountCents: Number(row.reversal_amount_cents || 0),
766
+ type: 'REVERSAL',
767
+ createdAt: ((_h = (_g = row.reversal_created_at) === null || _g === void 0 ? void 0 : _g.toISOString) === null || _h === void 0 ? void 0 : _h.call(_g)) || null,
768
+ createdBy: row.reversal_created_by || null,
769
+ memo: row.reversal_memo || null,
770
+ }
771
+ : null,
772
+ allocations: [],
773
+ statusLabel: row.reversal_id ? 'ESTORNADO' : 'ATIVO',
774
+ });
775
+ }
776
+ groups.get(key).allocations.push({
777
+ installmentId: String(row.installment_id),
778
+ installmentSeq: Number(row.installment_seq || 0),
779
+ amountCents: Number(row.allocation_amount_cents || 0),
780
+ });
781
+ }
782
+ return Array.from(groups.values());
783
+ }
784
+ async reverseSettlementById(settlementId, data, locale, userId) {
785
+ const updatedTitle = await this.reverseSettlementInternal(settlementId, data, locale, userId);
786
+ return this.mapTitleToFront(updatedTitle);
787
+ }
788
+ async unreconcileBankReconciliation(id, userId) {
789
+ const reconciliation = await this.prisma.bank_reconciliation.findUnique({
790
+ where: { id },
791
+ select: {
792
+ id: true,
793
+ settlement_id: true,
794
+ bank_statement_line_id: true,
795
+ },
796
+ });
797
+ if (!reconciliation) {
798
+ throw new common_1.NotFoundException('Conciliação bancária não encontrada');
799
+ }
800
+ await this.prisma.$transaction(async (tx) => {
801
+ await tx.bank_reconciliation.delete({
802
+ where: { id: reconciliation.id },
803
+ });
804
+ await tx.bank_statement_line.updateMany({
805
+ where: {
806
+ id: reconciliation.bank_statement_line_id,
807
+ status: {
808
+ in: ['reconciled', 'adjusted'],
809
+ },
810
+ },
811
+ data: {
812
+ status: 'pending',
813
+ },
814
+ });
815
+ await this.createAuditLog(tx, {
816
+ action: 'UNRECONCILE_SETTLEMENT',
817
+ entityTable: 'bank_reconciliation',
818
+ entityId: String(reconciliation.id),
819
+ actorUserId: userId,
820
+ summary: `Unreconciled settlement ${reconciliation.settlement_id}`,
821
+ });
822
+ });
823
+ return { success: true };
824
+ }
693
825
  async createTag(data) {
694
826
  const slug = this.normalizeTagSlug(data.name);
695
827
  if (!slug) {
@@ -1559,18 +1691,40 @@ let FinanceService = FinanceService_1 = class FinanceService {
1559
1691
  return { success: true };
1560
1692
  }
1561
1693
  async listTitles(titleType, paginationParams, status) {
1694
+ var _a;
1562
1695
  const prismaStatus = this.mapStatusFromPt(status);
1696
+ const search = (_a = paginationParams === null || paginationParams === void 0 ? void 0 : paginationParams.search) === null || _a === void 0 ? void 0 : _a.trim();
1563
1697
  const where = {
1564
1698
  title_type: titleType,
1565
1699
  };
1566
1700
  if (prismaStatus) {
1567
1701
  where.status = prismaStatus;
1568
1702
  }
1569
- return this.paginationService.paginate(this.prisma.financial_title, paginationParams, {
1703
+ if (search) {
1704
+ where.OR = [
1705
+ {
1706
+ document_number: {
1707
+ contains: search,
1708
+ mode: 'insensitive',
1709
+ },
1710
+ },
1711
+ {
1712
+ person: {
1713
+ name: {
1714
+ contains: search,
1715
+ mode: 'insensitive',
1716
+ },
1717
+ },
1718
+ },
1719
+ ];
1720
+ }
1721
+ 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 });
1722
+ const paginated = await this.paginationService.paginate(this.prisma.financial_title, normalizedPaginationParams, {
1570
1723
  where,
1571
1724
  include: this.defaultTitleInclude(),
1572
1725
  orderBy: { created_at: 'desc' },
1573
1726
  });
1727
+ return Object.assign(Object.assign({}, paginated), { data: (paginated.data || []).map((title) => this.mapTitleToFront(title)) });
1574
1728
  }
1575
1729
  async getTitleById(id, titleType, locale) {
1576
1730
  const title = await this.prisma.financial_title.findFirst({
@@ -2022,6 +2176,7 @@ let FinanceService = FinanceService_1 = class FinanceService {
2022
2176
  }
2023
2177
  async cancelTitle(titleId, data, titleType, locale, userId) {
2024
2178
  const updatedTitle = await this.prisma.$transaction(async (tx) => {
2179
+ var _a;
2025
2180
  const title = await tx.financial_title.findFirst({
2026
2181
  where: {
2027
2182
  id: titleId,
@@ -2039,21 +2194,19 @@ let FinanceService = FinanceService_1 = class FinanceService {
2039
2194
  if (title.status === 'settled' || title.status === 'canceled') {
2040
2195
  throw new common_1.BadRequestException('Title cannot be canceled in current status');
2041
2196
  }
2042
- const hasActiveSettlements = await tx.settlement_allocation.findFirst({
2043
- where: {
2044
- financial_installment: {
2045
- title_id: title.id,
2046
- },
2047
- settlement: {
2048
- status: {
2049
- not: 'reversed',
2050
- },
2051
- },
2052
- },
2053
- select: {
2054
- id: true,
2055
- },
2056
- });
2197
+ const activeSettlements = await tx.$queryRaw `
2198
+ SELECT EXISTS (
2199
+ SELECT 1
2200
+ FROM settlement_allocation sa
2201
+ INNER JOIN financial_installment fi ON fi.id = sa.installment_id
2202
+ INNER JOIN settlement s ON s.id = sa.settlement_id
2203
+ LEFT JOIN settlement r ON r.reverses_settlement_id = s.id
2204
+ WHERE fi.title_id = ${title.id}
2205
+ AND COALESCE(s.entry_type::text, 'normal') = 'normal'
2206
+ AND r.id IS NULL
2207
+ ) AS has_active
2208
+ `;
2209
+ const hasActiveSettlements = (_a = activeSettlements[0]) === null || _a === void 0 ? void 0 : _a.has_active;
2057
2210
  if (hasActiveSettlements) {
2058
2211
  throw new common_1.ConflictException('Não é possível cancelar enquanto houver liquidações ativas. Estorne primeiro.');
2059
2212
  }
@@ -2176,9 +2329,18 @@ let FinanceService = FinanceService_1 = class FinanceService {
2176
2329
  });
2177
2330
  await tx.settlement_allocation.create({
2178
2331
  data: {
2179
- settlement_id: settlement.id,
2180
- installment_id: installment.id,
2332
+ settlement: {
2333
+ connect: {
2334
+ id: settlement.id,
2335
+ },
2336
+ },
2337
+ financial_installment: {
2338
+ connect: {
2339
+ id: installment.id,
2340
+ },
2341
+ },
2181
2342
  allocated_amount_cents: amountCents,
2343
+ amount_cents: amountCents,
2182
2344
  discount_cents: this.toCents(data.discount || 0),
2183
2345
  interest_cents: this.toCents(data.interest || 0),
2184
2346
  penalty_cents: this.toCents(data.penalty || 0),
@@ -2270,69 +2432,167 @@ let FinanceService = FinanceService_1 = class FinanceService {
2270
2432
  }
2271
2433
  }
2272
2434
  async reverseTitleSettlement(titleId, settlementId, data, titleType, locale, userId) {
2273
- const updatedTitle = await this.prisma.$transaction(async (tx) => {
2274
- const title = await tx.financial_title.findFirst({
2275
- where: {
2276
- id: titleId,
2277
- title_type: titleType,
2278
- },
2279
- select: {
2280
- id: true,
2281
- status: true,
2282
- competence_date: true,
2283
- },
2284
- });
2285
- if (!title) {
2286
- throw new common_1.NotFoundException((0, api_locale_1.getLocaleText)('itemNotFound', locale, `Financial title with ID ${titleId} not found`).replace('{{item}}', 'Financial title'));
2435
+ const updatedTitle = await this.reverseSettlementInternal(settlementId, data, locale, userId, {
2436
+ titleId,
2437
+ titleType,
2438
+ });
2439
+ if (!updatedTitle) {
2440
+ throw new common_1.NotFoundException('Financial title not found');
2441
+ }
2442
+ return this.mapTitleToFront(updatedTitle);
2443
+ }
2444
+ async reverseSettlementInternal(settlementId, data, locale, userId, scope) {
2445
+ const { title } = await this.prisma.$transaction(async (tx) => {
2446
+ var _a, _b, _c, _d, _e;
2447
+ const settlementRows = await tx.$queryRaw `
2448
+ SELECT
2449
+ s.id,
2450
+ s.settlement_type::text,
2451
+ s.settled_at,
2452
+ s.amount_cents,
2453
+ s.description,
2454
+ s.person_id,
2455
+ s.bank_account_id,
2456
+ s.payment_method_id,
2457
+ s.created_by_user_id,
2458
+ COALESCE(s.entry_type::text, 'normal') AS entry_type,
2459
+ ft.id AS title_id,
2460
+ ft.title_type::text AS title_type,
2461
+ ft.status::text AS title_status,
2462
+ ft.competence_date AS title_competence_date
2463
+ FROM settlement s
2464
+ INNER JOIN settlement_allocation sa ON sa.settlement_id = s.id
2465
+ INNER JOIN financial_installment fi ON fi.id = sa.installment_id
2466
+ INNER JOIN financial_title ft ON ft.id = fi.title_id
2467
+ WHERE s.id = ${settlementId}
2468
+ LIMIT 1
2469
+ FOR UPDATE OF s
2470
+ `;
2471
+ const settlement = settlementRows[0];
2472
+ if (!settlement) {
2473
+ throw new common_1.NotFoundException('Settlement not found');
2287
2474
  }
2288
- await this.assertDateNotInClosedPeriod(tx, title.competence_date, 'reverse settlement');
2289
- const settlement = await tx.settlement.findFirst({
2290
- where: {
2291
- id: settlementId,
2292
- settlement_type: titleType,
2293
- settlement_allocation: {
2294
- some: {
2295
- financial_installment: {
2296
- title_id: title.id,
2475
+ if ((scope === null || scope === void 0 ? void 0 : scope.titleId) && settlement.title_id !== scope.titleId) {
2476
+ throw new common_1.NotFoundException('Settlement not found for this title');
2477
+ }
2478
+ if ((scope === null || scope === void 0 ? void 0 : scope.titleType) && settlement.title_type !== scope.titleType) {
2479
+ throw new common_1.NotFoundException('Settlement not found for this title type');
2480
+ }
2481
+ if (settlement.entry_type !== 'normal') {
2482
+ throw new common_1.BadRequestException('Somente liquidações normais podem ser estornadas');
2483
+ }
2484
+ await this.assertDateNotInClosedPeriod(tx, settlement.title_competence_date, 'reverse settlement');
2485
+ const alreadyReversed = await tx.$queryRaw `
2486
+ SELECT id
2487
+ FROM settlement
2488
+ WHERE reverses_settlement_id = ${settlement.id}
2489
+ LIMIT 1
2490
+ `;
2491
+ if (alreadyReversed.length > 0) {
2492
+ throw new common_1.ConflictException('Liquidação já estornada.');
2493
+ }
2494
+ const isReconciled = await tx.$queryRaw `
2495
+ SELECT id
2496
+ FROM bank_reconciliation
2497
+ WHERE settlement_id = ${settlement.id}
2498
+ AND status = 'reconciled'
2499
+ LIMIT 1
2500
+ `;
2501
+ if (isReconciled.length > 0) {
2502
+ throw new common_1.ConflictException('Desconciliar primeiro');
2503
+ }
2504
+ const allocations = await tx.$queryRaw `
2505
+ SELECT
2506
+ sa.id,
2507
+ sa.installment_id,
2508
+ sa.allocated_amount_cents,
2509
+ sa.amount_cents,
2510
+ sa.discount_cents,
2511
+ sa.interest_cents,
2512
+ sa.penalty_cents,
2513
+ fi.amount_cents AS installment_amount_cents,
2514
+ fi.open_amount_cents AS installment_open_amount_cents,
2515
+ fi.due_date AS installment_due_date,
2516
+ fi.status::text AS installment_status
2517
+ FROM settlement_allocation sa
2518
+ INNER JOIN financial_installment fi ON fi.id = sa.installment_id
2519
+ WHERE sa.settlement_id = ${settlement.id}
2520
+ FOR UPDATE OF fi
2521
+ `;
2522
+ if (allocations.length === 0) {
2523
+ throw new common_1.BadRequestException('Settlement has no allocations to reverse');
2524
+ }
2525
+ const reversalMemo = ((_a = data.reason) === null || _a === void 0 ? void 0 : _a.trim()) || ((_b = data.memo) === null || _b === void 0 ? void 0 : _b.trim()) || 'Estorno';
2526
+ const reversalAmountCents = -Math.abs(Number(settlement.amount_cents || 0));
2527
+ const reversalResult = await tx.$queryRaw `
2528
+ INSERT INTO settlement (
2529
+ person_id,
2530
+ bank_account_id,
2531
+ payment_method_id,
2532
+ settlement_type,
2533
+ entry_type,
2534
+ status,
2535
+ settled_at,
2536
+ amount_cents,
2537
+ description,
2538
+ external_reference,
2539
+ created_by_user_id,
2540
+ reverses_settlement_id,
2541
+ created_at,
2542
+ updated_at
2543
+ )
2544
+ VALUES (
2545
+ ${settlement.person_id},
2546
+ ${settlement.bank_account_id},
2547
+ ${settlement.payment_method_id},
2548
+ ${settlement.settlement_type}::settlement_settlement_type_enum,
2549
+ 'reversal'::settlement_entry_type_enum,
2550
+ 'confirmed'::settlement_status_enum,
2551
+ NOW(),
2552
+ ${reversalAmountCents},
2553
+ ${reversalMemo},
2554
+ NULL,
2555
+ ${userId || settlement.created_by_user_id || null},
2556
+ ${settlement.id},
2557
+ NOW(),
2558
+ NOW()
2559
+ )
2560
+ RETURNING id
2561
+ `;
2562
+ const reversalId = (_c = reversalResult[0]) === null || _c === void 0 ? void 0 : _c.id;
2563
+ if (!reversalId) {
2564
+ throw new common_1.BadRequestException('Could not create reversal settlement');
2565
+ }
2566
+ for (const allocation of allocations) {
2567
+ const originalAmount = Number((_e = (_d = allocation.amount_cents) !== null && _d !== void 0 ? _d : allocation.allocated_amount_cents) !== null && _e !== void 0 ? _e : 0);
2568
+ await tx.settlement_allocation.create({
2569
+ data: {
2570
+ settlement: {
2571
+ connect: {
2572
+ id: reversalId,
2297
2573
  },
2298
2574
  },
2299
- },
2300
- },
2301
- include: {
2302
- settlement_allocation: {
2303
- include: {
2304
- financial_installment: {
2305
- select: {
2306
- id: true,
2307
- amount_cents: true,
2308
- open_amount_cents: true,
2309
- due_date: true,
2310
- status: true,
2311
- },
2575
+ financial_installment: {
2576
+ connect: {
2577
+ id: allocation.installment_id,
2312
2578
  },
2313
2579
  },
2580
+ allocated_amount_cents: -Math.abs(originalAmount),
2581
+ amount_cents: -Math.abs(originalAmount),
2582
+ discount_cents: -Math.abs(allocation.discount_cents || 0),
2583
+ interest_cents: -Math.abs(allocation.interest_cents || 0),
2584
+ penalty_cents: -Math.abs(allocation.penalty_cents || 0),
2314
2585
  },
2315
- },
2316
- });
2317
- if (!settlement) {
2318
- throw new common_1.NotFoundException('Settlement not found for this title');
2319
- }
2320
- if (settlement.status === 'reversed') {
2321
- throw new common_1.ConflictException('Liquidação já estornada.');
2322
- }
2323
- for (const allocation of settlement.settlement_allocation) {
2324
- const installment = allocation.financial_installment;
2325
- if (!installment) {
2326
- continue;
2327
- }
2328
- const nextOpenAmountCents = installment.open_amount_cents + allocation.allocated_amount_cents;
2329
- if (nextOpenAmountCents > installment.amount_cents) {
2330
- throw new common_1.BadRequestException(`Reverse would exceed installment amount for installment ${installment.id}`);
2586
+ });
2587
+ const nextOpenAmountCents = Number(allocation.installment_open_amount_cents || 0) +
2588
+ Math.abs(originalAmount);
2589
+ if (nextOpenAmountCents > Number(allocation.installment_amount_cents || 0)) {
2590
+ throw new common_1.ConflictException(`Estorno excederia o valor original da parcela ${allocation.installment_id}`);
2331
2591
  }
2332
- const nextInstallmentStatus = this.resolveInstallmentStatus(installment.amount_cents, nextOpenAmountCents, installment.due_date);
2592
+ const nextInstallmentStatus = this.resolveInstallmentStatus(Number(allocation.installment_amount_cents || 0), nextOpenAmountCents, new Date(allocation.installment_due_date), allocation.installment_status);
2333
2593
  await tx.financial_installment.update({
2334
2594
  where: {
2335
- id: installment.id,
2595
+ id: allocation.installment_id,
2336
2596
  },
2337
2597
  data: {
2338
2598
  open_amount_cents: nextOpenAmountCents,
@@ -2340,69 +2600,39 @@ let FinanceService = FinanceService_1 = class FinanceService {
2340
2600
  },
2341
2601
  });
2342
2602
  }
2343
- await tx.settlement.update({
2344
- where: {
2345
- id: settlement.id,
2346
- },
2347
- data: {
2348
- status: 'reversed',
2349
- description: [
2350
- settlement.description,
2351
- data.reason ? `Reversed: ${data.reason.trim()}` : 'Reversed',
2352
- ]
2353
- .filter(Boolean)
2354
- .join(' | '),
2355
- },
2356
- });
2357
- await tx.bank_reconciliation.updateMany({
2358
- where: {
2359
- settlement_id: settlement.id,
2360
- status: 'pending',
2361
- },
2362
- data: {
2363
- status: 'reversed',
2364
- },
2365
- });
2366
- await tx.bank_reconciliation.updateMany({
2367
- where: {
2368
- settlement_id: settlement.id,
2369
- status: {
2370
- in: ['reconciled', 'adjusted'],
2371
- },
2372
- },
2373
- data: {
2374
- status: 'adjusted',
2375
- },
2376
- });
2377
- const previousTitleStatus = title.status;
2378
- const nextTitleStatus = await this.recalculateTitleStatus(tx, title.id);
2603
+ const nextTitleStatus = await this.recalculateTitleStatus(tx, settlement.title_id);
2379
2604
  await this.createAuditLog(tx, {
2380
2605
  action: 'REVERSE_SETTLEMENT',
2381
2606
  entityTable: 'financial_title',
2382
- entityId: String(title.id),
2607
+ entityId: String(settlement.title_id),
2383
2608
  actorUserId: userId,
2384
- summary: `Reversed settlement ${settlement.id} from title ${title.id}`,
2609
+ summary: `Created reversal ${reversalId} for settlement ${settlement.id}`,
2385
2610
  beforeData: JSON.stringify({
2386
- title_status: previousTitleStatus,
2387
- settlement_status: settlement.status,
2611
+ title_status: settlement.title_status,
2612
+ settlement_id: settlement.id,
2613
+ settlement_entry_type: settlement.entry_type,
2388
2614
  }),
2389
2615
  afterData: JSON.stringify({
2390
2616
  title_status: nextTitleStatus,
2391
- settlement_status: 'reversed',
2617
+ settlement_id: settlement.id,
2618
+ reversal_settlement_id: reversalId,
2392
2619
  }),
2393
2620
  });
2394
- return tx.financial_title.findFirst({
2621
+ const updatedTitle = await tx.financial_title.findFirst({
2395
2622
  where: {
2396
- id: title.id,
2397
- title_type: titleType,
2623
+ id: settlement.title_id,
2624
+ title_type: settlement.title_type,
2398
2625
  },
2399
2626
  include: this.defaultTitleInclude(),
2400
2627
  });
2628
+ if (!updatedTitle) {
2629
+ throw new common_1.NotFoundException('Financial title not found');
2630
+ }
2631
+ return {
2632
+ title: updatedTitle,
2633
+ };
2401
2634
  });
2402
- if (!updatedTitle) {
2403
- throw new common_1.NotFoundException('Financial title not found');
2404
- }
2405
- return this.mapTitleToFront(updatedTitle);
2635
+ return title;
2406
2636
  }
2407
2637
  async updateTitleTags(titleId, titleType, tagIds, locale) {
2408
2638
  const title = await this.getTitleById(titleId, titleType, locale);
@@ -3023,7 +3253,22 @@ let FinanceService = FinanceService_1 = class FinanceService {
3023
3253
  return Math.round(value * 100);
3024
3254
  }
3025
3255
  fromCents(value) {
3026
- return Number((value / 100).toFixed(2));
3256
+ if (value === null || value === undefined) {
3257
+ return 0;
3258
+ }
3259
+ if (typeof value === 'bigint') {
3260
+ const isNegative = value < BigInt(0);
3261
+ const absoluteValue = isNegative ? -value : value;
3262
+ const whole = absoluteValue / BigInt(100);
3263
+ const cents = absoluteValue % BigInt(100);
3264
+ const composedValue = Number(whole) + Number(cents) / 100;
3265
+ return Number((isNegative ? -composedValue : composedValue).toFixed(2));
3266
+ }
3267
+ const numericValue = typeof value === 'string' ? Number(value) : Number(value || 0);
3268
+ if (!Number.isFinite(numericValue)) {
3269
+ return 0;
3270
+ }
3271
+ return Number((numericValue / 100).toFixed(2));
3027
3272
  }
3028
3273
  };
3029
3274
  exports.FinanceService = FinanceService;