@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.
- package/dist/dto/reverse-settlement.dto.d.ts +1 -0
- package/dist/dto/reverse-settlement.dto.d.ts.map +1 -1
- package/dist/dto/reverse-settlement.dto.js +5 -0
- package/dist/dto/reverse-settlement.dto.js.map +1 -1
- package/dist/finance-installments.controller.d.ts +106 -4
- package/dist/finance-installments.controller.d.ts.map +1 -1
- package/dist/finance-installments.controller.js +38 -2
- package/dist/finance-installments.controller.js.map +1 -1
- package/dist/finance.service.d.ts +104 -2
- package/dist/finance.service.d.ts.map +1 -1
- package/dist/finance.service.js +366 -121
- package/dist/finance.service.js.map +1 -1
- package/hedhog/data/route.yaml +27 -0
- package/hedhog/frontend/app/_components/finance-entity-field-with-create.tsx.ejs +572 -0
- package/hedhog/frontend/app/_components/finance-title-actions-menu.tsx.ejs +244 -0
- package/hedhog/frontend/app/_components/person-field-with-create.tsx.ejs +143 -51
- package/hedhog/frontend/app/_lib/title-action-rules.ts.ejs +36 -0
- package/hedhog/frontend/app/accounts-payable/installments/[id]/page.tsx.ejs +449 -293
- package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +1189 -545
- package/hedhog/frontend/app/accounts-receivable/installments/[id]/page.tsx.ejs +176 -133
- package/hedhog/frontend/app/accounts-receivable/installments/page.tsx.ejs +1459 -312
- package/hedhog/frontend/app/page.tsx.ejs +15 -4
- package/hedhog/frontend/messages/en.json +294 -5
- package/hedhog/frontend/messages/pt.json +294 -5
- package/hedhog/query/settlement-auditability.sql +175 -0
- package/hedhog/table/bank_reconciliation.yaml +11 -0
- package/hedhog/table/settlement.yaml +17 -1
- package/hedhog/table/settlement_allocation.yaml +3 -0
- package/package.json +7 -7
- package/src/dto/reverse-settlement.dto.ts +4 -0
- package/src/finance-installments.controller.ts +45 -12
- package/src/finance.service.ts +521 -146
package/dist/finance.service.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
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
|
-
|
|
2180
|
-
|
|
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.
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
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
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2318
|
-
|
|
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(
|
|
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:
|
|
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
|
|
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(
|
|
2607
|
+
entityId: String(settlement.title_id),
|
|
2383
2608
|
actorUserId: userId,
|
|
2384
|
-
summary: `
|
|
2609
|
+
summary: `Created reversal ${reversalId} for settlement ${settlement.id}`,
|
|
2385
2610
|
beforeData: JSON.stringify({
|
|
2386
|
-
title_status:
|
|
2387
|
-
|
|
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
|
-
|
|
2617
|
+
settlement_id: settlement.id,
|
|
2618
|
+
reversal_settlement_id: reversalId,
|
|
2392
2619
|
}),
|
|
2393
2620
|
});
|
|
2394
|
-
|
|
2621
|
+
const updatedTitle = await tx.financial_title.findFirst({
|
|
2395
2622
|
where: {
|
|
2396
|
-
id:
|
|
2397
|
-
title_type:
|
|
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
|
-
|
|
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
|
-
|
|
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;
|