@hed-hog/finance 0.0.364 → 0.0.366
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/create-financial-title.dto.d.ts +8 -1
- package/dist/dto/create-financial-title.dto.d.ts.map +1 -1
- package/dist/dto/create-financial-title.dto.js +41 -3
- package/dist/dto/create-financial-title.dto.js.map +1 -1
- package/dist/finance.service.d.ts +1 -0
- package/dist/finance.service.d.ts.map +1 -1
- package/dist/finance.service.js +213 -120
- package/dist/finance.service.js.map +1 -1
- package/hedhog/data/route.yaml +6 -0
- package/hedhog/frontend/app/_components/finance-layout.tsx.ejs +15 -10
- package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +1135 -573
- package/hedhog/frontend/app/accounts-receivable/installments/page.tsx.ejs +1132 -570
- package/hedhog/frontend/messages/en.json +92 -24
- package/hedhog/frontend/messages/pt.json +92 -24
- package/package.json +7 -7
- package/src/dto/create-financial-title.dto.ts +36 -5
- package/src/finance.service.ts +314 -144
package/src/finance.service.ts
CHANGED
|
@@ -26,7 +26,10 @@ import { CreateCostCenterDto } from './dto/create-cost-center.dto';
|
|
|
26
26
|
import { CreateCurrencyDto } from './dto/create-currency.dto';
|
|
27
27
|
import { CreateFinanceCategoryDto } from './dto/create-finance-category.dto';
|
|
28
28
|
import { CreateFinanceTagDto } from './dto/create-finance-tag.dto';
|
|
29
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
CreateFinancialInstallmentDto,
|
|
31
|
+
CreateFinancialTitleDto,
|
|
32
|
+
} from './dto/create-financial-title.dto';
|
|
30
33
|
import { CreatePeriodCloseDto } from './dto/create-period-close.dto';
|
|
31
34
|
import { CreateTransferDto } from './dto/create-transfer.dto';
|
|
32
35
|
import { MoveFinanceCategoryDto } from './dto/move-finance-category.dto';
|
|
@@ -5113,8 +5116,29 @@ export class FinanceService {
|
|
|
5113
5116
|
);
|
|
5114
5117
|
}
|
|
5115
5118
|
|
|
5119
|
+
if (rule && (data.installments || []).some((installment) => installment.paid)) {
|
|
5120
|
+
throw new BadRequestException(
|
|
5121
|
+
getLocaleText(
|
|
5122
|
+
'recurringCannotBePaid',
|
|
5123
|
+
locale,
|
|
5124
|
+
'Recurring titles cannot be created as already paid',
|
|
5125
|
+
),
|
|
5126
|
+
);
|
|
5127
|
+
}
|
|
5128
|
+
|
|
5116
5129
|
const isRecurring = Boolean(rule);
|
|
5117
|
-
|
|
5130
|
+
type NormalizedInstallment = {
|
|
5131
|
+
installment_number: number;
|
|
5132
|
+
due_date: string;
|
|
5133
|
+
amount_cents: number;
|
|
5134
|
+
paid?: boolean;
|
|
5135
|
+
paid_at?: string;
|
|
5136
|
+
paid_amount_cents?: number;
|
|
5137
|
+
discount_cents?: number;
|
|
5138
|
+
interest_cents?: number;
|
|
5139
|
+
penalty_cents?: number;
|
|
5140
|
+
};
|
|
5141
|
+
const installments: NormalizedInstallment[] = isRecurring
|
|
5118
5142
|
? this.buildRecurrenceInstallments(
|
|
5119
5143
|
data.due_date,
|
|
5120
5144
|
rule!.frequency,
|
|
@@ -5124,7 +5148,11 @@ export class FinanceService {
|
|
|
5124
5148
|
)
|
|
5125
5149
|
: this.normalizeAndValidateInstallments(data, locale);
|
|
5126
5150
|
|
|
5127
|
-
const
|
|
5151
|
+
const hasPaidInstallments = installments.some(
|
|
5152
|
+
(installment) => installment.paid,
|
|
5153
|
+
);
|
|
5154
|
+
|
|
5155
|
+
const createResult = await this.prisma.$transaction(async (tx) => {
|
|
5128
5156
|
const person = await tx.person.findUnique({
|
|
5129
5157
|
where: { id: data.person_id },
|
|
5130
5158
|
select: { id: true },
|
|
@@ -5188,6 +5216,19 @@ export class FinanceService {
|
|
|
5188
5216
|
}
|
|
5189
5217
|
}
|
|
5190
5218
|
|
|
5219
|
+
if (hasPaidInstallments && data.bank_account_id) {
|
|
5220
|
+
const bankAccount = await tx.bank_account.findUnique({
|
|
5221
|
+
where: { id: data.bank_account_id },
|
|
5222
|
+
select: { id: true },
|
|
5223
|
+
});
|
|
5224
|
+
|
|
5225
|
+
if (!bankAccount) {
|
|
5226
|
+
throw new BadRequestException(
|
|
5227
|
+
getLocaleText('bankAccountNotFound', locale, 'Bank account not found'),
|
|
5228
|
+
);
|
|
5229
|
+
}
|
|
5230
|
+
}
|
|
5231
|
+
|
|
5191
5232
|
await this.assertDateNotInClosedPeriod(
|
|
5192
5233
|
tx,
|
|
5193
5234
|
data.competence_date
|
|
@@ -5205,7 +5246,7 @@ export class FinanceService {
|
|
|
5205
5246
|
person_id: data.person_id,
|
|
5206
5247
|
title_type: titleType,
|
|
5207
5248
|
status: 'draft',
|
|
5208
|
-
document_number: data.document_number,
|
|
5249
|
+
document_number: data.document_number?.trim() || null,
|
|
5209
5250
|
description: data.description,
|
|
5210
5251
|
competence_date: data.competence_date
|
|
5211
5252
|
? this.parseLocalDate(data.competence_date)
|
|
@@ -5230,6 +5271,13 @@ export class FinanceService {
|
|
|
5230
5271
|
});
|
|
5231
5272
|
}
|
|
5232
5273
|
|
|
5274
|
+
const appliedSettlements: Array<{
|
|
5275
|
+
settlementId: number;
|
|
5276
|
+
installmentId: number;
|
|
5277
|
+
openAmountCentsBefore: number;
|
|
5278
|
+
openAmountCentsAfter: number;
|
|
5279
|
+
}> = [];
|
|
5280
|
+
|
|
5233
5281
|
for (let index = 0; index < installments.length; index++) {
|
|
5234
5282
|
const installment = installments[index];
|
|
5235
5283
|
const amountCents = installment.amount_cents;
|
|
@@ -5262,25 +5310,84 @@ export class FinanceService {
|
|
|
5262
5310
|
},
|
|
5263
5311
|
});
|
|
5264
5312
|
}
|
|
5313
|
+
|
|
5314
|
+
if (installment.paid) {
|
|
5315
|
+
const appliedSettlement = await this.applyInstallmentSettlement(tx, {
|
|
5316
|
+
title: { id: title.id, person_id: data.person_id },
|
|
5317
|
+
titleType,
|
|
5318
|
+
installmentId: createdInstallment.id,
|
|
5319
|
+
amountCents: installment.paid_amount_cents ?? amountCents,
|
|
5320
|
+
settledAt: this.parseLocalDate(
|
|
5321
|
+
installment.paid_at || installment.due_date,
|
|
5322
|
+
),
|
|
5323
|
+
bankAccountId: data.bank_account_id ?? null,
|
|
5324
|
+
paymentChannel: data.payment_channel,
|
|
5325
|
+
discountCents: installment.discount_cents,
|
|
5326
|
+
interestCents: installment.interest_cents,
|
|
5327
|
+
penaltyCents: installment.penalty_cents,
|
|
5328
|
+
userId,
|
|
5329
|
+
});
|
|
5330
|
+
|
|
5331
|
+
appliedSettlements.push(appliedSettlement);
|
|
5332
|
+
}
|
|
5265
5333
|
}
|
|
5266
5334
|
|
|
5335
|
+
const finalStatus =
|
|
5336
|
+
appliedSettlements.length > 0
|
|
5337
|
+
? await this.recalculateTitleStatus(tx, title.id)
|
|
5338
|
+
: 'draft';
|
|
5339
|
+
|
|
5267
5340
|
await this.createAuditLog(tx, {
|
|
5268
5341
|
action: 'CREATE_TITLE',
|
|
5269
5342
|
entityTable: 'financial_title',
|
|
5270
5343
|
entityId: String(title.id),
|
|
5271
5344
|
actorUserId: userId,
|
|
5272
|
-
summary: `Created ${titleType} title ${title.id} in
|
|
5345
|
+
summary: `Created ${titleType} title ${title.id} in ${finalStatus}`,
|
|
5273
5346
|
afterData: JSON.stringify({
|
|
5274
|
-
status:
|
|
5347
|
+
status: finalStatus,
|
|
5275
5348
|
total_amount_cents: this.toCents(data.total_amount),
|
|
5276
5349
|
}),
|
|
5277
5350
|
});
|
|
5278
5351
|
|
|
5279
|
-
|
|
5352
|
+
for (const appliedSettlement of appliedSettlements) {
|
|
5353
|
+
await this.createAuditLog(tx, {
|
|
5354
|
+
action: 'SETTLE_INSTALLMENT',
|
|
5355
|
+
entityTable: 'financial_title',
|
|
5356
|
+
entityId: String(title.id),
|
|
5357
|
+
actorUserId: userId,
|
|
5358
|
+
summary: `Settled installment ${appliedSettlement.installmentId} of title ${title.id}`,
|
|
5359
|
+
beforeData: JSON.stringify({
|
|
5360
|
+
title_status: 'draft',
|
|
5361
|
+
installment_open_amount_cents:
|
|
5362
|
+
appliedSettlement.openAmountCentsBefore,
|
|
5363
|
+
}),
|
|
5364
|
+
afterData: JSON.stringify({
|
|
5365
|
+
title_status: finalStatus,
|
|
5366
|
+
installment_open_amount_cents:
|
|
5367
|
+
appliedSettlement.openAmountCentsAfter,
|
|
5368
|
+
settlement_id: appliedSettlement.settlementId,
|
|
5369
|
+
bank_reconciliation_id: null,
|
|
5370
|
+
}),
|
|
5371
|
+
});
|
|
5372
|
+
}
|
|
5373
|
+
|
|
5374
|
+
return {
|
|
5375
|
+
titleId: title.id,
|
|
5376
|
+
settlementIds: appliedSettlements.map(
|
|
5377
|
+
(appliedSettlement) => appliedSettlement.settlementId,
|
|
5378
|
+
),
|
|
5379
|
+
};
|
|
5280
5380
|
});
|
|
5281
5381
|
|
|
5282
|
-
const createdTitle = await this.getTitleById(
|
|
5283
|
-
|
|
5382
|
+
const createdTitle = await this.getTitleById(
|
|
5383
|
+
createResult.titleId,
|
|
5384
|
+
titleType,
|
|
5385
|
+
locale,
|
|
5386
|
+
);
|
|
5387
|
+
this.financeRealtime.publish({ domain: 'installment', type: 'created', entityId: createResult.titleId });
|
|
5388
|
+
for (const settlementId of createResult.settlementIds) {
|
|
5389
|
+
this.financeRealtime.publish({ domain: 'settlement', type: 'settled', entityId: settlementId });
|
|
5390
|
+
}
|
|
5284
5391
|
return this.mapTitleToFront(createdTitle, data.payment_channel);
|
|
5285
5392
|
}
|
|
5286
5393
|
|
|
@@ -5438,7 +5545,7 @@ export class FinanceService {
|
|
|
5438
5545
|
where: { id: title.id },
|
|
5439
5546
|
data: {
|
|
5440
5547
|
person_id: data.person_id,
|
|
5441
|
-
document_number: data.document_number,
|
|
5548
|
+
document_number: data.document_number?.trim() || null,
|
|
5442
5549
|
description: data.description,
|
|
5443
5550
|
competence_date: data.competence_date
|
|
5444
5551
|
? this.parseLocalDate(data.competence_date)
|
|
@@ -5602,7 +5709,7 @@ export class FinanceService {
|
|
|
5602
5709
|
) {
|
|
5603
5710
|
const fallbackDueDate = data.due_date;
|
|
5604
5711
|
const totalAmountCents = this.toCents(data.total_amount);
|
|
5605
|
-
const sourceInstallments =
|
|
5712
|
+
const sourceInstallments: CreateFinancialInstallmentDto[] =
|
|
5606
5713
|
data.installments && data.installments.length > 0
|
|
5607
5714
|
? data.installments
|
|
5608
5715
|
: [
|
|
@@ -5639,10 +5746,31 @@ export class FinanceService {
|
|
|
5639
5746
|
);
|
|
5640
5747
|
}
|
|
5641
5748
|
|
|
5749
|
+
const paidAmountCents =
|
|
5750
|
+
installment.paid_amount != null
|
|
5751
|
+
? this.toCents(installment.paid_amount)
|
|
5752
|
+
: amountCents;
|
|
5753
|
+
|
|
5754
|
+
if (installment.paid && paidAmountCents > amountCents) {
|
|
5755
|
+
throw new BadRequestException(
|
|
5756
|
+
getLocaleText(
|
|
5757
|
+
'paidAmountExceedsInstallment',
|
|
5758
|
+
locale,
|
|
5759
|
+
'Paid amount cannot exceed installment amount',
|
|
5760
|
+
),
|
|
5761
|
+
);
|
|
5762
|
+
}
|
|
5763
|
+
|
|
5642
5764
|
return {
|
|
5643
5765
|
installment_number: installment.installment_number || index + 1,
|
|
5644
5766
|
due_date: installmentDueDate,
|
|
5645
5767
|
amount_cents: amountCents,
|
|
5768
|
+
paid: installment.paid === true,
|
|
5769
|
+
paid_at: installment.paid_at,
|
|
5770
|
+
paid_amount_cents: paidAmountCents,
|
|
5771
|
+
discount_cents: this.toCents(installment.discount || 0),
|
|
5772
|
+
interest_cents: this.toCents(installment.interest || 0),
|
|
5773
|
+
penalty_cents: this.toCents(installment.penalty || 0),
|
|
5646
5774
|
};
|
|
5647
5775
|
},
|
|
5648
5776
|
);
|
|
@@ -5925,6 +6053,163 @@ export class FinanceService {
|
|
|
5925
6053
|
return this.mapTitleToFront(updatedTitle);
|
|
5926
6054
|
}
|
|
5927
6055
|
|
|
6056
|
+
private async applyInstallmentSettlement(
|
|
6057
|
+
tx: any,
|
|
6058
|
+
params: {
|
|
6059
|
+
title: { id: number; person_id: number };
|
|
6060
|
+
titleType: TitleType;
|
|
6061
|
+
installmentId: number;
|
|
6062
|
+
amountCents: number;
|
|
6063
|
+
settledAt: Date;
|
|
6064
|
+
bankAccountId?: number | null;
|
|
6065
|
+
paymentChannel?: string;
|
|
6066
|
+
discountCents?: number;
|
|
6067
|
+
interestCents?: number;
|
|
6068
|
+
penaltyCents?: number;
|
|
6069
|
+
description?: string | null;
|
|
6070
|
+
userId?: number;
|
|
6071
|
+
},
|
|
6072
|
+
) {
|
|
6073
|
+
const { title, titleType, installmentId, amountCents, settledAt } = params;
|
|
6074
|
+
|
|
6075
|
+
await this.assertDateNotInClosedPeriod(
|
|
6076
|
+
tx,
|
|
6077
|
+
settledAt,
|
|
6078
|
+
'settle installment',
|
|
6079
|
+
);
|
|
6080
|
+
|
|
6081
|
+
const installment = await tx.financial_installment.findFirst({
|
|
6082
|
+
where: {
|
|
6083
|
+
id: installmentId,
|
|
6084
|
+
title_id: title.id,
|
|
6085
|
+
},
|
|
6086
|
+
select: {
|
|
6087
|
+
id: true,
|
|
6088
|
+
title_id: true,
|
|
6089
|
+
amount_cents: true,
|
|
6090
|
+
open_amount_cents: true,
|
|
6091
|
+
due_date: true,
|
|
6092
|
+
status: true,
|
|
6093
|
+
},
|
|
6094
|
+
});
|
|
6095
|
+
|
|
6096
|
+
if (!installment) {
|
|
6097
|
+
throw new BadRequestException('Installment not found for this title');
|
|
6098
|
+
}
|
|
6099
|
+
|
|
6100
|
+
if (installment.status === 'settled' || installment.status === 'canceled') {
|
|
6101
|
+
if (installment.status === 'settled') {
|
|
6102
|
+
throw new ConflictException('Parcela já liquidada');
|
|
6103
|
+
}
|
|
6104
|
+
|
|
6105
|
+
throw new ConflictException('Parcela cancelada não pode receber baixa');
|
|
6106
|
+
}
|
|
6107
|
+
|
|
6108
|
+
if (amountCents > installment.open_amount_cents) {
|
|
6109
|
+
throw new ConflictException('Settlement amount exceeds open amount');
|
|
6110
|
+
}
|
|
6111
|
+
|
|
6112
|
+
const paymentMethodId = await this.resolvePaymentMethodId(
|
|
6113
|
+
tx,
|
|
6114
|
+
params.paymentChannel,
|
|
6115
|
+
);
|
|
6116
|
+
|
|
6117
|
+
const settlement = await tx.settlement.create({
|
|
6118
|
+
data: {
|
|
6119
|
+
person_id: title.person_id,
|
|
6120
|
+
bank_account_id: params.bankAccountId || null,
|
|
6121
|
+
payment_method_id: paymentMethodId,
|
|
6122
|
+
settlement_type: titleType,
|
|
6123
|
+
status: 'confirmed',
|
|
6124
|
+
settled_at: settledAt,
|
|
6125
|
+
amount_cents: amountCents,
|
|
6126
|
+
description: params.description?.trim() || null,
|
|
6127
|
+
created_by_user_id: params.userId,
|
|
6128
|
+
},
|
|
6129
|
+
});
|
|
6130
|
+
|
|
6131
|
+
await tx.settlement_allocation.create({
|
|
6132
|
+
data: {
|
|
6133
|
+
settlement: {
|
|
6134
|
+
connect: {
|
|
6135
|
+
id: settlement.id,
|
|
6136
|
+
},
|
|
6137
|
+
},
|
|
6138
|
+
financial_installment: {
|
|
6139
|
+
connect: {
|
|
6140
|
+
id: installment.id,
|
|
6141
|
+
},
|
|
6142
|
+
},
|
|
6143
|
+
allocated_amount_cents: amountCents,
|
|
6144
|
+
amount_cents: amountCents,
|
|
6145
|
+
discount_cents: params.discountCents || 0,
|
|
6146
|
+
interest_cents: params.interestCents || 0,
|
|
6147
|
+
penalty_cents: params.penaltyCents || 0,
|
|
6148
|
+
},
|
|
6149
|
+
});
|
|
6150
|
+
|
|
6151
|
+
const decrementResult = await tx.financial_installment.updateMany({
|
|
6152
|
+
where: {
|
|
6153
|
+
id: installment.id,
|
|
6154
|
+
open_amount_cents: {
|
|
6155
|
+
gte: amountCents,
|
|
6156
|
+
},
|
|
6157
|
+
},
|
|
6158
|
+
data: {
|
|
6159
|
+
open_amount_cents: {
|
|
6160
|
+
decrement: amountCents,
|
|
6161
|
+
},
|
|
6162
|
+
},
|
|
6163
|
+
});
|
|
6164
|
+
|
|
6165
|
+
if (decrementResult.count !== 1) {
|
|
6166
|
+
throw new BadRequestException(
|
|
6167
|
+
'Installment was updated concurrently, please try again',
|
|
6168
|
+
);
|
|
6169
|
+
}
|
|
6170
|
+
|
|
6171
|
+
const updatedInstallment = await tx.financial_installment.findUnique({
|
|
6172
|
+
where: {
|
|
6173
|
+
id: installment.id,
|
|
6174
|
+
},
|
|
6175
|
+
select: {
|
|
6176
|
+
id: true,
|
|
6177
|
+
amount_cents: true,
|
|
6178
|
+
open_amount_cents: true,
|
|
6179
|
+
due_date: true,
|
|
6180
|
+
status: true,
|
|
6181
|
+
},
|
|
6182
|
+
});
|
|
6183
|
+
|
|
6184
|
+
if (!updatedInstallment) {
|
|
6185
|
+
throw new NotFoundException('Installment not found');
|
|
6186
|
+
}
|
|
6187
|
+
|
|
6188
|
+
const nextInstallmentStatus = this.resolveInstallmentStatus(
|
|
6189
|
+
Number(updatedInstallment.amount_cents),
|
|
6190
|
+
Number(updatedInstallment.open_amount_cents),
|
|
6191
|
+
updatedInstallment.due_date,
|
|
6192
|
+
);
|
|
6193
|
+
|
|
6194
|
+
if (updatedInstallment.status !== nextInstallmentStatus) {
|
|
6195
|
+
await tx.financial_installment.update({
|
|
6196
|
+
where: {
|
|
6197
|
+
id: updatedInstallment.id,
|
|
6198
|
+
},
|
|
6199
|
+
data: {
|
|
6200
|
+
status: nextInstallmentStatus,
|
|
6201
|
+
},
|
|
6202
|
+
});
|
|
6203
|
+
}
|
|
6204
|
+
|
|
6205
|
+
return {
|
|
6206
|
+
settlementId: settlement.id as number,
|
|
6207
|
+
installmentId: installment.id as number,
|
|
6208
|
+
openAmountCentsBefore: Number(installment.open_amount_cents),
|
|
6209
|
+
openAmountCentsAfter: Number(updatedInstallment.open_amount_cents),
|
|
6210
|
+
};
|
|
6211
|
+
}
|
|
6212
|
+
|
|
5928
6213
|
private async settleTitleInstallment(
|
|
5929
6214
|
titleId: number,
|
|
5930
6215
|
data: SettleInstallmentDto,
|
|
@@ -5978,60 +6263,19 @@ export class FinanceService {
|
|
|
5978
6263
|
);
|
|
5979
6264
|
}
|
|
5980
6265
|
|
|
5981
|
-
await this.
|
|
5982
|
-
|
|
6266
|
+
const appliedSettlement = await this.applyInstallmentSettlement(tx, {
|
|
6267
|
+
title: { id: title.id, person_id: title.person_id },
|
|
6268
|
+
titleType,
|
|
6269
|
+
installmentId: data.installment_id,
|
|
6270
|
+
amountCents,
|
|
5983
6271
|
settledAt,
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
},
|
|
5992
|
-
select: {
|
|
5993
|
-
id: true,
|
|
5994
|
-
title_id: true,
|
|
5995
|
-
amount_cents: true,
|
|
5996
|
-
open_amount_cents: true,
|
|
5997
|
-
due_date: true,
|
|
5998
|
-
status: true,
|
|
5999
|
-
},
|
|
6000
|
-
});
|
|
6001
|
-
|
|
6002
|
-
if (!installment) {
|
|
6003
|
-
throw new BadRequestException('Installment not found for this title');
|
|
6004
|
-
}
|
|
6005
|
-
|
|
6006
|
-
if (installment.status === 'settled' || installment.status === 'canceled') {
|
|
6007
|
-
if (installment.status === 'settled') {
|
|
6008
|
-
throw new ConflictException('Parcela já liquidada');
|
|
6009
|
-
}
|
|
6010
|
-
|
|
6011
|
-
throw new ConflictException('Parcela cancelada não pode receber baixa');
|
|
6012
|
-
}
|
|
6013
|
-
|
|
6014
|
-
if (amountCents > installment.open_amount_cents) {
|
|
6015
|
-
throw new ConflictException('Settlement amount exceeds open amount');
|
|
6016
|
-
}
|
|
6017
|
-
|
|
6018
|
-
const paymentMethodId = await this.resolvePaymentMethodId(
|
|
6019
|
-
tx,
|
|
6020
|
-
data.payment_channel,
|
|
6021
|
-
);
|
|
6022
|
-
|
|
6023
|
-
const settlement = await tx.settlement.create({
|
|
6024
|
-
data: {
|
|
6025
|
-
person_id: title.person_id,
|
|
6026
|
-
bank_account_id: data.bank_account_id || null,
|
|
6027
|
-
payment_method_id: paymentMethodId,
|
|
6028
|
-
settlement_type: titleType,
|
|
6029
|
-
status: 'confirmed',
|
|
6030
|
-
settled_at: settledAt,
|
|
6031
|
-
amount_cents: amountCents,
|
|
6032
|
-
description: data.description?.trim() || null,
|
|
6033
|
-
created_by_user_id: userId,
|
|
6034
|
-
},
|
|
6272
|
+
bankAccountId: data.bank_account_id,
|
|
6273
|
+
paymentChannel: data.payment_channel,
|
|
6274
|
+
discountCents: this.toCents(data.discount || 0),
|
|
6275
|
+
interestCents: this.toCents(data.interest || 0),
|
|
6276
|
+
penaltyCents: this.toCents(data.penalty || 0),
|
|
6277
|
+
description: data.description,
|
|
6278
|
+
userId,
|
|
6035
6279
|
});
|
|
6036
6280
|
|
|
6037
6281
|
let reconciliationId: number | null = null;
|
|
@@ -6085,7 +6329,7 @@ export class FinanceService {
|
|
|
6085
6329
|
const createdReconciliation = await tx.bank_reconciliation.create({
|
|
6086
6330
|
data: {
|
|
6087
6331
|
bank_statement_line_id: statementLine.id,
|
|
6088
|
-
settlement_id:
|
|
6332
|
+
settlement_id: appliedSettlement.settlementId,
|
|
6089
6333
|
status: 'reconciled',
|
|
6090
6334
|
matched_at: settledAt,
|
|
6091
6335
|
reconciled_at: settledAt,
|
|
@@ -6111,80 +6355,6 @@ export class FinanceService {
|
|
|
6111
6355
|
}
|
|
6112
6356
|
}
|
|
6113
6357
|
|
|
6114
|
-
await tx.settlement_allocation.create({
|
|
6115
|
-
data: {
|
|
6116
|
-
settlement: {
|
|
6117
|
-
connect: {
|
|
6118
|
-
id: settlement.id,
|
|
6119
|
-
},
|
|
6120
|
-
},
|
|
6121
|
-
financial_installment: {
|
|
6122
|
-
connect: {
|
|
6123
|
-
id: installment.id,
|
|
6124
|
-
},
|
|
6125
|
-
},
|
|
6126
|
-
allocated_amount_cents: amountCents,
|
|
6127
|
-
amount_cents: amountCents,
|
|
6128
|
-
discount_cents: this.toCents(data.discount || 0),
|
|
6129
|
-
interest_cents: this.toCents(data.interest || 0),
|
|
6130
|
-
penalty_cents: this.toCents(data.penalty || 0),
|
|
6131
|
-
},
|
|
6132
|
-
});
|
|
6133
|
-
|
|
6134
|
-
const decrementResult = await tx.financial_installment.updateMany({
|
|
6135
|
-
where: {
|
|
6136
|
-
id: installment.id,
|
|
6137
|
-
open_amount_cents: {
|
|
6138
|
-
gte: amountCents,
|
|
6139
|
-
},
|
|
6140
|
-
},
|
|
6141
|
-
data: {
|
|
6142
|
-
open_amount_cents: {
|
|
6143
|
-
decrement: amountCents,
|
|
6144
|
-
},
|
|
6145
|
-
},
|
|
6146
|
-
});
|
|
6147
|
-
|
|
6148
|
-
if (decrementResult.count !== 1) {
|
|
6149
|
-
throw new BadRequestException(
|
|
6150
|
-
'Installment was updated concurrently, please try again',
|
|
6151
|
-
);
|
|
6152
|
-
}
|
|
6153
|
-
|
|
6154
|
-
const updatedInstallment = await tx.financial_installment.findUnique({
|
|
6155
|
-
where: {
|
|
6156
|
-
id: installment.id,
|
|
6157
|
-
},
|
|
6158
|
-
select: {
|
|
6159
|
-
id: true,
|
|
6160
|
-
amount_cents: true,
|
|
6161
|
-
open_amount_cents: true,
|
|
6162
|
-
due_date: true,
|
|
6163
|
-
status: true,
|
|
6164
|
-
},
|
|
6165
|
-
});
|
|
6166
|
-
|
|
6167
|
-
if (!updatedInstallment) {
|
|
6168
|
-
throw new NotFoundException('Installment not found');
|
|
6169
|
-
}
|
|
6170
|
-
|
|
6171
|
-
const nextInstallmentStatus = this.resolveInstallmentStatus(
|
|
6172
|
-
Number(updatedInstallment.amount_cents),
|
|
6173
|
-
Number(updatedInstallment.open_amount_cents),
|
|
6174
|
-
updatedInstallment.due_date,
|
|
6175
|
-
);
|
|
6176
|
-
|
|
6177
|
-
if (updatedInstallment.status !== nextInstallmentStatus) {
|
|
6178
|
-
await tx.financial_installment.update({
|
|
6179
|
-
where: {
|
|
6180
|
-
id: updatedInstallment.id,
|
|
6181
|
-
},
|
|
6182
|
-
data: {
|
|
6183
|
-
status: nextInstallmentStatus,
|
|
6184
|
-
},
|
|
6185
|
-
});
|
|
6186
|
-
}
|
|
6187
|
-
|
|
6188
6358
|
const previousTitleStatus = title.status;
|
|
6189
6359
|
const nextTitleStatus = await this.recalculateTitleStatus(tx, title.id);
|
|
6190
6360
|
|
|
@@ -6193,15 +6363,15 @@ export class FinanceService {
|
|
|
6193
6363
|
entityTable: 'financial_title',
|
|
6194
6364
|
entityId: String(title.id),
|
|
6195
6365
|
actorUserId: userId,
|
|
6196
|
-
summary: `Settled installment ${
|
|
6366
|
+
summary: `Settled installment ${appliedSettlement.installmentId} of title ${title.id}`,
|
|
6197
6367
|
beforeData: JSON.stringify({
|
|
6198
6368
|
title_status: previousTitleStatus,
|
|
6199
|
-
installment_open_amount_cents:
|
|
6369
|
+
installment_open_amount_cents: appliedSettlement.openAmountCentsBefore,
|
|
6200
6370
|
}),
|
|
6201
6371
|
afterData: JSON.stringify({
|
|
6202
6372
|
title_status: nextTitleStatus,
|
|
6203
|
-
installment_open_amount_cents:
|
|
6204
|
-
settlement_id:
|
|
6373
|
+
installment_open_amount_cents: appliedSettlement.openAmountCentsAfter,
|
|
6374
|
+
settlement_id: appliedSettlement.settlementId,
|
|
6205
6375
|
bank_reconciliation_id: reconciliationId,
|
|
6206
6376
|
}),
|
|
6207
6377
|
});
|
|
@@ -6220,7 +6390,7 @@ export class FinanceService {
|
|
|
6220
6390
|
|
|
6221
6391
|
return {
|
|
6222
6392
|
title: updatedTitle,
|
|
6223
|
-
settlementId:
|
|
6393
|
+
settlementId: appliedSettlement.settlementId,
|
|
6224
6394
|
reconciliationId,
|
|
6225
6395
|
};
|
|
6226
6396
|
});
|