gemcap-be-common 1.3.97 → 1.3.99
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/models/Prospect.model.d.ts +1 -0
- package/models/Prospect.model.js +2 -0
- package/models/Prospect.model.ts +2 -0
- package/package.json +1 -1
- package/services/compliance-borrowers.service.d.ts +1 -1
- package/services/loan-transactions.service.d.ts +1 -0
- package/services/loan-transactions.service.js +202 -109
- package/services/loan-transactions.service.ts +221 -109
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -5,7 +5,6 @@ import Decimal from 'decimal.js';
|
|
|
5
5
|
|
|
6
6
|
import { IEmail } from '../interfaces/email.interface';
|
|
7
7
|
import { calculateTimeZone } from '../helpers/date.helper';
|
|
8
|
-
import { roundToXDigits } from '../helpers/numbers.helper';
|
|
9
8
|
import { createFilteredObject } from '../helpers/common.helper';
|
|
10
9
|
import { IPaginatorOptions } from '../interfaces/collaterals.interface';
|
|
11
10
|
import { BBCDateModel } from '../models/BBCDate.model';
|
|
@@ -400,7 +399,7 @@ export class LoanTransactionsService {
|
|
|
400
399
|
}
|
|
401
400
|
}
|
|
402
401
|
|
|
403
|
-
async
|
|
402
|
+
async recalculateBalanceLegacy(transactionId: string) {
|
|
404
403
|
const changedTransaction = await LoanTransaction.findById<ILoanTransactionDoc & {
|
|
405
404
|
createdAt: Date
|
|
406
405
|
}>(transactionId);
|
|
@@ -408,127 +407,240 @@ export class LoanTransactionsService {
|
|
|
408
407
|
console.error(`no transactions with id ${transactionId}`);
|
|
409
408
|
return;
|
|
410
409
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
410
|
+
|
|
411
|
+
const recalcBalance = async () => {
|
|
412
|
+
const session = await mongoose.startSession();
|
|
413
|
+
try {
|
|
414
|
+
await session.withTransaction(async () => {
|
|
415
|
+
await LoanProduct.findByIdAndUpdate(changedTransaction.productId, { isBalanceActual: false });
|
|
416
|
+
const previousTransaction = await LoanTransaction.aggregate<ILoanTransactionDoc>([
|
|
417
|
+
{
|
|
418
|
+
$match: {
|
|
419
|
+
'productId': new mongoose.Types.ObjectId(changedTransaction.productId.toString()),
|
|
420
|
+
'date': { $lt: new Date(changedTransaction.date) },
|
|
421
|
+
},
|
|
422
|
+
}, {
|
|
423
|
+
$sort: {
|
|
424
|
+
'date': -1,
|
|
425
|
+
'order': -1,
|
|
426
|
+
'createdAt': -1,
|
|
427
|
+
},
|
|
428
|
+
}, {
|
|
429
|
+
$limit: 1,
|
|
430
|
+
},
|
|
431
|
+
]);
|
|
432
|
+
let currentBalance = previousTransaction.length
|
|
433
|
+
? previousTransaction[0].balance ?? 0
|
|
434
|
+
: 0;
|
|
435
|
+
const recalculatedTransactions = await LoanTransaction.aggregate<ILoanTransactionDoc>([
|
|
436
|
+
{
|
|
437
|
+
$match: {
|
|
438
|
+
'productId': new mongoose.Types.ObjectId(changedTransaction.productId.toString()),
|
|
439
|
+
'date': { $gte: new Date(changedTransaction.date) },
|
|
440
|
+
},
|
|
441
|
+
}, {
|
|
442
|
+
$sort: {
|
|
443
|
+
'date': 1,
|
|
444
|
+
'order': 1,
|
|
445
|
+
'createdAt': 1,
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
]);
|
|
449
|
+
for (const transaction of recalculatedTransactions) {
|
|
450
|
+
currentBalance = new Decimal(currentBalance).add(transaction.amount).toNumber();
|
|
451
|
+
await LoanTransaction.findByIdAndUpdate(transaction._id, { balance: currentBalance });
|
|
452
|
+
}
|
|
453
|
+
await LoanProduct.findByIdAndUpdate(changedTransaction.productId, { isBalanceActual: true });
|
|
454
|
+
});
|
|
455
|
+
} finally {
|
|
456
|
+
await session.endSession();
|
|
449
457
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
await
|
|
454
|
-
|
|
455
|
-
{
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
const recalcFloatedBalance = async () => {
|
|
461
|
+
const session = await mongoose.startSession();
|
|
462
|
+
try {
|
|
463
|
+
await session.withTransaction(async () => {
|
|
464
|
+
await LoanProduct.findByIdAndUpdate(changedTransaction.productId, { isFloatedBalanceActual: false });
|
|
465
|
+
const previousEqualBalancesTransaction = await LoanTransaction.aggregate<ILoanTransactionDoc>([
|
|
466
|
+
{
|
|
467
|
+
$match: {
|
|
468
|
+
'productId': new mongoose.Types.ObjectId(changedTransaction.productId.toString()),
|
|
469
|
+
'date': { $lt: new Date(changedTransaction.date) },
|
|
470
|
+
$expr: { $eq: ['$balance', '$floatedBalance'] },
|
|
471
|
+
},
|
|
472
|
+
}, {
|
|
473
|
+
$sort: {
|
|
474
|
+
'date': -1,
|
|
475
|
+
'order': -1,
|
|
476
|
+
'createdAt': -1,
|
|
477
|
+
},
|
|
478
|
+
}, {
|
|
479
|
+
$limit: 1,
|
|
480
|
+
},
|
|
481
|
+
]);
|
|
482
|
+
let currentFloatedBalance = 0;
|
|
483
|
+
if (previousEqualBalancesTransaction.length) {
|
|
484
|
+
const previousTransaction = await LoanTransaction.aggregate<ILoanTransactionDoc>([
|
|
485
|
+
{
|
|
486
|
+
$match: {
|
|
487
|
+
'productId': new mongoose.Types.ObjectId(previousEqualBalancesTransaction[0].productId.toString()),
|
|
488
|
+
'date': { $lt: new Date(previousEqualBalancesTransaction[0].date) },
|
|
489
|
+
},
|
|
490
|
+
}, {
|
|
491
|
+
$sort: {
|
|
492
|
+
'date': -1,
|
|
493
|
+
'order': -1,
|
|
494
|
+
'createdAt': -1,
|
|
495
|
+
},
|
|
496
|
+
}, {
|
|
497
|
+
$limit: 1,
|
|
498
|
+
},
|
|
499
|
+
]);
|
|
500
|
+
if (previousTransaction.length) {
|
|
501
|
+
currentFloatedBalance = previousTransaction[0].floatedBalance;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
const initTransaction = previousEqualBalancesTransaction.length ? previousEqualBalancesTransaction[0] : changedTransaction;
|
|
505
|
+
const recalculatedTransactions = await LoanTransaction.aggregate<ILoanTransactionDoc>([
|
|
506
|
+
{
|
|
507
|
+
$match: {
|
|
508
|
+
'productId': new mongoose.Types.ObjectId(initTransaction.productId.toString()),
|
|
509
|
+
'date': { $gte: new Date(initTransaction.date) },
|
|
510
|
+
},
|
|
511
|
+
}, {
|
|
512
|
+
$sort: {
|
|
513
|
+
'date': 1,
|
|
514
|
+
'order': 1,
|
|
515
|
+
'createdAt': 1,
|
|
516
|
+
},
|
|
517
|
+
},
|
|
518
|
+
]);
|
|
519
|
+
for (const transaction of recalculatedTransactions) {
|
|
520
|
+
const totalPostponed = await getPostponedTransactions(transaction.date, transaction.productId.toString(), true);
|
|
521
|
+
switch (transaction.transactionType) {
|
|
522
|
+
case ELoanTransactionTypes.COLLECTION:
|
|
523
|
+
await this.cleanRecalculated(transaction._id.toString());
|
|
524
|
+
await this.createPostponedTransaction(transaction);
|
|
525
|
+
currentFloatedBalance = new Decimal(currentFloatedBalance).add(totalPostponed).toNumber();
|
|
526
|
+
break;
|
|
527
|
+
case ELoanTransactionTypes.DISBURSEMENT:
|
|
528
|
+
currentFloatedBalance = new Decimal(currentFloatedBalance).add(transaction.amount).add(totalPostponed).toNumber();
|
|
529
|
+
break;
|
|
530
|
+
case ELoanTransactionTypes.ADJUSTMENT:
|
|
531
|
+
currentFloatedBalance = new Decimal(currentFloatedBalance).add(transaction.amount).add(totalPostponed).toNumber();
|
|
532
|
+
break;
|
|
533
|
+
}
|
|
534
|
+
await LoanTransaction.findByIdAndUpdate(transaction._id, { floatedBalance: currentFloatedBalance });
|
|
535
|
+
}
|
|
536
|
+
await LoanProduct.findByIdAndUpdate(changedTransaction.productId, { isFloatedBalanceActual: true });
|
|
537
|
+
});
|
|
538
|
+
} finally {
|
|
539
|
+
await session.endSession();
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
await recalcBalance();
|
|
544
|
+
await recalcFloatedBalance();
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
async recalculateBalance(transactionId: string) {
|
|
548
|
+
const startTransaction = await LoanTransaction.findById(transactionId).lean();
|
|
549
|
+
if (!startTransaction) {
|
|
550
|
+
console.error(`Transaction not found: ${transactionId}`);
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const session = await mongoose.startSession();
|
|
555
|
+
try {
|
|
556
|
+
await session.withTransaction(async () => {
|
|
557
|
+
// Получаем все транзакции, начиная с самой ранней, которую нужно пересчитать
|
|
558
|
+
const earliestTransaction = await LoanTransaction.aggregate<ILoanTransactionDoc>([
|
|
559
|
+
{
|
|
560
|
+
$match: {
|
|
561
|
+
productId: startTransaction.productId,
|
|
562
|
+
date: { $lte: new Date(startTransaction.date) }
|
|
563
|
+
}
|
|
460
564
|
},
|
|
461
|
-
|
|
462
|
-
$
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
565
|
+
{ $sort: { date: 1, order: 1, createdAt: 1 } },
|
|
566
|
+
{ $limit: 1 }
|
|
567
|
+
]);
|
|
568
|
+
|
|
569
|
+
const fromDate = earliestTransaction.length
|
|
570
|
+
? earliestTransaction[0].date
|
|
571
|
+
: startTransaction.date;
|
|
572
|
+
|
|
573
|
+
const allTransactions = await LoanTransaction.aggregate<ILoanTransactionDoc>([
|
|
574
|
+
{
|
|
575
|
+
$match: {
|
|
576
|
+
productId: startTransaction.productId,
|
|
577
|
+
date: { $gte: fromDate }
|
|
578
|
+
}
|
|
466
579
|
},
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
580
|
+
{ $sort: { date: 1, order: 1, createdAt: 1 } }
|
|
581
|
+
]);
|
|
582
|
+
|
|
583
|
+
let balance = 0;
|
|
584
|
+
let floatedBalance = 0;
|
|
585
|
+
|
|
586
|
+
// Найдём стартовые значения
|
|
587
|
+
const prevBeforeStart = await LoanTransaction.aggregate<ILoanTransactionDoc>([
|
|
474
588
|
{
|
|
475
589
|
$match: {
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
}
|
|
479
|
-
}, {
|
|
480
|
-
$sort: {
|
|
481
|
-
'date': -1,
|
|
482
|
-
'order': -1,
|
|
483
|
-
'createdAt': -1,
|
|
484
|
-
},
|
|
485
|
-
}, {
|
|
486
|
-
$limit: 1,
|
|
590
|
+
productId: startTransaction.productId,
|
|
591
|
+
date: { $lt: fromDate }
|
|
592
|
+
}
|
|
487
593
|
},
|
|
594
|
+
{ $sort: { date: -1, order: -1, createdAt: -1 } },
|
|
595
|
+
{ $limit: 1 }
|
|
488
596
|
]);
|
|
489
|
-
|
|
490
|
-
|
|
597
|
+
|
|
598
|
+
if (prevBeforeStart.length) {
|
|
599
|
+
balance = prevBeforeStart[0].balance || 0;
|
|
600
|
+
floatedBalance = prevBeforeStart[0].floatedBalance || 0;
|
|
491
601
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
currentFloatedBalance = roundToXDigits(currentFloatedBalance + transaction.amount + totalPostponed);
|
|
518
|
-
break;
|
|
519
|
-
case ELoanTransactionTypes.ADJUSTMENT:
|
|
520
|
-
currentFloatedBalance = roundToXDigits(currentFloatedBalance + transaction.amount + totalPostponed);
|
|
521
|
-
break;
|
|
602
|
+
|
|
603
|
+
// Один проход для расчёта balance и floatedBalance
|
|
604
|
+
for (const tr of allTransactions) {
|
|
605
|
+
// Пересчёт обычного баланса
|
|
606
|
+
balance = new Decimal(balance).add(tr.amount).toNumber();
|
|
607
|
+
|
|
608
|
+
// Пересчёт floatedBalance
|
|
609
|
+
const totalPostponed = await getPostponedTransactions(tr.date, tr.productId.toString(), true);
|
|
610
|
+
switch (tr.transactionType) {
|
|
611
|
+
case ELoanTransactionTypes.COLLECTION:
|
|
612
|
+
await this.cleanRecalculated(tr._id.toString());
|
|
613
|
+
await this.createPostponedTransaction(tr);
|
|
614
|
+
floatedBalance = new Decimal(floatedBalance).add(totalPostponed).toNumber();
|
|
615
|
+
break;
|
|
616
|
+
case ELoanTransactionTypes.DISBURSEMENT:
|
|
617
|
+
case ELoanTransactionTypes.ADJUSTMENT:
|
|
618
|
+
floatedBalance = new Decimal(floatedBalance).add(tr.amount).add(totalPostponed).toNumber();
|
|
619
|
+
break;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
await LoanTransaction.updateOne(
|
|
623
|
+
{ _id: tr._id },
|
|
624
|
+
{ balance, floatedBalance },
|
|
625
|
+
{ session }
|
|
626
|
+
);
|
|
522
627
|
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
628
|
+
|
|
629
|
+
await LoanProduct.updateOne(
|
|
630
|
+
{ _id: startTransaction.productId },
|
|
631
|
+
{ isBalanceActual: true, isFloatedBalanceActual: true },
|
|
632
|
+
{ session }
|
|
633
|
+
);
|
|
634
|
+
});
|
|
635
|
+
} finally {
|
|
636
|
+
await session.endSession();
|
|
637
|
+
}
|
|
527
638
|
}
|
|
528
639
|
|
|
529
640
|
async deleteLoanTransaction(transactionId: string, updateLoanPayment: boolean, userId = null) {
|
|
530
641
|
const currentTransaction = await LoanTransaction.findById(transactionId).lean();
|
|
531
642
|
if (!currentTransaction) {
|
|
643
|
+
console.error(`no transactions with id ${transactionId}`);
|
|
532
644
|
return;
|
|
533
645
|
}
|
|
534
646
|
const nextTransaction = await LoanTransaction.aggregate<ILoanTransactionDoc>([
|