Loan transaction date handling enhancement + Make Loan transaction auditable

This commit is contained in:
Adam Saghy
2022-07-07 15:56:43 +02:00
committed by Arnold Gálovics
parent ad4fa563de
commit b30d5c5a16
14 changed files with 259 additions and 299 deletions
@@ -664,7 +664,7 @@ public class TellerManagementReadPlatformServiceImpl implements TellerManagement
sqlBuilder.append(" left join m_loan loan on loan_txn.loan_id = loan.id "); sqlBuilder.append(" left join m_loan loan on loan_txn.loan_id = loan.id ");
sqlBuilder.append(" left join m_client cl on loan.client_id = cl.id "); sqlBuilder.append(" left join m_client cl on loan.client_id = cl.id ");
sqlBuilder.append(" left join m_office o on cl.office_id = o.id "); sqlBuilder.append(" left join m_office o on cl.office_id = o.id ");
sqlBuilder.append(" left join m_appuser user on loan_txn.appuser_id = user.id "); sqlBuilder.append(" left join m_appuser user on loan_txn.createdby_id = user.id ");
sqlBuilder.append(" left join m_staff staff on user.staff_id = staff.id "); sqlBuilder.append(" left join m_staff staff on user.staff_id = staff.id ");
sqlBuilder.append(" left join m_cashiers c on c.staff_id = staff.id "); sqlBuilder.append(" left join m_cashiers c on c.staff_id = staff.id ");
sqlBuilder.append(" left join m_payment_detail payDetails on payDetails.id = loan_txn.payment_detail_id "); sqlBuilder.append(" left join m_payment_detail payDetails on payDetails.id = loan_txn.payment_detail_id ");
@@ -829,7 +829,7 @@ public class TellerManagementReadPlatformServiceImpl implements TellerManagement
sqlBuilder.append(" left join m_loan loan on loan_txn.loan_id = loan.id "); sqlBuilder.append(" left join m_loan loan on loan_txn.loan_id = loan.id ");
sqlBuilder.append(" left join m_client cl on loan.client_id = cl.id "); sqlBuilder.append(" left join m_client cl on loan.client_id = cl.id ");
sqlBuilder.append(" left join m_office o on cl.office_id = o.id "); sqlBuilder.append(" left join m_office o on cl.office_id = o.id ");
sqlBuilder.append(" left join m_appuser user on loan_txn.appuser_id = user.id "); sqlBuilder.append(" left join m_appuser user on loan_txn.createdby_id = user.id ");
sqlBuilder.append(" left join m_staff staff on user.staff_id = staff.id "); sqlBuilder.append(" left join m_staff staff on user.staff_id = staff.id ");
sqlBuilder.append(" left join m_cashiers c on c.staff_id = staff.id "); sqlBuilder.append(" left join m_cashiers c on c.staff_id = staff.id ");
sqlBuilder.append(" left join m_payment_detail payDetails on payDetails.id = loan_txn.payment_detail_id "); sqlBuilder.append(" left join m_payment_detail payDetails on payDetails.id = loan_txn.payment_detail_id ");
@@ -688,8 +688,7 @@ public class Loan extends AbstractPersistableCustom {
* @param suppliedTransactionDate * @param suppliedTransactionDate
* @return * @return
*/ */
public LoanTransaction handleChargeAppliedTransaction(final LoanCharge loanCharge, final LocalDate suppliedTransactionDate, public LoanTransaction handleChargeAppliedTransaction(final LoanCharge loanCharge, final LocalDate suppliedTransactionDate) {
final AppUser currentUser) {
final Money chargeAmount = loanCharge.getAmount(getCurrency()); final Money chargeAmount = loanCharge.getAmount(getCurrency());
Money feeCharges = chargeAmount; Money feeCharges = chargeAmount;
Money penaltyCharges = Money.zero(loanCurrency()); Money penaltyCharges = Money.zero(loanCurrency());
@@ -714,7 +713,7 @@ public class Loan extends AbstractPersistableCustom {
} }
final LoanTransaction applyLoanChargeTransaction = LoanTransaction.accrueLoanCharge(this, getOffice(), chargeAmount, final LoanTransaction applyLoanChargeTransaction = LoanTransaction.accrueLoanCharge(this, getOffice(), chargeAmount,
transactionDate, feeCharges, penaltyCharges, DateUtils.getLocalDateTimeOfTenant(), currentUser); transactionDate, feeCharges, penaltyCharges);
Integer installmentNumber = null; Integer installmentNumber = null;
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(applyLoanChargeTransaction, loanCharge, final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(applyLoanChargeTransaction, loanCharge,
@@ -1009,8 +1008,7 @@ public class Loan extends AbstractPersistableCustom {
public LoanTransaction waiveLoanCharge(final LoanCharge loanCharge, final LoanLifecycleStateMachine loanLifecycleStateMachine, public LoanTransaction waiveLoanCharge(final LoanCharge loanCharge, final LoanLifecycleStateMachine loanLifecycleStateMachine,
final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds, final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final Integer loanInstallmentNumber, final ScheduleGeneratorDTO scheduleGeneratorDTO, final Money accruedCharge, final Integer loanInstallmentNumber, final ScheduleGeneratorDTO scheduleGeneratorDTO, final Money accruedCharge) {
final AppUser currentUser) {
validateLoanIsNotClosed(loanCharge); validateLoanIsNotClosed(loanCharge);
@@ -1062,14 +1060,14 @@ public class Loan extends AbstractPersistableCustom {
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
final LoanTransaction waiveLoanChargeTransaction = LoanTransaction.waiveLoanCharge(this, getOffice(), amountWaived, transactionDate, final LoanTransaction waiveLoanChargeTransaction = LoanTransaction.waiveLoanCharge(this, getOffice(), amountWaived, transactionDate,
feeChargesWaived, penaltyChargesWaived, unrecognizedIncome, DateUtils.getLocalDateTimeOfTenant(), currentUser); feeChargesWaived, penaltyChargesWaived, unrecognizedIncome);
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(waiveLoanChargeTransaction, loanCharge, final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(waiveLoanChargeTransaction, loanCharge,
waiveLoanChargeTransaction.getAmount(getCurrency()).getAmount(), loanInstallmentNumber); waiveLoanChargeTransaction.getAmount(getCurrency()).getAmount(), loanInstallmentNumber);
waiveLoanChargeTransaction.getLoanChargesPaid().add(loanChargePaidBy); waiveLoanChargeTransaction.getLoanChargesPaid().add(loanChargePaidBy);
addLoanTransaction(waiveLoanChargeTransaction); addLoanTransaction(waiveLoanChargeTransaction);
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled() if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()
&& (loanCharge.getDueLocalDate() == null || DateUtils.getBusinessLocalDate().isAfter(loanCharge.getDueLocalDate()))) { && (loanCharge.getDueLocalDate() == null || DateUtils.getBusinessLocalDate().isAfter(loanCharge.getDueLocalDate()))) {
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser); regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
} }
// Waive of charges whose due date falls after latest 'repayment' // Waive of charges whose due date falls after latest 'repayment'
// transaction dont require entire loan schedule to be reprocessed. // transaction dont require entire loan schedule to be reprocessed.
@@ -1225,7 +1223,7 @@ public class Loan extends AbstractPersistableCustom {
this.rates.addAll(loanRates); this.rates.addAll(loanRates);
} }
public void updateLoanSchedule(final LoanScheduleModel modifiedLoanSchedule, AppUser currentUser) { public void updateLoanSchedule(final LoanScheduleModel modifiedLoanSchedule) {
this.repaymentScheduleInstallments.clear(); this.repaymentScheduleInstallments.clear();
for (final LoanScheduleModelPeriod scheduledLoanInstallment : modifiedLoanSchedule.getPeriods()) { for (final LoanScheduleModelPeriod scheduledLoanInstallment : modifiedLoanSchedule.getPeriods()) {
@@ -1242,11 +1240,11 @@ public class Loan extends AbstractPersistableCustom {
updateLoanScheduleDependentDerivedFields(); updateLoanScheduleDependentDerivedFields();
updateLoanSummaryDerivedFields(); updateLoanSummaryDerivedFields();
applyAccurals(currentUser); applyAccurals();
} }
public void updateLoanSchedule(final Collection<LoanRepaymentScheduleInstallment> installments, AppUser currentUser) { public void updateLoanSchedule(final Collection<LoanRepaymentScheduleInstallment> installments) {
List<LoanRepaymentScheduleInstallment> existingInstallments = new ArrayList<>(this.repaymentScheduleInstallments); List<LoanRepaymentScheduleInstallment> existingInstallments = new ArrayList<>(this.repaymentScheduleInstallments);
repaymentScheduleInstallments.clear(); repaymentScheduleInstallments.clear();
for (final LoanRepaymentScheduleInstallment installment : installments) { for (final LoanRepaymentScheduleInstallment installment : installments) {
@@ -1262,7 +1260,7 @@ public class Loan extends AbstractPersistableCustom {
} }
updateLoanScheduleDependentDerivedFields(); updateLoanScheduleDependentDerivedFields();
updateLoanSummaryDerivedFields(); updateLoanSummaryDerivedFields();
applyAccurals(currentUser); applyAccurals();
} }
@@ -1279,13 +1277,13 @@ public class Loan extends AbstractPersistableCustom {
/** /**
* method updates accrual derived fields on installments and reverse the unprocessed transactions * method updates accrual derived fields on installments and reverse the unprocessed transactions
*/ */
private void applyAccurals(AppUser currentUser) { private void applyAccurals() {
Collection<LoanTransaction> accruals = retreiveListOfAccrualTransactions(); Collection<LoanTransaction> accruals = retreiveListOfAccrualTransactions();
if (accruals.size() > 0) { if (accruals.size() > 0) {
if (isPeriodicAccrualAccountingEnabledOnLoanProduct()) { if (isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
applyPeriodicAccruals(accruals); applyPeriodicAccruals(accruals);
} else if (isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) { } else if (isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) {
updateAccrualsForNonPeriodicAccruals(accruals, currentUser); updateAccrualsForNonPeriodicAccruals(accruals);
} }
} }
} }
@@ -1325,7 +1323,7 @@ public class Loan extends AbstractPersistableCustom {
} }
} }
private void updateAccrualsForNonPeriodicAccruals(final Collection<LoanTransaction> accruals, final AppUser currentUser) { private void updateAccrualsForNonPeriodicAccruals(final Collection<LoanTransaction> accruals) {
final Money interestApplied = Money.of(getCurrency(), this.summary.getTotalInterestCharged()); final Money interestApplied = Money.of(getCurrency(), this.summary.getTotalInterestCharged());
for (LoanTransaction loanTransaction : accruals) { for (LoanTransaction loanTransaction : accruals) {
@@ -1334,7 +1332,7 @@ public class Loan extends AbstractPersistableCustom {
loanTransaction.reverse(); loanTransaction.reverse();
final LocalDateTime currentDateTime = DateUtils.getLocalDateTimeOfTenant(); final LocalDateTime currentDateTime = DateUtils.getLocalDateTimeOfTenant();
final LoanTransaction interestAppliedTransaction = LoanTransaction.accrueInterest(getOffice(), this, interestApplied, final LoanTransaction interestAppliedTransaction = LoanTransaction.accrueInterest(getOffice(), this, interestApplied,
getDisbursementDate(), currentDateTime, currentUser); getDisbursementDate());
addLoanTransaction(interestAppliedTransaction); addLoanTransaction(interestAppliedTransaction);
} }
} else { } else {
@@ -1344,7 +1342,7 @@ public class Loan extends AbstractPersistableCustom {
Money chargeAmount = loanCharge.getAmount(getCurrency()); Money chargeAmount = loanCharge.getAmount(getCurrency());
if (chargeAmount.isNotEqualTo(loanTransaction.getAmount(getCurrency()))) { if (chargeAmount.isNotEqualTo(loanTransaction.getAmount(getCurrency()))) {
loanTransaction.reverse(); loanTransaction.reverse();
handleChargeAppliedTransaction(loanCharge, loanTransaction.getTransactionDate(), currentUser); handleChargeAppliedTransaction(loanCharge, loanTransaction.getTransactionDate());
} }
} }
@@ -2068,7 +2066,7 @@ public class Loan extends AbstractPersistableCustom {
final LocalDate submittedOn, final String externalId, final boolean allowTransactionsOnHoliday, final List<Holiday> holidays, final LocalDate submittedOn, final String externalId, final boolean allowTransactionsOnHoliday, final List<Holiday> holidays,
final WorkingDays workingDays, final boolean allowTransactionsOnNonWorkingDay) { final WorkingDays workingDays, final boolean allowTransactionsOnNonWorkingDay) {
updateLoanSchedule(loanSchedule, currentUser); updateLoanSchedule(loanSchedule);
LoanStatus from = null; LoanStatus from = null;
if (this.loanStatus != null) { if (this.loanStatus != null) {
@@ -2483,12 +2481,11 @@ public class Loan extends AbstractPersistableCustom {
validateDisbursementDateIsOnHoliday(holidayDetailDTO.isAllowTransactionsOnHoliday(), holidayDetailDTO.getHolidays()); validateDisbursementDateIsOnHoliday(holidayDetailDTO.isAllowTransactionsOnHoliday(), holidayDetailDTO.getHolidays());
regenerateRepaymentScheduleWithInterestRecalculationIfNeeded(this.repaymentScheduleDetail().isInterestRecalculationEnabled(), regenerateRepaymentScheduleWithInterestRecalculationIfNeeded(this.repaymentScheduleDetail().isInterestRecalculationEnabled(),
isDisbursementMissed(), scheduleGeneratorDTO, currentUser); isDisbursementMissed(), scheduleGeneratorDTO);
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate); updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
LocalDateTime createdDate = DateUtils.getLocalDateTimeOfTenant(); handleDisbursementTransaction(actualDisbursementDate, paymentDetail);
handleDisbursementTransaction(actualDisbursementDate, createdDate, currentUser, paymentDetail);
updateLoanSummaryDerivedFields(); updateLoanSummaryDerivedFields();
final Money interestApplied = Money.of(getCurrency(), this.summary.getTotalInterestCharged()); final Money interestApplied = Money.of(getCurrency(), this.summary.getTotalInterestCharged());
@@ -2500,7 +2497,7 @@ public class Loan extends AbstractPersistableCustom {
if (((isMultiDisburmentLoan() && getDisbursedLoanDisbursementDetails().size() == 1) || !isMultiDisburmentLoan()) if (((isMultiDisburmentLoan() && getDisbursedLoanDisbursementDetails().size() == 1) || !isMultiDisburmentLoan())
&& isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) { && isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) {
final LoanTransaction interestAppliedTransaction = LoanTransaction.accrueInterest(getOffice(), this, interestApplied, final LoanTransaction interestAppliedTransaction = LoanTransaction.accrueInterest(getOffice(), this, interestApplied,
actualDisbursementDate, createdDate, currentUser); actualDisbursementDate);
addLoanTransaction(interestAppliedTransaction); addLoanTransaction(interestAppliedTransaction);
} }
@@ -2509,12 +2506,12 @@ public class Loan extends AbstractPersistableCustom {
} }
private void regenerateRepaymentScheduleWithInterestRecalculationIfNeeded(boolean interestRecalculationEnabledParam, private void regenerateRepaymentScheduleWithInterestRecalculationIfNeeded(boolean interestRecalculationEnabledParam,
boolean disbursementMissedParam, ScheduleGeneratorDTO scheduleGeneratorDTO, AppUser currentUser) { boolean disbursementMissedParam, ScheduleGeneratorDTO scheduleGeneratorDTO) {
LocalDate firstInstallmentDueDate = fetchRepaymentScheduleInstallment(1).getDueDate(); LocalDate firstInstallmentDueDate = fetchRepaymentScheduleInstallment(1).getDueDate();
if (interestRecalculationEnabledParam if (interestRecalculationEnabledParam
&& (firstInstallmentDueDate.isBefore(DateUtils.getBusinessLocalDate()) || disbursementMissedParam)) { && (firstInstallmentDueDate.isBefore(DateUtils.getBusinessLocalDate()) || disbursementMissedParam)) {
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser); regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
} }
} }
@@ -2532,7 +2529,7 @@ public class Loan extends AbstractPersistableCustom {
} }
public void regenerateScheduleOnDisbursement(final ScheduleGeneratorDTO scheduleGeneratorDTO, final boolean recalculateSchedule, public void regenerateScheduleOnDisbursement(final ScheduleGeneratorDTO scheduleGeneratorDTO, final boolean recalculateSchedule,
final LocalDate actualDisbursementDate, BigDecimal emiAmount, final AppUser currentUser, LocalDate nextPossibleRepaymentDate, final LocalDate actualDisbursementDate, BigDecimal emiAmount, LocalDate nextPossibleRepaymentDate,
Date rescheduledRepaymentDate) { Date rescheduledRepaymentDate) {
boolean isEmiAmountChanged = false; boolean isEmiAmountChanged = false;
if ((this.loanProduct.isMultiDisburseLoan() || this.loanProduct.canDefineInstallmentAmount()) && emiAmount != null if ((this.loanProduct.isMultiDisburseLoan() || this.loanProduct.canDefineInstallmentAmount()) && emiAmount != null
@@ -2565,9 +2562,9 @@ public class Loan extends AbstractPersistableCustom {
if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate) || recalculateSchedule || isEmiAmountChanged if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate) || recalculateSchedule || isEmiAmountChanged
|| rescheduledRepaymentDate != null) { || rescheduledRepaymentDate != null) {
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser); regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
} else { } else {
regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser); regenerateRepaymentSchedule(scheduleGeneratorDTO);
} }
} }
} }
@@ -2789,12 +2786,12 @@ public class Loan extends AbstractPersistableCustom {
/* /*
* Ability to regenerate the repayment schedule based on the loans current details/state. * Ability to regenerate the repayment schedule based on the loans current details/state.
*/ */
public void regenerateRepaymentSchedule(final ScheduleGeneratorDTO scheduleGeneratorDTO, final AppUser currentUser) { public void regenerateRepaymentSchedule(final ScheduleGeneratorDTO scheduleGeneratorDTO) {
final LoanScheduleModel loanSchedule = regenerateScheduleModel(scheduleGeneratorDTO); final LoanScheduleModel loanSchedule = regenerateScheduleModel(scheduleGeneratorDTO);
if (loanSchedule == null) { if (loanSchedule == null) {
return; return;
} }
updateLoanSchedule(loanSchedule, currentUser); updateLoanSchedule(loanSchedule);
final Set<LoanCharge> charges = this.charges(); final Set<LoanCharge> charges = this.charges();
for (final LoanCharge loanCharge : charges) { for (final LoanCharge loanCharge : charges) {
if (!loanCharge.isWaived()) { if (!loanCharge.isWaived()) {
@@ -2855,8 +2852,7 @@ public class Loan extends AbstractPersistableCustom {
return interestRate; return interestRate;
} }
private void handleDisbursementTransaction(final LocalDate disbursedOn, final LocalDateTime createdDate, final AppUser currentUser, private void handleDisbursementTransaction(final LocalDate disbursedOn, final PaymentDetail paymentDetail) {
final PaymentDetail paymentDetail) {
// add repayment transaction to track incoming money from client to mfi // add repayment transaction to track incoming money from client to mfi
// for (charges due at time of disbursement) // for (charges due at time of disbursement)
@@ -2873,7 +2869,7 @@ public class Loan extends AbstractPersistableCustom {
Money disbursentMoney = Money.zero(getCurrency()); Money disbursentMoney = Money.zero(getCurrency());
final LoanTransaction chargesPayment = LoanTransaction.repaymentAtDisbursement(getOffice(), disbursentMoney, paymentDetail, final LoanTransaction chargesPayment = LoanTransaction.repaymentAtDisbursement(getOffice(), disbursentMoney, paymentDetail,
disbursedOn, null, createdDate, currentUser); disbursedOn, null);
final Integer installmentNumber = null; final Integer installmentNumber = null;
for (final LoanCharge charge : charges()) { for (final LoanCharge charge : charges()) {
Date actualDisbursementDate = getActualDisbursementDate(charge); Date actualDisbursementDate = getActualDisbursementDate(charge);
@@ -2898,7 +2894,7 @@ public class Loan extends AbstractPersistableCustom {
* create a Charge applied transaction if Up front Accrual, None or Cash based accounting is enabled * create a Charge applied transaction if Up front Accrual, None or Cash based accounting is enabled
**/ **/
if (isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) { if (isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) {
handleChargeAppliedTransaction(charge, disbursedOn, currentUser); handleChargeAppliedTransaction(charge, disbursedOn);
} }
} }
} }
@@ -2968,7 +2964,7 @@ public class Loan extends AbstractPersistableCustom {
} }
public Map<String, Object> undoDisbursal(final ScheduleGeneratorDTO scheduleGeneratorDTO, final List<Long> existingTransactionIds, public Map<String, Object> undoDisbursal(final ScheduleGeneratorDTO scheduleGeneratorDTO, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds, AppUser currentUser) { final List<Long> existingReversedTransactionIds) {
validateAccountStatus(LoanEvent.LOAN_DISBURSAL_UNDO); validateAccountStatus(LoanEvent.LOAN_DISBURSAL_UNDO);
@@ -3003,7 +2999,7 @@ public class Loan extends AbstractPersistableCustom {
// clear off actual disbusrement date so schedule regeneration // clear off actual disbusrement date so schedule regeneration
// uses expected date. // uses expected date.
regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser); regenerateRepaymentSchedule(scheduleGeneratorDTO);
if (isDisbursedAmountChanged) { if (isDisbursedAmountChanged) {
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
} }
@@ -3070,7 +3066,7 @@ public class Loan extends AbstractPersistableCustom {
public ChangedTransactionDetail waiveInterest(final LoanTransaction waiveInterestTransaction, public ChangedTransactionDetail waiveInterest(final LoanTransaction waiveInterestTransaction,
final LoanLifecycleStateMachine loanLifecycleStateMachine, final List<Long> existingTransactionIds, final LoanLifecycleStateMachine loanLifecycleStateMachine, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds, final ScheduleGeneratorDTO scheduleGeneratorDTO, final AppUser currentUser) { final List<Long> existingReversedTransactionIds, final ScheduleGeneratorDTO scheduleGeneratorDTO) {
validateAccountStatus(LoanEvent.LOAN_REPAYMENT_OR_WAIVER); validateAccountStatus(LoanEvent.LOAN_REPAYMENT_OR_WAIVER);
@@ -3082,7 +3078,7 @@ public class Loan extends AbstractPersistableCustom {
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(waiveInterestTransaction, final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(waiveInterestTransaction,
loanLifecycleStateMachine, null, scheduleGeneratorDTO, currentUser); loanLifecycleStateMachine, null, scheduleGeneratorDTO);
return changedTransactionDetail; return changedTransactionDetail;
} }
@@ -3091,7 +3087,7 @@ public class Loan extends AbstractPersistableCustom {
public ChangedTransactionDetail makeRepayment(final LoanTransaction repaymentTransaction, public ChangedTransactionDetail makeRepayment(final LoanTransaction repaymentTransaction,
final LoanLifecycleStateMachine loanLifecycleStateMachine, final List<Long> existingTransactionIds, final LoanLifecycleStateMachine loanLifecycleStateMachine, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds, boolean isRecoveryRepayment, final ScheduleGeneratorDTO scheduleGeneratorDTO, final List<Long> existingReversedTransactionIds, boolean isRecoveryRepayment, final ScheduleGeneratorDTO scheduleGeneratorDTO,
final AppUser currentUser, Boolean isHolidayValidationDone) { Boolean isHolidayValidationDone) {
HolidayDetailDTO holidayDetailDTO = null; HolidayDetailDTO holidayDetailDTO = null;
LoanEvent event = null; LoanEvent event = null;
if (isRecoveryRepayment) { if (isRecoveryRepayment) {
@@ -3115,7 +3111,7 @@ public class Loan extends AbstractPersistableCustom {
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(repaymentTransaction, final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(repaymentTransaction,
loanLifecycleStateMachine, null, scheduleGeneratorDTO, currentUser); loanLifecycleStateMachine, null, scheduleGeneratorDTO);
return changedTransactionDetail; return changedTransactionDetail;
} }
@@ -3184,7 +3180,7 @@ public class Loan extends AbstractPersistableCustom {
private ChangedTransactionDetail handleRepaymentOrRecoveryOrWaiverTransaction(final LoanTransaction loanTransaction, private ChangedTransactionDetail handleRepaymentOrRecoveryOrWaiverTransaction(final LoanTransaction loanTransaction,
final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction adjustedTransaction, final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction adjustedTransaction,
final ScheduleGeneratorDTO scheduleGeneratorDTO, final AppUser currentUser) { final ScheduleGeneratorDTO scheduleGeneratorDTO) {
ChangedTransactionDetail changedTransactionDetail = null; ChangedTransactionDetail changedTransactionDetail = null;
@@ -3289,7 +3285,7 @@ public class Loan extends AbstractPersistableCustom {
} }
if (reprocess) { if (reprocess) {
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser); regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
} }
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
@@ -3457,8 +3453,8 @@ public class Loan extends AbstractPersistableCustom {
BigDecimal penaltyToPost = cumulativeIncomeFromInstallments.get("penalty") BigDecimal penaltyToPost = cumulativeIncomeFromInstallments.get("penalty")
.subtract(cumulativeIncomeFromIncomePosting.get("penalty")); .subtract(cumulativeIncomeFromIncomePosting.get("penalty"));
BigDecimal amountToPost = interestToPost.add(feeToPost).add(penaltyToPost); BigDecimal amountToPost = interestToPost.add(feeToPost).add(penaltyToPost);
LoanTransaction finalIncomeTransaction = LoanTransaction.incomePosting(this, this.getOffice(), closedDate, amountToPost, LoanTransaction finalIncomeTransaction = LoanTransaction.incomePosting(this, this.getOffice(), closedLocalDate, amountToPost,
interestToPost, feeToPost, penaltyToPost, null); interestToPost, feeToPost, penaltyToPost);
addLoanTransaction(finalIncomeTransaction); addLoanTransaction(finalIncomeTransaction);
if (isPeriodicAccrualAccountingEnabledOnLoanProduct()) { if (isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
List<LoanTransaction> updatedAccrualTransactions = retreiveListOfAccrualTransactions(); List<LoanTransaction> updatedAccrualTransactions = retreiveListOfAccrualTransactions();
@@ -3469,7 +3465,7 @@ public class Loan extends AbstractPersistableCustom {
HashMap<String, Object> feeDetails = new HashMap<>(); HashMap<String, Object> feeDetails = new HashMap<>();
determineFeeDetails(lastAccruedDate, closedLocalDate, feeDetails); determineFeeDetails(lastAccruedDate, closedLocalDate, feeDetails);
LoanTransaction finalAccrual = LoanTransaction.accrueTransaction(this, this.getOffice(), closedLocalDate, amountToPost, LoanTransaction finalAccrual = LoanTransaction.accrueTransaction(this, this.getOffice(), closedLocalDate, amountToPost,
interestToPost, feeToPost, penaltyToPost, null); interestToPost, feeToPost, penaltyToPost);
updateLoanChargesPaidBy(finalAccrual, feeDetails, null); updateLoanChargesPaidBy(finalAccrual, feeDetails, null);
addLoanTransaction(finalAccrual); addLoanTransaction(finalAccrual);
} }
@@ -3607,7 +3603,7 @@ public class Loan extends AbstractPersistableCustom {
return loanRepaymentScheduleInstallment; return loanRepaymentScheduleInstallment;
} }
public LoanTransaction deriveDefaultInterestWaiverTransaction(final LocalDateTime createdDate, final AppUser currentUser) { public LoanTransaction deriveDefaultInterestWaiverTransaction() {
final Money totalInterestOutstanding = getTotalInterestOutstandingOnLoan(); final Money totalInterestOutstanding = getTotalInterestOutstandingOnLoan();
Money possibleInterestToWaive = totalInterestOutstanding.copy(); Money possibleInterestToWaive = totalInterestOutstanding.copy();
@@ -3630,13 +3626,13 @@ public class Loan extends AbstractPersistableCustom {
} }
return LoanTransaction.waiver(getOffice(), this, possibleInterestToWaive, transactionDate, possibleInterestToWaive, return LoanTransaction.waiver(getOffice(), this, possibleInterestToWaive, transactionDate, possibleInterestToWaive,
possibleInterestToWaive.zero(), createdDate, currentUser); possibleInterestToWaive.zero());
} }
public ChangedTransactionDetail adjustExistingTransaction(final LoanTransaction newTransactionDetail, public ChangedTransactionDetail adjustExistingTransaction(final LoanTransaction newTransactionDetail,
final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction transactionForAdjustment, final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction transactionForAdjustment,
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final ScheduleGeneratorDTO scheduleGeneratorDTO, final AppUser currentUser) { final ScheduleGeneratorDTO scheduleGeneratorDTO) {
HolidayDetailDTO holidayDetailDTO = scheduleGeneratorDTO.getHolidayDetailDTO(); HolidayDetailDTO holidayDetailDTO = scheduleGeneratorDTO.getHolidayDetailDTO();
validateActivityNotBeforeLastTransactionDate(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, transactionForAdjustment.getTransactionDate()); validateActivityNotBeforeLastTransactionDate(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, transactionForAdjustment.getTransactionDate());
@@ -3675,14 +3671,14 @@ public class Loan extends AbstractPersistableCustom {
if (newTransactionDetail.isRepaymentType() || newTransactionDetail.isInterestWaiver()) { if (newTransactionDetail.isRepaymentType() || newTransactionDetail.isInterestWaiver()) {
changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(newTransactionDetail, loanLifecycleStateMachine, changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(newTransactionDetail, loanLifecycleStateMachine,
transactionForAdjustment, scheduleGeneratorDTO, currentUser); transactionForAdjustment, scheduleGeneratorDTO);
} }
return changedTransactionDetail; return changedTransactionDetail;
} }
public ChangedTransactionDetail undoWrittenOff(final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds, public ChangedTransactionDetail undoWrittenOff(final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final ScheduleGeneratorDTO scheduleGeneratorDTO, final AppUser currentUser) { final ScheduleGeneratorDTO scheduleGeneratorDTO) {
validateAccountStatus(LoanEvent.WRITE_OFF_OUTSTANDING_UNDO); validateAccountStatus(LoanEvent.WRITE_OFF_OUTSTANDING_UNDO);
existingTransactionIds.addAll(findExistingTransactionIds()); existingTransactionIds.addAll(findExistingTransactionIds());
@@ -3694,7 +3690,7 @@ public class Loan extends AbstractPersistableCustom {
.determineProcessor(this.transactionProcessingStrategy); .determineProcessor(this.transactionProcessingStrategy);
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser); regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
} }
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction( ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(),
@@ -3766,7 +3762,7 @@ public class Loan extends AbstractPersistableCustom {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy); .determineProcessor(this.transactionProcessingStrategy);
ChangedTransactionDetail changedTransactionDetail = closeDisbursements(scheduleGeneratorDTO, ChangedTransactionDetail changedTransactionDetail = closeDisbursements(scheduleGeneratorDTO,
loanRepaymentScheduleTransactionProcessor, currentUser); loanRepaymentScheduleTransactionProcessor);
validateAccountStatus(LoanEvent.WRITE_OFF_OUTSTANDING); validateAccountStatus(LoanEvent.WRITE_OFF_OUTSTANDING);
@@ -3805,7 +3801,7 @@ public class Loan extends AbstractPersistableCustom {
} }
LocalDateTime createdDate = DateUtils.getLocalDateTimeOfTenant(); LocalDateTime createdDate = DateUtils.getLocalDateTimeOfTenant();
loanTransaction = LoanTransaction.writeoff(this, getOffice(), writtenOffOnLocalDate, txnExternalId, createdDate, currentUser); loanTransaction = LoanTransaction.writeoff(this, getOffice(), writtenOffOnLocalDate, txnExternalId);
LocalDate lastTransactionDate = getLastUserTransactionDate(); LocalDate lastTransactionDate = getLastUserTransactionDate();
if (lastTransactionDate.isAfter(writtenOffOnLocalDate)) { if (lastTransactionDate.isAfter(writtenOffOnLocalDate)) {
final String errorMessage = "The date of the writeoff transaction must occur on or before previous transactions."; final String errorMessage = "The date of the writeoff transaction must occur on or before previous transactions.";
@@ -3826,14 +3822,14 @@ public class Loan extends AbstractPersistableCustom {
} }
private ChangedTransactionDetail closeDisbursements(final ScheduleGeneratorDTO scheduleGeneratorDTO, private ChangedTransactionDetail closeDisbursements(final ScheduleGeneratorDTO scheduleGeneratorDTO,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final AppUser currentUser) { final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {
ChangedTransactionDetail changedTransactionDetail = null; ChangedTransactionDetail changedTransactionDetail = null;
if (isDisbursementAllowed() && atleastOnceDisbursed()) { if (isDisbursementAllowed() && atleastOnceDisbursed()) {
this.loanRepaymentScheduleDetail.setPrincipal(getDisbursedAmount()); this.loanRepaymentScheduleDetail.setPrincipal(getDisbursedAmount());
removeDisbursementDetail(); removeDisbursementDetail();
regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser); regenerateRepaymentSchedule(scheduleGeneratorDTO);
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser); regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
} }
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
@@ -3862,7 +3858,7 @@ public class Loan extends AbstractPersistableCustom {
public ChangedTransactionDetail close(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine, public ChangedTransactionDetail close(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine,
final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds, final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final ScheduleGeneratorDTO scheduleGeneratorDTO, final AppUser currentUser) { final ScheduleGeneratorDTO scheduleGeneratorDTO) {
validateAccountStatus(LoanEvent.LOAN_CLOSED); validateAccountStatus(LoanEvent.LOAN_CLOSED);
@@ -3890,7 +3886,7 @@ public class Loan extends AbstractPersistableCustom {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy); .determineProcessor(this.transactionProcessingStrategy);
ChangedTransactionDetail changedTransactionDetail = closeDisbursements(scheduleGeneratorDTO, ChangedTransactionDetail changedTransactionDetail = closeDisbursements(scheduleGeneratorDTO,
loanRepaymentScheduleTransactionProcessor, currentUser); loanRepaymentScheduleTransactionProcessor);
LoanTransaction loanTransaction = null; LoanTransaction loanTransaction = null;
if (isOpen()) { if (isOpen()) {
@@ -3904,8 +3900,7 @@ public class Loan extends AbstractPersistableCustom {
changes.put("status", LoanEnumerations.status(this.loanStatus)); changes.put("status", LoanEnumerations.status(this.loanStatus));
} }
this.closedOnDate = Date.from(closureDate.atStartOfDay(ZoneId.systemDefault()).toInstant()); this.closedOnDate = Date.from(closureDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
loanTransaction = LoanTransaction.writeoff(this, getOffice(), closureDate, txnExternalId, loanTransaction = LoanTransaction.writeoff(this, getOffice(), closureDate, txnExternalId);
DateUtils.getLocalDateTimeOfTenant(), currentUser);
final boolean isLastTransaction = isChronologicallyLatestTransaction(loanTransaction, getLoanTransactions()); final boolean isLastTransaction = isChronologicallyLatestTransaction(loanTransaction, getLoanTransactions());
if (!isLastTransaction) { if (!isLastTransaction) {
final String errorMessage = "The closing date of the loan must be on or after latest transaction date."; final String errorMessage = "The closing date of the loan must be on or after latest transaction date.";
@@ -5239,8 +5234,7 @@ public class Loan extends AbstractPersistableCustom {
} }
public ChangedTransactionDetail updateDisbursementDateAndAmountForTranche(final LoanDisbursementDetails disbursementDetails, public ChangedTransactionDetail updateDisbursementDateAndAmountForTranche(final LoanDisbursementDetails disbursementDetails,
final JsonCommand command, final Map<String, Object> actualChanges, final ScheduleGeneratorDTO scheduleGeneratorDTO, final JsonCommand command, final Map<String, Object> actualChanges, final ScheduleGeneratorDTO scheduleGeneratorDTO) {
final AppUser currentUser) {
final Locale locale = command.extractLocale(); final Locale locale = command.extractLocale();
validateAccountStatus(LoanEvent.LOAN_EDIT_MULTI_DISBURSE_DATE); validateAccountStatus(LoanEvent.LOAN_EDIT_MULTI_DISBURSE_DATE);
final BigDecimal principal = command.bigDecimalValueOfParameterNamed(LoanApiConstants.updatedDisbursementPrincipalParameterName, final BigDecimal principal = command.bigDecimalValueOfParameterNamed(LoanApiConstants.updatedDisbursementPrincipalParameterName,
@@ -5266,9 +5260,9 @@ public class Loan extends AbstractPersistableCustom {
this.loanRepaymentScheduleDetail.setPrincipal(setPrincipalAmount); this.loanRepaymentScheduleDetail.setPrincipal(setPrincipalAmount);
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser); regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
} else { } else {
regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser); regenerateRepaymentSchedule(scheduleGeneratorDTO);
} }
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
@@ -5365,7 +5359,7 @@ public class Loan extends AbstractPersistableCustom {
} }
public ChangedTransactionDetail recalculateScheduleFromLastTransaction(final ScheduleGeneratorDTO generatorDTO, public ChangedTransactionDetail recalculateScheduleFromLastTransaction(final ScheduleGeneratorDTO generatorDTO,
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds, final AppUser currentUser) { final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) {
existingTransactionIds.addAll(findExistingTransactionIds()); existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
/* /*
@@ -5376,17 +5370,16 @@ public class Loan extends AbstractPersistableCustom {
* loanTransaction.getTransactionDate(); } } generatorDTO.setRecalculateFrom(recalculateFrom); * loanTransaction.getTransactionDate(); } } generatorDTO.setRecalculateFrom(recalculateFrom);
*/ */
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
regenerateRepaymentScheduleWithInterestRecalculation(generatorDTO, currentUser); regenerateRepaymentScheduleWithInterestRecalculation(generatorDTO);
} else { } else {
regenerateRepaymentSchedule(generatorDTO, currentUser); regenerateRepaymentSchedule(generatorDTO);
} }
return processTransactions(); return processTransactions();
} }
public ChangedTransactionDetail handleRegenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO, public ChangedTransactionDetail handleRegenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO) {
final AppUser currentUser) { regenerateRepaymentScheduleWithInterestRecalculation(generatorDTO);
regenerateRepaymentScheduleWithInterestRecalculation(generatorDTO, currentUser);
return processTransactions(); return processTransactions();
} }
@@ -5413,14 +5406,14 @@ public class Loan extends AbstractPersistableCustom {
return changedTransactionDetail; return changedTransactionDetail;
} }
public void regenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO, final AppUser currentUser) { public void regenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO) {
LocalDate lastTransactionDate = getLastUserTransactionDate(); LocalDate lastTransactionDate = getLastUserTransactionDate();
final LoanScheduleDTO loanSchedule = getRecalculatedSchedule(generatorDTO); final LoanScheduleDTO loanSchedule = getRecalculatedSchedule(generatorDTO);
if (loanSchedule == null) { if (loanSchedule == null) {
return; return;
} }
updateLoanSchedule(loanSchedule.getInstallments(), currentUser); updateLoanSchedule(loanSchedule.getInstallments());
this.interestRecalculatedOn = DateUtils.getBusinessDate(); this.interestRecalculatedOn = DateUtils.getBusinessDate();
LocalDate lastRepaymentDate = this.getLastRepaymentPeriodDueDate(true); LocalDate lastRepaymentDate = this.getLastRepaymentPeriodDueDate(true);
Set<LoanCharge> charges = this.charges(); Set<LoanCharge> charges = this.charges();
@@ -5440,7 +5433,7 @@ public class Loan extends AbstractPersistableCustom {
} }
processPostDisbursementTransactions(); processPostDisbursementTransactions();
processIncomeTransactions(currentUser); processIncomeTransactions();
} }
private void updateLoanChargesPaidBy(LoanTransaction accrual, HashMap<String, Object> feeDetails, private void updateLoanChargesPaidBy(LoanTransaction accrual, HashMap<String, Object> feeDetails,
@@ -5468,7 +5461,7 @@ public class Loan extends AbstractPersistableCustom {
} }
} }
public void processIncomeTransactions(AppUser currentUser) { public void processIncomeTransactions() {
if (this.loanInterestRecalculationDetails != null && this.loanInterestRecalculationDetails.isCompoundingToBePostedAsTransaction()) { if (this.loanInterestRecalculationDetails != null && this.loanInterestRecalculationDetails.isCompoundingToBePostedAsTransaction()) {
LocalDate lastCompoundingDate = this.getDisbursementDate(); LocalDate lastCompoundingDate = this.getDisbursementDate();
List<LoanInterestRecalcualtionAdditionalDetails> compoundingDetails = extractInterestRecalculationAdditionalDetails(); List<LoanInterestRecalcualtionAdditionalDetails> compoundingDetails = extractInterestRecalculationAdditionalDetails();
@@ -5480,8 +5473,7 @@ public class Loan extends AbstractPersistableCustom {
} }
LoanTransaction incomeTransaction = getTransactionForDate(incomeTransactions, compoundingDetail.getEffectiveDate()); LoanTransaction incomeTransaction = getTransactionForDate(incomeTransactions, compoundingDetail.getEffectiveDate());
LoanTransaction accrualTransaction = getTransactionForDate(accrualTransactions, compoundingDetail.getEffectiveDate()); LoanTransaction accrualTransaction = getTransactionForDate(accrualTransactions, compoundingDetail.getEffectiveDate());
addUpdateIncomeAndAccrualTransaction(compoundingDetail, lastCompoundingDate, currentUser, incomeTransaction, addUpdateIncomeAndAccrualTransaction(compoundingDetail, lastCompoundingDate, incomeTransaction, accrualTransaction);
accrualTransaction);
lastCompoundingDate = compoundingDetail.getEffectiveDate(); lastCompoundingDate = compoundingDetail.getEffectiveDate();
} }
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments(); List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
@@ -5501,8 +5493,7 @@ public class Loan extends AbstractPersistableCustom {
} }
private void addUpdateIncomeAndAccrualTransaction(LoanInterestRecalcualtionAdditionalDetails compoundingDetail, private void addUpdateIncomeAndAccrualTransaction(LoanInterestRecalcualtionAdditionalDetails compoundingDetail,
LocalDate lastCompoundingDate, AppUser currentUser, LoanTransaction existingIncomeTransaction, LocalDate lastCompoundingDate, LoanTransaction existingIncomeTransaction, LoanTransaction existingAccrualTransaction) {
LoanTransaction existingAccrualTransaction) {
BigDecimal interest = BigDecimal.ZERO; BigDecimal interest = BigDecimal.ZERO;
BigDecimal fee = BigDecimal.ZERO; BigDecimal fee = BigDecimal.ZERO;
BigDecimal penalties = BigDecimal.ZERO; BigDecimal penalties = BigDecimal.ZERO;
@@ -5525,28 +5516,26 @@ public class Loan extends AbstractPersistableCustom {
} }
if (existingIncomeTransaction == null) { if (existingIncomeTransaction == null) {
LoanTransaction transaction = LoanTransaction.incomePosting(this, this.getOffice(), LoanTransaction transaction = LoanTransaction.incomePosting(this, this.getOffice(), compoundingDetail.getEffectiveDate(),
Date.from(compoundingDetail.getEffectiveDate().atStartOfDay(ZoneId.systemDefault()).toInstant()), compoundingDetail.getAmount(), interest, fee, penalties);
compoundingDetail.getAmount(), interest, fee, penalties, currentUser);
addLoanTransaction(transaction); addLoanTransaction(transaction);
} else if (existingIncomeTransaction.getAmount(getCurrency()).getAmount().compareTo(compoundingDetail.getAmount()) != 0) { } else if (existingIncomeTransaction.getAmount(getCurrency()).getAmount().compareTo(compoundingDetail.getAmount()) != 0) {
existingIncomeTransaction.reverse(); existingIncomeTransaction.reverse();
LoanTransaction transaction = LoanTransaction.incomePosting(this, this.getOffice(), LoanTransaction transaction = LoanTransaction.incomePosting(this, this.getOffice(), compoundingDetail.getEffectiveDate(),
Date.from(compoundingDetail.getEffectiveDate().atStartOfDay(ZoneId.systemDefault()).toInstant()), compoundingDetail.getAmount(), interest, fee, penalties);
compoundingDetail.getAmount(), interest, fee, penalties, currentUser);
addLoanTransaction(transaction); addLoanTransaction(transaction);
} }
if (isPeriodicAccrualAccountingEnabledOnLoanProduct()) { if (isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
if (existingAccrualTransaction == null) { if (existingAccrualTransaction == null) {
LoanTransaction accrual = LoanTransaction.accrueTransaction(this, this.getOffice(), compoundingDetail.getEffectiveDate(), LoanTransaction accrual = LoanTransaction.accrueTransaction(this, this.getOffice(), compoundingDetail.getEffectiveDate(),
compoundingDetail.getAmount(), interest, fee, penalties, currentUser); compoundingDetail.getAmount(), interest, fee, penalties);
updateLoanChargesPaidBy(accrual, feeDetails, null); updateLoanChargesPaidBy(accrual, feeDetails, null);
addLoanTransaction(accrual); addLoanTransaction(accrual);
} else if (existingAccrualTransaction.getAmount(getCurrency()).getAmount().compareTo(compoundingDetail.getAmount()) != 0) { } else if (existingAccrualTransaction.getAmount(getCurrency()).getAmount().compareTo(compoundingDetail.getAmount()) != 0) {
existingAccrualTransaction.reverse(); existingAccrualTransaction.reverse();
LoanTransaction accrual = LoanTransaction.accrueTransaction(this, this.getOffice(), compoundingDetail.getEffectiveDate(), LoanTransaction accrual = LoanTransaction.accrueTransaction(this, this.getOffice(), compoundingDetail.getEffectiveDate(),
compoundingDetail.getAmount(), interest, fee, penalties, currentUser); compoundingDetail.getAmount(), interest, fee, penalties);
updateLoanChargesPaidBy(accrual, feeDetails, null); updateLoanChargesPaidBy(accrual, feeDetails, null);
addLoanTransaction(accrual); addLoanTransaction(accrual);
} }
@@ -6293,7 +6282,7 @@ public class Loan extends AbstractPersistableCustom {
} }
public Map<String, Object> undoLastDisbursal(ScheduleGeneratorDTO scheduleGeneratorDTO, List<Long> existingTransactionIds, public Map<String, Object> undoLastDisbursal(ScheduleGeneratorDTO scheduleGeneratorDTO, List<Long> existingTransactionIds,
List<Long> existingReversedTransactionIds, AppUser currentUser, Loan loan) { List<Long> existingReversedTransactionIds, Loan loan) {
validateAccountStatus(LoanEvent.LOAN_DISBURSAL_UNDO_LAST); validateAccountStatus(LoanEvent.LOAN_DISBURSAL_UNDO_LAST);
existingTransactionIds.addAll(findExistingTransactionIds()); existingTransactionIds.addAll(findExistingTransactionIds());
@@ -6331,8 +6320,7 @@ public class Loan extends AbstractPersistableCustom {
} }
} }
reverseExistingTransactionsTillLastDisbursal(actualDisbursementDate); reverseExistingTransactionsTillLastDisbursal(actualDisbursementDate);
loan.recalculateScheduleFromLastTransaction(scheduleGeneratorDTO, existingTransactionIds, existingReversedTransactionIds, loan.recalculateScheduleFromLastTransaction(scheduleGeneratorDTO, existingTransactionIds, existingReversedTransactionIds);
currentUser);
actualChanges.put("undolastdisbursal", "true"); actualChanges.put("undolastdisbursal", "true");
actualChanges.put("disbursedAmount", this.getDisbursedAmount()); actualChanges.put("disbursedAmount", this.getDisbursedAmount());
updateLoanSummaryDerivedFields(); updateLoanSummaryDerivedFields();
@@ -6671,16 +6659,14 @@ public class Loan extends AbstractPersistableCustom {
} }
public ChangedTransactionDetail handleForeClosureTransactions(final LoanTransaction repaymentTransaction, public ChangedTransactionDetail handleForeClosureTransactions(final LoanTransaction repaymentTransaction,
final LoanLifecycleStateMachine loanLifecycleStateMachine, final ScheduleGeneratorDTO scheduleGeneratorDTO, final LoanLifecycleStateMachine loanLifecycleStateMachine, final ScheduleGeneratorDTO scheduleGeneratorDTO) {
final AppUser appUser) {
LoanEvent event = LoanEvent.LOAN_FORECLOSURE; LoanEvent event = LoanEvent.LOAN_FORECLOSURE;
validateAccountStatus(event); validateAccountStatus(event);
validateForForeclosure(repaymentTransaction.getTransactionDate()); validateForForeclosure(repaymentTransaction.getTransactionDate());
this.loanSubStatus = LoanSubStatus.FORECLOSED.getValue(); this.loanSubStatus = LoanSubStatus.FORECLOSED.getValue();
applyAccurals(appUser); applyAccurals();
return handleRepaymentOrRecoveryOrWaiverTransaction(repaymentTransaction, loanLifecycleStateMachine, null, scheduleGeneratorDTO, return handleRepaymentOrRecoveryOrWaiverTransaction(repaymentTransaction, loanLifecycleStateMachine, null, scheduleGeneratorDTO);
appUser);
} }
public Money retrieveAccruedAmountAfterDate(final LocalDate tillDate) { public Money retrieveAccruedAmountAfterDate(final LocalDate tillDate) {
@@ -178,13 +178,12 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
final Money repaymentAmount = Money.of(loan.getCurrency(), transactionAmount); final Money repaymentAmount = Money.of(loan.getCurrency(), transactionAmount);
LoanTransaction newRepaymentTransaction = null; LoanTransaction newRepaymentTransaction = null;
final LocalDateTime currentDateTime = DateUtils.getLocalDateTimeOfTenant();
if (isRecoveryRepayment) { if (isRecoveryRepayment) {
newRepaymentTransaction = LoanTransaction.recoveryRepayment(loan.getOffice(), repaymentAmount, paymentDetail, transactionDate, newRepaymentTransaction = LoanTransaction.recoveryRepayment(loan.getOffice(), repaymentAmount, paymentDetail, transactionDate,
txnExternalId, currentDateTime, currentUser); txnExternalId);
} else { } else {
newRepaymentTransaction = LoanTransaction.repaymentType(repaymentTransactionType, loan.getOffice(), repaymentAmount, newRepaymentTransaction = LoanTransaction.repaymentType(repaymentTransactionType, loan.getOffice(), repaymentAmount,
paymentDetail, transactionDate, txnExternalId, currentDateTime, currentUser); paymentDetail, transactionDate, txnExternalId);
} }
LocalDate recalculateFrom = null; LocalDate recalculateFrom = null;
@@ -196,7 +195,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
final ChangedTransactionDetail changedTransactionDetail = loan.makeRepayment(newRepaymentTransaction, final ChangedTransactionDetail changedTransactionDetail = loan.makeRepayment(newRepaymentTransaction,
defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, isRecoveryRepayment, defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, isRecoveryRepayment,
scheduleGeneratorDTO, currentUser, isHolidayValidationDone); scheduleGeneratorDTO, isHolidayValidationDone);
saveLoanTransactionWithDataIntegrityViolationChecks(newRepaymentTransaction); saveLoanTransactionWithDataIntegrityViolationChecks(newRepaymentTransaction);
@@ -361,7 +360,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
public LoanTransaction makeChargePayment(final Loan loan, final Long chargeId, final LocalDate transactionDate, public LoanTransaction makeChargePayment(final Loan loan, final Long chargeId, final LocalDate transactionDate,
final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final String noteText, final String txnExternalId, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final String noteText, final String txnExternalId,
final Integer transactionType, Integer installmentNumber) { final Integer transactionType, Integer installmentNumber) {
AppUser currentUser = getAppUserIfPresent();
boolean isAccountTransfer = true; boolean isAccountTransfer = true;
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
businessEventNotifierService.notifyPreBusinessEvent(new LoanChargePaymentPreBusinessEvent(loan)); businessEventNotifierService.notifyPreBusinessEvent(new LoanChargePaymentPreBusinessEvent(loan));
@@ -373,7 +371,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
final LoanTransactionType loanTransactionType = LoanTransactionType.fromInt(transactionType); final LoanTransactionType loanTransactionType = LoanTransactionType.fromInt(transactionType);
final LoanTransaction newPaymentTransaction = LoanTransaction.loanPayment(null, loan.getOffice(), paymentAmout, paymentDetail, final LoanTransaction newPaymentTransaction = LoanTransaction.loanPayment(null, loan.getOffice(), paymentAmout, paymentDetail,
transactionDate, txnExternalId, loanTransactionType, DateUtils.getLocalDateTimeOfTenant(), currentUser); transactionDate, txnExternalId, loanTransactionType);
if (loanTransactionType.isRepaymentAtDisbursement()) { if (loanTransactionType.isRepaymentAtDisbursement()) {
loan.handlePayDisbursementTransaction(chargeId, newPaymentTransaction, existingTransactionIds, existingReversedTransactionIds); loan.handlePayDisbursementTransaction(chargeId, newPaymentTransaction, existingTransactionIds, existingReversedTransactionIds);
@@ -445,7 +443,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
public LoanTransaction makeRefund(final Long accountId, final CommandProcessingResultBuilder builderResult, public LoanTransaction makeRefund(final Long accountId, final CommandProcessingResultBuilder builderResult,
final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final String noteText, final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final String noteText,
final String txnExternalId) { final String txnExternalId) {
AppUser currentUser = getAppUserIfPresent();
boolean isAccountTransfer = true; boolean isAccountTransfer = true;
final Loan loan = this.loanAccountAssembler.assembleFrom(accountId); final Loan loan = this.loanAccountAssembler.assembleFrom(accountId);
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
@@ -455,7 +452,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
final Money refundAmount = Money.of(loan.getCurrency(), transactionAmount); final Money refundAmount = Money.of(loan.getCurrency(), transactionAmount);
final LoanTransaction newRefundTransaction = LoanTransaction.refund(loan.getOffice(), refundAmount, paymentDetail, transactionDate, final LoanTransaction newRefundTransaction = LoanTransaction.refund(loan.getOffice(), refundAmount, paymentDetail, transactionDate,
txnExternalId, DateUtils.getLocalDateTimeOfTenant(), currentUser); txnExternalId);
final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled(); final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(),
Date.from(transactionDate.atStartOfDay(ZoneId.systemDefault()).toInstant()), HolidayStatusType.ACTIVE.getValue()); Date.from(transactionDate.atStartOfDay(ZoneId.systemDefault()).toInstant()), HolidayStatusType.ACTIVE.getValue());
@@ -492,7 +489,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
@Override @Override
public LoanTransaction makeDisburseTransaction(final Long loanId, final LocalDate transactionDate, final BigDecimal transactionAmount, public LoanTransaction makeDisburseTransaction(final Long loanId, final LocalDate transactionDate, final BigDecimal transactionAmount,
final PaymentDetail paymentDetail, final String noteText, final String txnExternalId, final boolean isLoanToLoanTransfer) { final PaymentDetail paymentDetail, final String noteText, final String txnExternalId, final boolean isLoanToLoanTransfer) {
AppUser currentUser = getAppUserIfPresent();
final Loan loan = this.loanAccountAssembler.assembleFrom(loanId); final Loan loan = this.loanAccountAssembler.assembleFrom(loanId);
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
boolean isAccountTransfer = true; boolean isAccountTransfer = true;
@@ -500,7 +496,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
final List<Long> existingReversedTransactionIds = new ArrayList<>(); final List<Long> existingReversedTransactionIds = new ArrayList<>();
final Money amount = Money.of(loan.getCurrency(), transactionAmount); final Money amount = Money.of(loan.getCurrency(), transactionAmount);
LoanTransaction disbursementTransaction = LoanTransaction.disbursement(loan.getOffice(), amount, paymentDetail, transactionDate, LoanTransaction disbursementTransaction = LoanTransaction.disbursement(loan.getOffice(), amount, paymentDetail, transactionDate,
txnExternalId, DateUtils.getLocalDateTimeOfTenant(), currentUser); txnExternalId);
// Subtract Previous loan outstanding balance from netDisbursalAmount // Subtract Previous loan outstanding balance from netDisbursalAmount
loan.deductFromNetDisbursalAmount(transactionAmount); loan.deductFromNetDisbursalAmount(transactionAmount);
@@ -646,11 +642,10 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
businessEventNotifierService.notifyPreBusinessEvent(new LoanCreditBalanceRefundPreBusinessEvent(loan)); businessEventNotifierService.notifyPreBusinessEvent(new LoanCreditBalanceRefundPreBusinessEvent(loan));
final List<Long> existingTransactionIds = new ArrayList<>(); final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>(); final List<Long> existingReversedTransactionIds = new ArrayList<>();
AppUser currentUser = getAppUserIfPresent();
final Money refundAmount = Money.of(loan.getCurrency(), transactionAmount); final Money refundAmount = Money.of(loan.getCurrency(), transactionAmount);
final LoanTransaction newCreditBalanceRefundTransaction = LoanTransaction.creditBalanceRefund(loan, loan.getOffice(), refundAmount, final LoanTransaction newCreditBalanceRefundTransaction = LoanTransaction.creditBalanceRefund(loan, loan.getOffice(), refundAmount,
transactionDate, externalId, DateUtils.getLocalDateTimeOfTenant(), currentUser); transactionDate, externalId);
loan.creditBalanceRefund(newCreditBalanceRefundTransaction, defaultLoanLifecycleStateMachine(), existingTransactionIds, loan.creditBalanceRefund(newCreditBalanceRefundTransaction, defaultLoanLifecycleStateMachine(), existingTransactionIds,
existingReversedTransactionIds); existingReversedTransactionIds);
@@ -681,11 +676,10 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
businessEventNotifierService.notifyPreBusinessEvent(new LoanRefundPreBusinessEvent(loan)); businessEventNotifierService.notifyPreBusinessEvent(new LoanRefundPreBusinessEvent(loan));
final List<Long> existingTransactionIds = new ArrayList<>(); final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>(); final List<Long> existingReversedTransactionIds = new ArrayList<>();
AppUser currentUser = getAppUserIfPresent();
final Money refundAmount = Money.of(loan.getCurrency(), transactionAmount); final Money refundAmount = Money.of(loan.getCurrency(), transactionAmount);
final LoanTransaction newRefundTransaction = LoanTransaction.refundForActiveLoan(loan.getOffice(), refundAmount, paymentDetail, final LoanTransaction newRefundTransaction = LoanTransaction.refundForActiveLoan(loan.getOffice(), refundAmount, paymentDetail,
transactionDate, txnExternalId, DateUtils.getLocalDateTimeOfTenant(), currentUser); transactionDate, txnExternalId);
final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled(); final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(),
Date.from(transactionDate.atStartOfDay(ZoneId.systemDefault()).toInstant()), HolidayStatusType.ACTIVE.getValue()); Date.from(transactionDate.atStartOfDay(ZoneId.systemDefault()).toInstant()), HolidayStatusType.ACTIVE.getValue());
@@ -716,7 +710,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
public Map<String, Object> foreCloseLoan(final Loan loan, final LocalDate foreClosureDate, final String noteText) { public Map<String, Object> foreCloseLoan(final Loan loan, final LocalDate foreClosureDate, final String noteText) {
businessEventNotifierService.notifyPreBusinessEvent(new LoanForeClosurePreBusinessEvent(loan)); businessEventNotifierService.notifyPreBusinessEvent(new LoanForeClosurePreBusinessEvent(loan));
MonetaryCurrency currency = loan.getCurrency(); MonetaryCurrency currency = loan.getCurrency();
LocalDateTime createdDate = DateUtils.getLocalDateTimeOfTenant();
final Map<String, Object> changes = new LinkedHashMap<>(); final Map<String, Object> changes = new LinkedHashMap<>();
List<LoanTransaction> newTransactions = new ArrayList<>(); List<LoanTransaction> newTransactions = new ArrayList<>();
@@ -737,12 +730,11 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
Money total = interestPortion.plus(feePortion).plus(penaltyPortion); Money total = interestPortion.plus(feePortion).plus(penaltyPortion);
if (total.isGreaterThanZero()) { if (total.isGreaterThanZero()) {
LoanTransaction accrualTransaction = LoanTransaction.accrueTransaction(loan, loan.getOffice(), foreClosureDate, LoanTransaction accrualTransaction = LoanTransaction.accrueTransaction(loan, loan.getOffice(), foreClosureDate,
total.getAmount(), interestPortion.getAmount(), feePortion.getAmount(), penaltyPortion.getAmount(), appUser); total.getAmount(), interestPortion.getAmount(), feePortion.getAmount(), penaltyPortion.getAmount());
LocalDate fromDate = loan.getDisbursementDate(); LocalDate fromDate = loan.getDisbursementDate();
if (loan.getAccruedTill() != null) { if (loan.getAccruedTill() != null) {
fromDate = loan.getAccruedTill(); fromDate = loan.getAccruedTill();
} }
createdDate = createdDate.plusSeconds(1);
newTransactions.add(accrualTransaction); newTransactions.add(accrualTransaction);
loan.addLoanTransaction(accrualTransaction); loan.addLoanTransaction(accrualTransaction);
Set<LoanChargePaidBy> accrualCharges = accrualTransaction.getLoanChargesPaid(); Set<LoanChargePaidBy> accrualCharges = accrualTransaction.getLoanChargesPaid();
@@ -771,16 +763,14 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
String externalId = null; String externalId = null;
final LocalDateTime currentDateTime = DateUtils.getLocalDateTimeOfTenant(); final LocalDateTime currentDateTime = DateUtils.getLocalDateTimeOfTenant();
payment = LoanTransaction.repayment(loan.getOffice(), payPrincipal.plus(interestPayable).plus(feePayable).plus(penaltyPayable), payment = LoanTransaction.repayment(loan.getOffice(), payPrincipal.plus(interestPayable).plus(feePayable).plus(penaltyPayable),
paymentDetail, foreClosureDate, externalId, currentDateTime, appUser); paymentDetail, foreClosureDate, externalId);
createdDate = createdDate.plusSeconds(1);
payment.updateCreatedDate(Date.from(createdDate.atZone(DateUtils.getDateTimeZoneOfTenant()).toInstant()));
payment.updateLoan(loan); payment.updateLoan(loan);
newTransactions.add(payment); newTransactions.add(payment);
} }
List<Long> transactionIds = new ArrayList<>(); List<Long> transactionIds = new ArrayList<>();
final ChangedTransactionDetail changedTransactionDetail = loan.handleForeClosureTransactions(payment, final ChangedTransactionDetail changedTransactionDetail = loan.handleForeClosureTransactions(payment,
defaultLoanLifecycleStateMachine(), scheduleGeneratorDTO, appUser); defaultLoanLifecycleStateMachine(), scheduleGeneratorDTO);
/*** /***
* TODO Vishwas Batch save is giving me a HibernateOptimisticLockingFailureException, looping and saving for the * TODO Vishwas Batch save is giving me a HibernateOptimisticLockingFailureException, looping and saving for the
@@ -21,10 +21,8 @@ package org.apache.fineract.portfolio.loanaccount.domain;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Date;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
@@ -38,10 +36,8 @@ import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne; import javax.persistence.ManyToOne;
import javax.persistence.OneToMany; import javax.persistence.OneToMany;
import javax.persistence.Table; import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.UniqueConstraint; import javax.persistence.UniqueConstraint;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
@@ -53,7 +49,6 @@ import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData;
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations; import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData; import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData;
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
import org.apache.fineract.useradministration.domain.AppUser;
/** /**
* All monetary transactions against a loan are modelled through this entity. Disbursements, Repayments, Waivers, * All monetary transactions against a loan are modelled through this entity. Disbursements, Repayments, Waivers,
@@ -61,7 +56,7 @@ import org.apache.fineract.useradministration.domain.AppUser;
*/ */
@Entity @Entity
@Table(name = "m_loan_transaction", uniqueConstraints = { @UniqueConstraint(columnNames = { "external_id" }, name = "external_id_UNIQUE") }) @Table(name = "m_loan_transaction", uniqueConstraints = { @UniqueConstraint(columnNames = { "external_id" }, name = "external_id_UNIQUE") })
public class LoanTransaction extends AbstractPersistableCustom { public class LoanTransaction extends AbstractAuditableCustom {
@ManyToOne(optional = false) @ManyToOne(optional = false)
@JoinColumn(name = "loan_id", nullable = false) @JoinColumn(name = "loan_id", nullable = false)
@@ -78,13 +73,11 @@ public class LoanTransaction extends AbstractPersistableCustom {
@Column(name = "transaction_type_enum", nullable = false) @Column(name = "transaction_type_enum", nullable = false)
private Integer typeOf; private Integer typeOf;
@Temporal(TemporalType.DATE)
@Column(name = "transaction_date", nullable = false) @Column(name = "transaction_date", nullable = false)
private Date dateOf; private LocalDate dateOf;
@Temporal(TemporalType.DATE)
@Column(name = "submitted_on_date", nullable = false) @Column(name = "submitted_on_date", nullable = false)
private Date submittedOnDate; private LocalDate submittedOnDate;
@Column(name = "amount", scale = 6, precision = 19, nullable = false) @Column(name = "amount", scale = 6, precision = 19, nullable = false)
private BigDecimal amount; private BigDecimal amount;
@@ -113,14 +106,6 @@ public class LoanTransaction extends AbstractPersistableCustom {
@Column(name = "external_id", length = 100, nullable = true, unique = true) @Column(name = "external_id", length = 100, nullable = true, unique = true)
private String externalId; private String externalId;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created_date", nullable = false)
private Date createdDate;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "appuser_id", nullable = true)
private AppUser appUser;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "loanTransaction", orphanRemoval = true, fetch = FetchType.EAGER) @OneToMany(cascade = CascadeType.ALL, mappedBy = "loanTransaction", orphanRemoval = true, fetch = FetchType.EAGER)
private Set<LoanChargePaidBy> loanChargesPaid = new HashSet<>(); private Set<LoanChargePaidBy> loanChargesPaid = new HashSet<>();
@@ -136,44 +121,34 @@ public class LoanTransaction extends AbstractPersistableCustom {
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "loanTransaction") @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "loanTransaction")
private Set<LoanTransactionToRepaymentScheduleMapping> loanTransactionToRepaymentScheduleMappings = new HashSet<>(); private Set<LoanTransactionToRepaymentScheduleMapping> loanTransactionToRepaymentScheduleMappings = new HashSet<>();
protected LoanTransaction() { protected LoanTransaction() {}
/*
* this.loan = null; this.dateOf = null; this.typeOf = null; this.submittedOnDate = DateUtils.getDateOfTenant();
* this.createdDate = new Date(); this.appUser = null;
*/
}
public static LoanTransaction incomePosting(final Loan loan, final Office office, final Date dateOf, final BigDecimal amount, public static LoanTransaction incomePosting(final Loan loan, final Office office, final LocalDate dateOf, final BigDecimal amount,
final BigDecimal interestPortion, final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion, final BigDecimal interestPortion, final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion) {
final AppUser appUser) {
final Integer typeOf = LoanTransactionType.INCOME_POSTING.getValue(); final Integer typeOf = LoanTransactionType.INCOME_POSTING.getValue();
final BigDecimal principalPortion = BigDecimal.ZERO; final BigDecimal principalPortion = BigDecimal.ZERO;
final BigDecimal overPaymentPortion = BigDecimal.ZERO; final BigDecimal overPaymentPortion = BigDecimal.ZERO;
final boolean reversed = false; final boolean reversed = false;
final PaymentDetail paymentDetail = null; final PaymentDetail paymentDetail = null;
final String externalId = null; final String externalId = null;
final LocalDateTime createdDate = DateUtils.getLocalDateTimeOfTenant();
return new LoanTransaction(loan, office, typeOf, dateOf, amount, principalPortion, interestPortion, feeChargesPortion, return new LoanTransaction(loan, office, typeOf, dateOf, amount, principalPortion, interestPortion, feeChargesPortion,
penaltyChargesPortion, overPaymentPortion, reversed, paymentDetail, externalId, createdDate, appUser); penaltyChargesPortion, overPaymentPortion, reversed, paymentDetail, externalId);
} }
public static LoanTransaction disbursement(final Office office, final Money amount, final PaymentDetail paymentDetail, public static LoanTransaction disbursement(final Office office, final Money amount, final PaymentDetail paymentDetail,
final LocalDate disbursementDate, final String externalId, final LocalDateTime createdDate, final AppUser appUser) { final LocalDate disbursementDate, final String externalId) {
return new LoanTransaction(null, office, LoanTransactionType.DISBURSEMENT, paymentDetail, amount.getAmount(), disbursementDate, return new LoanTransaction(null, office, LoanTransactionType.DISBURSEMENT, paymentDetail, amount.getAmount(), disbursementDate,
externalId, createdDate, appUser); externalId);
} }
public static LoanTransaction repayment(final Office office, final Money amount, final PaymentDetail paymentDetail, public static LoanTransaction repayment(final Office office, final Money amount, final PaymentDetail paymentDetail,
final LocalDate paymentDate, final String externalId, final LocalDateTime createdDate, final AppUser appUser) { final LocalDate paymentDate, final String externalId) {
return new LoanTransaction(null, office, LoanTransactionType.REPAYMENT, paymentDetail, amount.getAmount(), paymentDate, externalId, return new LoanTransaction(null, office, LoanTransactionType.REPAYMENT, paymentDetail, amount.getAmount(), paymentDate, externalId);
createdDate, appUser);
} }
public static LoanTransaction repaymentType(final LoanTransactionType repaymentType, final Office office, final Money amount, public static LoanTransaction repaymentType(final LoanTransactionType repaymentType, final Office office, final Money amount,
final PaymentDetail paymentDetail, final LocalDate paymentDate, final String externalId, final LocalDateTime createdDate, final PaymentDetail paymentDetail, final LocalDate paymentDate, final String externalId) {
final AppUser appUser) { return new LoanTransaction(null, office, repaymentType, paymentDetail, amount.getAmount(), paymentDate, externalId);
return new LoanTransaction(null, office, repaymentType, paymentDetail, amount.getAmount(), paymentDate, externalId, createdDate,
appUser);
} }
public void setLoanTransactionToRepaymentScheduleMappings(final Integer installmentId, final BigDecimal chargePerInstallment) { public void setLoanTransactionToRepaymentScheduleMappings(final Integer installmentId, final BigDecimal chargePerInstallment) {
@@ -189,34 +164,32 @@ public class LoanTransaction extends AbstractPersistableCustom {
} }
public static LoanTransaction recoveryRepayment(final Office office, final Money amount, final PaymentDetail paymentDetail, public static LoanTransaction recoveryRepayment(final Office office, final Money amount, final PaymentDetail paymentDetail,
final LocalDate paymentDate, final String externalId, final LocalDateTime createdDate, final AppUser appUser) { final LocalDate paymentDate, final String externalId) {
return new LoanTransaction(null, office, LoanTransactionType.RECOVERY_REPAYMENT, paymentDetail, amount.getAmount(), paymentDate, return new LoanTransaction(null, office, LoanTransactionType.RECOVERY_REPAYMENT, paymentDetail, amount.getAmount(), paymentDate,
externalId, createdDate, appUser); externalId);
} }
public static LoanTransaction loanPayment(final Loan loan, final Office office, final Money amount, final PaymentDetail paymentDetail, public static LoanTransaction loanPayment(final Loan loan, final Office office, final Money amount, final PaymentDetail paymentDetail,
final LocalDate paymentDate, final String externalId, final LoanTransactionType transactionType, final LocalDate paymentDate, final String externalId, final LoanTransactionType transactionType) {
final LocalDateTime createdDate, final AppUser appUser) { return new LoanTransaction(loan, office, transactionType, paymentDetail, amount.getAmount(), paymentDate, externalId);
return new LoanTransaction(loan, office, transactionType, paymentDetail, amount.getAmount(), paymentDate, externalId, createdDate,
appUser);
} }
public static LoanTransaction repaymentAtDisbursement(final Office office, final Money amount, final PaymentDetail paymentDetail, public static LoanTransaction repaymentAtDisbursement(final Office office, final Money amount, final PaymentDetail paymentDetail,
final LocalDate paymentDate, final String externalId, final LocalDateTime createdDate, final AppUser appUser) { final LocalDate paymentDate, final String externalId) {
return new LoanTransaction(null, office, LoanTransactionType.REPAYMENT_AT_DISBURSEMENT, paymentDetail, amount.getAmount(), return new LoanTransaction(null, office, LoanTransactionType.REPAYMENT_AT_DISBURSEMENT, paymentDetail, amount.getAmount(),
paymentDate, externalId, createdDate, appUser); paymentDate, externalId);
} }
public static LoanTransaction waiver(final Office office, final Loan loan, final Money amount, final LocalDate waiveDate, public static LoanTransaction waiver(final Office office, final Loan loan, final Money amount, final LocalDate waiveDate,
final Money waived, final Money unrecognizedPortion, final LocalDateTime createdDate, final AppUser appUser) { final Money waived, final Money unrecognizedPortion) {
LoanTransaction loanTransaction = new LoanTransaction(loan, office, LoanTransactionType.WAIVE_INTEREST, amount.getAmount(), LoanTransaction loanTransaction = new LoanTransaction(loan, office, LoanTransactionType.WAIVE_INTEREST, amount.getAmount(),
waiveDate, null, createdDate, appUser); waiveDate, null);
loanTransaction.updateInterestComponent(waived, unrecognizedPortion); loanTransaction.updateInterestComponent(waived, unrecognizedPortion);
return loanTransaction; return loanTransaction;
} }
public static LoanTransaction accrueInterest(final Office office, final Loan loan, final Money amount, public static LoanTransaction accrueInterest(final Office office, final Loan loan, final Money amount,
final LocalDate interestAppliedDate, final LocalDateTime createdDate, final AppUser appUser) { final LocalDate interestAppliedDate) {
BigDecimal principalPortion = null; BigDecimal principalPortion = null;
BigDecimal feesPortion = null; BigDecimal feesPortion = null;
BigDecimal penaltiesPortion = null; BigDecimal penaltiesPortion = null;
@@ -225,96 +198,80 @@ public class LoanTransaction extends AbstractPersistableCustom {
boolean reversed = false; boolean reversed = false;
PaymentDetail paymentDetail = null; PaymentDetail paymentDetail = null;
String externalId = null; String externalId = null;
return new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL.getValue(), return new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL.getValue(), interestAppliedDate, interestPortion,
Date.from(interestAppliedDate.atStartOfDay(ZoneId.systemDefault()).toInstant()), interestPortion, principalPortion, principalPortion, interestPortion, feesPortion, penaltiesPortion, overPaymentPortion, reversed, paymentDetail, externalId);
interestPortion, feesPortion, penaltiesPortion, overPaymentPortion, reversed, paymentDetail, externalId, createdDate,
appUser);
} }
public static LoanTransaction accrual(final Loan loan, final Office office, final Money amount, final Money interest, public static LoanTransaction accrual(final Loan loan, final Office office, final Money amount, final Money interest,
final Money feeCharges, final Money penaltyCharges, final LocalDate transactionDate) { final Money feeCharges, final Money penaltyCharges, final LocalDate transactionDate) {
final AppUser appUser = null;
return accrueTransaction(loan, office, transactionDate, amount.getAmount(), interest.getAmount(), feeCharges.getAmount(), return accrueTransaction(loan, office, transactionDate, amount.getAmount(), interest.getAmount(), feeCharges.getAmount(),
penaltyCharges.getAmount(), appUser); penaltyCharges.getAmount());
} }
public static LoanTransaction accrueTransaction(final Loan loan, final Office office, final LocalDate dateOf, final BigDecimal amount, public static LoanTransaction accrueTransaction(final Loan loan, final Office office, final LocalDate dateOf, final BigDecimal amount,
final BigDecimal interestPortion, final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion, final BigDecimal interestPortion, final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion) {
final AppUser appUser) {
BigDecimal principalPortion = null; BigDecimal principalPortion = null;
BigDecimal overPaymentPortion = null; BigDecimal overPaymentPortion = null;
boolean reversed = false; boolean reversed = false;
PaymentDetail paymentDetail = null; PaymentDetail paymentDetail = null;
String externalId = null; String externalId = null;
LocalDateTime createdDate = DateUtils.getLocalDateTimeOfTenant(); return new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL.getValue(), dateOf, amount, principalPortion, interestPortion,
return new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL.getValue(), feeChargesPortion, penaltyChargesPortion, overPaymentPortion, reversed, paymentDetail, externalId);
Date.from(dateOf.atStartOfDay(ZoneId.systemDefault()).toInstant()), amount, principalPortion, interestPortion,
feeChargesPortion, penaltyChargesPortion, overPaymentPortion, reversed, paymentDetail, externalId, createdDate, appUser);
} }
public static LoanTransaction initiateTransfer(final Office office, final Loan loan, final LocalDate transferDate, public static LoanTransaction initiateTransfer(final Office office, final Loan loan, final LocalDate transferDate) {
final LocalDateTime createdDate, final AppUser appUser) { return new LoanTransaction(loan, office, LoanTransactionType.INITIATE_TRANSFER.getValue(), transferDate,
return new LoanTransaction(loan, office, LoanTransactionType.INITIATE_TRANSFER.getValue(), loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalPrincipalOutstanding(),
Date.from(transferDate.atStartOfDay(ZoneId.systemDefault()).toInstant()), loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalInterestOutstanding(), loan.getSummary().getTotalFeeChargesOutstanding(),
loan.getSummary().getTotalPrincipalOutstanding(), loan.getSummary().getTotalInterestOutstanding(), loan.getSummary().getTotalPenaltyChargesOutstanding(), null, false, null, null);
loan.getSummary().getTotalFeeChargesOutstanding(), loan.getSummary().getTotalPenaltyChargesOutstanding(), null, false, null,
null, createdDate, appUser);
} }
public static LoanTransaction approveTransfer(final Office office, final Loan loan, final LocalDate transferDate, public static LoanTransaction approveTransfer(final Office office, final Loan loan, final LocalDate transferDate) {
final LocalDateTime createdDate, final AppUser appUser) { return new LoanTransaction(loan, office, LoanTransactionType.APPROVE_TRANSFER.getValue(), transferDate,
return new LoanTransaction(loan, office, LoanTransactionType.APPROVE_TRANSFER.getValue(), loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalPrincipalOutstanding(),
Date.from(transferDate.atStartOfDay(ZoneId.systemDefault()).toInstant()), loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalInterestOutstanding(), loan.getSummary().getTotalFeeChargesOutstanding(),
loan.getSummary().getTotalPrincipalOutstanding(), loan.getSummary().getTotalInterestOutstanding(), loan.getSummary().getTotalPenaltyChargesOutstanding(), null, false, null, null);
loan.getSummary().getTotalFeeChargesOutstanding(), loan.getSummary().getTotalPenaltyChargesOutstanding(), null, false, null,
null, createdDate, appUser);
} }
public static LoanTransaction withdrawTransfer(final Office office, final Loan loan, final LocalDate transferDate, public static LoanTransaction withdrawTransfer(final Office office, final Loan loan, final LocalDate transferDate) {
final LocalDateTime createdDate, final AppUser appUser) { return new LoanTransaction(loan, office, LoanTransactionType.WITHDRAW_TRANSFER.getValue(), transferDate,
return new LoanTransaction(loan, office, LoanTransactionType.WITHDRAW_TRANSFER.getValue(), loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalPrincipalOutstanding(),
Date.from(transferDate.atStartOfDay(ZoneId.systemDefault()).toInstant()), loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalInterestOutstanding(), loan.getSummary().getTotalFeeChargesOutstanding(),
loan.getSummary().getTotalPrincipalOutstanding(), loan.getSummary().getTotalInterestOutstanding(), loan.getSummary().getTotalPenaltyChargesOutstanding(), null, false, null, null);
loan.getSummary().getTotalFeeChargesOutstanding(), loan.getSummary().getTotalPenaltyChargesOutstanding(), null, false, null,
null, createdDate, appUser);
} }
public static LoanTransaction refund(final Office office, final Money amount, final PaymentDetail paymentDetail, public static LoanTransaction refund(final Office office, final Money amount, final PaymentDetail paymentDetail,
final LocalDate paymentDate, final String externalId, final LocalDateTime createdDate, final AppUser appUser) { final LocalDate paymentDate, final String externalId) {
return new LoanTransaction(null, office, LoanTransactionType.REFUND, paymentDetail, amount.getAmount(), paymentDate, externalId, return new LoanTransaction(null, office, LoanTransactionType.REFUND, paymentDetail, amount.getAmount(), paymentDate, externalId);
createdDate, appUser);
} }
public static LoanTransaction copyTransactionProperties(final LoanTransaction loanTransaction) { public static LoanTransaction copyTransactionProperties(final LoanTransaction loanTransaction) {
return new LoanTransaction(loanTransaction.loan, loanTransaction.office, loanTransaction.typeOf, loanTransaction.dateOf, return new LoanTransaction(loanTransaction.loan, loanTransaction.office, loanTransaction.typeOf, loanTransaction.dateOf,
loanTransaction.amount, loanTransaction.principalPortion, loanTransaction.interestPortion, loanTransaction.amount, loanTransaction.principalPortion, loanTransaction.interestPortion,
loanTransaction.feeChargesPortion, loanTransaction.penaltyChargesPortion, loanTransaction.overPaymentPortion, loanTransaction.feeChargesPortion, loanTransaction.penaltyChargesPortion, loanTransaction.overPaymentPortion,
loanTransaction.reversed, loanTransaction.paymentDetail, loanTransaction.externalId, loanTransaction.reversed, loanTransaction.paymentDetail, loanTransaction.externalId);
(loanTransaction.createdDate == null) ? LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant())
: LocalDateTime.ofInstant(loanTransaction.createdDate.toInstant(), DateUtils.getDateTimeZoneOfTenant()),
loanTransaction.appUser);
} }
public static LoanTransaction accrueLoanCharge(final Loan loan, final Office office, final Money amount, final LocalDate applyDate, public static LoanTransaction accrueLoanCharge(final Loan loan, final Office office, final Money amount, final LocalDate applyDate,
final Money feeCharges, final Money penaltyCharges, final LocalDateTime createdDate, final AppUser appUser) { final Money feeCharges, final Money penaltyCharges) {
String externalId = null; String externalId = null;
final LoanTransaction applyCharge = new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL, amount.getAmount(), applyDate, final LoanTransaction applyCharge = new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL, amount.getAmount(), applyDate,
externalId, createdDate, appUser); externalId);
applyCharge.updateChargesComponents(feeCharges, penaltyCharges); applyCharge.updateChargesComponents(feeCharges, penaltyCharges);
return applyCharge; return applyCharge;
} }
public static LoanTransaction creditBalanceRefund(final Loan loan, final Office office, final Money amount, final LocalDate paymentDate, public static LoanTransaction creditBalanceRefund(final Loan loan, final Office office, final Money amount, final LocalDate paymentDate,
final String externalId, final LocalDateTime createdDate, final AppUser appUser) { final String externalId) {
final PaymentDetail paymentDetail = null; final PaymentDetail paymentDetail = null;
return new LoanTransaction(loan, office, LoanTransactionType.CREDIT_BALANCE_REFUND, paymentDetail, amount.getAmount(), paymentDate, return new LoanTransaction(loan, office, LoanTransactionType.CREDIT_BALANCE_REFUND, paymentDetail, amount.getAmount(), paymentDate,
externalId, createdDate, appUser); externalId);
} }
public static LoanTransaction refundForActiveLoan(final Office office, final Money amount, final PaymentDetail paymentDetail, public static LoanTransaction refundForActiveLoan(final Office office, final Money amount, final PaymentDetail paymentDetail,
final LocalDate paymentDate, final String externalId, final LocalDateTime createdDate, final AppUser appUser) { final LocalDate paymentDate, final String externalId) {
return new LoanTransaction(null, office, LoanTransactionType.REFUND_FOR_ACTIVE_LOAN, paymentDetail, amount.getAmount(), paymentDate, return new LoanTransaction(null, office, LoanTransactionType.REFUND_FOR_ACTIVE_LOAN, paymentDetail, amount.getAmount(), paymentDate,
externalId, createdDate, appUser); externalId);
} }
public static boolean transactionAmountsMatch(final MonetaryCurrency currency, final LoanTransaction loanTransaction, public static boolean transactionAmountsMatch(final MonetaryCurrency currency, final LoanTransaction loanTransaction,
@@ -330,10 +287,10 @@ public class LoanTransaction extends AbstractPersistableCustom {
return false; return false;
} }
private LoanTransaction(final Loan loan, final Office office, final Integer typeOf, final Date dateOf, final BigDecimal amount, private LoanTransaction(final Loan loan, final Office office, final Integer typeOf, final LocalDate dateOf, final BigDecimal amount,
final BigDecimal principalPortion, final BigDecimal interestPortion, final BigDecimal feeChargesPortion, final BigDecimal principalPortion, final BigDecimal interestPortion, final BigDecimal feeChargesPortion,
final BigDecimal penaltyChargesPortion, final BigDecimal overPaymentPortion, final boolean reversed, final BigDecimal penaltyChargesPortion, final BigDecimal overPaymentPortion, final boolean reversed,
final PaymentDetail paymentDetail, final String externalId, final LocalDateTime createdDate, final AppUser appUser) { final PaymentDetail paymentDetail, final String externalId) {
this.loan = loan; this.loan = loan;
this.typeOf = typeOf; this.typeOf = typeOf;
@@ -348,52 +305,43 @@ public class LoanTransaction extends AbstractPersistableCustom {
this.paymentDetail = paymentDetail; this.paymentDetail = paymentDetail;
this.office = office; this.office = office;
this.externalId = externalId; this.externalId = externalId;
this.submittedOnDate = DateUtils.getBusinessDate(); this.submittedOnDate = DateUtils.getBusinessLocalDate();
this.createdDate = Date.from(createdDate.atZone(DateUtils.getDateTimeZoneOfTenant()).toInstant());
this.appUser = appUser;
} }
public static LoanTransaction waiveLoanCharge(final Loan loan, final Office office, final Money waived, final LocalDate waiveDate, public static LoanTransaction waiveLoanCharge(final Loan loan, final Office office, final Money waived, final LocalDate waiveDate,
final Money feeChargesWaived, final Money penaltyChargesWaived, final Money unrecognizedCharge, final LocalDateTime createdDate, final Money feeChargesWaived, final Money penaltyChargesWaived, final Money unrecognizedCharge) {
final AppUser appUser) {
final LoanTransaction waiver = new LoanTransaction(loan, office, LoanTransactionType.WAIVE_CHARGES, waived.getAmount(), waiveDate, final LoanTransaction waiver = new LoanTransaction(loan, office, LoanTransactionType.WAIVE_CHARGES, waived.getAmount(), waiveDate,
null, createdDate, appUser); null);
waiver.updateChargesComponents(feeChargesWaived, penaltyChargesWaived, unrecognizedCharge); waiver.updateChargesComponents(feeChargesWaived, penaltyChargesWaived, unrecognizedCharge);
return waiver; return waiver;
} }
public static LoanTransaction writeoff(final Loan loan, final Office office, final LocalDate writeOffDate, final String externalId, public static LoanTransaction writeoff(final Loan loan, final Office office, final LocalDate writeOffDate, final String externalId) {
final LocalDateTime createdDate, final AppUser appUser) { return new LoanTransaction(loan, office, LoanTransactionType.WRITEOFF, null, writeOffDate, externalId);
return new LoanTransaction(loan, office, LoanTransactionType.WRITEOFF, null, writeOffDate, externalId, createdDate, appUser);
} }
private LoanTransaction(final Loan loan, final Office office, final LoanTransactionType type, final BigDecimal amount, private LoanTransaction(final Loan loan, final Office office, final LoanTransactionType type, final BigDecimal amount,
final LocalDate date, final String externalId, final LocalDateTime createdDate, final AppUser appUser) { final LocalDate date, final String externalId) {
this.loan = loan; this.loan = loan;
this.typeOf = type.getValue(); this.typeOf = type.getValue();
this.amount = amount; this.amount = amount;
this.dateOf = Date.from(date.atStartOfDay(ZoneId.systemDefault()).toInstant()); this.dateOf = date;
this.externalId = externalId; this.externalId = externalId;
this.office = office; this.office = office;
this.submittedOnDate = DateUtils.getBusinessDate(); this.submittedOnDate = DateUtils.getBusinessLocalDate();
this.createdDate = Date.from(createdDate.atZone(DateUtils.getDateTimeZoneOfTenant()).toInstant());
this.appUser = appUser;
} }
private LoanTransaction(final Loan loan, final Office office, final LoanTransactionType type, final PaymentDetail paymentDetail, private LoanTransaction(final Loan loan, final Office office, final LoanTransactionType type, final PaymentDetail paymentDetail,
final BigDecimal amount, final LocalDate date, final String externalId, final LocalDateTime createdDate, final BigDecimal amount, final LocalDate date, final String externalId) {
final AppUser appUser) {
this.loan = loan; this.loan = loan;
this.typeOf = type.getValue(); this.typeOf = type.getValue();
this.paymentDetail = paymentDetail; this.paymentDetail = paymentDetail;
this.amount = amount; this.amount = amount;
this.dateOf = Date.from(date.atStartOfDay(ZoneId.systemDefault()).toInstant()); this.dateOf = date;
this.externalId = externalId; this.externalId = externalId;
this.office = office; this.office = office;
this.submittedOnDate = DateUtils.getBusinessDate(); this.submittedOnDate = DateUtils.getBusinessLocalDate();
this.createdDate = Date.from(createdDate.atZone(DateUtils.getDateTimeZoneOfTenant()).toInstant());
this.appUser = appUser;
} }
public void reverse() { public void reverse() {
@@ -504,10 +452,10 @@ public class LoanTransaction extends AbstractPersistableCustom {
} }
public LocalDate getTransactionDate() { public LocalDate getTransactionDate() {
return LocalDate.ofInstant(this.dateOf.toInstant(), DateUtils.getDateTimeZoneOfTenant()); return this.dateOf;
} }
public Date getDateOf() { public LocalDate getDateOf() {
return this.dateOf; return this.dateOf;
} }
@@ -778,14 +726,10 @@ public class LoanTransaction extends AbstractPersistableCustom {
this.manuallyAdjustedOrReversed = true; this.manuallyAdjustedOrReversed = true;
} }
private LocalDate getCreatedDate() {
return (this.createdDate == null) ? LocalDate.now(DateUtils.getDateTimeZoneOfTenant())
: LocalDate.ofInstant(this.createdDate.toInstant(), DateUtils.getDateTimeZoneOfTenant());
}
public LocalDateTime getCreatedDateTime() { public LocalDateTime getCreatedDateTime() {
return (this.createdDate == null) ? LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant()) return (this.getCreatedDate().isPresent()
: LocalDateTime.ofInstant(this.createdDate.toInstant(), DateUtils.getDateTimeZoneOfTenant()); ? LocalDateTime.ofInstant(this.getCreatedDate().get(), DateUtils.getDateTimeZoneOfTenant())
: DateUtils.getLocalDateTimeOfTenant());
} }
public boolean isLastTransaction(final LoanTransaction loanTransaction) { public boolean isLastTransaction(final LoanTransaction loanTransaction) {
@@ -793,7 +737,7 @@ public class LoanTransaction extends AbstractPersistableCustom {
if (loanTransaction != null) { if (loanTransaction != null) {
isLatest = this.getTransactionDate().isBefore(loanTransaction.getTransactionDate()) isLatest = this.getTransactionDate().isBefore(loanTransaction.getTransactionDate())
|| (this.getTransactionDate().isEqual(loanTransaction.getTransactionDate()) || (this.getTransactionDate().isEqual(loanTransaction.getTransactionDate())
&& this.getCreatedDate().isBefore(loanTransaction.getCreatedDate())); && this.getCreatedDateTime().isBefore(loanTransaction.getCreatedDateTime()));
} }
return isLatest; return isLatest;
} }
@@ -801,7 +745,7 @@ public class LoanTransaction extends AbstractPersistableCustom {
public boolean isLatestTransaction(final LoanTransaction loanTransaction) { public boolean isLatestTransaction(final LoanTransaction loanTransaction) {
boolean isLatest = false; boolean isLatest = false;
if (loanTransaction != null) { if (loanTransaction != null) {
isLatest = this.getCreatedDate().isBefore(loanTransaction.getCreatedDate()); isLatest = this.getCreatedDateTime().isBefore(loanTransaction.getCreatedDateTime());
} }
return isLatest; return isLatest;
} }
@@ -845,10 +789,6 @@ public class LoanTransaction extends AbstractPersistableCustom {
return isDisbursement() || isAccrual() || isRepaymentAtDisbursement(); return isDisbursement() || isAccrual() || isRepaymentAtDisbursement();
} }
public void updateCreatedDate(Date createdDate) {
this.createdDate = createdDate;
}
public boolean isAccrualTransaction() { public boolean isAccrualTransaction() {
return isAccrual(); return isAccrual();
} }
@@ -881,14 +881,14 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
currentPeriodParams.minusEarlyPaidAmount(unprocessed); currentPeriodParams.minusEarlyPaidAmount(unprocessed);
updateMapWithAmount(scheduleParams.getPrincipalPortionMap(), unprocessed.negated(), applicableDate); updateMapWithAmount(scheduleParams.getPrincipalPortionMap(), unprocessed.negated(), applicableDate);
LoanTransaction loanTransaction = LoanTransaction.repayment(null, unprocessed, null, LoanTransaction loanTransaction = LoanTransaction.repayment(null, unprocessed, null,
transactionDate, null, DateUtils.getLocalDateTimeOfTenant(), null); transactionDate, null);
RecalculationDetail recalculationDetail = new RecalculationDetail(transactionDate, loanTransaction); RecalculationDetail recalculationDetail = new RecalculationDetail(transactionDate, loanTransaction);
unprocessedTransactions.add(recalculationDetail); unprocessedTransactions.add(recalculationDetail);
break; break;
} }
} }
LoanTransaction loanTransaction = LoanTransaction.repayment(null, unprocessed, null, scheduledDueDate, null, LoanTransaction loanTransaction = LoanTransaction.repayment(null, unprocessed, null, scheduledDueDate,
DateUtils.getLocalDateTimeOfTenant(), null); null);
RecalculationDetail recalculationDetail = new RecalculationDetail(scheduledDueDate, loanTransaction); RecalculationDetail recalculationDetail = new RecalculationDetail(scheduledDueDate, loanTransaction);
unprocessedTransactions.add(recalculationDetail); unprocessedTransactions.add(recalculationDetail);
checkForOutstanding = false; checkForOutstanding = false;
@@ -116,7 +116,6 @@ import org.apache.fineract.portfolio.loanproduct.domain.LoanProductVariableInsta
import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType; import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType;
import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException; import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException;
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations; import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
import org.apache.fineract.useradministration.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -878,8 +877,7 @@ public class LoanScheduleAssembler {
} }
final LocalDate recalculateFrom = null; final LocalDate recalculateFrom = null;
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom); ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
AppUser currentUser = this.context.getAuthenticatedUserIfPresent(); loan.regenerateRepaymentSchedule(scheduleGeneratorDTO);
loan.regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser);
} }
private List<LoanTermVariations> adjustExistingVariations(List<LoanTermVariations> variations, List<LoanTermVariations> newVariations, private List<LoanTermVariations> adjustExistingVariations(List<LoanTermVariations> variations, List<LoanTermVariations> newVariations,
@@ -34,7 +34,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations; import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler; import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService; import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
import org.apache.fineract.useradministration.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -104,8 +103,7 @@ public class LoanScheduleWritePlatformServiceImpl implements LoanScheduleWritePl
loan.getLoanTermVariations().clear(); loan.getLoanTermVariations().clear();
final LocalDate recalculateFrom = null; final LocalDate recalculateFrom = null;
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom); ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
AppUser currentUser = this.context.getAuthenticatedUserIfPresent(); loan.regenerateRepaymentSchedule(scheduleGeneratorDTO);
loan.regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser);
this.loanAccountDomainService.saveLoanWithDataIntegrityViolationChecks(loan); this.loanAccountDomainService.saveLoanWithDataIntegrityViolationChecks(loan);
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withLoanId(loanId) // .withLoanId(loanId) //
@@ -487,7 +487,7 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
final LoanScheduleDTO loanSchedule = loanScheduleGenerator.rescheduleNextInstallments(mathContext, loanApplicationTerms, loan, final LoanScheduleDTO loanSchedule = loanScheduleGenerator.rescheduleNextInstallments(mathContext, loanApplicationTerms, loan,
loanApplicationTerms.getHolidayDetailDTO(), loanRepaymentScheduleTransactionProcessor, rescheduleFromDate); loanApplicationTerms.getHolidayDetailDTO(), loanRepaymentScheduleTransactionProcessor, rescheduleFromDate);
loan.updateLoanSchedule(loanSchedule.getInstallments(), appUser); loan.updateLoanSchedule(loanSchedule.getInstallments());
loan.recalculateAllCharges(); loan.recalculateAllCharges();
ChangedTransactionDetail changedTransactionDetail = loan.processTransactions(); ChangedTransactionDetail changedTransactionDetail = loan.processTransactions();
@@ -499,7 +499,7 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat
final List<Long> existingReversedTransactionIds = new ArrayList<>(); final List<Long> existingReversedTransactionIds = new ArrayList<>();
existingTransactionIds.addAll(loan.findExistingTransactionIds()); existingTransactionIds.addAll(loan.findExistingTransactionIds());
existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds()); existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds());
loan.processIncomeTransactions(this.userRepository.fetchSystemUser()); loan.processIncomeTransactions();
this.loanRepositoryWrapper.saveAndFlush(loan); this.loanRepositoryWrapper.saveAndFlush(loan);
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
} }
@@ -1050,7 +1050,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
final JsonQuery query = JsonQuery.from(command.json(), parsedQuery, this.fromJsonHelper); final JsonQuery query = JsonQuery.from(command.json(), parsedQuery, this.fromJsonHelper);
final LoanScheduleModel loanSchedule = this.calculationPlatformService.calculateLoanSchedule(query, false); final LoanScheduleModel loanSchedule = this.calculationPlatformService.calculateLoanSchedule(query, false);
existingLoanApplication.updateLoanSchedule(loanSchedule, currentUser); existingLoanApplication.updateLoanSchedule(loanSchedule);
existingLoanApplication.recalculateAllCharges(); existingLoanApplication.recalculateAllCharges();
} }
@@ -1449,7 +1449,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|| changes.containsKey("expectedDisbursementDate")) { || changes.containsKey("expectedDisbursementDate")) {
LocalDate recalculateFrom = null; LocalDate recalculateFrom = null;
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom); ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
loan.regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser); loan.regenerateRepaymentSchedule(scheduleGeneratorDTO);
} }
if (loan.isTopup() && loan.getClientId() != null) { if (loan.isTopup() && loan.getClientId() != null) {
@@ -1553,7 +1553,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|| changes.containsKey(LoanApiConstants.disbursementPrincipalParameterName)) { || changes.containsKey(LoanApiConstants.disbursementPrincipalParameterName)) {
LocalDate recalculateFrom = null; LocalDate recalculateFrom = null;
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom); ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
loan.regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser); loan.regenerateRepaymentSchedule(scheduleGeneratorDTO);
} }
loan.adjustNetDisbursalAmount(loan.getProposedPrincipal()); loan.adjustNetDisbursalAmount(loan.getProposedPrincipal());
@@ -503,8 +503,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
final CurrencyData currencyData = applicationCurrency.toData(); final CurrencyData currencyData = applicationCurrency.toData();
final LoanTransaction waiveOfInterest = loan.deriveDefaultInterestWaiverTransaction(DateUtils.getLocalDateTimeOfTenant(), final LoanTransaction waiveOfInterest = loan.deriveDefaultInterestWaiverTransaction();
currentUser);
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.WAIVE_INTEREST); final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.WAIVE_INTEREST);
@@ -448,7 +448,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
existingTransactionIds.addAll(loan.findExistingTransactionIds()); existingTransactionIds.addAll(loan.findExistingTransactionIds());
existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds()); existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds());
LoanTransaction disbursementTransaction = LoanTransaction.disbursement(loan.getOffice(), amountToDisburse, paymentDetail, LoanTransaction disbursementTransaction = LoanTransaction.disbursement(loan.getOffice(), amountToDisburse, paymentDetail,
actualDisbursementDate, txnExternalId, DateUtils.getLocalDateTimeOfTenant(), currentUser); actualDisbursementDate, txnExternalId);
disbursementTransaction.updateLoan(loan); disbursementTransaction.updateLoan(loan);
loan.addLoanTransaction(disbursementTransaction); loan.addLoanTransaction(disbursementTransaction);
} }
@@ -713,7 +713,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
existingTransactionIds.addAll(loan.findExistingTransactionIds()); existingTransactionIds.addAll(loan.findExistingTransactionIds());
existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds()); existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds());
LoanTransaction disbursementTransaction = LoanTransaction.disbursement(loan.getOffice(), disburseAmount, paymentDetail, LoanTransaction disbursementTransaction = LoanTransaction.disbursement(loan.getOffice(), disburseAmount, paymentDetail,
actualDisbursementDate, txnExternalId, DateUtils.getLocalDateTimeOfTenant(), currentUser); actualDisbursementDate, txnExternalId);
disbursementTransaction.updateLoan(loan); disbursementTransaction.updateLoan(loan);
loan.addLoanTransaction(disbursementTransaction); loan.addLoanTransaction(disbursementTransaction);
} }
@@ -826,8 +826,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
// Remove post dated checks if added. // Remove post dated checks if added.
loan.removePostDatedChecks(); loan.removePostDatedChecks();
final Map<String, Object> changes = loan.undoDisbursal(scheduleGeneratorDTO, existingTransactionIds, existingReversedTransactionIds, final Map<String, Object> changes = loan.undoDisbursal(scheduleGeneratorDTO, existingTransactionIds,
currentUser); existingReversedTransactionIds);
if (!changes.isEmpty()) { if (!changes.isEmpty()) {
if (loan.isTopup() && loan.getClientId() != null) { if (loan.isTopup() && loan.getClientId() != null) {
@@ -1050,7 +1050,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Money transactionAmountAsMoney = Money.of(loan.getCurrency(), transactionAmount); final Money transactionAmountAsMoney = Money.of(loan.getCurrency(), transactionAmount);
final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createPaymentDetail(command, changes); final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createPaymentDetail(command, changes);
LoanTransaction newTransactionDetail = LoanTransaction.repayment(loan.getOffice(), transactionAmountAsMoney, paymentDetail, LoanTransaction newTransactionDetail = LoanTransaction.repayment(loan.getOffice(), transactionAmountAsMoney, paymentDetail,
transactionDate, txnExternalId, DateUtils.getLocalDateTimeOfTenant(), currentUser); transactionDate, txnExternalId);
if (transactionToAdjust.isInterestWaiver()) { if (transactionToAdjust.isInterestWaiver()) {
Money unrecognizedIncome = transactionAmountAsMoney.zero(); Money unrecognizedIncome = transactionAmountAsMoney.zero();
Money interestComponent = transactionAmountAsMoney; Money interestComponent = transactionAmountAsMoney;
@@ -1062,7 +1062,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
} }
newTransactionDetail = LoanTransaction.waiver(loan.getOffice(), loan, transactionAmountAsMoney, transactionDate, newTransactionDetail = LoanTransaction.waiver(loan.getOffice(), loan, transactionAmountAsMoney, transactionDate,
interestComponent, unrecognizedIncome, DateUtils.getLocalDateTimeOfTenant(), currentUser); interestComponent, unrecognizedIncome);
} }
LocalDate recalculateFrom = null; LocalDate recalculateFrom = null;
@@ -1076,7 +1076,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final ChangedTransactionDetail changedTransactionDetail = loan.adjustExistingTransaction(newTransactionDetail, final ChangedTransactionDetail changedTransactionDetail = loan.adjustExistingTransaction(newTransactionDetail,
defaultLoanLifecycleStateMachine(), transactionToAdjust, existingTransactionIds, existingReversedTransactionIds, defaultLoanLifecycleStateMachine(), transactionToAdjust, existingTransactionIds, existingReversedTransactionIds,
scheduleGeneratorDTO, currentUser); scheduleGeneratorDTO);
if (newTransactionDetail.isGreaterThanZero(loan.getPrincpal().getCurrency())) { if (newTransactionDetail.isGreaterThanZero(loan.getPrincpal().getCurrency())) {
if (paymentDetail != null) { if (paymentDetail != null) {
@@ -1176,7 +1176,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
} }
final LoanTransaction waiveInterestTransaction = LoanTransaction.waiver(loan.getOffice(), loan, transactionAmountAsMoney, final LoanTransaction waiveInterestTransaction = LoanTransaction.waiver(loan.getOffice(), loan, transactionAmountAsMoney,
transactionDate, interestComponent, unrecognizedIncome, DateUtils.getLocalDateTimeOfTenant(), currentUser); transactionDate, interestComponent, unrecognizedIncome);
businessEventNotifierService.notifyPreBusinessEvent(new LoanWaiveInterestBusinessEvent(waiveInterestTransaction)); businessEventNotifierService.notifyPreBusinessEvent(new LoanWaiveInterestBusinessEvent(waiveInterestTransaction));
LocalDate recalculateFrom = null; LocalDate recalculateFrom = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
@@ -1185,8 +1185,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom); ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
final ChangedTransactionDetail changedTransactionDetail = loan.waiveInterest(waiveInterestTransaction, final ChangedTransactionDetail changedTransactionDetail = loan.waiveInterest(waiveInterestTransaction,
defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO, defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO);
currentUser);
this.loanTransactionRepository.saveAndFlush(waiveInterestTransaction); this.loanTransactionRepository.saveAndFlush(waiveInterestTransaction);
@@ -1312,7 +1311,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom); ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
ChangedTransactionDetail changedTransactionDetail = loan.close(command, defaultLoanLifecycleStateMachine(), changes, ChangedTransactionDetail changedTransactionDetail = loan.close(command, defaultLoanLifecycleStateMachine(), changes,
existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO, currentUser); existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO);
final LoanTransaction possibleClosingTransaction = changedTransactionDetail.getNewTransactionMappings().remove(0L); final LoanTransaction possibleClosingTransaction = changedTransactionDetail.getNewTransactionMappings().remove(0L);
if (possibleClosingTransaction != null) { if (possibleClosingTransaction != null) {
this.loanTransactionRepository.saveAndFlush(possibleClosingTransaction); this.loanTransactionRepository.saveAndFlush(possibleClosingTransaction);
@@ -1561,11 +1560,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
public void runScheduleRecalculation(final Loan loan, final LocalDate recalculateFrom) { public void runScheduleRecalculation(final Loan loan, final LocalDate recalculateFrom) {
AppUser currentUser = getAppUserIfPresent();
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
ScheduleGeneratorDTO generatorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom); ScheduleGeneratorDTO generatorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
ChangedTransactionDetail changedTransactionDetail = loan ChangedTransactionDetail changedTransactionDetail = loan
.handleRegenerateRepaymentScheduleWithInterestRecalculation(generatorDTO, currentUser); .handleRegenerateRepaymentScheduleWithInterestRecalculation(generatorDTO);
saveLoanWithDataIntegrityViolationChecks(loan); saveLoanWithDataIntegrityViolationChecks(loan);
if (changedTransactionDetail != null) { if (changedTransactionDetail != null) {
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
@@ -1615,7 +1613,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
* the loan product uses Upfront Accruals * the loan product uses Upfront Accruals
**/ **/
if (loan.status().isActive() && loan.isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) { if (loan.status().isActive() && loan.isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) {
final LoanTransaction applyLoanChargeTransaction = loan.handleChargeAppliedTransaction(loanCharge, null, currentUser); final LoanTransaction applyLoanChargeTransaction = loan.handleChargeAppliedTransaction(loanCharge, null);
this.loanTransactionRepository.saveAndFlush(applyLoanChargeTransaction); this.loanTransactionRepository.saveAndFlush(applyLoanChargeTransaction);
} }
boolean isAppliedOnBackDate = false; boolean isAppliedOnBackDate = false;
@@ -1868,8 +1866,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
final LoanTransaction waiveTransaction = loan.waiveLoanCharge(loanCharge, defaultLoanLifecycleStateMachine(), changes, final LoanTransaction waiveTransaction = loan.waiveLoanCharge(loanCharge, defaultLoanLifecycleStateMachine(), changes,
existingTransactionIds, existingReversedTransactionIds, loanInstallmentNumber, scheduleGeneratorDTO, accruedCharge, existingTransactionIds, existingReversedTransactionIds, loanInstallmentNumber, scheduleGeneratorDTO, accruedCharge);
currentUser);
this.loanTransactionRepository.saveAndFlush(waiveTransaction); this.loanTransactionRepository.saveAndFlush(waiveTransaction);
saveLoanWithDataIntegrityViolationChecks(loan); saveLoanWithDataIntegrityViolationChecks(loan);
@@ -2113,7 +2110,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
@Override @Override
public LoanTransaction initiateLoanTransfer(final Loan loan, final LocalDate transferDate) { public LoanTransaction initiateLoanTransfer(final Loan loan, final LocalDate transferDate) {
AppUser currentUser = getAppUserIfPresent();
this.loanAssembler.setHelpers(loan); this.loanAssembler.setHelpers(loan);
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
validateTransactionsForTransfer(loan, transferDate); validateTransactionsForTransfer(loan, transferDate);
@@ -2123,8 +2119,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds()); final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds());
final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds()); final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds());
final LoanTransaction newTransferTransaction = LoanTransaction.initiateTransfer(loan.getOffice(), loan, transferDate, final LoanTransaction newTransferTransaction = LoanTransaction.initiateTransfer(loan.getOffice(), loan, transferDate);
DateUtils.getLocalDateTimeOfTenant(), currentUser);
loan.addLoanTransaction(newTransferTransaction); loan.addLoanTransaction(newTransferTransaction);
loan.setLoanStatus(LoanStatus.TRANSFER_IN_PROGRESS.getValue()); loan.setLoanStatus(LoanStatus.TRANSFER_IN_PROGRESS.getValue());
@@ -2140,14 +2135,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
@Override @Override
public LoanTransaction acceptLoanTransfer(final Loan loan, final LocalDate transferDate, final Office acceptedInOffice, public LoanTransaction acceptLoanTransfer(final Loan loan, final LocalDate transferDate, final Office acceptedInOffice,
final Staff loanOfficer) { final Staff loanOfficer) {
AppUser currentUser = getAppUserIfPresent();
this.loanAssembler.setHelpers(loan); this.loanAssembler.setHelpers(loan);
businessEventNotifierService.notifyPreBusinessEvent(new LoanAcceptTransferBusinessEvent(loan)); businessEventNotifierService.notifyPreBusinessEvent(new LoanAcceptTransferBusinessEvent(loan));
final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds()); final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds());
final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds()); final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds());
final LoanTransaction newTransferAcceptanceTransaction = LoanTransaction.approveTransfer(acceptedInOffice, loan, transferDate, final LoanTransaction newTransferAcceptanceTransaction = LoanTransaction.approveTransfer(acceptedInOffice, loan, transferDate);
DateUtils.getLocalDateTimeOfTenant(), currentUser);
loan.addLoanTransaction(newTransferAcceptanceTransaction); loan.addLoanTransaction(newTransferAcceptanceTransaction);
if (loan.getTotalOverpaid() != null) { if (loan.getTotalOverpaid() != null) {
loan.setLoanStatus(LoanStatus.OVERPAID.getValue()); loan.setLoanStatus(LoanStatus.OVERPAID.getValue());
@@ -2170,15 +2163,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
@Transactional @Transactional
@Override @Override
public LoanTransaction withdrawLoanTransfer(final Loan loan, final LocalDate transferDate) { public LoanTransaction withdrawLoanTransfer(final Loan loan, final LocalDate transferDate) {
AppUser currentUser = getAppUserIfPresent();
this.loanAssembler.setHelpers(loan); this.loanAssembler.setHelpers(loan);
businessEventNotifierService.notifyPreBusinessEvent(new LoanWithdrawTransferBusinessEvent(loan)); businessEventNotifierService.notifyPreBusinessEvent(new LoanWithdrawTransferBusinessEvent(loan));
final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds()); final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds());
final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds()); final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds());
final LoanTransaction newTransferAcceptanceTransaction = LoanTransaction.withdrawTransfer(loan.getOffice(), loan, transferDate, final LoanTransaction newTransferAcceptanceTransaction = LoanTransaction.withdrawTransfer(loan.getOffice(), loan, transferDate);
DateUtils.getLocalDateTimeOfTenant(), currentUser);
loan.addLoanTransaction(newTransferAcceptanceTransaction); loan.addLoanTransaction(newTransferAcceptanceTransaction);
loan.setLoanStatus(LoanStatus.ACTIVE.getValue()); loan.setLoanStatus(LoanStatus.ACTIVE.getValue());
@@ -2375,7 +2366,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom); ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
loan.setHelpers(null, this.loanSummaryWrapper, this.transactionProcessingStrategy); loan.setHelpers(null, this.loanSummaryWrapper, this.transactionProcessingStrategy);
loan.recalculateScheduleFromLastTransaction(scheduleGeneratorDTO, existingTransactionIds, loan.recalculateScheduleFromLastTransaction(scheduleGeneratorDTO, existingTransactionIds,
existingReversedTransactionIds, currentUser); existingReversedTransactionIds);
createAndSaveLoanScheduleArchive(loan, scheduleGeneratorDTO); createAndSaveLoanScheduleArchive(loan, scheduleGeneratorDTO);
} else if (reschedulebasedOnMeetingDates != null && reschedulebasedOnMeetingDates) { } else if (reschedulebasedOnMeetingDates != null && reschedulebasedOnMeetingDates) {
loan.updateLoanRepaymentScheduleDates(calendar.getStartDateLocalDate(), calendar.getRecurrence(), isHolidayEnabled, loan.updateLoanRepaymentScheduleDates(calendar.getStartDateLocalDate(), calendar.getRecurrence(), isHolidayEnabled,
@@ -2786,7 +2777,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom); ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
ChangedTransactionDetail changedTransactionDetail = loan.undoWrittenOff(existingTransactionIds, existingReversedTransactionIds, ChangedTransactionDetail changedTransactionDetail = loan.undoWrittenOff(existingTransactionIds, existingReversedTransactionIds,
scheduleGeneratorDTO, currentUser); scheduleGeneratorDTO);
if (changedTransactionDetail != null) { if (changedTransactionDetail != null) {
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
this.loanTransactionRepository.save(mapEntry.getValue()); this.loanTransactionRepository.save(mapEntry.getValue());
@@ -2914,7 +2905,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
if (command.entityId() != null) { if (command.entityId() != null) {
changedTransactionDetail = loan.updateDisbursementDateAndAmountForTranche(loanDisbursementDetails, command, changes, changedTransactionDetail = loan.updateDisbursementDateAndAmountForTranche(loanDisbursementDetails, command, changes,
scheduleGeneratorDTO, currentUser); scheduleGeneratorDTO);
} else { } else {
// BigDecimal setAmount = loan.getApprovedPrincipal(); // BigDecimal setAmount = loan.getApprovedPrincipal();
Collection<LoanDisbursementDetails> loanDisburseDetails = loan.getDisbursementDetails(); Collection<LoanDisbursementDetails> loanDisburseDetails = loan.getDisbursementDetails();
@@ -2928,9 +2919,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.repaymentScheduleDetail().setPrincipal(setAmount); loan.repaymentScheduleDetail().setPrincipal(setAmount);
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
loan.regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser); loan.regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
} else { } else {
loan.regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser); loan.regenerateRepaymentSchedule(scheduleGeneratorDTO);
loan.processPostDisbursementTransactions(); loan.processPostDisbursementTransactions();
} }
} }
@@ -3049,7 +3040,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
public void recalculateInterest(final long loanId) { public void recalculateInterest(final long loanId) {
Loan loan = this.loanAssembler.assembleFrom(loanId); Loan loan = this.loanAssembler.assembleFrom(loanId);
LocalDate recalculateFrom = loan.fetchInterestRecalculateFromDate(); LocalDate recalculateFrom = loan.fetchInterestRecalculateFromDate();
AppUser currentUser = getAppUserIfPresent();
businessEventNotifierService.notifyPreBusinessEvent(new LoanInterestRecalculationBusinessEvent(loan)); businessEventNotifierService.notifyPreBusinessEvent(new LoanInterestRecalculationBusinessEvent(loan));
final List<Long> existingTransactionIds = new ArrayList<>(); final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>(); final List<Long> existingReversedTransactionIds = new ArrayList<>();
@@ -3057,7 +3047,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
ScheduleGeneratorDTO generatorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom); ScheduleGeneratorDTO generatorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
ChangedTransactionDetail changedTransactionDetail = loan.recalculateScheduleFromLastTransaction(generatorDTO, ChangedTransactionDetail changedTransactionDetail = loan.recalculateScheduleFromLastTransaction(generatorDTO,
existingTransactionIds, existingReversedTransactionIds, currentUser); existingTransactionIds, existingReversedTransactionIds);
saveLoanWithDataIntegrityViolationChecks(loan); saveLoanWithDataIntegrityViolationChecks(loan);
@@ -3101,7 +3091,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
AppUser currentUser = getAppUserIfPresent(); AppUser currentUser = getAppUserIfPresent();
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate"); final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
BigDecimal emiAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.emiAmountParameterName); BigDecimal emiAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.emiAmountParameterName);
loan.regenerateScheduleOnDisbursement(scheduleGeneratorDTO, recalculateSchedule, actualDisbursementDate, emiAmount, currentUser, loan.regenerateScheduleOnDisbursement(scheduleGeneratorDTO, recalculateSchedule, actualDisbursementDate, emiAmount,
nextPossibleRepaymentDate, rescheduledRepaymentDate); nextPossibleRepaymentDate, rescheduledRepaymentDate);
} }
@@ -3213,7 +3203,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
@Override @Override
@Transactional @Transactional
public CommandProcessingResult undoLastLoanDisbursal(Long loanId, JsonCommand command) { public CommandProcessingResult undoLastLoanDisbursal(Long loanId, JsonCommand command) {
final AppUser currentUser = getAppUserIfPresent();
final Loan loan = this.loanAssembler.assembleFrom(loanId); final Loan loan = this.loanAssembler.assembleFrom(loanId);
final LocalDate recalculateFromDate = loan.getLastRepaymentDate(); final LocalDate recalculateFromDate = loan.getLastRepaymentDate();
@@ -3229,7 +3218,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFromDate); ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFromDate);
final Map<String, Object> changes = loan.undoLastDisbursal(scheduleGeneratorDTO, existingTransactionIds, final Map<String, Object> changes = loan.undoLastDisbursal(scheduleGeneratorDTO, existingTransactionIds,
existingReversedTransactionIds, currentUser, loan); existingReversedTransactionIds, loan);
if (!changes.isEmpty()) { if (!changes.isEmpty()) {
saveAndFlushLoanWithDataIntegrityViolationChecks(loan); saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
String noteText = null; String noteText = null;
@@ -38,4 +38,5 @@
<include file="parts/0016_changed_unique_constraint_of_ref_no.xml" relativeToChangelogFile="true"/> <include file="parts/0016_changed_unique_constraint_of_ref_no.xml" relativeToChangelogFile="true"/>
<include file="parts/0017_fix_stretchy_reports.xml" relativeToChangelogFile="true"/> <include file="parts/0017_fix_stretchy_reports.xml" relativeToChangelogFile="true"/>
<include file="parts/0018_pentaho_reports_to_table.xml" relativeToChangelogFile="true"/> <include file="parts/0018_pentaho_reports_to_table.xml" relativeToChangelogFile="true"/>
<include file="parts/0019_refactor_loan_transaction.xml" relativeToChangelogFile="true"/>
</databaseChangeLog> </databaseChangeLog>
@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<changeSet author="fineract" id="1">
<addColumn tableName="m_loan_transaction">
<column name="createdby_id" type="BIGINT" valueComputed="appuser_id"/>
<column name="lastmodifiedby_id" type="BIGINT" valueComputed="appuser_id"/>
<column name="lastmodified_date" type="DATETIME" valueComputed="created_date"/>
</addColumn>
</changeSet>
<changeSet id="2" author="fineract">
<dropColumn tableName="m_loan_transaction">
<column name="appuser_id"/>
</dropColumn>
</changeSet>
<changeSet author="fineract" id="3" context="postgresql">
<update tableName="stretchy_report">
<column name="report_sql" value="SELECT mc.id AS id, mc.firstname AS firstName, mc.middlename AS middleName, mc.lastname AS lastName, mc.display_name AS fullName, mc.mobile_no AS mobileNo, ml.principal_amount AS loanAmount, (COALESCE(ml.principal_outstanding_derived, 0) + COALESCE(ml.interest_outstanding_derived, 0) + COALESCE(ml.fee_charges_outstanding_derived, 0) + COALESCE(ml.penalty_charges_outstanding_derived, 0)) AS loanOutstanding, ounder.id AS officeNumber, ml.account_no AS loanAccountNumber, SUM(lt.amount) AS repaymentAmount FROM m_office mo JOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(mo.hierarchy, '%') INNER JOIN m_client mc ON mc.office_id = ounder.id INNER JOIN m_loan ml ON ml.client_id = mc.id INNER JOIN r_enum_value rev ON rev.enum_id = ml.loan_status_id AND rev.enum_name = 'loan_status_id' INNER JOIN m_loan_transaction lt ON lt.loan_id = ml.id INNER JOIN m_appuser au ON au.id = lt.createdby_id LEFT JOIN m_loan_arrears_aging laa ON laa.loan_id = ml.id LEFT JOIN m_payment_detail mpd ON mpd.id = lt.payment_detail_id LEFT JOIN m_currency cur ON cur.code = ml.currency_code LEFT JOIN m_group_client gc ON gc.client_id = mc.id LEFT JOIN m_group g ON g.id = gc.group_id LEFT JOIN m_staff lo ON lo.id = ml.loan_officer_id LEFT JOIN m_guarantor gua ON gua.loan_id = ml.id WHERE ml.loan_status_id = 300 AND mo.id = '${officeId}' AND (COALESCE(ml.loan_officer_id, -10) = ${loanOfficerId} OR '-1' = ${loanOfficerId}) AND (EXTRACT(DAY FROM (CURRENT_DATE - lt.transaction_date::TIMESTAMP)) BETWEEN 9${fromX} AND ${toY}) AND lt.is_reversed = false AND lt.transaction_type_enum = 2 AND laa.loan_id IS NULL GROUP BY ml.id, mc.id, ounder.id ORDER BY ounder.hierarchy, ml.currency_code, mc.account_no, ml.account_no"/>
<where>id='174' AND report_name='Loan payments received (Active Loans)'</where>
</update>
<update tableName="stretchy_report">
<column name="report_sql" value="SELECT ml.id AS loanId, mc.id AS id, mc.firstname AS firstName, mc.middlename AS middleName, mc.lastname AS lastName, mc.display_name AS fullName, mc.mobile_no AS mobileNo, ml.principal_amount AS loanAmount, (COALESCE(ml.principal_outstanding_derived, 0) + COALESCE(ml.interest_outstanding_derived, 0) + COALESCE(ml.fee_charges_outstanding_derived, 0) + COALESCE(ml.penalty_charges_outstanding_derived, 0)) AS loanOutstanding, ounder.id AS officeNumber, ml.account_no AS loanAccountNumber, SUM(lt.amount) AS repaymentAmount FROM m_office mo JOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(mo.hierarchy, '%') INNER JOIN m_client mc ON mc.office_id = ounder.id INNER JOIN m_loan ml ON ml.client_id = mc.id INNER JOIN r_enum_value rev ON rev.enum_id = ml.loan_status_id AND rev.enum_name = 'loan_status_id' INNER JOIN m_loan_arrears_aging laa ON laa.loan_id = ml.id INNER JOIN m_loan_transaction lt ON lt.loan_id = ml.id INNER JOIN m_appuser au ON au.id = lt.createdby_id LEFT JOIN m_payment_detail mpd ON mpd.id = lt.payment_detail_id LEFT JOIN m_currency cur ON cur.code = ml.currency_code LEFT JOIN m_group_client gc ON gc.client_id = mc.id LEFT JOIN m_group g ON g.id = gc.group_id LEFT JOIN m_staff lo ON lo.id = ml.loan_officer_id LEFT JOIN m_guarantor gua ON gua.loan_id = ml.id WHERE ml.loan_status_id = 300 AND mo.id = '${officeId}' AND (COALESCE(ml.loan_officer_id, -10) = ${loanOfficerId} OR '-1' = ${loanOfficerId}) AND (EXTRACT(DAY FROM(CURRENT_DATE - lt.transaction_date::TIMESTAMP)) BETWEEN ${fromX} AND ${toY}) AND (EXTRACT(DAY FROM(CURRENT_DATE - laa.overdue_since_date_derived::TIMESTAMP)) BETWEEN ${overdueX} AND ${overdueY}) AND lt.is_reversed = false AND lt.transaction_type_enum = 2 GROUP BY ml.id, mc.id, ounder.id ORDER BY ounder.hierarchy, ml.currency_code, mc.account_no, ml.account_no"/>
<where>id='175' AND report_name='Loan payments received (Overdue Loans)'</where>
</update>
</changeSet>
<changeSet author="fineract" id="4" context="mysql">
<update tableName="stretchy_report">
<column name="report_sql"
value="SELECT &#13;&#10;mc.id AS &quot;id&quot;, &#13;&#10;mc.firstname AS &quot;firstName&quot;,&#13;&#10;mc.middlename AS &quot;middleName&quot;,&#13;&#10;mc.lastname AS &quot;lastName&quot;,&#13;&#10;mc.display_name AS &quot;fullName&quot;,&#13;&#10;mc.mobile_no AS &quot;mobileNo&quot;, &#13;&#10;ml.principal_amount AS &quot;loanAmount&quot;, &#13;&#10;(IFNULL(ml.principal_outstanding_derived, 0) + IFNULL(ml.interest_outstanding_derived, 0) + IFNULL(ml.fee_charges_outstanding_derived, 0) + IFNULL(ml.penalty_charges_outstanding_derived, 0)) AS &quot;loanOutstanding&quot;,&#13;&#10;ounder.id AS &quot;officeNumber&quot;, &#13;&#10;ml.account_no AS &quot;loanAccountNumber&quot;,&#13;&#10;SUM(lt.amount) AS &quot;repaymentAmount&quot;&#13;&#10;FROM m_office mo&#13;&#10;JOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(mo.hierarchy, '%')&#13;&#10;INNER JOIN m_client mc ON mc.office_id=ounder.id&#13;&#10;INNER JOIN m_loan ml ON ml.client_id = mc.id&#13;&#10;INNER JOIN r_enum_value rev ON rev.enum_id=ml.loan_status_id AND rev.enum_name = 'loan_status_id'&#13;&#10;INNER JOIN m_loan_transaction lt ON lt.loan_id = ml.id&#13;&#10;INNER JOIN m_appuser au ON au.id = lt.createdby_id&#13;&#10;LEFT JOIN m_loan_arrears_aging laa ON laa.loan_id=ml.id&#13;&#10;LEFT JOIN m_payment_detail mpd ON mpd.id=lt.payment_detail_id&#13;&#10;LEFT JOIN m_currency cur ON cur.code = ml.currency_code&#13;&#10;LEFT JOIN m_group_client gc ON gc.client_id = mc.id&#13;&#10;LEFT JOIN m_group g ON g.id = gc.group_id&#13;&#10;LEFT JOIN m_staff lo ON lo.id = ml.loan_officer_id&#13;&#10;LEFT JOIN m_guarantor gua ON gua.loan_id = ml.id&#13;&#10;WHERE ml.loan_status_id=300 AND mo.id=${officeId} AND (IFNULL(ml.loan_officer_id, -10) = ${loanOfficerId} OR &quot;-1&quot; = ${loanOfficerId}) AND (DATEDIFF(CURDATE(), lt.transaction_date) BETWEEN ${fromX} AND ${toY}) AND lt.is_reversed=0 AND lt.transaction_type_enum=2 AND laa.loan_id IS NULL&#13;&#10;GROUP BY ml.id&#13;&#10;ORDER BY ounder.hierarchy, ml.currency_code, mc.account_no, ml.account_no"/>
<where>id='174' AND report_name='Loan payments received (Active Loans)'</where>
</update>
<update tableName="stretchy_report">
<column name="report_sql"
value="SELECT &#13;&#10;ml.id AS &quot;loanId&quot;, &#13;&#10;mc.id AS &quot;id&quot;, &#13;&#10;mc.firstname AS &quot;firstName&quot;,&#13;&#10;mc.middlename AS &quot;middleName&quot;,&#13;&#10;mc.lastname AS &quot;lastName&quot;,&#13;&#10;mc.display_name AS &quot;fullName&quot;,&#13;&#10;mc.mobile_no AS &quot;mobileNo&quot;, &#13;&#10;ml.principal_amount AS &quot;loanAmount&quot;, &#13;&#10;(IFNULL(ml.principal_outstanding_derived, 0) + IFNULL(ml.interest_outstanding_derived, 0) + IFNULL(ml.fee_charges_outstanding_derived, 0) + IFNULL(ml.penalty_charges_outstanding_derived, 0)) AS &quot;loanOutstanding&quot;,&#13;&#10;ounder.id AS &quot;officeNumber&quot;, &#13;&#10;ml.account_no AS &quot;loanAccountNumber&quot;,&#13;&#10;SUM(lt.amount) AS &quot;repaymentAmount&quot;&#13;&#10;FROM m_office mo&#13;&#10;JOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(mo.hierarchy, '%')&#13;&#10;INNER JOIN m_client mc ON mc.office_id=ounder.id&#13;&#10;INNER JOIN m_loan ml ON ml.client_id = mc.id&#13;&#10;INNER JOIN r_enum_value rev ON rev.enum_id=ml.loan_status_id AND rev.enum_name = 'loan_status_id'&#13;&#10;INNER JOIN m_loan_arrears_aging laa ON laa.loan_id=ml.id&#13;&#10;INNER JOIN m_loan_transaction lt ON lt.loan_id = ml.id&#13;&#10;INNER JOIN m_appuser au ON au.id = lt.createdby_id&#13;&#10;LEFT JOIN m_payment_detail mpd ON mpd.id=lt.payment_detail_id&#13;&#10;LEFT JOIN m_currency cur ON cur.code = ml.currency_code&#13;&#10;LEFT JOIN m_group_client gc ON gc.client_id = mc.id&#13;&#10;LEFT JOIN m_group g ON g.id = gc.group_id&#13;&#10;LEFT JOIN m_staff lo ON lo.id = ml.loan_officer_id&#13;&#10;LEFT JOIN m_guarantor gua ON gua.loan_id = ml.id&#13;&#10;WHERE ml.loan_status_id=300 AND mo.id=${officeId} AND (IFNULL(ml.loan_officer_id, -10) = ${loanOfficerId} OR &quot;-1&quot; = ${loanOfficerId}) AND (DATEDIFF(CURDATE(), lt.transaction_date) BETWEEN ${fromX} AND ${toY}) AND (DATEDIFF(CURDATE(), laa.overdue_since_date_derived) BETWEEN ${overdueX} AND ${overdueY}) AND lt.is_reversed=0 AND lt.transaction_type_enum=2&#13;&#10;GROUP BY ml.id&#13;&#10;ORDER BY ounder.hierarchy, ml.currency_code, mc.account_no, ml.account_no"/>
<where>id='175' AND report_name='Loan payments received (Overdue Loans)'</where>
</update>
</changeSet>
</databaseChangeLog>