From c11f3af1801b9f4b3d231bbaada5f95b5385311b Mon Sep 17 00:00:00 2001 From: Binny G Sreevas Date: Thu, 20 Nov 2014 02:03:21 +0530 Subject: [PATCH] MIFOSX-1628 - Added User ID and Datetime to Loan and Savings Transactions --- .../core/service/DateUtils.java | 18 + ...ountTransfersWritePlatformServiceImpl.java | 15 +- .../portfolio/loanaccount/domain/Loan.java | 128 +- .../domain/LoanAccountDomainService.java | 11 +- .../domain/LoanAccountDomainServiceJpa.java | 25 +- .../loanaccount/domain/LoanTransaction.java | 93 +- ...cheduleCalculationPlatformServiceImpl.java | 4 +- ...WritePlatformServiceJpaRepositoryImpl.java | 10 +- .../service/LoanReadPlatformServiceImpl.java | 5 +- ...WritePlatformServiceJpaRepositoryImpl.java | 102 +- .../data/SavingsAccountTransactionDTO.java | 13 +- .../domain/DepositAccountAssembler.java | 12 +- .../DepositAccountDomainServiceJpa.java | 2 +- .../savings/domain/FixedDepositAccount.java | 2 +- .../domain/RecurringDepositAccount.java | 2506 ++++++++++------- .../savings/domain/SavingsAccount.java | 47 +- .../SavingsAccountDomainServiceJpa.java | 19 +- .../domain/SavingsAccountTransaction.java | 60 +- ...WritePlatformServiceJpaRepositoryImpl.java | 46 +- ...WritePlatformServiceJpaRepositoryImpl.java | 68 +- 20 files changed, 1899 insertions(+), 1287 deletions(-) diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/service/DateUtils.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/service/DateUtils.java index ca7a9801c..86117c0b6 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/service/DateUtils.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/service/DateUtils.java @@ -14,6 +14,7 @@ import java.util.List; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.LocalDate; +import org.joda.time.LocalDateTime; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import org.mifosplatform.infrastructure.core.data.ApiParameterError; @@ -41,6 +42,23 @@ public class DateUtils { return today; } + + public static LocalDateTime getLocalDateTimeOfTenant() { + + LocalDateTime today = new LocalDateTime(); + + final MifosPlatformTenant tenant = ThreadLocalContextUtil.getTenant(); + + if (tenant != null) { + final DateTimeZone zone = DateTimeZone.forID(tenant.getTimezoneId()); + if (zone != null) { + today = new LocalDateTime(zone); + } + } + + return today; + } + public static LocalDate parseLocalDate(final String stringDate, final String pattern) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java index 17010488f..e46d199f7 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java @@ -142,7 +142,7 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer final boolean isRecoveryRepayment = false; final LoanTransaction loanRepaymentTransaction = this.loanAccountDomainService.makeRepayment(toLoanAccount, new CommandProcessingResultBuilder(), transactionDate, transactionAmount, paymentDetail, null, null, - isRecoveryRepayment, isAccountTransfer); + isRecoveryRepayment, isAccountTransfer, null); // FIXME: should not be sending null for currentUser for account transfers final AccountTransferDetails accountTransferDetails = this.accountTransferAssembler.assembleSavingsToLoanTransfer(command, fromSavingsAccount, toLoanAccount, withdrawal, loanRepaymentTransaction); @@ -157,7 +157,8 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer final Loan fromLoanAccount = this.loanAccountAssembler.assembleFrom(fromLoanAccountId); final LoanTransaction loanRefundTransaction = this.loanAccountDomainService.makeRefund(fromLoanAccountId, - new CommandProcessingResultBuilder(), transactionDate, transactionAmount, paymentDetail, null, null); + new CommandProcessingResultBuilder(), transactionDate, transactionAmount, paymentDetail, null, null, + null); // FIXME: should not be sending null for currentUser for account transfers final Long toSavingsAccountId = command.longValueOfParameterNamed(toAccountIdParamName); final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsAccountId); @@ -271,13 +272,14 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer loanTransaction = this.loanAccountDomainService.makeChargePayment(toLoanAccount, accountTransferDTO.getChargeId(), accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(), accountTransferDTO.getPaymentDetail(), null, null, accountTransferDTO.getToTransferType(), - accountTransferDTO.getLoanInstallmentNumber()); + accountTransferDTO.getLoanInstallmentNumber(), null); // FIXME: should not be sending null for currentUser for account transfers } else { final boolean isRecoveryRepayment = false; loanTransaction = this.loanAccountDomainService.makeRepayment(toLoanAccount, new CommandProcessingResultBuilder(), accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(), - accountTransferDTO.getPaymentDetail(), null, null, isRecoveryRepayment, isAccountTransfer); + accountTransferDTO.getPaymentDetail(), null, null, isRecoveryRepayment, isAccountTransfer, + null); // FIXME: should not be sending null for currentUser for account transfers } accountTransferDetails = this.accountTransferAssembler.assembleSavingsToLoanTransfer(accountTransferDTO, fromSavingsAccount, @@ -344,12 +346,13 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer if (LoanTransactionType.DISBURSEMENT.getValue().equals(accountTransferDTO.getFromTransferType())) { loanTransaction = this.loanAccountDomainService.makeDisburseTransaction(accountTransferDTO.getFromAccountId(), accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(), - accountTransferDTO.getPaymentDetail(), accountTransferDTO.getNoteText(), accountTransferDTO.getTxnExternalId()); + accountTransferDTO.getPaymentDetail(), accountTransferDTO.getNoteText(), accountTransferDTO.getTxnExternalId(), + null); // FIXME: should not be sending null for currentUser for account transfers } else { loanTransaction = this.loanAccountDomainService.makeRefund(accountTransferDTO.getFromAccountId(), new CommandProcessingResultBuilder(), accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(), accountTransferDTO.getPaymentDetail(), accountTransferDTO.getNoteText(), - accountTransferDTO.getTxnExternalId()); + accountTransferDTO.getTxnExternalId(), null); // FIXME: should not be sending null for currentUser for account transfers } final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(toSavingsAccount, diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java index 9043893de..d09b9c221 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java @@ -41,6 +41,7 @@ import org.apache.commons.lang.StringUtils; import org.hibernate.annotations.LazyCollection; import org.hibernate.annotations.LazyCollectionOption; import org.joda.time.LocalDate; +import org.joda.time.LocalDateTime; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import org.mifosplatform.infrastructure.codes.domain.CodeValue; @@ -584,7 +585,8 @@ public class Loan extends AbstractPersistable { * @param suppliedTransactionDate * @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()); Money feeCharges = chargeAmount; Money penaltyCharges = Money.zero(loanCurrency()); @@ -609,7 +611,7 @@ public class Loan extends AbstractPersistable { } final LoanTransaction applyLoanChargeTransaction = LoanTransaction.accrueLoanCharge(this, getOffice(), chargeAmount, - transactionDate, feeCharges, penaltyCharges); + transactionDate, feeCharges, penaltyCharges, DateUtils.getLocalDateTimeOfTenant(), currentUser); Integer installmentNumber = null; final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(applyLoanChargeTransaction, loanCharge, loanCharge.getAmount( getCurrency()).getAmount(), installmentNumber); @@ -864,7 +866,8 @@ public class Loan extends AbstractPersistable { public LoanTransaction waiveLoanCharge(final LoanCharge loanCharge, final LoanLifecycleStateMachine loanLifecycleStateMachine, final Map changes, final List existingTransactionIds, final List existingReversedTransactionIds, - final Integer loanInstallmentNumber, final ScheduleGeneratorDTO scheduleGeneratorDTO, final Money accruedCharge) { + final Integer loanInstallmentNumber, final ScheduleGeneratorDTO scheduleGeneratorDTO, final Money accruedCharge, + final AppUser currentUser) { validateLoanIsNotClosed(loanCharge); @@ -902,7 +905,7 @@ public class Loan extends AbstractPersistable { existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); final LoanTransaction waiveLoanChargeTransaction = LoanTransaction.waiveLoanCharge(this, getOffice(), amountWaived, - transactionDate, feeChargesWaived, penaltyChargesWaived, unrecognizedIncome); + transactionDate, feeChargesWaived, penaltyChargesWaived, unrecognizedIncome, DateUtils.getLocalDateTimeOfTenant(), currentUser ); final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(waiveLoanChargeTransaction, loanCharge, waiveLoanChargeTransaction .getAmount(getCurrency()).getAmount(), loanInstallmentNumber); waiveLoanChargeTransaction.getLoanChargesPaid().add(loanChargePaidBy); @@ -910,7 +913,7 @@ public class Loan extends AbstractPersistable { if (this.repaymentScheduleDetail().isInterestRecalculationEnabled() && (loanCharge.getDueLocalDate() == null || LocalDate.now().isAfter(loanCharge.getDueLocalDate()))) { - regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO); + regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser); } // Waive of charges whose due date falls after latest 'repayment' // transaction dont require entire loan schedule to be reprocessed. @@ -1037,7 +1040,8 @@ public class Loan extends AbstractPersistable { this.collateral.addAll(associateWithThisLoan(loanCollateral)); } - public void updateLoanSchedule(final LoanScheduleModel modifiedLoanSchedule) { + public void updateLoanSchedule(final LoanScheduleModel modifiedLoanSchedule, + AppUser currentUser) { this.repaymentScheduleInstallments.clear(); for (final LoanScheduleModelPeriod scheduledLoanInstallment : modifiedLoanSchedule.getPeriods()) { @@ -1054,7 +1058,7 @@ public class Loan extends AbstractPersistable { updateLoanScheduleDependentDerivedFields(); updateLoanSummaryDerivedFields(); - applyAccurals(); + applyAccurals(currentUser); } @@ -1062,12 +1066,12 @@ public class Loan extends AbstractPersistable { * method updates accrual derived fields on installments and reverse the * unprocessed transactions */ - private void applyAccurals() { + private void applyAccurals(AppUser currentUser) { Collection accruals = retreiveListOfAccrualTransactions(); if (isPeriodicAccrualAccountingEnabledOnLoanProduct()) { applyPeriodicAccruals(accruals); } else if (isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) { - updateAccrualsForNonPeriodicAccruals(accruals); + updateAccrualsForNonPeriodicAccruals(accruals, currentUser); } } @@ -1097,15 +1101,17 @@ public class Loan extends AbstractPersistable { } } - private void updateAccrualsForNonPeriodicAccruals(final Collection accruals) { + private void updateAccrualsForNonPeriodicAccruals(final Collection accruals, + final AppUser currentUser) { final Money interestApplied = Money.of(getCurrency(), this.summary.getTotalInterestCharged()); for (LoanTransaction loanTransaction : accruals) { if (loanTransaction.getInterestPortion(getCurrency()).isGreaterThanZero()) { if (loanTransaction.getInterestPortion(getCurrency()).isNotEqualTo(interestApplied)) { loanTransaction.reverse(); + final LocalDateTime currentDateTime = DateUtils.getLocalDateTimeOfTenant(); final LoanTransaction interestAppliedTransaction = LoanTransaction.accrueInterest(getOffice(), this, interestApplied, - getDisbursementDate()); + getDisbursementDate(), currentDateTime, currentUser); this.loanTransactions.add(interestAppliedTransaction); } } else { @@ -1115,7 +1121,7 @@ public class Loan extends AbstractPersistable { Money chargeAmount = loanCharge.getAmount(getCurrency()); if (chargeAmount.isNotEqualTo(loanTransaction.getAmount(getCurrency()))) { loanTransaction.reverse(); - handleChargeAppliedTransaction(loanCharge, loanTransaction.getTransactionDate()); + handleChargeAppliedTransaction(loanCharge, loanTransaction.getTransactionDate(), currentUser); } } @@ -1664,7 +1670,7 @@ public class Loan extends AbstractPersistable { final LocalDate submittedOn, final String externalId, final boolean allowTransactionsOnHoliday, final List holidays, final WorkingDays workingDays, final boolean allowTransactionsOnNonWorkingDay, final LocalDate recalculationRestFrequencyDate) { - updateLoanSchedule(loanSchedule); + updateLoanSchedule(loanSchedule, currentUser); LoanStatus from = null; if (this.loanStatus != null) { @@ -2027,13 +2033,14 @@ public class Loan extends AbstractPersistable { if (this.repaymentScheduleDetail().isInterestRecalculationEnabled() && (fetchRepaymentScheduleInstallment(1).getDueDate().isBefore(LocalDate.now()) || isDisbursementMissed())) { - regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO); + regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser); } updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate); updateLoanSummaryDerivedFields(); - handleDisbursementTransaction(actualDisbursementDate); + LocalDateTime createdDate = DateUtils.getLocalDateTimeOfTenant(); + handleDisbursementTransaction(actualDisbursementDate, createdDate, currentUser); final Money interestApplied = Money.of(getCurrency(), this.summary.getTotalInterestCharged()); @@ -2045,7 +2052,7 @@ public class Loan extends AbstractPersistable { if (isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) { final LoanTransaction interestAppliedTransaction = LoanTransaction.accrueInterest(getOffice(), this, interestApplied, - actualDisbursementDate); + actualDisbursementDate, createdDate, currentUser); this.loanTransactions.add(interestAppliedTransaction); } @@ -2054,7 +2061,7 @@ public class Loan extends AbstractPersistable { } public void regenerateScheduleOnDisbursement(final ScheduleGeneratorDTO scheduleGeneratorDTO, final boolean recalculateSchedule, - final LocalDate actualDisbursementDate, BigDecimal emiAmount) { + final LocalDate actualDisbursementDate, BigDecimal emiAmount, final AppUser currentUser) { boolean isEmiAmountChanged = false; if (this.loanProduct.isMultiDisburseLoan() && emiAmount != null && emiAmount.compareTo(retriveLastEmiAmount()) != 0) { LoanTermVariations loanVariationTerms = new LoanTermVariations(LoanTermVariationType.EMI_AMOUNT.getValue(), @@ -2064,7 +2071,7 @@ public class Loan extends AbstractPersistable { } if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate) || recalculateSchedule || isEmiAmountChanged) { - regenerateRepaymentSchedule(scheduleGeneratorDTO); + regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser); } } @@ -2249,11 +2256,12 @@ public class Loan extends AbstractPersistable { * Ability to regenerate the repayment schedule based on the loans current * details/state. */ - public void regenerateRepaymentSchedule(final ScheduleGeneratorDTO scheduleGeneratorDTO) { + public void regenerateRepaymentSchedule(final ScheduleGeneratorDTO scheduleGeneratorDTO, + AppUser currentUser) { final LoanScheduleModel loanSchedule = regenerateScheduleModel(scheduleGeneratorDTO); - updateLoanSchedule(loanSchedule); + updateLoanSchedule(loanSchedule, currentUser); Set charges = this.charges(); for (LoanCharge loanCharge : charges) { recalculateLoanCharge(loanCharge, scheduleGeneratorDTO.getPenaltyWaitPeriod()); @@ -2302,7 +2310,7 @@ public class Loan extends AbstractPersistable { return loanSchedule; } - private void handleDisbursementTransaction(final LocalDate disbursedOn) { + private void handleDisbursementTransaction(final LocalDate disbursedOn, final LocalDateTime createdDate, final AppUser currentUser) { // add repayment transaction to track incoming money from client to mfi // for (charges due at time of disbursement) @@ -2320,7 +2328,7 @@ public class Loan extends AbstractPersistable { if (disbursedOn.toDate().equals(this.actualDisbursementDate)) { Money disbursentMoney = Money.zero(getCurrency()); final LoanTransaction chargesPayment = LoanTransaction.repaymentAtDisbursement(getOffice(), disbursentMoney, null, disbursedOn, - null); + null, createdDate, currentUser); final Integer installmentNumber = null; for (final LoanCharge charge : charges()) { if (charge.isDueAtDisbursement()) { @@ -2339,7 +2347,7 @@ public class Loan extends AbstractPersistable { * None or Cash based accounting is enabled **/ if (isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) { - handleChargeAppliedTransaction(charge, disbursedOn); + handleChargeAppliedTransaction(charge, disbursedOn, currentUser); } } } @@ -2399,7 +2407,7 @@ public class Loan extends AbstractPersistable { } public Map undoDisbursal(final ScheduleGeneratorDTO scheduleGeneratorDTO, final List existingTransactionIds, - final List existingReversedTransactionIds) { + final List existingReversedTransactionIds, AppUser currentUser) { validateAccountStatus(LoanEvent.LOAN_DISBURSAL_UNDO); @@ -2430,7 +2438,7 @@ public class Loan extends AbstractPersistable { // clear off actual disbusrement date so schedule regeneration // uses expected date. - regenerateRepaymentSchedule(scheduleGeneratorDTO); + regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser); if (isDisbursedAmountChanged) { updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); } @@ -2481,7 +2489,8 @@ public class Loan extends AbstractPersistable { public ChangedTransactionDetail waiveInterest(final LoanTransaction waiveInterestTransaction, final LoanLifecycleStateMachine loanLifecycleStateMachine, final List existingTransactionIds, - final List existingReversedTransactionIds, final ScheduleGeneratorDTO scheduleGeneratorDTO) { + final List existingReversedTransactionIds, final ScheduleGeneratorDTO scheduleGeneratorDTO, + final AppUser currentUser) { validateAccountStatus(LoanEvent.LOAN_REPAYMENT_OR_WAIVER); @@ -2494,14 +2503,15 @@ public class Loan extends AbstractPersistable { existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(waiveInterestTransaction, - loanLifecycleStateMachine, null, scheduleGeneratorDTO); + loanLifecycleStateMachine, null, scheduleGeneratorDTO, currentUser); return changedTransactionDetail; } public ChangedTransactionDetail makeRepayment(final LoanTransaction repaymentTransaction, final LoanLifecycleStateMachine loanLifecycleStateMachine, final List existingTransactionIds, - final List existingReversedTransactionIds, boolean isRecoveryRepayment, final ScheduleGeneratorDTO scheduleGeneratorDTO) { + final List existingReversedTransactionIds, boolean isRecoveryRepayment, final ScheduleGeneratorDTO scheduleGeneratorDTO, + final AppUser currentUser) { LoanEvent event = null; if (isRecoveryRepayment) { @@ -2523,7 +2533,7 @@ public class Loan extends AbstractPersistable { existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(repaymentTransaction, - loanLifecycleStateMachine, null, scheduleGeneratorDTO); + loanLifecycleStateMachine, null, scheduleGeneratorDTO, currentUser); return changedTransactionDetail; } @@ -2592,7 +2602,7 @@ public class Loan extends AbstractPersistable { private ChangedTransactionDetail handleRepaymentOrRecoveryOrWaiverTransaction(final LoanTransaction loanTransaction, final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction adjustedTransaction, - final ScheduleGeneratorDTO scheduleGeneratorDTO) { + final ScheduleGeneratorDTO scheduleGeneratorDTO, final AppUser currentUser) { ChangedTransactionDetail changedTransactionDetail = null; @@ -2691,7 +2701,7 @@ public class Loan extends AbstractPersistable { } if (reprocess) { if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { - regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO); + regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser); } final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), @@ -2926,7 +2936,8 @@ public class Loan extends AbstractPersistable { return loanRepaymentScheduleInstallment; } - public LoanTransaction deriveDefaultInterestWaiverTransaction() { + public LoanTransaction deriveDefaultInterestWaiverTransaction( + final LocalDateTime createdDate, final AppUser currentUser) { final Money totalInterestOutstanding = getTotalInterestOutstandingOnLoan(); Money possibleInterestToWaive = totalInterestOutstanding.copy(); @@ -2948,13 +2959,13 @@ public class Loan extends AbstractPersistable { } return LoanTransaction.waiver(getOffice(), this, possibleInterestToWaive, transactionDate, possibleInterestToWaive, - possibleInterestToWaive.zero()); + possibleInterestToWaive.zero(), createdDate, currentUser); } public ChangedTransactionDetail adjustExistingTransaction(final LoanTransaction newTransactionDetail, final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction transactionForAdjustment, final List existingTransactionIds, final List existingReversedTransactionIds, - final ScheduleGeneratorDTO scheduleGeneratorDTO) { + final ScheduleGeneratorDTO scheduleGeneratorDTO, final AppUser currentUser) { HolidayDetailDTO holidayDetailDTO = scheduleGeneratorDTO.getHolidayDetailDTO(); validateRepaymentDateIsOnHoliday(newTransactionDetail.getTransactionDate(), holidayDetailDTO.isAllowTransactionsOnHoliday(), @@ -2991,14 +3002,15 @@ public class Loan extends AbstractPersistable { if (newTransactionDetail.isRepayment() || newTransactionDetail.isInterestWaiver()) { changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(newTransactionDetail, loanLifecycleStateMachine, - transactionForAdjustment, scheduleGeneratorDTO); + transactionForAdjustment, scheduleGeneratorDTO, currentUser); } return changedTransactionDetail; } public ChangedTransactionDetail undoWrittenOff(final List existingTransactionIds, - final List existingReversedTransactionIds, final ScheduleGeneratorDTO scheduleGeneratorDTO) { + final List existingReversedTransactionIds, final ScheduleGeneratorDTO scheduleGeneratorDTO, + final AppUser currentUser) { validateAccountStatus(LoanEvent.WRITE_OFF_OUTSTANDING_UNDO); existingTransactionIds.addAll(findExistingTransactionIds()); @@ -3010,7 +3022,7 @@ public class Loan extends AbstractPersistable { .determineProcessor(this.transactionProcessingStrategy); final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { - regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO); + regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser); } ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction( getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, @@ -3081,7 +3093,7 @@ public class Loan extends AbstractPersistable { final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory .determineProcessor(this.transactionProcessingStrategy); ChangedTransactionDetail changedTransactionDetail = closeDisbursements(scheduleGeneratorDTO, - loanRepaymentScheduleTransactionProcessor); + loanRepaymentScheduleTransactionProcessor, currentUser); validateAccountStatus(LoanEvent.WRITE_OFF_OUTSTANDING); @@ -3119,7 +3131,9 @@ public class Loan extends AbstractPersistable { throw new InvalidLoanStateTransitionException("writeoff", "cannot.be.a.future.date", errorMessage, writtenOffOnLocalDate); } - loanTransaction = LoanTransaction.writeoff(this, getOffice(), writtenOffOnLocalDate, txnExternalId); + LocalDateTime createdDate = DateUtils.getLocalDateTimeOfTenant(); + loanTransaction = LoanTransaction.writeoff(this, getOffice(), writtenOffOnLocalDate, txnExternalId, + createdDate, currentUser); final boolean isLastTransaction = isChronologicallyLatestTransaction(loanTransaction, this.loanTransactions); if (!isLastTransaction) { final String errorMessage = "The date of the writeoff transaction must occur on or before previous transactions."; @@ -3141,14 +3155,15 @@ public class Loan extends AbstractPersistable { } private ChangedTransactionDetail closeDisbursements(final ScheduleGeneratorDTO scheduleGeneratorDTO, - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) { + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, + final AppUser currentUser) { ChangedTransactionDetail changedTransactionDetail = null; if (isDisbursementAllowed() && atleastOnceDisbursed()) { this.loanRepaymentScheduleDetail.setPrincipal(getDisbursedAmount()); removeDisbursementDetail(); - regenerateRepaymentSchedule(scheduleGeneratorDTO); + regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser); if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { - regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO); + regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser); } final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), @@ -3178,7 +3193,7 @@ public class Loan extends AbstractPersistable { public ChangedTransactionDetail close(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine, final Map changes, final List existingTransactionIds, final List existingReversedTransactionIds, - final ScheduleGeneratorDTO scheduleGeneratorDTO) { + final ScheduleGeneratorDTO scheduleGeneratorDTO, final AppUser currentUser) { validateAccountStatus(LoanEvent.LOAN_CLOSED); @@ -3206,7 +3221,7 @@ public class Loan extends AbstractPersistable { final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory .determineProcessor(this.transactionProcessingStrategy); ChangedTransactionDetail changedTransactionDetail = closeDisbursements(scheduleGeneratorDTO, - loanRepaymentScheduleTransactionProcessor); + loanRepaymentScheduleTransactionProcessor, currentUser); LoanTransaction loanTransaction = null; if (isOpen()) { @@ -3220,7 +3235,8 @@ public class Loan extends AbstractPersistable { changes.put("status", LoanEnumerations.status(this.loanStatus)); } this.closedOnDate = closureDate.toDate(); - loanTransaction = LoanTransaction.writeoff(this, getOffice(), closureDate, txnExternalId); + loanTransaction = LoanTransaction.writeoff(this, getOffice(), closureDate, txnExternalId, + DateUtils.getLocalDateTimeOfTenant(), currentUser); final boolean isLastTransaction = isChronologicallyLatestTransaction(loanTransaction, this.loanTransactions); if (!isLastTransaction) { final String errorMessage = "The closing date of the loan must be on or after latest transaction date."; @@ -4316,7 +4332,8 @@ public class Loan extends AbstractPersistable { public ChangedTransactionDetail updateDisbursementDateForTranche(final LoanDisbursementDetails disbursementDetails, final JsonCommand command, final List existingTransactionIds, final List existingReversedTransactionIds, - final Map actualChanges, final ScheduleGeneratorDTO scheduleGeneratorDTO) { + final Map actualChanges, final ScheduleGeneratorDTO scheduleGeneratorDTO, + final AppUser currentUser) { validateAccountStatus(LoanEvent.LOAN_EDIT_MULTI_DISBURSE_DATE); final LocalDate expectedDisbursementDate = command.localDateValueOfParameterNamed(LoanApiConstants.disbursementDateParameterName); disbursementDetails.updateExpectedDisbursementDate(expectedDisbursementDate.toDate()); @@ -4328,9 +4345,9 @@ public class Loan extends AbstractPersistable { existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { - regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO); + regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser); } else { - regenerateRepaymentSchedule(scheduleGeneratorDTO); + regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser); } final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory @@ -4409,7 +4426,8 @@ public class Loan extends AbstractPersistable { } public ChangedTransactionDetail recalculateScheduleFromLastTransaction(final ScheduleGeneratorDTO generatorDTO, - final List existingTransactionIds, final List existingReversedTransactionIds) { + final List existingTransactionIds, final List existingReversedTransactionIds, + final AppUser currentUser) { existingTransactionIds.addAll(findExistingTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); /* @@ -4423,14 +4441,15 @@ public class Loan extends AbstractPersistable { * generatorDTO.setRecalculateFrom(recalculateFrom); */ if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { - regenerateRepaymentScheduleWithInterestRecalculation(generatorDTO); + regenerateRepaymentScheduleWithInterestRecalculation(generatorDTO, currentUser); } return processTransactions(); } - public ChangedTransactionDetail handleRegenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO) { - regenerateRepaymentScheduleWithInterestRecalculation(generatorDTO); + public ChangedTransactionDetail handleRegenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO, + final AppUser currentUser) { + regenerateRepaymentScheduleWithInterestRecalculation(generatorDTO, currentUser); return processTransactions(); } @@ -4459,13 +4478,14 @@ public class Loan extends AbstractPersistable { return changedTransactionDetail; } - private void regenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO) { + private void regenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO, + final AppUser currentUser) { LocalDate lastTransactionDate = getLastUserTransactionDate(); generatorDTO.setLastTransactionDate(lastTransactionDate); final LoanScheduleModel loanSchedule = getRecalculatedSchedule(generatorDTO); if (loanSchedule == null) { return; } - updateLoanSchedule(loanSchedule); + updateLoanSchedule(loanSchedule, currentUser); LocalDate lastRepaymentDate = this.getLastRepaymentPeriodDueDate(); final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainService.java index 0dc4ca143..aaab87ec5 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainService.java @@ -12,23 +12,26 @@ import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; import org.mifosplatform.portfolio.loanaccount.data.ScheduleGeneratorDTO; import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail; +import org.mifosplatform.useradministration.domain.AppUser; public interface LoanAccountDomainService { LoanTransaction makeRepayment(Loan loan, CommandProcessingResultBuilder builderResult, LocalDate transactionDate, BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId, - final boolean isRecoveryRepayment, boolean isAccountTransfer); + final boolean isRecoveryRepayment, boolean isAccountTransfer, final AppUser currentUser); LoanTransaction makeRefund(Long accountId, CommandProcessingResultBuilder builderResult, LocalDate transactionDate, - BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId); + BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId, + final AppUser currentUser); void reverseTransfer(LoanTransaction loanTransaction); LoanTransaction makeChargePayment(Loan loan, Long chargeId, LocalDate transactionDate, BigDecimal transactionAmount, - PaymentDetail paymentDetail, String noteText, String txnExternalId, Integer transactionType, Integer installmentNumber); + PaymentDetail paymentDetail, String noteText, String txnExternalId, Integer transactionType, Integer installmentNumber, + final AppUser currentUser); LoanTransaction makeDisburseTransaction(Long loanId, LocalDate transactionDate, BigDecimal transactionAmount, - PaymentDetail paymentDetail, String noteText, String txnExternalId); + PaymentDetail paymentDetail, String noteText, String txnExternalId, final AppUser currentUser); LocalDate getCalculatedRepaymentsStartingFromDate(LocalDate actualDisbursementDate, Loan loan, CalendarInstance calendarInstance); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java index 7439fe687..cba3676ae 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java @@ -15,6 +15,7 @@ import java.util.Set; import org.apache.commons.lang.StringUtils; import org.joda.time.LocalDate; +import org.joda.time.LocalDateTime; import org.mifosplatform.accounting.journalentry.service.JournalEntryWritePlatformService; import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService; import org.mifosplatform.infrastructure.core.data.ApiParameterError; @@ -22,6 +23,7 @@ import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; import org.mifosplatform.infrastructure.core.exception.GeneralPlatformDomainRuleException; import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; +import org.mifosplatform.infrastructure.core.service.DateUtils; import org.mifosplatform.organisation.holiday.domain.Holiday; import org.mifosplatform.organisation.holiday.domain.HolidayRepository; import org.mifosplatform.organisation.holiday.domain.HolidayStatusType; @@ -54,6 +56,7 @@ import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.mifosplatform.portfolio.note.domain.Note; import org.mifosplatform.portfolio.note.domain.NoteRepository; import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail; +import org.mifosplatform.useradministration.domain.AppUser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; @@ -112,7 +115,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { @Override public LoanTransaction makeRepayment(final Loan loan, final CommandProcessingResultBuilder builderResult, final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final String noteText, - final String txnExternalId, final boolean isRecoveryRepayment, boolean isAccountTransfer) { + final String txnExternalId, final boolean isRecoveryRepayment, boolean isAccountTransfer, + final AppUser currentUser) { checkClientOrGroupActive(loan); @@ -134,12 +138,13 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { final Money repaymentAmount = Money.of(loan.getCurrency(), transactionAmount); LoanTransaction newRepaymentTransaction = null; + final LocalDateTime currentDateTime = DateUtils.getLocalDateTimeOfTenant(); if (isRecoveryRepayment) { newRepaymentTransaction = LoanTransaction.recoveryRepayment(loan.getOffice(), repaymentAmount, paymentDetail, transactionDate, - txnExternalId); + txnExternalId, currentDateTime, currentUser); } else { newRepaymentTransaction = LoanTransaction.repayment(loan.getOffice(), repaymentAmount, paymentDetail, transactionDate, - txnExternalId); + txnExternalId, currentDateTime, currentUser); } final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled(); @@ -177,7 +182,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { final ChangedTransactionDetail changedTransactionDetail = loan.makeRepayment(newRepaymentTransaction, defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, isRecoveryRepayment, - scheduleGeneratorDTO); + scheduleGeneratorDTO, currentUser); saveLoanTransactionWithDataIntegrityViolationChecks(newRepaymentTransaction); @@ -257,7 +262,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { @Transactional 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 Integer transactionType, Integer installmentNumber) { + final Integer transactionType, Integer installmentNumber, final AppUser currentUser) { boolean isAccountTransfer = true; checkClientOrGroupActive(loan); @@ -269,7 +274,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { final LoanTransactionType loanTransactionType = LoanTransactionType.fromInt(transactionType); final LoanTransaction newPaymentTransaction = LoanTransaction.loanPayment(null, loan.getOffice(), paymentAmout, paymentDetail, - transactionDate, txnExternalId, loanTransactionType); + transactionDate, txnExternalId, loanTransactionType, DateUtils.getLocalDateTimeOfTenant(), currentUser); if (loanTransactionType.isRepaymentAtDisbursement()) { loan.handlePayDisbursementTransaction(chargeId, newPaymentTransaction, existingTransactionIds, existingReversedTransactionIds); @@ -329,7 +334,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { @Override public LoanTransaction makeRefund(final Long accountId, final CommandProcessingResultBuilder builderResult, final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final String noteText, - final String txnExternalId) { + final String txnExternalId, final AppUser currentUser) { boolean isAccountTransfer = true; final Loan loan = this.loanAccountAssembler.assembleFrom(accountId); checkClientOrGroupActive(loan); @@ -339,7 +344,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { final Money refundAmount = Money.of(loan.getCurrency(), transactionAmount); final LoanTransaction newRefundTransaction = LoanTransaction.refund(loan.getOffice(), refundAmount, paymentDetail, transactionDate, - txnExternalId); + txnExternalId, DateUtils.getLocalDateTimeOfTenant(), currentUser); final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled(); final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), transactionDate.toDate(), HolidayStatusType.ACTIVE.getValue()); @@ -370,7 +375,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { @Transactional @Override public LoanTransaction makeDisburseTransaction(final Long loanId, final LocalDate transactionDate, final BigDecimal transactionAmount, - final PaymentDetail paymentDetail, final String noteText, final String txnExternalId) { + final PaymentDetail paymentDetail, final String noteText, final String txnExternalId, final AppUser currentUser) { final Loan loan = this.loanAccountAssembler.assembleFrom(loanId); checkClientOrGroupActive(loan); boolean isAccountTransfer = true; @@ -378,7 +383,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { final List existingReversedTransactionIds = new ArrayList<>(); final Money amount = Money.of(loan.getCurrency(), transactionAmount); LoanTransaction disbursementTransaction = LoanTransaction.disbursement(loan.getOffice(), amount, paymentDetail, transactionDate, - txnExternalId); + txnExternalId, DateUtils.getLocalDateTimeOfTenant(), currentUser); disbursementTransaction.updateLoan(loan); loan.getLoanTransactions().add(disbursementTransaction); saveLoanTransactionWithDataIntegrityViolationChecks(disbursementTransaction); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java index 5d936d8ad..3d77661be 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java @@ -28,6 +28,7 @@ import javax.persistence.UniqueConstraint; import org.hibernate.annotations.LazyCollection; import org.hibernate.annotations.LazyCollectionOption; import org.joda.time.LocalDate; +import org.joda.time.LocalDateTime; import org.mifosplatform.infrastructure.core.service.DateUtils; import org.mifosplatform.organisation.monetary.data.CurrencyData; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; @@ -39,6 +40,7 @@ import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionEnumData; import org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations; import org.mifosplatform.portfolio.paymentdetail.data.PaymentDetailData; import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail; +import org.mifosplatform.useradministration.domain.AppUser; import org.springframework.data.jpa.domain.AbstractPersistable; /** @@ -98,6 +100,14 @@ public final class LoanTransaction extends AbstractPersistable { @Column(name = "external_id", length = 100, nullable = true, unique = true) private String externalId; + + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "created_date", nullable = false) + private final Date createdDate; + + @ManyToOne + @JoinColumn(name="appuser_id", nullable = true) + private final AppUser appUser; @LazyCollection(LazyCollectionOption.FALSE) @OneToMany(cascade = CascadeType.ALL, mappedBy = "loanTransaction", orphanRemoval = true) @@ -114,46 +124,51 @@ public final class LoanTransaction extends AbstractPersistable { this.dateOf = null; this.typeOf = null; this.submittedOnDate = DateUtils.getDateOfTenant(); + this.createdDate = new Date(); + this.appUser = null; } public static LoanTransaction disbursement(final Office office, final Money amount, final PaymentDetail paymentDetail, - final LocalDate disbursementDate, final String externalId) { + final LocalDate disbursementDate, final String externalId, final LocalDateTime createdDate, final AppUser appUser) { return new LoanTransaction(null, office, LoanTransactionType.DISBURSEMENT, paymentDetail, amount.getAmount(), disbursementDate, - externalId); + externalId, createdDate, appUser); } public static LoanTransaction repayment(final Office office, final Money amount, final PaymentDetail paymentDetail, - final LocalDate paymentDate, final String externalId) { - return new LoanTransaction(null, office, LoanTransactionType.REPAYMENT, paymentDetail, amount.getAmount(), paymentDate, externalId); + final LocalDate paymentDate, final String externalId, final LocalDateTime createdDate, final AppUser appUser) { + return new LoanTransaction(null, office, LoanTransactionType.REPAYMENT, paymentDetail, amount.getAmount(), paymentDate, externalId, + createdDate, appUser); } public static LoanTransaction recoveryRepayment(final Office office, final Money amount, final PaymentDetail paymentDetail, - final LocalDate paymentDate, final String externalId) { + final LocalDate paymentDate, final String externalId, final LocalDateTime createdDate, final AppUser appUser) { return new LoanTransaction(null, office, LoanTransactionType.RECOVERY_REPAYMENT, paymentDetail, amount.getAmount(), paymentDate, - externalId); + externalId, createdDate, appUser); } 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) { - return new LoanTransaction(loan, office, transactionType, paymentDetail, amount.getAmount(), paymentDate, externalId); + 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, + createdDate, appUser); } public static LoanTransaction repaymentAtDisbursement(final Office office, final Money amount, final PaymentDetail paymentDetail, - final LocalDate paymentDate, final String externalId) { + final LocalDate paymentDate, final String externalId, final LocalDateTime createdDate, final AppUser appUser) { return new LoanTransaction(null, office, LoanTransactionType.REPAYMENT_AT_DISBURSEMENT, paymentDetail, amount.getAmount(), - paymentDate, externalId); + paymentDate, externalId, createdDate, appUser); } public static LoanTransaction waiver(final Office office, final Loan loan, final Money amount, final LocalDate waiveDate, - final Money waived, final Money unrecognizedPortion) { + final Money waived, final Money unrecognizedPortion, final LocalDateTime createdDate, final AppUser appUser) { LoanTransaction loanTransaction = new LoanTransaction(loan, office, LoanTransactionType.WAIVE_INTEREST, amount.getAmount(), - waiveDate, null); + waiveDate, null, createdDate, appUser); loanTransaction.updateInterestComponent(waived, unrecognizedPortion); return loanTransaction; } public static LoanTransaction accrueInterest(final Office office, final Loan loan, final Money amount, - final LocalDate interestAppliedDate) { + final LocalDate interestAppliedDate, final LocalDateTime createdDate, final AppUser appUser) { BigDecimal principalPortion = null; BigDecimal feesPortion = null; BigDecimal penaltiesPortion = null; @@ -163,47 +178,53 @@ public final class LoanTransaction extends AbstractPersistable { PaymentDetail paymentDetail = null; String externalId = null; return new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL.getValue(), interestAppliedDate.toDate(), interestPortion, - principalPortion, interestPortion, feesPortion, penaltiesPortion, overPaymentPortion, reversed, paymentDetail, externalId); + principalPortion, interestPortion, feesPortion, penaltiesPortion, 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.toDateTimeAtStartOfDay() .toDate(), loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalPrincipalOutstanding(), loan.getSummary() .getTotalInterestOutstanding(), loan.getSummary().getTotalFeeChargesOutstanding(), loan.getSummary() - .getTotalPenaltyChargesOutstanding(), null, false, null, null); + .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.toDateTimeAtStartOfDay() .toDate(), loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalPrincipalOutstanding(), loan.getSummary() .getTotalInterestOutstanding(), loan.getSummary().getTotalFeeChargesOutstanding(), loan.getSummary() - .getTotalPenaltyChargesOutstanding(), null, false, null, null); + .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.toDateTimeAtStartOfDay() .toDate(), loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalPrincipalOutstanding(), loan.getSummary() .getTotalInterestOutstanding(), loan.getSummary().getTotalFeeChargesOutstanding(), loan.getSummary() - .getTotalPenaltyChargesOutstanding(), null, false, null, null); + .getTotalPenaltyChargesOutstanding(), null, false, null, null, createdDate, appUser); } public static LoanTransaction refund(final Office office, final Money amount, final PaymentDetail paymentDetail, - final LocalDate paymentDate, final String externalId) { - return new LoanTransaction(null, office, LoanTransactionType.REFUND, paymentDetail, amount.getAmount(), paymentDate, externalId); + final LocalDate paymentDate, final String externalId, final LocalDateTime createdDate, final AppUser appUser) { + return new LoanTransaction(null, office, LoanTransactionType.REFUND, paymentDetail, amount.getAmount(), paymentDate, externalId, + createdDate, appUser); } public static LoanTransaction copyTransactionProperties(final LoanTransaction loanTransaction) { return new LoanTransaction(loanTransaction.loan, loanTransaction.office, loanTransaction.typeOf, loanTransaction.dateOf, loanTransaction.amount, loanTransaction.principalPortion, loanTransaction.interestPortion, loanTransaction.feeChargesPortion, loanTransaction.penaltyChargesPortion, loanTransaction.overPaymentPortion, - loanTransaction.reversed, loanTransaction.paymentDetail, loanTransaction.externalId); + loanTransaction.reversed, loanTransaction.paymentDetail, loanTransaction.externalId, + new LocalDateTime(loanTransaction.createdDate), loanTransaction.appUser); } public static LoanTransaction accrueLoanCharge(final Loan loan, final Office office, final Money amount, final LocalDate applyDate, - final Money feeCharges, final Money penaltyCharges) { + final Money feeCharges, final Money penaltyCharges, final LocalDateTime createdDate, final AppUser appUser) { String externalId = null; final LoanTransaction applyCharge = new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL, amount.getAmount(), applyDate, - externalId); + externalId, createdDate, appUser); applyCharge.updateChargesComponents(feeCharges, penaltyCharges); return applyCharge; } @@ -222,7 +243,7 @@ public final class LoanTransaction extends AbstractPersistable { private LoanTransaction(final Loan loan, final Office office, final Integer typeOf, final Date dateOf, final BigDecimal amount, final BigDecimal principalPortion, final BigDecimal interestPortion, final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion, final BigDecimal overPaymentPortion, final boolean reversed, - final PaymentDetail paymentDetail, final String externalId) { + final PaymentDetail paymentDetail, final String externalId, final LocalDateTime createdDate, final AppUser appUser) { super(); this.loan = loan; this.typeOf = typeOf; @@ -238,23 +259,27 @@ public final class LoanTransaction extends AbstractPersistable { this.office = office; this.externalId = externalId; this.submittedOnDate = DateUtils.getDateOfTenant(); + this.createdDate = createdDate.toDate(); + this.appUser = appUser; } 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 Money feeChargesWaived, final Money penaltyChargesWaived, final Money unrecognizedCharge, + final LocalDateTime createdDate, final AppUser appUser) { final LoanTransaction waiver = new LoanTransaction(loan, office, LoanTransactionType.WAIVE_CHARGES, waived.getAmount(), waiveDate, - null); + null, createdDate, appUser); waiver.updateChargesComponents(feeChargesWaived, penaltyChargesWaived, unrecognizedCharge); return waiver; } - public static LoanTransaction writeoff(final Loan loan, final Office office, final LocalDate writeOffDate, final String externalId) { - return new LoanTransaction(loan, office, LoanTransactionType.WRITEOFF, null, writeOffDate, 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, createdDate, appUser); } private LoanTransaction(final Loan loan, final Office office, final LoanTransactionType type, final BigDecimal amount, - final LocalDate date, final String externalId) { + final LocalDate date, final String externalId, final LocalDateTime createdDate, final AppUser appUser) { this.loan = loan; this.typeOf = type.getValue(); this.amount = amount; @@ -262,10 +287,12 @@ public final class LoanTransaction extends AbstractPersistable { this.externalId = externalId; this.office = office; this.submittedOnDate = DateUtils.getDateOfTenant(); + this.createdDate = createdDate.toDate(); + this.appUser = appUser; } private LoanTransaction(final Loan loan, final Office office, final LoanTransactionType type, final PaymentDetail paymentDetail, - final BigDecimal amount, final LocalDate date, final String externalId) { + final BigDecimal amount, final LocalDate date, final String externalId, final LocalDateTime createdDate, final AppUser appUser) { this.loan = loan; this.typeOf = type.getValue(); this.paymentDetail = paymentDetail; @@ -274,6 +301,8 @@ public final class LoanTransaction extends AbstractPersistable { this.externalId = externalId; this.office = office; this.submittedOnDate = DateUtils.getDateOfTenant(); + this.createdDate = createdDate.toDate(); + this.appUser = appUser; } public void reverse() { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java index a76021818..0c403ab16 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java @@ -16,6 +16,7 @@ import org.mifosplatform.infrastructure.core.data.ApiParameterError; import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; +import org.mifosplatform.infrastructure.core.service.DateUtils; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; @@ -169,7 +170,8 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC modifiedTransactions.add(LoanTransaction.copyTransactionProperties(loanTransaction)); } if (isNewPaymentRequired) { - LoanTransaction ondayPaymentTransaction = LoanTransaction.repayment(null, totalAmount, null, LocalDate.now(), null); + LoanTransaction ondayPaymentTransaction = LoanTransaction.repayment(null, totalAmount, null, LocalDate.now(), null, + DateUtils.getLocalDateTimeOfTenant(), null); modifiedTransactions.add(ondayPaymentTransaction); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java index bd04b0fd2..35a5ee517 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java @@ -360,7 +360,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa public CommandProcessingResult modifyApplication(final Long loanId, final JsonCommand command) { try { - this.context.authenticatedUser(); + AppUser currentUser = this.context.authenticatedUser(); this.fromApiJsonDeserializer.validateForModify(command.json()); @@ -519,7 +519,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa final JsonQuery query = JsonQuery.from(command.json(), parsedQuery, this.fromJsonHelper); final LoanScheduleModel loanSchedule = this.calculationPlatformService.calculateLoanSchedule(query, false); - existingLoanApplication.updateLoanSchedule(loanSchedule); + existingLoanApplication.updateLoanSchedule(loanSchedule, currentUser); existingLoanApplication.recalculateAllCharges(); } @@ -733,7 +733,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa // to recompute the schedule if (changes.containsKey(LoanApiConstants.approvedLoanAmountParameterName)) { ScheduleGeneratorDTO scheduleGeneratorDTO = loanAccountDomainService.buildScheduleGeneratorDTO(loan); - loan.regenerateRepaymentSchedule(scheduleGeneratorDTO); + loan.regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser); } this.loanRepository.save(loan); @@ -761,7 +761,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa @Override public CommandProcessingResult undoApplicationApproval(final Long loanId, final JsonCommand command) { - this.context.authenticatedUser(); + AppUser currentUser = this.context.authenticatedUser(); this.fromApiJsonDeserializer.validateForUndo(command.json()); @@ -777,7 +777,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa if (changes.containsKey(LoanApiConstants.approvedLoanAmountParameterName) || changes.containsKey(LoanApiConstants.disbursementPrincipalParameterName )) { ScheduleGeneratorDTO scheduleGeneratorDTO = loanAccountDomainService.buildScheduleGeneratorDTO(loan); - loan.regenerateRepaymentSchedule(scheduleGeneratorDTO); + loan.regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser); } this.loanRepository.save(loan); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index 25287cf61..715bcaa04 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -405,7 +405,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { @Override public LoanTransactionData retrieveWaiveInterestDetails(final Long loanId) { - this.context.authenticatedUser(); + AppUser currentUser = this.context.authenticatedUser(); // TODO - KW -OPTIMIZE - write simple sql query to fetch back overdue // interest that can be waived along with the date of repayment period @@ -417,7 +417,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); final CurrencyData currencyData = applicationCurrency.toData(); - final LoanTransaction waiveOfInterest = loan.deriveDefaultInterestWaiverTransaction(); + final LoanTransaction waiveOfInterest = loan.deriveDefaultInterestWaiverTransaction( + DateUtils.getLocalDateTimeOfTenant(), currentUser); final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.WAIVE_INTEREST); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 07ae8f202..02ad1f4d8 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -32,6 +32,7 @@ import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRul import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; import org.mifosplatform.infrastructure.core.exception.PlatformServiceUnavailableException; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; +import org.mifosplatform.infrastructure.core.service.DateUtils; import org.mifosplatform.infrastructure.jobs.annotation.CronTarget; import org.mifosplatform.infrastructure.jobs.exception.JobExecutionException; import org.mifosplatform.infrastructure.jobs.service.JobName; @@ -316,7 +317,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf existingTransactionIds.addAll(loan.findExistingTransactionIds()); existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds()); LoanTransaction disbursementTransaction = LoanTransaction.disbursement(loan.getOffice(), disburseAmount, paymentDetail, - actualDisbursementDate, txnExternalId); + actualDisbursementDate, txnExternalId, DateUtils.getLocalDateTimeOfTenant(), currentUser); disbursementTransaction.updateLoan(loan); loan.getLoanTransactions().add(disbursementTransaction); } @@ -565,7 +566,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf existingTransactionIds.addAll(loan.findExistingTransactionIds()); existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds()); LoanTransaction disbursementTransaction = LoanTransaction.disbursement(loan.getOffice(), disburseAmount, paymentDetail, - actualDisbursementDate, txnExternalId); + actualDisbursementDate, txnExternalId, DateUtils.getLocalDateTimeOfTenant(), currentUser); disbursementTransaction.updateLoan(loan); loan.getLoanTransactions().add(disbursementTransaction); } @@ -641,6 +642,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf @Override public CommandProcessingResult undoLoanDisbursal(final Long loanId, final JsonCommand command) { + final AppUser currentUser = this.context.authenticatedUser(); + final Loan loan = this.loanAssembler.assembleFrom(loanId); checkClientOrGroupActive(loan); removeLoanCycle(loan); @@ -668,7 +671,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf calculatedRepaymentsStartingFromDate, holidayDetailDTO); final Map changes = loan - .undoDisbursal(scheduleGeneratorDTO, existingTransactionIds, existingReversedTransactionIds); + .undoDisbursal(scheduleGeneratorDTO, existingTransactionIds, existingReversedTransactionIds, currentUser); if (!changes.isEmpty()) { saveAndFlushLoanWithDataIntegrityViolationChecks(loan); @@ -702,7 +705,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf @Override public CommandProcessingResult makeLoanRepayment(final Long loanId, final JsonCommand command, final boolean isRecoveryRepayment) { - this.context.authenticatedUser(); + AppUser currentUser = null; + if (this.context != null) { + currentUser = this.context.authenticatedUser(); + } this.loanEventApiJsonValidator.validateNewRepaymentTransaction(command.json()); @@ -727,7 +733,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf boolean isAccountTransfer = false; final CommandProcessingResultBuilder commandProcessingResultBuilder = new CommandProcessingResultBuilder(); this.loanAccountDomainService.makeRepayment(loan, commandProcessingResultBuilder, transactionDate, transactionAmount, - paymentDetail, noteText, txnExternalId, isRecoveryRepayment, isAccountTransfer); + paymentDetail, noteText, txnExternalId, isRecoveryRepayment, isAccountTransfer, currentUser); return commandProcessingResultBuilder.withCommandId(command.commandId()) // .withLoanId(loanId) // @@ -739,7 +745,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf @Override public Map makeLoanBulkRepayment(final CollectionSheetBulkRepaymentCommand bulkRepaymentCommand) { - this.context.authenticatedUser(); + AppUser currentUser = this.context.authenticatedUser(); final SingleRepaymentCommand[] repaymentCommand = bulkRepaymentCommand.getLoanTransactions(); final Map changes = new LinkedHashMap<>(); final boolean isRecoveryRepayment = false; @@ -756,7 +762,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final CommandProcessingResultBuilder commandProcessingResultBuilder = new CommandProcessingResultBuilder(); this.loanAccountDomainService.makeRepayment(loan, commandProcessingResultBuilder, bulkRepaymentCommand.getTransactionDate(), singleLoanRepaymentCommand.getTransactionAmount(), paymentDetail, bulkRepaymentCommand.getNote(), null, - isRecoveryRepayment, isAccountTransfer); + isRecoveryRepayment, isAccountTransfer, currentUser); changes.put("bulkTransactions", singleLoanRepaymentCommand); } @@ -767,7 +773,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf @Override public CommandProcessingResult adjustLoanTransaction(final Long loanId, final Long transactionId, final JsonCommand command) { - this.context.authenticatedUser(); + AppUser currentUser = null; + if (this.context != null) { + currentUser = this.context.authenticatedUser(); + } this.loanEventApiJsonValidator.validateTransaction(command.json()); @@ -800,7 +809,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final Money transactionAmountAsMoney = Money.of(loan.getCurrency(), transactionAmount); final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createPaymentDetail(command, changes); LoanTransaction newTransactionDetail = LoanTransaction.repayment(loan.getOffice(), transactionAmountAsMoney, paymentDetail, - transactionDate, txnExternalId); + transactionDate, txnExternalId, DateUtils.getLocalDateTimeOfTenant(), currentUser); if (transactionToAdjust.isInterestWaiver()) { Money unrecognizedIncome = transactionAmountAsMoney.zero(); Money interestComponent = transactionAmountAsMoney; @@ -812,7 +821,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } } newTransactionDetail = LoanTransaction.waiver(loan.getOffice(), loan, transactionAmountAsMoney, transactionDate, - interestComponent, unrecognizedIncome); + interestComponent, unrecognizedIncome, DateUtils.getLocalDateTimeOfTenant(), currentUser); } final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled(); @@ -852,7 +861,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final ChangedTransactionDetail changedTransactionDetail = loan.adjustExistingTransaction(newTransactionDetail, defaultLoanLifecycleStateMachine(), transactionToAdjust, existingTransactionIds, existingReversedTransactionIds, - scheduleGeneratorDTO); + scheduleGeneratorDTO, currentUser); if (newTransactionDetail.isGreaterThanZero(loan.getPrincpal().getCurrency())) { if (paymentDetail != null) { @@ -914,7 +923,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf @Override public CommandProcessingResult waiveInterestOnLoan(final Long loanId, final JsonCommand command) { - this.context.authenticatedUser(); + AppUser currentUser = this.context.authenticatedUser(); this.loanEventApiJsonValidator.validateTransaction(command.json()); @@ -943,7 +952,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } } final LoanTransaction waiveInterestTransaction = LoanTransaction.waiver(loan.getOffice(), loan, transactionAmountAsMoney, - transactionDate, interestComponent, unrecognizedIncome); + transactionDate, interestComponent, unrecognizedIncome, DateUtils.getLocalDateTimeOfTenant(), currentUser); CalendarInstance restCalendarInstance = null; ApplicationCurrency applicationCurrency = null; @@ -976,7 +985,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod, lastTransactionDate); final ChangedTransactionDetail changedTransactionDetail = loan.waiveInterest(waiveInterestTransaction, - defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO); + defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO, + currentUser); this.loanTransactionRepository.save(waiveInterestTransaction); @@ -1096,7 +1106,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf @Override public CommandProcessingResult closeLoan(final Long loanId, final JsonCommand command) { - this.context.authenticatedUser(); + AppUser currentUser = this.context.authenticatedUser(); this.loanEventApiJsonValidator.validateTransactionWithNoAmount(command.json()); @@ -1140,7 +1150,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod, lastTransactionDate); ChangedTransactionDetail changedTransactionDetail = loan.close(command, defaultLoanLifecycleStateMachine(), changes, - existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO); + existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO, currentUser); final LoanTransaction possibleClosingTransaction = changedTransactionDetail.getNewTransactionMappings().remove(0L); if (possibleClosingTransaction != null) { this.loanTransactionRepository.save(possibleClosingTransaction); @@ -1300,6 +1310,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } public void runScheduleRecalculation(Loan loan) { + AppUser currentUser = null; + if (this.context != null) { + currentUser = this.context.authenticatedUser(); + } if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { CalendarInstance restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); @@ -1325,7 +1339,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod, lastTransactionDate); ChangedTransactionDetail changedTransactionDetail = loan - .handleRegenerateRepaymentScheduleWithInterestRecalculation(generatorDTO); + .handleRegenerateRepaymentScheduleWithInterestRecalculation(generatorDTO, currentUser); saveLoanWithDataIntegrityViolationChecks(loan); if (changedTransactionDetail != null) { for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { @@ -1365,6 +1379,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf private boolean addCharge(final Loan loan, final Charge chargeDefinition, final LoanCharge loanCharge) { + AppUser currentUser = null; + if (this.context != null) { + currentUser = this.context.authenticatedUser(); + } if (!loan.hasCurrencyCodeOf(chargeDefinition.getCurrencyCode())) { final String errorMessage = "Charge and Loan must have the same currency."; throw new InvalidCurrencyException("loanCharge", "attach.to.loan", errorMessage); @@ -1390,7 +1408,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf **/ if (loan.status().isActive()) { if (loan.isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) { - final LoanTransaction applyLoanChargeTransaction = loan.handleChargeAppliedTransaction(loanCharge, null); + final LoanTransaction applyLoanChargeTransaction = loan.handleChargeAppliedTransaction(loanCharge, null, currentUser); this.loanTransactionRepository.save(applyLoanChargeTransaction); } /*** @@ -1455,7 +1473,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf @Override public CommandProcessingResult waiveLoanCharge(final Long loanId, final Long loanChargeId, final JsonCommand command) { - this.context.authenticatedUser(); + AppUser currentUser = this.context.authenticatedUser(); final Loan loan = this.loanAssembler.assembleFrom(loanId); checkClientOrGroupActive(loan); @@ -1539,7 +1557,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } final LoanTransaction waiveTransaction = loan.waiveLoanCharge(loanCharge, defaultLoanLifecycleStateMachine(), changes, - existingTransactionIds, existingReversedTransactionIds, loanInstallmentNumber, scheduleGeneratorDTO, accruedCharge); + existingTransactionIds, existingReversedTransactionIds, loanInstallmentNumber, scheduleGeneratorDTO, accruedCharge, + currentUser); this.loanTransactionRepository.save(waiveTransaction); saveLoanWithDataIntegrityViolationChecks(loan); @@ -1758,6 +1777,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf @Transactional @Override public LoanTransaction initiateLoanTransfer(final Long accountId, final LocalDate transferDate) { + + AppUser currentUser = null; + if (this.context != null) { + currentUser = this.context.authenticatedUser(); + } final Loan loan = this.loanAssembler.assembleFrom(accountId); checkClientOrGroupActive(loan); @@ -1765,7 +1789,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final List existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds()); final List 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.getLoanTransactions().add(newTransferTransaction); loan.setLoanStatus(LoanStatus.TRANSFER_IN_PROGRESS.getValue()); @@ -1781,13 +1806,19 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf @Override public LoanTransaction acceptLoanTransfer(final Long accountId, final LocalDate transferDate, final Office acceptedInOffice, final Staff loanOfficer) { + + AppUser currentUser = null; + if (this.context != null) { + currentUser = this.context.authenticatedUser(); + } final Loan loan = this.loanAssembler.assembleFrom(accountId); final List existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds()); final List 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.getLoanTransactions().add(newTransferAcceptanceTransaction); if (loan.getTotalOverpaid() != null) { loan.setLoanStatus(LoanStatus.OVERPAID.getValue()); @@ -1809,12 +1840,19 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf @Transactional @Override public LoanTransaction withdrawLoanTransfer(final Long accountId, final LocalDate transferDate) { + + AppUser currentUser = null; + if (this.context != null) { + currentUser = this.context.authenticatedUser(); + } + final Loan loan = this.loanAssembler.assembleFrom(accountId); final List existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds()); final List 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.getLoanTransactions().add(newTransferAcceptanceTransaction); loan.setLoanStatus(LoanStatus.ACTIVE.getValue()); @@ -2355,6 +2393,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf @Override public CommandProcessingResult undoWriteOff(Long loanId) { + final AppUser currentUser = this.context.authenticatedUser(); + final Loan loan = this.loanAssembler.assembleFrom(loanId); checkClientOrGroupActive(loan); final List existingTransactionIds = new ArrayList<>(); @@ -2395,7 +2435,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf lastTransactionDate); ChangedTransactionDetail changedTransactionDetail = loan.undoWrittenOff(existingTransactionIds, existingReversedTransactionIds, - scheduleGeneratorDTO); + scheduleGeneratorDTO, currentUser); if (changedTransactionDetail != null) { for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { this.loanTransactionRepository.save(mapEntry.getValue()); @@ -2417,6 +2457,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf @Override @Transactional public CommandProcessingResult updateDisbursementDateForTranche(final Long loanId, final Long disbursementId, final JsonCommand command) { + + AppUser currentUser = this.context.authenticatedUser(); final Loan loan = this.loanAssembler.assembleFrom(loanId); checkClientOrGroupActive(loan); @@ -2452,7 +2494,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf lastTransactionDate); final ChangedTransactionDetail changedTransactionDetail = loan.updateDisbursementDateForTranche(loanDisbursementDetails, command, - existingTransactionIds, existingReversedTransactionIds, changes, scheduleGeneratorDTO); + existingTransactionIds, existingReversedTransactionIds, changes, scheduleGeneratorDTO, currentUser); saveAndFlushLoanWithDataIntegrityViolationChecks(loan); @@ -2555,6 +2597,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf @Transactional public void recalculateInterest(final long loanId) { + AppUser currentUser = null; + if (this.context != null) { + currentUser = this.context.authenticatedUser(); + } Loan loan = this.loanAssembler.assembleFrom(loanId); CalendarInstance restCalendarInstance = null; ApplicationCurrency applicationCurrency = null; @@ -2589,7 +2635,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf lastTransactionDate); ChangedTransactionDetail changedTransactionDetail = loan.recalculateScheduleFromLastTransaction(generatorDTO, - existingTransactionIds, existingReversedTransactionIds); + existingTransactionIds, existingReversedTransactionIds, currentUser); saveLoanWithDataIntegrityViolationChecks(loan); @@ -2620,9 +2666,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf private void regenerateScheduleOnDisbursement(final JsonCommand command, final Loan loan, final boolean recalculateSchedule, final ScheduleGeneratorDTO scheduleGeneratorDTO) { + AppUser currentUser = this.context.authenticatedUser(); final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate"); BigDecimal emiAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.emiAmountParameterName); - loan.regenerateScheduleOnDisbursement(scheduleGeneratorDTO, recalculateSchedule, actualDisbursementDate, emiAmount); + loan.regenerateScheduleOnDisbursement(scheduleGeneratorDTO, recalculateSchedule, actualDisbursementDate, emiAmount, + currentUser); } private List retrieveRepaymentScheduleFromModel(LoanScheduleModel model) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionDTO.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionDTO.java index 34608f867..c13b2c11b 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionDTO.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionDTO.java @@ -11,6 +11,7 @@ import java.util.Date; import org.joda.time.LocalDate; import org.joda.time.format.DateTimeFormatter; import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail; +import org.mifosplatform.useradministration.domain.AppUser; public class SavingsAccountTransactionDTO { @@ -20,15 +21,18 @@ public class SavingsAccountTransactionDTO { private final PaymentDetail paymentDetail; private final Date createdDate; private final Long savingsAccountId; + private final AppUser appUser; public SavingsAccountTransactionDTO(final DateTimeFormatter formatter, final LocalDate transactionDate, - final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final Date createdDate) { + final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final Date createdDate, + final AppUser appUser) { this.formatter = formatter; this.transactionDate = transactionDate; this.transactionAmount = transactionAmount; this.paymentDetail = paymentDetail; this.createdDate = createdDate; this.savingsAccountId = null; + this.appUser = appUser; } /** @@ -42,13 +46,14 @@ public class SavingsAccountTransactionDTO { * @param savingsAccountId */ public SavingsAccountTransactionDTO(DateTimeFormatter formatter, LocalDate transactionDate, BigDecimal transactionAmount, - PaymentDetail paymentDetail, Date createdDate, Long savingsAccountId) { + PaymentDetail paymentDetail, Date createdDate, Long savingsAccountId, AppUser appUser) { this.formatter = formatter; this.transactionDate = transactionDate; this.transactionAmount = transactionAmount; this.paymentDetail = paymentDetail; this.createdDate = createdDate; this.savingsAccountId = savingsAccountId; + this.appUser = appUser; } public DateTimeFormatter getFormatter() { @@ -74,5 +79,9 @@ public class SavingsAccountTransactionDTO { public Long getSavingsAccountId() { return this.savingsAccountId; } + + public AppUser getAppUser() { + return this.appUser; + } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositAccountAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositAccountAssembler.java index 2e15dd687..931c5816e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositAccountAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositAccountAssembler.java @@ -52,6 +52,7 @@ import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; import org.mifosplatform.infrastructure.core.service.DateUtils; +import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.organisation.staff.domain.Staff; import org.mifosplatform.organisation.staff.domain.StaffRepositoryWrapper; import org.mifosplatform.portfolio.account.service.AccountTransfersReadPlatformService; @@ -88,6 +89,7 @@ import com.google.gson.JsonObject; @Service public class DepositAccountAssembler { + private final PlatformSecurityContext context; private final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper; private final SavingsHelper savingsHelper; private final ClientRepositoryWrapper clientRepository; @@ -108,7 +110,8 @@ public class DepositAccountAssembler { final SavingsAccountChargeAssembler savingsAccountChargeAssembler, final FromJsonHelper fromApiJsonHelper, final DepositProductAssembler depositProductAssembler, final RecurringDepositProductRepository recurringDepositProductRepository, - final AccountTransfersReadPlatformService accountTransfersReadPlatformService) { + final AccountTransfersReadPlatformService accountTransfersReadPlatformService, + final PlatformSecurityContext context) { this.savingsAccountTransactionSummaryWrapper = savingsAccountTransactionSummaryWrapper; this.clientRepository = clientRepository; @@ -121,6 +124,7 @@ public class DepositAccountAssembler { this.depositProductAssembler = depositProductAssembler; this.recurringDepositProductRepository = recurringDepositProductRepository; this.savingsHelper = new SavingsHelper(accountTransfersReadPlatformService); + this.context = context; } /** @@ -390,6 +394,10 @@ public class DepositAccountAssembler { } public Collection assembleBulkMandatorySavingsAccountTransactionDTOs(final JsonCommand command) { + AppUser user = null; + if (this.context != null) { + user = this.context.authenticatedUser(); + } final String json = command.json(); if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } final JsonElement element = this.fromApiJsonHelper.parse(json); @@ -412,7 +420,7 @@ public class DepositAccountAssembler { final BigDecimal dueAmount = this.fromApiJsonHelper.extractBigDecimalNamed(transactionAmountParamName, savingsTransactionElement, locale); final SavingsAccountTransactionDTO savingsAccountTransactionDTO = new SavingsAccountTransactionDTO(formatter, - transactionDate, dueAmount, paymentDetail, new Date(), savingsId); + transactionDate, dueAmount, paymentDetail, new Date(), savingsId, user); savingsAccountTransactions.add(savingsAccountTransactionDTO); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositAccountDomainServiceJpa.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositAccountDomainServiceJpa.java index c6a1ba12f..1a261a271 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositAccountDomainServiceJpa.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositAccountDomainServiceJpa.java @@ -232,7 +232,7 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi depositAccountAssembler.assignSavingAccountHelpers(reinvestedDeposit); reinvestedDeposit.updateMaturityDateAndAmount(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); - reinvestedDeposit.processAccountUponActivation(fmt); + reinvestedDeposit.processAccountUponActivation(fmt, user); reinvestedDeposit.updateMaturityDateAndAmount(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); this.savingsAccountRepository.save(reinvestedDeposit); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/FixedDepositAccount.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/FixedDepositAccount.java index 6f963aac0..7c91b5a76 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/FixedDepositAccount.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/FixedDepositAccount.java @@ -180,7 +180,7 @@ public class FixedDepositAccount extends SavingsAccount { List allTransactions = new ArrayList<>(); final Money transactionAmountMoney = Money.of(getCurrency(), this.accountTermAndPreClosure.depositAmount()); final SavingsAccountTransaction transaction = SavingsAccountTransaction.deposit(null, office(), null, - this.accountSubmittedOrActivationDate(), transactionAmountMoney, new Date()); + this.accountSubmittedOrActivationDate(), transactionAmountMoney, new Date(), null); // TODO: verify if it is ok to pass null for AppUser transaction.updateRunningBalance(transactionAmountMoney); transaction.updateCumulativeBalanceAndDates(this.getCurrency(), interestCalculatedUpto()); allTransactions.add(transaction); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/RecurringDepositAccount.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/RecurringDepositAccount.java index d17b89b4a..c7d2133db 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/RecurringDepositAccount.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/RecurringDepositAccount.java @@ -66,1073 +66,1441 @@ import org.mifosplatform.useradministration.domain.AppUser; @DiscriminatorValue("300") public class RecurringDepositAccount extends SavingsAccount { - @OneToOne(mappedBy = "account", cascade = CascadeType.ALL) - private DepositAccountTermAndPreClosure accountTermAndPreClosure; - - @OneToOne(mappedBy = "account", cascade = CascadeType.ALL) - private DepositAccountRecurringDetail recurringDetail; - - @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "account") - private DepositAccountInterestRateChart chart; - - @LazyCollection(LazyCollectionOption.FALSE) - @OneToMany(cascade = CascadeType.ALL, mappedBy = "account", orphanRemoval = true) - private List depositScheduleInstallments = new ArrayList<>(); - - protected RecurringDepositAccount() { - // - } - - public static RecurringDepositAccount createNewApplicationForSubmittal(final Client client, final Group group, - final SavingsProduct product, final Staff fieldOfficer, final String accountNo, final String externalId, - final AccountType accountType, final LocalDate submittedOnDate, final AppUser submittedBy, final BigDecimal interestRate, - final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, - final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, - final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, - final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, - final boolean withdrawalFeeApplicableForTransfer, final Set savingsAccountCharges, - final DepositAccountTermAndPreClosure accountTermAndPreClosure, final DepositAccountRecurringDetail recurringDetail, - final DepositAccountInterestRateChart chart) { - - final boolean allowOverdraft = false; - final BigDecimal overdraftLimit = new BigDecimal(0); - - final SavingsAccountStatusType status = SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL; - return new RecurringDepositAccount(client, group, product, fieldOfficer, accountNo, externalId, status, accountType, - submittedOnDate, submittedBy, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, - interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, - lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, savingsAccountCharges, accountTermAndPreClosure, - recurringDetail, chart, allowOverdraft, overdraftLimit); - } - - public static RecurringDepositAccount createNewActivatedAccount(final Client client, final Group group, final SavingsProduct product, - final Staff fieldOfficer, final String accountNo, final String externalId, final AccountType accountType, - final LocalDate submittedOnDate, final AppUser submittedBy, final BigDecimal interestRate, - final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, - final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, - final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, - final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, - final boolean withdrawalFeeApplicableForTransfer, final Set savingsAccountCharges, - final DepositAccountTermAndPreClosure accountTermAndPreClosure, final DepositAccountRecurringDetail recurringDetail, - final DepositAccountInterestRateChart chart) { - - final boolean allowOverdraft = false; - final BigDecimal overdraftLimit = new BigDecimal(0); - - final SavingsAccountStatusType status = SavingsAccountStatusType.ACTIVE; - return new RecurringDepositAccount(client, group, product, fieldOfficer, accountNo, externalId, status, accountType, - submittedOnDate, submittedBy, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, - interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, - lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, savingsAccountCharges, accountTermAndPreClosure, - recurringDetail, chart, allowOverdraft, overdraftLimit); - } - - private RecurringDepositAccount(final Client client, final Group group, final SavingsProduct product, final Staff fieldOfficer, - final String accountNo, final String externalId, final SavingsAccountStatusType status, final AccountType accountType, - final LocalDate submittedOnDate, final AppUser submittedBy, final BigDecimal nominalAnnualInterestRate, - final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, - final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, - final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, - final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, - final boolean withdrawalFeeApplicableForTransfer, final Set savingsAccountCharges, - final DepositAccountTermAndPreClosure accountTermAndPreClosure, final DepositAccountRecurringDetail recurringDetail, - final DepositAccountInterestRateChart chart, final boolean allowOverdraft, final BigDecimal overdraftLimit) { - - super(client, group, product, fieldOfficer, accountNo, externalId, status, accountType, submittedOnDate, submittedBy, - nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, - interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, - withdrawalFeeApplicableForTransfer, savingsAccountCharges, allowOverdraft, overdraftLimit); - - this.accountTermAndPreClosure = accountTermAndPreClosure; - this.recurringDetail = recurringDetail; - this.chart = chart; - if (this.chart != null) { - this.chart.updateDepositAccountReference(this); - } - } - - @Override - public void modifyApplication(final JsonCommand command, final Map actualChanges) { - final List dataValidationErrors = new ArrayList<>(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) - .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.modifyApplicationAction); - super.modifyApplication(command, actualChanges, baseDataValidator); - final Map termAndPreClosureChanges = accountTermAndPreClosure.update(command, baseDataValidator); - actualChanges.putAll(termAndPreClosureChanges); - recurringDetail.update(command); - - validateDomainRules(baseDataValidator); - super.validateInterestPostingAndCompoundingPeriodTypes(baseDataValidator); - if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } - } - - @Override - protected BigDecimal getEffectiveInterestRateAsFraction(final MathContext mc, final LocalDate interestPostingUpToDate) { - boolean isPreMatureClosure = false; - return getEffectiveInterestRateAsFraction(mc, interestPostingUpToDate, isPreMatureClosure); - } - - protected BigDecimal getEffectiveInterestRateAsFraction(final MathContext mc, final LocalDate interestPostingUpToDate, - final boolean isPreMatureClosure) { - - boolean applyPreMaturePenalty = false; - BigDecimal penalInterest = BigDecimal.ZERO; - LocalDate depositCloseDate = calculateMaturityDate(); - if (isPreMatureClosure) { - if (this.accountTermAndPreClosure.isPreClosurePenalApplicable()) { - applyPreMaturePenalty = true; - penalInterest = this.accountTermAndPreClosure.depositPreClosureDetail().preClosurePenalInterest(); - final PreClosurePenalInterestOnType preClosurePenalInterestOnType = this.accountTermAndPreClosure.depositPreClosureDetail() - .preClosurePenalInterestOnType(); - if (preClosurePenalInterestOnType.isWholeTerm()) { - depositCloseDate = interestCalculatedUpto(); - } else if (preClosurePenalInterestOnType.isTillPrematureWithdrawal()) { - depositCloseDate = interestPostingUpToDate; - } - } - } - - final BigDecimal depositAmount = accountTermAndPreClosure.depositAmount(); - BigDecimal applicableInterestRate = this.chart.getApplicableInterestRate(depositAmount, depositStartDate(), depositCloseDate, - this.client); - - if (applyPreMaturePenalty) { - applicableInterestRate = applicableInterestRate.subtract(penalInterest); - applicableInterestRate = applicableInterestRate.compareTo(BigDecimal.ZERO) == -1 ? BigDecimal.ZERO : applicableInterestRate; - } - - this.nominalAnnualInterestRate = applicableInterestRate; - - return applicableInterestRate.divide(BigDecimal.valueOf(100l), mc); - } - - public void updateMaturityDateAndAmount(final MathContext mc, final boolean isPreMatureClosure, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, - final Integer financialYearBeginningMonth) { - final LocalDate maturityDate = calculateMaturityDate(); - final LocalDate interestCalculationUpto = maturityDate.minusDays(1); - final List allTransactions = generateIncludingFutureTransactions(interestCalculationUpto); - - final List postingPeriods = calculateInterestPayable(mc, interestCalculationUpto, allTransactions, - isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); - Money totalInterestPayable = Money.zero(getCurrency()); - Money totalDepositAmount = Money.zero(getCurrency()); - for (PostingPeriod postingPeriod : postingPeriods) { - totalInterestPayable = totalInterestPayable.plus(postingPeriod.getInterestEarned()); - totalDepositAmount = totalDepositAmount.plus(postingPeriod.closingBalance()).minus(postingPeriod.openingBalance()); - } - - this.accountTermAndPreClosure.updateMaturityDetails(totalDepositAmount.getAmount(), totalInterestPayable.getAmount(), maturityDate); - } - - public void updateMaturityStatus(final boolean isSavingsInterestPostingAtCurrentPeriodEnd, - final Integer financialYearBeginningMonth) { - final List dataValidationErrors = new ArrayList<>(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) - .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.updateMaturityDetailsAction); - - final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); - if (!SavingsAccountStatusType.ACTIVE.hasStateOf(currentStatus)) { - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.in.active.state"); - if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } - } - - final LocalDate todayDate = DateUtils.getLocalDateOfTenant(); - if (!this.maturityDate().isAfter(todayDate)) { - // update account status - this.status = SavingsAccountStatusType.MATURED.getValue(); - postMaturityInterest(isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); - } - } - - public LocalDate calculateMaturityDate() { - - final LocalDate startDate = depositStartDate(); - LocalDate maturityDate = null; - final Integer depositPeriod = this.accountTermAndPreClosure.depositPeriod(); - switch (this.accountTermAndPreClosure.depositPeriodFrequencyType()) { - case DAYS: - maturityDate = startDate.plusDays(depositPeriod); - break; - case WEEKS: - maturityDate = startDate.plusWeeks(depositPeriod); - break; - case MONTHS: - maturityDate = startDate.plusMonths(depositPeriod); - break; - case YEARS: - maturityDate = startDate.plusYears(depositPeriod); - break; - case INVALID: - break; - } - - return maturityDate; - } - - private List calculateInterestPayable(final MathContext mc, final LocalDate maturityDate, - final List transactions, final boolean isPreMatureClosure, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, - final Integer financialYearBeginningMonth) { - - // 1. default to calculate interest based on entire history OR - // 2. determine latest 'posting period' and find interest credited to - // that period - - // A generate list of EndOfDayBalances (not including interest postings) - final SavingsPostingInterestPeriodType postingPeriodType = SavingsPostingInterestPeriodType.fromInt(this.interestPostingPeriodType); - - final SavingsCompoundingInterestPeriodType compoundingPeriodType = SavingsCompoundingInterestPeriodType - .fromInt(this.interestCompoundingPeriodType); - - final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType - .fromInt(this.interestCalculationDaysInYearType); - - final List postingPeriodIntervals = this.savingsHelper.determineInterestPostingPeriods(depositStartDate(), - maturityDate, postingPeriodType, financialYearBeginningMonth); - - final List allPostingPeriods = new ArrayList<>(); - - Money periodStartingBalance = Money.zero(currency); - - final SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType.fromInt(this.interestCalculationType); - final BigDecimal interestRateAsFraction = getEffectiveInterestRateAsFraction(mc, maturityDate, isPreMatureClosure); - final Collection interestPostTransactions = this.savingsHelper.fetchPostInterestTransactionIds(getId()); - boolean isInterestTransfer = false; - final Money minBalanceForInterestCalculation = Money.of(getCurrency(), minBalanceForInterestCalculation()); - for (final LocalDateInterval periodInterval : postingPeriodIntervals) { - final PostingPeriod postingPeriod = PostingPeriod.createFrom(periodInterval, periodStartingBalance, transactions, - this.currency, compoundingPeriodType, interestCalculationType, interestRateAsFraction, daysInYearType.getValue(), - maturityDate, interestPostTransactions, isInterestTransfer, minBalanceForInterestCalculation, - isSavingsInterestPostingAtCurrentPeriodEnd); - - periodStartingBalance = postingPeriod.closingBalance(); - - allPostingPeriods.add(postingPeriod); - } - - this.savingsHelper.calculateInterestForAllPostingPeriods(this.currency, allPostingPeriods, this.getLockedInUntilLocalDate(), - isTransferInterestToOtherAccount()); - // this.summary.updateFromInterestPeriodSummaries(this.currency, - // allPostingPeriods); - return allPostingPeriods; - } - - private List generateIncludingFutureTransactions(final LocalDate depositEndDate) { - List allTransactions = new ArrayList<>(); - // add existing transactions - allTransactions.addAll(retreiveOrderedNonInterestPostingTransactions()); - for (RecurringDepositScheduleInstallment installment : depositScheduleInstallments()) { - if (installment.isPrincipalNotCompleted(getCurrency())) { - final SavingsAccountTransaction transaction = SavingsAccountTransaction.deposit(null, office(), null, - installment.dueDate(), installment.getDepositAmountOutstanding(getCurrency()), installment.dueDate().toDate()); - allTransactions.add(transaction); - } - } - - allTransactions = sortTransactions(allTransactions); - Money runningBalance = Money.zero(getCurrency()); - for (final SavingsAccountTransaction transaction : allTransactions) { - if (transaction.isReversed()) { - transaction.zeroBalanceFields(); - } else { - - Money transactionAmount = Money.zero(this.currency); - if (transaction.isCredit()) { - transactionAmount = transactionAmount.plus(transaction.getAmount(this.currency)); - } else if (transaction.isDebit()) { - transactionAmount = transactionAmount.minus(transaction.getAmount(this.currency)); - } - - runningBalance = runningBalance.plus(transactionAmount); - transaction.updateRunningBalance(runningBalance); - } - } - // loop over transactions in reverse - LocalDate endOfBalanceDate = depositEndDate; - for (int i = allTransactions.size() - 1; i >= 0; i--) { - final SavingsAccountTransaction transaction = allTransactions.get(i); - if (transaction.isNotReversed() && !transaction.isInterestPostingAndNotReversed()) { - transaction.updateCumulativeBalanceAndDates(this.currency, endOfBalanceDate); - endOfBalanceDate = transaction.transactionLocalDate().minusDays(1); - } - } - return allTransactions; - } - - private LocalDate calculateNextDepositDate(final LocalDate lastDepositDate, final PeriodFrequencyType frequency, - final int recurringEvery) { - LocalDate nextDepositDate = lastDepositDate; - - switch (frequency) { - case DAYS: - nextDepositDate = lastDepositDate.plusDays(recurringEvery); - break; - case WEEKS: - nextDepositDate = lastDepositDate.plusWeeks(recurringEvery); - break; - case MONTHS: - nextDepositDate = lastDepositDate.plusMonths(recurringEvery); - break; - case YEARS: - nextDepositDate = lastDepositDate.plusYears(recurringEvery); - break; - case INVALID: - break; - } - return nextDepositDate; - } - - public LocalDate depositStartDate() { - final LocalDate depositStartDate = accountTermAndPreClosure.getExpectedFirstDepositOnDate(); - if (depositStartDate == null) return accountSubmittedOrActivationDate(); - return depositStartDate; - } - - public void prematureClosure(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate, - final Map actualChanges) { - - final List dataValidationErrors = new ArrayList<>(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) - .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME + DepositsApiConstants.preMatureCloseAction); - - final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); - if (!SavingsAccountStatusType.ACTIVE.hasStateOf(currentStatus)) { - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.in.active.state"); - if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } - } - - final Locale locale = command.extractLocale(); - final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); - final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName); - - if (closedDate.isBefore(getActivationLocalDate())) { - baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) - .failWithCode("must.be.after.activation.date"); - if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } - } - - if (isAccountLocked(closedDate)) { - baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) - .failWithCode("must.be.after.lockin.period"); - if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } - } - - if (closedDate.isAfter(maturityDate())) { - baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) - .failWithCode("must.be.before.maturity.date"); - if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } - } - - if (closedDate.isAfter(tenantsTodayDate)) { - baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) - .failWithCode("cannot.be.a.future.date"); - if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } - } - - if (isAccountLocked(calculateMaturityDate())) { - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("deposit.period.must.be.greater.than.lock.in.period", - "Deposit period must be greater than account lock-in period."); - if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } - } - - final List savingsAccountTransactions = retreiveListOfTransactions(); - if (savingsAccountTransactions.size() > 0) { - final SavingsAccountTransaction accountTransaction = savingsAccountTransactions.get(savingsAccountTransactions.size() - 1); - if (accountTransaction.isAfter(closedDate)) { - baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) - .failWithCode("must.be.after.last.transaction.date"); - if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } - } - } - - validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_CLOSE_ACCOUNT, closedDate); - this.status = SavingsAccountStatusType.PRE_MATURE_CLOSURE.getValue(); - - final Integer onAccountClosureId = command.integerValueOfParameterNamed(onAccountClosureIdParamName); - final DepositAccountOnClosureType onClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId); - this.accountTermAndPreClosure.updateOnAccountClosureStatus(onClosureType); - - /* - * // withdraw deposit amount before closing the account final Money - * transactionAmountMoney = Money.of(this.currency, - * this.getAccountBalance()); final SavingsAccountTransaction withdraw = - * SavingsAccountTransaction.withdrawal(this, office(), paymentDetail, - * closedDate, transactionAmountMoney, new Date()); - * this.transactions.add(withdraw); - */ - actualChanges.put(SavingsApiConstants.statusParamName, SavingsEnumerations.status(this.status)); - actualChanges.put(SavingsApiConstants.localeParamName, command.locale()); - actualChanges.put(SavingsApiConstants.dateFormatParamName, command.dateFormat()); - actualChanges.put(SavingsApiConstants.closedOnDateParamName, closedDate.toString(fmt)); - - this.rejectedOnDate = null; - this.rejectedBy = null; - this.withdrawnOnDate = null; - this.withdrawnBy = null; - this.closedOnDate = closedDate.toDate(); - this.closedBy = currentUser; - this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); - - } - - @Override - public Money activateWithBalance() { - return Money.of(this.currency, this.minRequiredOpeningBalance); - } - - protected void processAccountUponActivation(final DateTimeFormatter fmt) { - final Money minRequiredOpeningBalance = Money.of(this.currency, this.minRequiredOpeningBalance); - if (minRequiredOpeningBalance.isGreaterThanZero()) { - final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, getActivationLocalDate(), - minRequiredOpeningBalance.getAmount(), null, new Date()); - deposit(transactionDTO); - - // update existing transactions so derived balance fields are - // correct. - recalculateDailyBalances(Money.zero(this.currency), DateUtils.getLocalDateOfTenant()); - } - } - - public void close(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate, - final Map actualChanges) { - - final List dataValidationErrors = new ArrayList<>(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) - .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.closeAction); - - final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); - if (!SavingsAccountStatusType.MATURED.hasStateOf(currentStatus)) { - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.in.matured.state"); - if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } - } - - final Locale locale = command.extractLocale(); - final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); - final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName); - - if (closedDate.isBefore(getActivationLocalDate())) { - baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) - .failWithCode("must.be.after.activation.date"); - if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } - } - if (closedDate.isBefore(maturityDate())) { - baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) - .failWithCode("must.be.after.account.maturity.date"); - if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } - } - if (closedDate.isAfter(tenantsTodayDate)) { - baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) - .failWithCode("cannot.be.a.future.date"); - if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } - } - final List savingsAccountTransactions = retreiveListOfTransactions(); - if (savingsAccountTransactions.size() > 0) { - final SavingsAccountTransaction accountTransaction = savingsAccountTransactions.get(savingsAccountTransactions.size() - 1); - if (accountTransaction.isAfter(closedDate)) { - baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) - .failWithCode("must.be.after.last.transaction.date"); - if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } - } - } - - validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_CLOSE_ACCOUNT, closedDate); - this.status = SavingsAccountStatusType.CLOSED.getValue(); - - final Integer onAccountClosureId = command.integerValueOfParameterNamed(onAccountClosureIdParamName); - final DepositAccountOnClosureType onClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId); - this.accountTermAndPreClosure.updateOnAccountClosureStatus(onClosureType); - - /* - * // withdraw deposit amount before closing the account final Money - * transactionAmountMoney = Money.of(this.currency, - * this.getAccountBalance()); final SavingsAccountTransaction withdraw = - * SavingsAccountTransaction.withdrawal(this, office(), paymentDetail, - * closedDate, transactionAmountMoney, new Date()); - * this.transactions.add(withdraw); - */ - - actualChanges.put(SavingsApiConstants.statusParamName, SavingsEnumerations.status(this.status)); - actualChanges.put(SavingsApiConstants.localeParamName, command.locale()); - actualChanges.put(SavingsApiConstants.dateFormatParamName, command.dateFormat()); - actualChanges.put(SavingsApiConstants.closedOnDateParamName, closedDate.toString(fmt)); - - this.rejectedOnDate = null; - this.rejectedBy = null; - this.withdrawnOnDate = null; - this.withdrawnBy = null; - this.closedOnDate = closedDate.toDate(); - this.closedBy = currentUser; - this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); - } - - public void postMaturityInterest(final boolean isSavingsInterestPostingAtCurrentPeriodEnd, - final Integer financialYearBeginningMonth) { - final LocalDate interestPostingUpToDate = maturityDate(); - final MathContext mc = MathContext.DECIMAL64; - boolean isInterestTransfer = false; - final List postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate, - isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); - - Money interestPostedToDate = Money.zero(this.currency); - - boolean recalucateDailyBalanceDetails = false; - - for (final PostingPeriod interestPostingPeriod : postingPeriods) { - - LocalDate interestPostingTransactionDate = interestPostingPeriod.dateOfPostingTransaction(); - interestPostingTransactionDate = interestPostingTransactionDate.isAfter(interestPostingUpToDate) ? interestPostingUpToDate - : interestPostingTransactionDate; - final Money interestEarnedToBePostedForPeriod = interestPostingPeriod.getInterestEarned(); - - interestPostedToDate = interestPostedToDate.plus(interestEarnedToBePostedForPeriod); - - final SavingsAccountTransaction postingTransaction = findInterestPostingTransactionFor(interestPostingTransactionDate); - if (postingTransaction == null) { - final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(), - interestPostingTransactionDate, interestEarnedToBePostedForPeriod); - this.transactions.add(newPostingTransaction); - recalucateDailyBalanceDetails = true; - } else { - final boolean correctionRequired = postingTransaction.hasNotAmount(interestEarnedToBePostedForPeriod); - if (correctionRequired) { - postingTransaction.reverse(); - final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(), - interestPostingTransactionDate, interestEarnedToBePostedForPeriod); - this.transactions.add(newPostingTransaction); - recalucateDailyBalanceDetails = true; - } - } - } - - if (recalucateDailyBalanceDetails) { - // update existing transactions so derived balance fields are - // correct. - recalculateDailyBalances(Money.zero(this.currency), interestPostingUpToDate); - } - - this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); - } - - public void postPreMaturityInterest(final LocalDate accountCloseDate, final boolean isPreMatureClosure, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, - final Integer financialYearBeginningMonth) { - - final Money interestPostedToDate = totalInterestPosted(); - // calculate interest before one day of closure date - final LocalDate interestCalculatedToDate = accountCloseDate.minusDays(1); - final Money interestOnMaturity = calculatePreMatureInterest(interestCalculatedToDate, - retreiveOrderedNonInterestPostingTransactions(), isPreMatureClosure, - isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); - - boolean recalucateDailyBalance = false; - - // post remaining interest - final Money remainigInterestToBePosted = interestOnMaturity.minus(interestPostedToDate); - if (!remainigInterestToBePosted.isZero()) { - final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(), - accountCloseDate, remainigInterestToBePosted); - this.transactions.add(newPostingTransaction); - recalucateDailyBalance = true; - } - - if (recalucateDailyBalance) { - // update existing transactions so derived balance fields are - // correct. - recalculateDailyBalances(Money.zero(this.currency), accountCloseDate); - } - - this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); - this.accountTermAndPreClosure.updateMaturityDetails(this.getAccountBalance(), accountCloseDate); - } - - public BigDecimal calculatePreMatureAmount(final LocalDate preMatureDate, final boolean isPreMatureClosure, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, - final Integer financialYearBeginningMonth) { - - final Money interestPostedToDate = totalInterestPosted().copy(); - - final Money interestEarnedTillDate = calculatePreMatureInterest(preMatureDate, retreiveOrderedNonInterestPostingTransactions(), - isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); - - final Money accountBalance = Money.of(getCurrency(), getAccountBalance()); - final Money maturityAmount = accountBalance.minus(interestPostedToDate).plus(interestEarnedTillDate); - - return maturityAmount.getAmount(); - } - - private Money calculatePreMatureInterest(final LocalDate preMatureDate, final List transactions, - final boolean isPreMatureClosure, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, - final Integer financialYearBeginningMonth) { - final MathContext mc = MathContext.DECIMAL64; - final List postingPeriods = calculateInterestPayable(mc, preMatureDate, transactions, - isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); - - Money interestOnMaturity = Money.zero(this.currency); - - for (final PostingPeriod interestPostingPeriod : postingPeriods) { - final Money interestEarnedForPeriod = interestPostingPeriod.getInterestEarned(); - interestOnMaturity = interestOnMaturity.plus(interestEarnedForPeriod); - } - this.summary.updateFromInterestPeriodSummaries(this.currency, postingPeriods); - return interestOnMaturity; - } - - @Override - public void postInterest(final MathContext mc, final LocalDate postingDate, final boolean isInterestTransfer, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { - final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate); - super.postInterest(mc, interestPostingUpToDate, isInterestTransfer, - isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); - } - - @Override - public List calculateInterestUsing(final MathContext mc, final LocalDate postingDate, - boolean isInterestTransfer, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, - final Integer financialYearBeginningMonth) { - final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate); - return super.calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer, - isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); - } - - private LocalDate interestPostingUpToDate(final LocalDate interestPostingDate) { - LocalDate interestPostingUpToDate = interestPostingDate; - final LocalDate uptoMaturityDate = interestCalculatedUpto(); - if (uptoMaturityDate != null && uptoMaturityDate.isBefore(interestPostingDate)) { - interestPostingUpToDate = uptoMaturityDate; - } - return interestPostingUpToDate; - } - - public LocalDate maturityDate() { - return this.accountTermAndPreClosure.getMaturityLocalDate(); - } - - public BigDecimal maturityAmount() { - return this.accountTermAndPreClosure.maturityAmount(); - } - - private LocalDate interestCalculatedUpto() { - LocalDate uptoMaturityDate = calculateMaturityDate(); - if (uptoMaturityDate != null) { - // interest should not be calculated for maturity day - uptoMaturityDate = uptoMaturityDate.minusDays(1); - } - return uptoMaturityDate; - } - - private Money totalInterestPosted() { - Money interestPostedToDate = Money.zero(this.currency); - for (final SavingsAccountTransaction transaction : this.transactions) { - if (transaction.isInterestPostingAndNotReversed()) { - interestPostedToDate = interestPostedToDate.plus(transaction.getAmount(currency)); - } - } - - return interestPostedToDate; - } - - @Override - public Map activate(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate) { - - final Map actualChanges = super.activate(currentUser, command, tenantsTodayDate); - - if (accountTermAndPreClosure.isAfterExpectedFirstDepositDate(getActivationLocalDate())) { - final List dataValidationErrors = new ArrayList<>(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) - .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME); - final DateTimeFormatter formatter = DateTimeFormat.forPattern(command.dateFormat()).withLocale(command.extractLocale()); - final String dateAsString = formatter.print(this.accountTermAndPreClosure.getExpectedFirstDepositOnDate()); - baseDataValidator.reset().parameter(DepositsApiConstants.activatedOnDateParamName).value(dateAsString) - .failWithCodeNoParameterAddedToErrorCode("cannot.be.before.expected.first.deposit.date"); - if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } - } - - return actualChanges; - } - - protected List sortTransactions(final List transactions) { - final List listOfTransactionsSorted = new ArrayList<>(); - listOfTransactionsSorted.addAll(transactions); - - final SavingsAccountTransactionComparator transactionComparator = new SavingsAccountTransactionComparator(); - Collections.sort(listOfTransactionsSorted, transactionComparator); - return listOfTransactionsSorted; - } - - @Override - public SavingsAccountTransaction deposit(final SavingsAccountTransactionDTO transactionDTO) { - - if (isAccountMatured()) { - final String defaultUserMessage = "Transaction is not allowed. Account is matured."; - final ApiParameterError error = ApiParameterError.parameterError( - "error.msg.recurring.deposit.account.transaction.account.is.matured", defaultUserMessage, "transactionDate", - transactionDTO.getTransactionDate().toString(transactionDTO.getFormatter())); - - final List dataValidationErrors = new ArrayList<>(); - dataValidationErrors.add(error); - - throw new PlatformApiDataValidationException(dataValidationErrors); - } - - if (!isBeforeMaturityDate(transactionDTO.getTransactionDate())) { - final String defaultUserMessage = "Transaction is not allowed. Transaction date is on or after account maturity date."; - final ApiParameterError error = ApiParameterError.parameterError( - "error.msg.recurring.deposit.account.transaction.date.is.after.account.maturity.date", defaultUserMessage, - "transactionDate", transactionDTO.getTransactionDate().toString(transactionDTO.getFormatter())); - - final List dataValidationErrors = new ArrayList<>(); - dataValidationErrors.add(error); - - throw new PlatformApiDataValidationException(dataValidationErrors); - } - - if (isBeforeDepositStartDate(transactionDTO.getTransactionDate())) { - final String defaultUserMessage = "Transaction is not allowed. Transaction date is on or after account activation and deposit start date."; - final ApiParameterError error = ApiParameterError.parameterError( - "error.msg.recurring.deposit.account.transaction.date.is.before.account.activation.or.deposit.date", - defaultUserMessage, "transactionDate", transactionDTO.getTransactionDate().toString(transactionDTO.getFormatter())); - - final List dataValidationErrors = new ArrayList<>(); - dataValidationErrors.add(error); - - throw new PlatformApiDataValidationException(dataValidationErrors); - } - - final SavingsAccountTransaction transaction = super.deposit(transactionDTO); - - return transaction; - } - - public void handleScheduleInstallments(final SavingsAccountTransaction transaction) { - - final LocalDate transactionDate = transaction.transactionLocalDate(); - Money transactionAmountUnprocessed = transaction.getAmount(getCurrency()); - - for (RecurringDepositScheduleInstallment currentInstallment : depositScheduleInstallments()) { - if (currentInstallment.isNotFullyPaidOff() && transactionAmountUnprocessed.isGreaterThanZero()) { - if (!this.adjustAdvanceTowardsFuturePayments() && currentInstallment.dueDate().isAfter(transactionDate)) { - transactionAmountUnprocessed = Money.zero(getCurrency()); - } - transactionAmountUnprocessed = handleInstallmentTransaction(currentInstallment, transactionAmountUnprocessed, - transactionDate); - } - } - - } - - public void updateScheduleInstallments() { - - // reset all installments to process from the beginning - for (RecurringDepositScheduleInstallment currentInstallment : depositScheduleInstallments()) { - currentInstallment.resetDerivedFields(); - } - - final List orderedDepositTransactions = retreiveOrderedDepositTransactions(); - for (SavingsAccountTransaction transaction : orderedDepositTransactions) { - handleScheduleInstallments(transaction); - } - } - - private List retreiveOrderedDepositTransactions() { - final List listOfTransactionsSorted = retreiveListOfTransactions(); - - final List orderedDepositTransactions = new ArrayList<>(); - - for (final SavingsAccountTransaction transaction : listOfTransactionsSorted) { - if (transaction.isDepositAndNotReversed()) { - orderedDepositTransactions.add(transaction); - } - } - - return orderedDepositTransactions; - } - - /** - * This method is responsible for checking if the current transaction is 'an - * advance/early payment' based on the details passed through. - * - * Default implementation is check transaction date is before installment - * due date. - */ - protected boolean isTransactionInAdvanceOfInstallment(final int currentInstallmentIndex, - final List installments, final LocalDate transactionDate) { - - final RecurringDepositScheduleInstallment currentInstallment = installments.get(currentInstallmentIndex); - - return transactionDate.isBefore(currentInstallment.dueDate()); - } - - private Money handleInstallmentTransaction(final RecurringDepositScheduleInstallment currentInstallment, - final Money transactionAmountUnprocessed, final LocalDate transactionDate) { - - Money transactionAmountRemaining = transactionAmountUnprocessed; - Money depositAmountPortion = Money.zero(transactionAmountRemaining.getCurrency()); - - depositAmountPortion = currentInstallment.payInstallment(transactionDate, transactionAmountRemaining); - transactionAmountRemaining = transactionAmountRemaining.minus(depositAmountPortion); - - return transactionAmountRemaining; - - } - - private boolean isAccountMatured() { - return SavingsAccountStatusType.fromInt(status).isMatured(); - } - - private boolean isBeforeMaturityDate(final LocalDate compareDate) { - final LocalDate maturityDate = this.maturityDate(); - return compareDate.isBefore(maturityDate); - } - - private boolean isBeforeDepositStartDate(LocalDate compareDate) { - return compareDate.isBefore(depositStartDate()); - } - - public void validateDomainRules() { - final List dataValidationErrors = new ArrayList<>(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) - .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME); - validateDomainRules(baseDataValidator); - super.validateInterestPostingAndCompoundingPeriodTypes(baseDataValidator); - if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } - } - - private void validateDomainRules(final DataValidatorBuilder baseDataValidator) { - - final boolean isMinTermGreaterThanMax = this.accountTermAndPreClosure.depositTermDetail() - .isMinDepositTermGreaterThanMaxDepositTerm(); - // deposit period should be within min and max deposit term - if (isMinTermGreaterThanMax) { - final Integer maxTerm = this.accountTermAndPreClosure.depositTermDetail().maxDepositTerm(); - baseDataValidator.reset().parameter(maxDepositTermParamName).value(maxTerm) - .failWithCodeNoParameterAddedToErrorCode("max.term.lessthan.min.term"); - } - - final Integer depositPeriod = this.accountTermAndPreClosure.depositPeriod(); - final SavingsPeriodFrequencyType depositPeriodFrequencyType = this.accountTermAndPreClosure.depositPeriodFrequencyType(); - final boolean isValidDepositPeriod = this.accountTermAndPreClosure.depositTermDetail().isDepositBetweenMinAndMax( - depositStartDate(), calculateMaturityDate()); - if (!isValidDepositPeriod) { - baseDataValidator.reset().parameter(depositPeriodParamName).value(depositPeriod) - .failWithCodeNoParameterAddedToErrorCode("deposit.period.not.between.min.and.max.deposit.term"); - } else { - final Integer inMultiplesOf = this.accountTermAndPreClosure.depositTermDetail().inMultiplesOfDepositTerm(); - if (inMultiplesOf != null) { - final boolean isValid = this.accountTermAndPreClosure.depositTermDetail().isValidInMultiplesOfPeriod(depositPeriod, - depositPeriodFrequencyType); - if (!isValid) { - baseDataValidator.reset().parameter(depositPeriodParamName).value(depositPeriod) - .failWithCodeNoParameterAddedToErrorCode("deposit.period.not.multiple.of.term"); - } - } - } - if (isAccountLocked(calculateMaturityDate())) { - baseDataValidator - .reset() - .parameter(depositPeriodParamName) - .value(depositPeriod) - .failWithCode("deposit.period.must.be.greater.than.lock.in.period", - "Deposit period must be greater than account lock-in period."); - } - - if (firstDepositDateBeforeAccountSubmittedOrActivationDate()) { - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( - "expected.first.deposit.date.must.be.after.account.submitted.or.activation.date"); - } - - // FIXME: Handle this scenario - /* - * //final boolean recurringFrequencyBeforeDepositPeriod = - * recurringFrequencyBeforeDepositPeriod(); - * - * if (!recurringFrequencyBeforeDepositPeriod) { - * baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( - * "recurring.frequency.not.before.deposit.period"); } - */ - } - - public void validateApplicableInterestRate() { - final List dataValidationErrors = new ArrayList<>(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) - .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME); - - if (this.chart != null) { - final LocalDate chartFromDate = this.chart.getFromDateAsLocalDate(); - LocalDate chartEndDate = this.chart.getEndDateAsLocalDate(); - chartEndDate = chartEndDate == null ? DateUtils.getLocalDateOfTenant() : chartEndDate; - - final LocalDateInterval chartInterval = LocalDateInterval.create(chartFromDate, chartEndDate); - if (!chartInterval.contains(accountSubmittedOrActivationDate())) { - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("no.valid.interest.rate.slab.available.for.date.range"); - } - - final BigDecimal maturityAmount = this.accountTermAndPreClosure.maturityAmount(); - BigDecimal applicableInterestRate = this.chart.getApplicableInterestRate(maturityAmount, depositStartDate(), - calculateMaturityDate(), this.client); - - if (applicableInterestRate.equals(BigDecimal.ZERO)) { - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( - "no.applicable.interest.rate.is.found.based.on.amount.and.deposit.period"); - } - - } else if (this.nominalAnnualInterestRate == null || this.nominalAnnualInterestRate.compareTo(BigDecimal.ZERO) == 0) { - baseDataValidator.reset().parameter(DepositsApiConstants.nominalAnnualInterestRateParamName).value(nominalAnnualInterestRate) - .failWithCodeNoParameterAddedToErrorCode("interest.chart.or.nominal.interest.rate.required"); - } - - /** - * final boolean recurringFrequencyBeforeDepositPeriod = - * recurringFrequencyBeforeDepositPeriod(); - * - * if (!recurringFrequencyBeforeDepositPeriod) { - * baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( - * "recurring.frequency.not.before.deposit.period"); } - **/ - } - - public boolean isReinvestOnClosure() { - return this.accountTermAndPreClosure.isReinvestOnClosure(); - } - - public boolean isTransferToSavingsOnClosure() { - return this.accountTermAndPreClosure.isTransferToSavingsOnClosure(); - } - - public RecurringDepositAccount reInvest(BigDecimal depositAmount) { - - final DepositAccountTermAndPreClosure newAccountTermAndPreClosure = this.accountTermAndPreClosure.copy(depositAmount); - final DepositAccountRecurringDetail recurringDetail = this.recurringDetail.copy(); - final SavingsProduct product = this.product; - final InterestRateChart productChart = product.applicableChart(getClosedOnDate()); - final DepositAccountInterestRateChart newChart = DepositAccountInterestRateChart.from(productChart); - final String accountNumber = null; - final String externalId = this.externalId; - final AccountType accountType = AccountType.fromInt(this.accountType); - final SavingsPostingInterestPeriodType postingPeriodType = SavingsPostingInterestPeriodType.fromInt(this.interestPostingPeriodType); - final SavingsCompoundingInterestPeriodType compoundingPeriodType = SavingsCompoundingInterestPeriodType - .fromInt(this.interestCompoundingPeriodType); - final SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType.fromInt(this.interestCalculationType); - final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType - .fromInt(this.interestCalculationDaysInYearType); - final BigDecimal minRequiredOpeningBalance = depositAmount; - final BigDecimal interestRate = BigDecimal.ZERO; - final Set savingsAccountCharges = null; - final SavingsPeriodFrequencyType lockinPeriodFrequencyType = SavingsPeriodFrequencyType.fromInt(this.lockinPeriodFrequencyType); - final Integer lockinPeriodFrequency = this.lockinPeriodFrequency; - final boolean withdrawalFeeApplicableForTransfer = false; - - LocalDate now = DateUtils.getLocalDateOfTenant(); - - newAccountTermAndPreClosure.updateExpectedFirstDepositDate(now); - - RecurringDepositAccount rdAccount = RecurringDepositAccount.createNewActivatedAccount(client, group, product, savingsOfficer, - accountNumber, externalId, accountType, getClosedOnDate(), closedBy, interestRate, compoundingPeriodType, - postingPeriodType, interestCalculationType, daysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, - lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, savingsAccountCharges, newAccountTermAndPreClosure, - recurringDetail, newChart); - - rdAccount.setDatesFrom(now); - newAccountTermAndPreClosure.updateAccountReference(rdAccount); - recurringDetail.updateAccountReference(rdAccount); - - return rdAccount; - - } - - private boolean firstDepositDateBeforeAccountSubmittedOrActivationDate() { - final LocalDate expectedFirstDepositLocalDate = accountTermAndPreClosure.getExpectedFirstDepositOnDate(); - if (expectedFirstDepositLocalDate == null) return false; - return expectedFirstDepositLocalDate.isBefore(accountSubmittedOrActivationDate()); - } - - public void setDatesFrom(final LocalDate now) { - this.rejectedOnDate = null; - this.rejectedBy = null; - this.withdrawnOnDate = null; - this.withdrawnBy = null; - this.closedOnDate = null; - this.closedBy = null; - - this.activatedBy = null; - this.lockedInUntilDate = null; - - this.activatedOnDate = now.toDate(); - } - - @Override - protected boolean isTransferInterestToOtherAccount() { - return this.accountTermAndPreClosure.isTransferInterestToLinkedAccount(); - } - - public void generateSchedule(final PeriodFrequencyType frequency, final Integer recurringEvery, final Calendar calendar) { - final List depositScheduleInstallments = depositScheduleInstallments(); - depositScheduleInstallments.clear(); - LocalDate installmentDate = null; - if (this.isCalendarInherited()) { - installmentDate = CalendarUtils.getNextScheduleDate(calendar, accountSubmittedOrActivationDate()); - } else { - installmentDate = depositStartDate(); - } - - final LocalDate maturityDate = calculateMaturityDate(); - int installmentNumber = 1; - final BigDecimal depositAmount = this.recurringDetail.mandatoryRecommendedDepositAmount(); - while (maturityDate.isAfter(installmentDate)) { - final RecurringDepositScheduleInstallment installment = RecurringDepositScheduleInstallment.installment(this, - installmentNumber, installmentDate.toDate(), depositAmount); - depositScheduleInstallments.add(installment); - installmentDate = calculateNextDepositDate(installmentDate, frequency, recurringEvery); - installmentNumber += 1; - } - } - - private List depositScheduleInstallments() { - if (this.depositScheduleInstallments == null) { - this.depositScheduleInstallments = new ArrayList<>(); - } - return this.depositScheduleInstallments; - } - - public boolean isCalendarInherited() { - return this.recurringDetail.isCalendarInherited(); - } - - public void updateOverduePayments(final LocalDate todayDate) { - LocalDate overdueUptoDate = this.maturityDate(); - if (overdueUptoDate.isAfter(todayDate)) { - overdueUptoDate = todayDate; - } - - final List installments = depositScheduleInstallments(); - int noOfOverdueInstallments = 0; - Money totalOverdueAmount = Money.zero(getCurrency()); - for (RecurringDepositScheduleInstallment installment : installments) { - if (installment.isNotFullyPaidOff() && overdueUptoDate.isAfter(installment.dueDate())) { - noOfOverdueInstallments++; - totalOverdueAmount = totalOverdueAmount.plus(installment.getDepositAmountOutstanding(getCurrency())); - } - } - this.recurringDetail.updateOverdueDetails(noOfOverdueInstallments, totalOverdueAmount); - } - - @Override - public boolean allowWithdrawal() { - return this.recurringDetail.allowWithdrawal(); - } - - public boolean adjustAdvanceTowardsFuturePayments() { - return this.recurringDetail.adjustAdvanceTowardsFuturePayments(); - } - - @Override - public boolean isTransactionsAllowed() { - return isActive() || isAccountMatured(); - } + @OneToOne(mappedBy = "account", cascade = CascadeType.ALL) + private DepositAccountTermAndPreClosure accountTermAndPreClosure; + + @OneToOne(mappedBy = "account", cascade = CascadeType.ALL) + private DepositAccountRecurringDetail recurringDetail; + + @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "account") + private DepositAccountInterestRateChart chart; + + @LazyCollection(LazyCollectionOption.FALSE) + @OneToMany(cascade = CascadeType.ALL, mappedBy = "account", orphanRemoval = true) + private List depositScheduleInstallments = new ArrayList<>(); + + protected RecurringDepositAccount() { + // + } + + public static RecurringDepositAccount createNewApplicationForSubmittal( + final Client client, + final Group group, + final SavingsProduct product, + final Staff fieldOfficer, + final String accountNo, + final String externalId, + final AccountType accountType, + final LocalDate submittedOnDate, + final AppUser submittedBy, + final BigDecimal interestRate, + final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, + final SavingsPostingInterestPeriodType interestPostingPeriodType, + final SavingsInterestCalculationType interestCalculationType, + final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, + final BigDecimal minRequiredOpeningBalance, + final Integer lockinPeriodFrequency, + final SavingsPeriodFrequencyType lockinPeriodFrequencyType, + final boolean withdrawalFeeApplicableForTransfer, + final Set savingsAccountCharges, + final DepositAccountTermAndPreClosure accountTermAndPreClosure, + final DepositAccountRecurringDetail recurringDetail, + final DepositAccountInterestRateChart chart) { + + final boolean allowOverdraft = false; + final BigDecimal overdraftLimit = new BigDecimal(0); + + final SavingsAccountStatusType status = SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL; + return new RecurringDepositAccount(client, group, product, + fieldOfficer, accountNo, externalId, status, accountType, + submittedOnDate, submittedBy, interestRate, + interestCompoundingPeriodType, interestPostingPeriodType, + interestCalculationType, interestCalculationDaysInYearType, + minRequiredOpeningBalance, lockinPeriodFrequency, + lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, + savingsAccountCharges, accountTermAndPreClosure, + recurringDetail, chart, allowOverdraft, overdraftLimit); + } + + public static RecurringDepositAccount createNewActivatedAccount( + final Client client, + final Group group, + final SavingsProduct product, + final Staff fieldOfficer, + final String accountNo, + final String externalId, + final AccountType accountType, + final LocalDate submittedOnDate, + final AppUser submittedBy, + final BigDecimal interestRate, + final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, + final SavingsPostingInterestPeriodType interestPostingPeriodType, + final SavingsInterestCalculationType interestCalculationType, + final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, + final BigDecimal minRequiredOpeningBalance, + final Integer lockinPeriodFrequency, + final SavingsPeriodFrequencyType lockinPeriodFrequencyType, + final boolean withdrawalFeeApplicableForTransfer, + final Set savingsAccountCharges, + final DepositAccountTermAndPreClosure accountTermAndPreClosure, + final DepositAccountRecurringDetail recurringDetail, + final DepositAccountInterestRateChart chart) { + + final boolean allowOverdraft = false; + final BigDecimal overdraftLimit = new BigDecimal(0); + + final SavingsAccountStatusType status = SavingsAccountStatusType.ACTIVE; + return new RecurringDepositAccount(client, group, product, + fieldOfficer, accountNo, externalId, status, accountType, + submittedOnDate, submittedBy, interestRate, + interestCompoundingPeriodType, interestPostingPeriodType, + interestCalculationType, interestCalculationDaysInYearType, + minRequiredOpeningBalance, lockinPeriodFrequency, + lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, + savingsAccountCharges, accountTermAndPreClosure, + recurringDetail, chart, allowOverdraft, overdraftLimit); + } + + private RecurringDepositAccount( + final Client client, + final Group group, + final SavingsProduct product, + final Staff fieldOfficer, + final String accountNo, + final String externalId, + final SavingsAccountStatusType status, + final AccountType accountType, + final LocalDate submittedOnDate, + final AppUser submittedBy, + final BigDecimal nominalAnnualInterestRate, + final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, + final SavingsPostingInterestPeriodType interestPostingPeriodType, + final SavingsInterestCalculationType interestCalculationType, + final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, + final BigDecimal minRequiredOpeningBalance, + final Integer lockinPeriodFrequency, + final SavingsPeriodFrequencyType lockinPeriodFrequencyType, + final boolean withdrawalFeeApplicableForTransfer, + final Set savingsAccountCharges, + final DepositAccountTermAndPreClosure accountTermAndPreClosure, + final DepositAccountRecurringDetail recurringDetail, + final DepositAccountInterestRateChart chart, + final boolean allowOverdraft, final BigDecimal overdraftLimit) { + + super(client, group, product, fieldOfficer, accountNo, externalId, + status, accountType, submittedOnDate, submittedBy, + nominalAnnualInterestRate, interestCompoundingPeriodType, + interestPostingPeriodType, interestCalculationType, + interestCalculationDaysInYearType, minRequiredOpeningBalance, + lockinPeriodFrequency, lockinPeriodFrequencyType, + withdrawalFeeApplicableForTransfer, savingsAccountCharges, + allowOverdraft, overdraftLimit); + + this.accountTermAndPreClosure = accountTermAndPreClosure; + this.recurringDetail = recurringDetail; + this.chart = chart; + if (this.chart != null) { + this.chart.updateDepositAccountReference(this); + } + } + + @Override + public void modifyApplication(final JsonCommand command, + final Map actualChanges) { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder( + dataValidationErrors) + .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME + + SavingsApiConstants.modifyApplicationAction); + super.modifyApplication(command, actualChanges, baseDataValidator); + final Map termAndPreClosureChanges = accountTermAndPreClosure + .update(command, baseDataValidator); + actualChanges.putAll(termAndPreClosureChanges); + recurringDetail.update(command); + + validateDomainRules(baseDataValidator); + super.validateInterestPostingAndCompoundingPeriodTypes(baseDataValidator); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + + @Override + protected BigDecimal getEffectiveInterestRateAsFraction( + final MathContext mc, final LocalDate interestPostingUpToDate) { + boolean isPreMatureClosure = false; + return getEffectiveInterestRateAsFraction(mc, interestPostingUpToDate, + isPreMatureClosure); + } + + protected BigDecimal getEffectiveInterestRateAsFraction( + final MathContext mc, final LocalDate interestPostingUpToDate, + final boolean isPreMatureClosure) { + + boolean applyPreMaturePenalty = false; + BigDecimal penalInterest = BigDecimal.ZERO; + LocalDate depositCloseDate = calculateMaturityDate(); + if (isPreMatureClosure) { + if (this.accountTermAndPreClosure.isPreClosurePenalApplicable()) { + applyPreMaturePenalty = true; + penalInterest = this.accountTermAndPreClosure + .depositPreClosureDetail().preClosurePenalInterest(); + final PreClosurePenalInterestOnType preClosurePenalInterestOnType = this.accountTermAndPreClosure + .depositPreClosureDetail() + .preClosurePenalInterestOnType(); + if (preClosurePenalInterestOnType.isWholeTerm()) { + depositCloseDate = interestCalculatedUpto(); + } else if (preClosurePenalInterestOnType + .isTillPrematureWithdrawal()) { + depositCloseDate = interestPostingUpToDate; + } + } + } + + final BigDecimal depositAmount = accountTermAndPreClosure + .depositAmount(); + BigDecimal applicableInterestRate = this.chart + .getApplicableInterestRate(depositAmount, depositStartDate(), + depositCloseDate, this.client); + + if (applyPreMaturePenalty) { + applicableInterestRate = applicableInterestRate + .subtract(penalInterest); + applicableInterestRate = applicableInterestRate + .compareTo(BigDecimal.ZERO) == -1 ? BigDecimal.ZERO + : applicableInterestRate; + } + + this.nominalAnnualInterestRate = applicableInterestRate; + + return applicableInterestRate.divide(BigDecimal.valueOf(100l), mc); + } + + public void updateMaturityDateAndAmount(final MathContext mc, + final boolean isPreMatureClosure, + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, + final Integer financialYearBeginningMonth) { + final LocalDate maturityDate = calculateMaturityDate(); + final LocalDate interestCalculationUpto = maturityDate.minusDays(1); + final List allTransactions = generateIncludingFutureTransactions(interestCalculationUpto); + + final List postingPeriods = calculateInterestPayable(mc, + interestCalculationUpto, allTransactions, isPreMatureClosure, + isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth); + Money totalInterestPayable = Money.zero(getCurrency()); + Money totalDepositAmount = Money.zero(getCurrency()); + for (PostingPeriod postingPeriod : postingPeriods) { + totalInterestPayable = totalInterestPayable.plus(postingPeriod + .getInterestEarned()); + totalDepositAmount = totalDepositAmount.plus( + postingPeriod.closingBalance()).minus( + postingPeriod.openingBalance()); + } + + this.accountTermAndPreClosure.updateMaturityDetails( + totalDepositAmount.getAmount(), + totalInterestPayable.getAmount(), maturityDate); + } + + public void updateMaturityStatus( + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, + final Integer financialYearBeginningMonth) { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder( + dataValidationErrors) + .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME + + SavingsApiConstants.updateMaturityDetailsAction); + + final SavingsAccountStatusType currentStatus = SavingsAccountStatusType + .fromInt(this.status); + if (!SavingsAccountStatusType.ACTIVE.hasStateOf(currentStatus)) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( + "not.in.active.state"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException( + dataValidationErrors); + } + } + + final LocalDate todayDate = DateUtils.getLocalDateOfTenant(); + if (!this.maturityDate().isAfter(todayDate)) { + // update account status + this.status = SavingsAccountStatusType.MATURED.getValue(); + postMaturityInterest(isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth); + } + } + + public LocalDate calculateMaturityDate() { + + final LocalDate startDate = depositStartDate(); + LocalDate maturityDate = null; + final Integer depositPeriod = this.accountTermAndPreClosure + .depositPeriod(); + switch (this.accountTermAndPreClosure.depositPeriodFrequencyType()) { + case DAYS: + maturityDate = startDate.plusDays(depositPeriod); + break; + case WEEKS: + maturityDate = startDate.plusWeeks(depositPeriod); + break; + case MONTHS: + maturityDate = startDate.plusMonths(depositPeriod); + break; + case YEARS: + maturityDate = startDate.plusYears(depositPeriod); + break; + case INVALID: + break; + } + + return maturityDate; + } + + private List calculateInterestPayable(final MathContext mc, + final LocalDate maturityDate, + final List transactions, + final boolean isPreMatureClosure, + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, + final Integer financialYearBeginningMonth) { + + // 1. default to calculate interest based on entire history OR + // 2. determine latest 'posting period' and find interest credited to + // that period + + // A generate list of EndOfDayBalances (not including interest postings) + final SavingsPostingInterestPeriodType postingPeriodType = SavingsPostingInterestPeriodType + .fromInt(this.interestPostingPeriodType); + + final SavingsCompoundingInterestPeriodType compoundingPeriodType = SavingsCompoundingInterestPeriodType + .fromInt(this.interestCompoundingPeriodType); + + final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType + .fromInt(this.interestCalculationDaysInYearType); + + final List postingPeriodIntervals = this.savingsHelper + .determineInterestPostingPeriods(depositStartDate(), + maturityDate, postingPeriodType, + financialYearBeginningMonth); + + final List allPostingPeriods = new ArrayList<>(); + + Money periodStartingBalance = Money.zero(currency); + + final SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType + .fromInt(this.interestCalculationType); + final BigDecimal interestRateAsFraction = getEffectiveInterestRateAsFraction( + mc, maturityDate, isPreMatureClosure); + final Collection interestPostTransactions = this.savingsHelper + .fetchPostInterestTransactionIds(getId()); + boolean isInterestTransfer = false; + final Money minBalanceForInterestCalculation = Money.of(getCurrency(), + minBalanceForInterestCalculation()); + for (final LocalDateInterval periodInterval : postingPeriodIntervals) { + final PostingPeriod postingPeriod = PostingPeriod.createFrom( + periodInterval, periodStartingBalance, transactions, + this.currency, compoundingPeriodType, + interestCalculationType, interestRateAsFraction, + daysInYearType.getValue(), maturityDate, + interestPostTransactions, isInterestTransfer, + minBalanceForInterestCalculation, + isSavingsInterestPostingAtCurrentPeriodEnd); + + periodStartingBalance = postingPeriod.closingBalance(); + + allPostingPeriods.add(postingPeriod); + } + + this.savingsHelper.calculateInterestForAllPostingPeriods(this.currency, + allPostingPeriods, this.getLockedInUntilLocalDate(), + isTransferInterestToOtherAccount()); + // this.summary.updateFromInterestPeriodSummaries(this.currency, + // allPostingPeriods); + return allPostingPeriods; + } + + private List generateIncludingFutureTransactions( + final LocalDate depositEndDate) { + List allTransactions = new ArrayList<>(); + // add existing transactions + allTransactions.addAll(retreiveOrderedNonInterestPostingTransactions()); + for (RecurringDepositScheduleInstallment installment : depositScheduleInstallments()) { + if (installment.isPrincipalNotCompleted(getCurrency())) { + final SavingsAccountTransaction transaction = SavingsAccountTransaction + .deposit( + null, + office(), + null, + installment.dueDate(), + installment + .getDepositAmountOutstanding(getCurrency()), + installment.dueDate().toDate(), null); + allTransactions.add(transaction); + } + } + + allTransactions = sortTransactions(allTransactions); + Money runningBalance = Money.zero(getCurrency()); + for (final SavingsAccountTransaction transaction : allTransactions) { + if (transaction.isReversed()) { + transaction.zeroBalanceFields(); + } else { + + Money transactionAmount = Money.zero(this.currency); + if (transaction.isCredit()) { + transactionAmount = transactionAmount.plus(transaction + .getAmount(this.currency)); + } else if (transaction.isDebit()) { + transactionAmount = transactionAmount.minus(transaction + .getAmount(this.currency)); + } + + runningBalance = runningBalance.plus(transactionAmount); + transaction.updateRunningBalance(runningBalance); + } + } + // loop over transactions in reverse + LocalDate endOfBalanceDate = depositEndDate; + for (int i = allTransactions.size() - 1; i >= 0; i--) { + final SavingsAccountTransaction transaction = allTransactions + .get(i); + if (transaction.isNotReversed() + && !transaction.isInterestPostingAndNotReversed()) { + transaction.updateCumulativeBalanceAndDates(this.currency, + endOfBalanceDate); + endOfBalanceDate = transaction.transactionLocalDate() + .minusDays(1); + } + } + return allTransactions; + } + + private LocalDate calculateNextDepositDate(final LocalDate lastDepositDate, + final PeriodFrequencyType frequency, final int recurringEvery) { + LocalDate nextDepositDate = lastDepositDate; + + switch (frequency) { + case DAYS: + nextDepositDate = lastDepositDate.plusDays(recurringEvery); + break; + case WEEKS: + nextDepositDate = lastDepositDate.plusWeeks(recurringEvery); + break; + case MONTHS: + nextDepositDate = lastDepositDate.plusMonths(recurringEvery); + break; + case YEARS: + nextDepositDate = lastDepositDate.plusYears(recurringEvery); + break; + case INVALID: + break; + } + return nextDepositDate; + } + + public LocalDate depositStartDate() { + final LocalDate depositStartDate = accountTermAndPreClosure + .getExpectedFirstDepositOnDate(); + if (depositStartDate == null) + return accountSubmittedOrActivationDate(); + return depositStartDate; + } + + public void prematureClosure(final AppUser currentUser, + final JsonCommand command, final LocalDate tenantsTodayDate, + final Map actualChanges) { + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder( + dataValidationErrors) + .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME + + DepositsApiConstants.preMatureCloseAction); + + final SavingsAccountStatusType currentStatus = SavingsAccountStatusType + .fromInt(this.status); + if (!SavingsAccountStatusType.ACTIVE.hasStateOf(currentStatus)) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( + "not.in.active.state"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException( + dataValidationErrors); + } + } + + final Locale locale = command.extractLocale(); + final DateTimeFormatter fmt = DateTimeFormat.forPattern( + command.dateFormat()).withLocale(locale); + final LocalDate closedDate = command + .localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName); + + if (closedDate.isBefore(getActivationLocalDate())) { + baseDataValidator.reset() + .parameter(SavingsApiConstants.closedOnDateParamName) + .value(closedDate) + .failWithCode("must.be.after.activation.date"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException( + dataValidationErrors); + } + } + + if (isAccountLocked(closedDate)) { + baseDataValidator.reset() + .parameter(SavingsApiConstants.closedOnDateParamName) + .value(closedDate) + .failWithCode("must.be.after.lockin.period"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException( + dataValidationErrors); + } + } + + if (closedDate.isAfter(maturityDate())) { + baseDataValidator.reset() + .parameter(SavingsApiConstants.closedOnDateParamName) + .value(closedDate) + .failWithCode("must.be.before.maturity.date"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException( + dataValidationErrors); + } + } + + if (closedDate.isAfter(tenantsTodayDate)) { + baseDataValidator.reset() + .parameter(SavingsApiConstants.closedOnDateParamName) + .value(closedDate).failWithCode("cannot.be.a.future.date"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException( + dataValidationErrors); + } + } + + if (isAccountLocked(calculateMaturityDate())) { + baseDataValidator + .reset() + .failWithCodeNoParameterAddedToErrorCode( + "deposit.period.must.be.greater.than.lock.in.period", + "Deposit period must be greater than account lock-in period."); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException( + dataValidationErrors); + } + } + + final List savingsAccountTransactions = retreiveListOfTransactions(); + if (savingsAccountTransactions.size() > 0) { + final SavingsAccountTransaction accountTransaction = savingsAccountTransactions + .get(savingsAccountTransactions.size() - 1); + if (accountTransaction.isAfter(closedDate)) { + baseDataValidator.reset() + .parameter(SavingsApiConstants.closedOnDateParamName) + .value(closedDate) + .failWithCode("must.be.after.last.transaction.date"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException( + dataValidationErrors); + } + } + } + + validateActivityNotBeforeClientOrGroupTransferDate( + SavingsEvent.SAVINGS_CLOSE_ACCOUNT, closedDate); + this.status = SavingsAccountStatusType.PRE_MATURE_CLOSURE.getValue(); + + final Integer onAccountClosureId = command + .integerValueOfParameterNamed(onAccountClosureIdParamName); + final DepositAccountOnClosureType onClosureType = DepositAccountOnClosureType + .fromInt(onAccountClosureId); + this.accountTermAndPreClosure + .updateOnAccountClosureStatus(onClosureType); + + /* + * // withdraw deposit amount before closing the account final Money + * transactionAmountMoney = Money.of(this.currency, + * this.getAccountBalance()); final SavingsAccountTransaction withdraw = + * SavingsAccountTransaction.withdrawal(this, office(), paymentDetail, + * closedDate, transactionAmountMoney, new Date()); + * this.transactions.add(withdraw); + */ + actualChanges.put(SavingsApiConstants.statusParamName, + SavingsEnumerations.status(this.status)); + actualChanges + .put(SavingsApiConstants.localeParamName, command.locale()); + actualChanges.put(SavingsApiConstants.dateFormatParamName, + command.dateFormat()); + actualChanges.put(SavingsApiConstants.closedOnDateParamName, + closedDate.toString(fmt)); + + this.rejectedOnDate = null; + this.rejectedBy = null; + this.withdrawnOnDate = null; + this.withdrawnBy = null; + this.closedOnDate = closedDate.toDate(); + this.closedBy = currentUser; + this.summary + .updateSummary(this.currency, + this.savingsAccountTransactionSummaryWrapper, + this.transactions); + + } + + @Override + public Money activateWithBalance() { + return Money.of(this.currency, this.minRequiredOpeningBalance); + } + + protected void processAccountUponActivation(final DateTimeFormatter fmt, + final AppUser user) { + final Money minRequiredOpeningBalance = Money.of(this.currency, + this.minRequiredOpeningBalance); + if (minRequiredOpeningBalance.isGreaterThanZero()) { + final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO( + fmt, getActivationLocalDate(), + minRequiredOpeningBalance.getAmount(), null, new Date(), + user); + deposit(transactionDTO); + + // update existing transactions so derived balance fields are + // correct. + recalculateDailyBalances(Money.zero(this.currency), + DateUtils.getLocalDateOfTenant()); + } + } + + public void close(final AppUser currentUser, final JsonCommand command, + final LocalDate tenantsTodayDate, + final Map actualChanges) { + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder( + dataValidationErrors) + .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME + + SavingsApiConstants.closeAction); + + final SavingsAccountStatusType currentStatus = SavingsAccountStatusType + .fromInt(this.status); + if (!SavingsAccountStatusType.MATURED.hasStateOf(currentStatus)) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( + "not.in.matured.state"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException( + dataValidationErrors); + } + } + + final Locale locale = command.extractLocale(); + final DateTimeFormatter fmt = DateTimeFormat.forPattern( + command.dateFormat()).withLocale(locale); + final LocalDate closedDate = command + .localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName); + + if (closedDate.isBefore(getActivationLocalDate())) { + baseDataValidator.reset() + .parameter(SavingsApiConstants.closedOnDateParamName) + .value(closedDate) + .failWithCode("must.be.after.activation.date"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException( + dataValidationErrors); + } + } + if (closedDate.isBefore(maturityDate())) { + baseDataValidator.reset() + .parameter(SavingsApiConstants.closedOnDateParamName) + .value(closedDate) + .failWithCode("must.be.after.account.maturity.date"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException( + dataValidationErrors); + } + } + if (closedDate.isAfter(tenantsTodayDate)) { + baseDataValidator.reset() + .parameter(SavingsApiConstants.closedOnDateParamName) + .value(closedDate).failWithCode("cannot.be.a.future.date"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException( + dataValidationErrors); + } + } + final List savingsAccountTransactions = retreiveListOfTransactions(); + if (savingsAccountTransactions.size() > 0) { + final SavingsAccountTransaction accountTransaction = savingsAccountTransactions + .get(savingsAccountTransactions.size() - 1); + if (accountTransaction.isAfter(closedDate)) { + baseDataValidator.reset() + .parameter(SavingsApiConstants.closedOnDateParamName) + .value(closedDate) + .failWithCode("must.be.after.last.transaction.date"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException( + dataValidationErrors); + } + } + } + + validateActivityNotBeforeClientOrGroupTransferDate( + SavingsEvent.SAVINGS_CLOSE_ACCOUNT, closedDate); + this.status = SavingsAccountStatusType.CLOSED.getValue(); + + final Integer onAccountClosureId = command + .integerValueOfParameterNamed(onAccountClosureIdParamName); + final DepositAccountOnClosureType onClosureType = DepositAccountOnClosureType + .fromInt(onAccountClosureId); + this.accountTermAndPreClosure + .updateOnAccountClosureStatus(onClosureType); + + /* + * // withdraw deposit amount before closing the account final Money + * transactionAmountMoney = Money.of(this.currency, + * this.getAccountBalance()); final SavingsAccountTransaction withdraw = + * SavingsAccountTransaction.withdrawal(this, office(), paymentDetail, + * closedDate, transactionAmountMoney, new Date()); + * this.transactions.add(withdraw); + */ + + actualChanges.put(SavingsApiConstants.statusParamName, + SavingsEnumerations.status(this.status)); + actualChanges + .put(SavingsApiConstants.localeParamName, command.locale()); + actualChanges.put(SavingsApiConstants.dateFormatParamName, + command.dateFormat()); + actualChanges.put(SavingsApiConstants.closedOnDateParamName, + closedDate.toString(fmt)); + + this.rejectedOnDate = null; + this.rejectedBy = null; + this.withdrawnOnDate = null; + this.withdrawnBy = null; + this.closedOnDate = closedDate.toDate(); + this.closedBy = currentUser; + this.summary + .updateSummary(this.currency, + this.savingsAccountTransactionSummaryWrapper, + this.transactions); + } + + public void postMaturityInterest( + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, + final Integer financialYearBeginningMonth) { + final LocalDate interestPostingUpToDate = maturityDate(); + final MathContext mc = MathContext.DECIMAL64; + boolean isInterestTransfer = false; + final List postingPeriods = calculateInterestUsing(mc, + interestPostingUpToDate, isInterestTransfer, + isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth); + + Money interestPostedToDate = Money.zero(this.currency); + + boolean recalucateDailyBalanceDetails = false; + + for (final PostingPeriod interestPostingPeriod : postingPeriods) { + + LocalDate interestPostingTransactionDate = interestPostingPeriod + .dateOfPostingTransaction(); + interestPostingTransactionDate = interestPostingTransactionDate + .isAfter(interestPostingUpToDate) ? interestPostingUpToDate + : interestPostingTransactionDate; + final Money interestEarnedToBePostedForPeriod = interestPostingPeriod + .getInterestEarned(); + + interestPostedToDate = interestPostedToDate + .plus(interestEarnedToBePostedForPeriod); + + final SavingsAccountTransaction postingTransaction = findInterestPostingTransactionFor(interestPostingTransactionDate); + if (postingTransaction == null) { + final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction + .interestPosting(this, office(), + interestPostingTransactionDate, + interestEarnedToBePostedForPeriod); + this.transactions.add(newPostingTransaction); + recalucateDailyBalanceDetails = true; + } else { + final boolean correctionRequired = postingTransaction + .hasNotAmount(interestEarnedToBePostedForPeriod); + if (correctionRequired) { + postingTransaction.reverse(); + final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction + .interestPosting(this, office(), + interestPostingTransactionDate, + interestEarnedToBePostedForPeriod); + this.transactions.add(newPostingTransaction); + recalucateDailyBalanceDetails = true; + } + } + } + + if (recalucateDailyBalanceDetails) { + // update existing transactions so derived balance fields are + // correct. + recalculateDailyBalances(Money.zero(this.currency), + interestPostingUpToDate); + } + + this.summary + .updateSummary(this.currency, + this.savingsAccountTransactionSummaryWrapper, + this.transactions); + } + + public void postPreMaturityInterest(final LocalDate accountCloseDate, + final boolean isPreMatureClosure, + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, + final Integer financialYearBeginningMonth) { + + final Money interestPostedToDate = totalInterestPosted(); + // calculate interest before one day of closure date + final LocalDate interestCalculatedToDate = accountCloseDate + .minusDays(1); + final Money interestOnMaturity = calculatePreMatureInterest( + interestCalculatedToDate, + retreiveOrderedNonInterestPostingTransactions(), + isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth); + + boolean recalucateDailyBalance = false; + + // post remaining interest + final Money remainigInterestToBePosted = interestOnMaturity + .minus(interestPostedToDate); + if (!remainigInterestToBePosted.isZero()) { + final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction + .interestPosting(this, office(), accountCloseDate, + remainigInterestToBePosted); + this.transactions.add(newPostingTransaction); + recalucateDailyBalance = true; + } + + if (recalucateDailyBalance) { + // update existing transactions so derived balance fields are + // correct. + recalculateDailyBalances(Money.zero(this.currency), + accountCloseDate); + } + + this.summary + .updateSummary(this.currency, + this.savingsAccountTransactionSummaryWrapper, + this.transactions); + this.accountTermAndPreClosure.updateMaturityDetails( + this.getAccountBalance(), accountCloseDate); + } + + public BigDecimal calculatePreMatureAmount(final LocalDate preMatureDate, + final boolean isPreMatureClosure, + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, + final Integer financialYearBeginningMonth) { + + final Money interestPostedToDate = totalInterestPosted().copy(); + + final Money interestEarnedTillDate = calculatePreMatureInterest( + preMatureDate, retreiveOrderedNonInterestPostingTransactions(), + isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth); + + final Money accountBalance = Money.of(getCurrency(), + getAccountBalance()); + final Money maturityAmount = accountBalance.minus(interestPostedToDate) + .plus(interestEarnedTillDate); + + return maturityAmount.getAmount(); + } + + private Money calculatePreMatureInterest(final LocalDate preMatureDate, + final List transactions, + final boolean isPreMatureClosure, + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, + final Integer financialYearBeginningMonth) { + final MathContext mc = MathContext.DECIMAL64; + final List postingPeriods = calculateInterestPayable(mc, + preMatureDate, transactions, isPreMatureClosure, + isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth); + + Money interestOnMaturity = Money.zero(this.currency); + + for (final PostingPeriod interestPostingPeriod : postingPeriods) { + final Money interestEarnedForPeriod = interestPostingPeriod + .getInterestEarned(); + interestOnMaturity = interestOnMaturity + .plus(interestEarnedForPeriod); + } + this.summary.updateFromInterestPeriodSummaries(this.currency, + postingPeriods); + return interestOnMaturity; + } + + @Override + public void postInterest(final MathContext mc, final LocalDate postingDate, + final boolean isInterestTransfer, + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, + final Integer financialYearBeginningMonth) { + final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate); + super.postInterest(mc, interestPostingUpToDate, isInterestTransfer, + isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth); + } + + @Override + public List calculateInterestUsing(final MathContext mc, + final LocalDate postingDate, boolean isInterestTransfer, + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, + final Integer financialYearBeginningMonth) { + final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate); + return super.calculateInterestUsing(mc, interestPostingUpToDate, + isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth); + } + + private LocalDate interestPostingUpToDate( + final LocalDate interestPostingDate) { + LocalDate interestPostingUpToDate = interestPostingDate; + final LocalDate uptoMaturityDate = interestCalculatedUpto(); + if (uptoMaturityDate != null + && uptoMaturityDate.isBefore(interestPostingDate)) { + interestPostingUpToDate = uptoMaturityDate; + } + return interestPostingUpToDate; + } + + public LocalDate maturityDate() { + return this.accountTermAndPreClosure.getMaturityLocalDate(); + } + + public BigDecimal maturityAmount() { + return this.accountTermAndPreClosure.maturityAmount(); + } + + private LocalDate interestCalculatedUpto() { + LocalDate uptoMaturityDate = calculateMaturityDate(); + if (uptoMaturityDate != null) { + // interest should not be calculated for maturity day + uptoMaturityDate = uptoMaturityDate.minusDays(1); + } + return uptoMaturityDate; + } + + private Money totalInterestPosted() { + Money interestPostedToDate = Money.zero(this.currency); + for (final SavingsAccountTransaction transaction : this.transactions) { + if (transaction.isInterestPostingAndNotReversed()) { + interestPostedToDate = interestPostedToDate.plus(transaction + .getAmount(currency)); + } + } + + return interestPostedToDate; + } + + @Override + public Map activate(final AppUser currentUser, + final JsonCommand command, final LocalDate tenantsTodayDate) { + + final Map actualChanges = super.activate(currentUser, + command, tenantsTodayDate); + + if (accountTermAndPreClosure + .isAfterExpectedFirstDepositDate(getActivationLocalDate())) { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder( + dataValidationErrors) + .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME); + final DateTimeFormatter formatter = DateTimeFormat.forPattern( + command.dateFormat()).withLocale(command.extractLocale()); + final String dateAsString = formatter + .print(this.accountTermAndPreClosure + .getExpectedFirstDepositOnDate()); + baseDataValidator + .reset() + .parameter(DepositsApiConstants.activatedOnDateParamName) + .value(dateAsString) + .failWithCodeNoParameterAddedToErrorCode( + "cannot.be.before.expected.first.deposit.date"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException( + dataValidationErrors); + } + } + + return actualChanges; + } + + protected List sortTransactions( + final List transactions) { + final List listOfTransactionsSorted = new ArrayList<>(); + listOfTransactionsSorted.addAll(transactions); + + final SavingsAccountTransactionComparator transactionComparator = new SavingsAccountTransactionComparator(); + Collections.sort(listOfTransactionsSorted, transactionComparator); + return listOfTransactionsSorted; + } + + @Override + public SavingsAccountTransaction deposit( + final SavingsAccountTransactionDTO transactionDTO) { + + if (isAccountMatured()) { + final String defaultUserMessage = "Transaction is not allowed. Account is matured."; + final ApiParameterError error = ApiParameterError + .parameterError( + "error.msg.recurring.deposit.account.transaction.account.is.matured", + defaultUserMessage, + "transactionDate", + transactionDTO.getTransactionDate().toString( + transactionDTO.getFormatter())); + + final List dataValidationErrors = new ArrayList<>(); + dataValidationErrors.add(error); + + throw new PlatformApiDataValidationException(dataValidationErrors); + } + + if (!isBeforeMaturityDate(transactionDTO.getTransactionDate())) { + final String defaultUserMessage = "Transaction is not allowed. Transaction date is on or after account maturity date."; + final ApiParameterError error = ApiParameterError + .parameterError( + "error.msg.recurring.deposit.account.transaction.date.is.after.account.maturity.date", + defaultUserMessage, + "transactionDate", + transactionDTO.getTransactionDate().toString( + transactionDTO.getFormatter())); + + final List dataValidationErrors = new ArrayList<>(); + dataValidationErrors.add(error); + + throw new PlatformApiDataValidationException(dataValidationErrors); + } + + if (isBeforeDepositStartDate(transactionDTO.getTransactionDate())) { + final String defaultUserMessage = "Transaction is not allowed. Transaction date is on or after account activation and deposit start date."; + final ApiParameterError error = ApiParameterError + .parameterError( + "error.msg.recurring.deposit.account.transaction.date.is.before.account.activation.or.deposit.date", + defaultUserMessage, + "transactionDate", + transactionDTO.getTransactionDate().toString( + transactionDTO.getFormatter())); + + final List dataValidationErrors = new ArrayList<>(); + dataValidationErrors.add(error); + + throw new PlatformApiDataValidationException(dataValidationErrors); + } + + final SavingsAccountTransaction transaction = super + .deposit(transactionDTO); + + return transaction; + } + + public void handleScheduleInstallments( + final SavingsAccountTransaction transaction) { + + final LocalDate transactionDate = transaction.transactionLocalDate(); + Money transactionAmountUnprocessed = transaction + .getAmount(getCurrency()); + + for (RecurringDepositScheduleInstallment currentInstallment : depositScheduleInstallments()) { + if (currentInstallment.isNotFullyPaidOff() + && transactionAmountUnprocessed.isGreaterThanZero()) { + if (!this.adjustAdvanceTowardsFuturePayments() + && currentInstallment.dueDate() + .isAfter(transactionDate)) { + transactionAmountUnprocessed = Money.zero(getCurrency()); + } + transactionAmountUnprocessed = handleInstallmentTransaction( + currentInstallment, transactionAmountUnprocessed, + transactionDate); + } + } + + } + + public void updateScheduleInstallments() { + + // reset all installments to process from the beginning + for (RecurringDepositScheduleInstallment currentInstallment : depositScheduleInstallments()) { + currentInstallment.resetDerivedFields(); + } + + final List orderedDepositTransactions = retreiveOrderedDepositTransactions(); + for (SavingsAccountTransaction transaction : orderedDepositTransactions) { + handleScheduleInstallments(transaction); + } + } + + private List retreiveOrderedDepositTransactions() { + final List listOfTransactionsSorted = retreiveListOfTransactions(); + + final List orderedDepositTransactions = new ArrayList<>(); + + for (final SavingsAccountTransaction transaction : listOfTransactionsSorted) { + if (transaction.isDepositAndNotReversed()) { + orderedDepositTransactions.add(transaction); + } + } + + return orderedDepositTransactions; + } + + /** + * This method is responsible for checking if the current transaction is 'an + * advance/early payment' based on the details passed through. + * + * Default implementation is check transaction date is before installment + * due date. + */ + protected boolean isTransactionInAdvanceOfInstallment( + final int currentInstallmentIndex, + final List installments, + final LocalDate transactionDate) { + + final RecurringDepositScheduleInstallment currentInstallment = installments + .get(currentInstallmentIndex); + + return transactionDate.isBefore(currentInstallment.dueDate()); + } + + private Money handleInstallmentTransaction( + final RecurringDepositScheduleInstallment currentInstallment, + final Money transactionAmountUnprocessed, + final LocalDate transactionDate) { + + Money transactionAmountRemaining = transactionAmountUnprocessed; + Money depositAmountPortion = Money.zero(transactionAmountRemaining + .getCurrency()); + + depositAmountPortion = currentInstallment.payInstallment( + transactionDate, transactionAmountRemaining); + transactionAmountRemaining = transactionAmountRemaining + .minus(depositAmountPortion); + + return transactionAmountRemaining; + + } + + private boolean isAccountMatured() { + return SavingsAccountStatusType.fromInt(status).isMatured(); + } + + private boolean isBeforeMaturityDate(final LocalDate compareDate) { + final LocalDate maturityDate = this.maturityDate(); + return compareDate.isBefore(maturityDate); + } + + private boolean isBeforeDepositStartDate(LocalDate compareDate) { + return compareDate.isBefore(depositStartDate()); + } + + public void validateDomainRules() { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder( + dataValidationErrors) + .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME); + validateDomainRules(baseDataValidator); + super.validateInterestPostingAndCompoundingPeriodTypes(baseDataValidator); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + + private void validateDomainRules( + final DataValidatorBuilder baseDataValidator) { + + final boolean isMinTermGreaterThanMax = this.accountTermAndPreClosure + .depositTermDetail() + .isMinDepositTermGreaterThanMaxDepositTerm(); + // deposit period should be within min and max deposit term + if (isMinTermGreaterThanMax) { + final Integer maxTerm = this.accountTermAndPreClosure + .depositTermDetail().maxDepositTerm(); + baseDataValidator + .reset() + .parameter(maxDepositTermParamName) + .value(maxTerm) + .failWithCodeNoParameterAddedToErrorCode( + "max.term.lessthan.min.term"); + } + + final Integer depositPeriod = this.accountTermAndPreClosure + .depositPeriod(); + final SavingsPeriodFrequencyType depositPeriodFrequencyType = this.accountTermAndPreClosure + .depositPeriodFrequencyType(); + final boolean isValidDepositPeriod = this.accountTermAndPreClosure + .depositTermDetail().isDepositBetweenMinAndMax( + depositStartDate(), calculateMaturityDate()); + if (!isValidDepositPeriod) { + baseDataValidator + .reset() + .parameter(depositPeriodParamName) + .value(depositPeriod) + .failWithCodeNoParameterAddedToErrorCode( + "deposit.period.not.between.min.and.max.deposit.term"); + } else { + final Integer inMultiplesOf = this.accountTermAndPreClosure + .depositTermDetail().inMultiplesOfDepositTerm(); + if (inMultiplesOf != null) { + final boolean isValid = this.accountTermAndPreClosure + .depositTermDetail().isValidInMultiplesOfPeriod( + depositPeriod, depositPeriodFrequencyType); + if (!isValid) { + baseDataValidator + .reset() + .parameter(depositPeriodParamName) + .value(depositPeriod) + .failWithCodeNoParameterAddedToErrorCode( + "deposit.period.not.multiple.of.term"); + } + } + } + if (isAccountLocked(calculateMaturityDate())) { + baseDataValidator + .reset() + .parameter(depositPeriodParamName) + .value(depositPeriod) + .failWithCode( + "deposit.period.must.be.greater.than.lock.in.period", + "Deposit period must be greater than account lock-in period."); + } + + if (firstDepositDateBeforeAccountSubmittedOrActivationDate()) { + baseDataValidator + .reset() + .failWithCodeNoParameterAddedToErrorCode( + "expected.first.deposit.date.must.be.after.account.submitted.or.activation.date"); + } + + // FIXME: Handle this scenario + /* + * //final boolean recurringFrequencyBeforeDepositPeriod = + * recurringFrequencyBeforeDepositPeriod(); + * + * if (!recurringFrequencyBeforeDepositPeriod) { + * baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( + * "recurring.frequency.not.before.deposit.period"); } + */ + } + + public void validateApplicableInterestRate() { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder( + dataValidationErrors) + .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME); + + if (this.chart != null) { + final LocalDate chartFromDate = this.chart.getFromDateAsLocalDate(); + LocalDate chartEndDate = this.chart.getEndDateAsLocalDate(); + chartEndDate = chartEndDate == null ? DateUtils + .getLocalDateOfTenant() : chartEndDate; + + final LocalDateInterval chartInterval = LocalDateInterval.create( + chartFromDate, chartEndDate); + if (!chartInterval.contains(accountSubmittedOrActivationDate())) { + baseDataValidator + .reset() + .failWithCodeNoParameterAddedToErrorCode( + "no.valid.interest.rate.slab.available.for.date.range"); + } + + final BigDecimal maturityAmount = this.accountTermAndPreClosure + .maturityAmount(); + BigDecimal applicableInterestRate = this.chart + .getApplicableInterestRate(maturityAmount, + depositStartDate(), calculateMaturityDate(), + this.client); + + if (applicableInterestRate.equals(BigDecimal.ZERO)) { + baseDataValidator + .reset() + .failWithCodeNoParameterAddedToErrorCode( + "no.applicable.interest.rate.is.found.based.on.amount.and.deposit.period"); + } + + } else if (this.nominalAnnualInterestRate == null + || this.nominalAnnualInterestRate.compareTo(BigDecimal.ZERO) == 0) { + baseDataValidator + .reset() + .parameter( + DepositsApiConstants.nominalAnnualInterestRateParamName) + .value(nominalAnnualInterestRate) + .failWithCodeNoParameterAddedToErrorCode( + "interest.chart.or.nominal.interest.rate.required"); + } + + /** + * final boolean recurringFrequencyBeforeDepositPeriod = + * recurringFrequencyBeforeDepositPeriod(); + * + * if (!recurringFrequencyBeforeDepositPeriod) { + * baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( + * "recurring.frequency.not.before.deposit.period"); } + **/ + } + + public boolean isReinvestOnClosure() { + return this.accountTermAndPreClosure.isReinvestOnClosure(); + } + + public boolean isTransferToSavingsOnClosure() { + return this.accountTermAndPreClosure.isTransferToSavingsOnClosure(); + } + + public RecurringDepositAccount reInvest(BigDecimal depositAmount) { + + final DepositAccountTermAndPreClosure newAccountTermAndPreClosure = this.accountTermAndPreClosure + .copy(depositAmount); + final DepositAccountRecurringDetail recurringDetail = this.recurringDetail + .copy(); + final SavingsProduct product = this.product; + final InterestRateChart productChart = product + .applicableChart(getClosedOnDate()); + final DepositAccountInterestRateChart newChart = DepositAccountInterestRateChart + .from(productChart); + final String accountNumber = null; + final String externalId = this.externalId; + final AccountType accountType = AccountType.fromInt(this.accountType); + final SavingsPostingInterestPeriodType postingPeriodType = SavingsPostingInterestPeriodType + .fromInt(this.interestPostingPeriodType); + final SavingsCompoundingInterestPeriodType compoundingPeriodType = SavingsCompoundingInterestPeriodType + .fromInt(this.interestCompoundingPeriodType); + final SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType + .fromInt(this.interestCalculationType); + final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType + .fromInt(this.interestCalculationDaysInYearType); + final BigDecimal minRequiredOpeningBalance = depositAmount; + final BigDecimal interestRate = BigDecimal.ZERO; + final Set savingsAccountCharges = null; + final SavingsPeriodFrequencyType lockinPeriodFrequencyType = SavingsPeriodFrequencyType + .fromInt(this.lockinPeriodFrequencyType); + final Integer lockinPeriodFrequency = this.lockinPeriodFrequency; + final boolean withdrawalFeeApplicableForTransfer = false; + + LocalDate now = DateUtils.getLocalDateOfTenant(); + + newAccountTermAndPreClosure.updateExpectedFirstDepositDate(now); + + RecurringDepositAccount rdAccount = RecurringDepositAccount + .createNewActivatedAccount(client, group, product, + savingsOfficer, accountNumber, externalId, accountType, + getClosedOnDate(), closedBy, interestRate, + compoundingPeriodType, postingPeriodType, + interestCalculationType, daysInYearType, + minRequiredOpeningBalance, lockinPeriodFrequency, + lockinPeriodFrequencyType, + withdrawalFeeApplicableForTransfer, + savingsAccountCharges, newAccountTermAndPreClosure, + recurringDetail, newChart); + + rdAccount.setDatesFrom(now); + newAccountTermAndPreClosure.updateAccountReference(rdAccount); + recurringDetail.updateAccountReference(rdAccount); + + return rdAccount; + + } + + private boolean firstDepositDateBeforeAccountSubmittedOrActivationDate() { + final LocalDate expectedFirstDepositLocalDate = accountTermAndPreClosure + .getExpectedFirstDepositOnDate(); + if (expectedFirstDepositLocalDate == null) + return false; + return expectedFirstDepositLocalDate + .isBefore(accountSubmittedOrActivationDate()); + } + + public void setDatesFrom(final LocalDate now) { + this.rejectedOnDate = null; + this.rejectedBy = null; + this.withdrawnOnDate = null; + this.withdrawnBy = null; + this.closedOnDate = null; + this.closedBy = null; + + this.activatedBy = null; + this.lockedInUntilDate = null; + + this.activatedOnDate = now.toDate(); + } + + @Override + protected boolean isTransferInterestToOtherAccount() { + return this.accountTermAndPreClosure + .isTransferInterestToLinkedAccount(); + } + + public void generateSchedule(final PeriodFrequencyType frequency, + final Integer recurringEvery, final Calendar calendar) { + final List depositScheduleInstallments = depositScheduleInstallments(); + depositScheduleInstallments.clear(); + LocalDate installmentDate = null; + if (this.isCalendarInherited()) { + installmentDate = CalendarUtils.getNextScheduleDate(calendar, + accountSubmittedOrActivationDate()); + } else { + installmentDate = depositStartDate(); + } + + final LocalDate maturityDate = calculateMaturityDate(); + int installmentNumber = 1; + final BigDecimal depositAmount = this.recurringDetail + .mandatoryRecommendedDepositAmount(); + while (maturityDate.isAfter(installmentDate)) { + final RecurringDepositScheduleInstallment installment = RecurringDepositScheduleInstallment + .installment(this, installmentNumber, + installmentDate.toDate(), depositAmount); + depositScheduleInstallments.add(installment); + installmentDate = calculateNextDepositDate(installmentDate, + frequency, recurringEvery); + installmentNumber += 1; + } + } + + private List depositScheduleInstallments() { + if (this.depositScheduleInstallments == null) { + this.depositScheduleInstallments = new ArrayList<>(); + } + return this.depositScheduleInstallments; + } + + public boolean isCalendarInherited() { + return this.recurringDetail.isCalendarInherited(); + } + + public void updateOverduePayments(final LocalDate todayDate) { + LocalDate overdueUptoDate = this.maturityDate(); + if (overdueUptoDate.isAfter(todayDate)) { + overdueUptoDate = todayDate; + } + + final List installments = depositScheduleInstallments(); + int noOfOverdueInstallments = 0; + Money totalOverdueAmount = Money.zero(getCurrency()); + for (RecurringDepositScheduleInstallment installment : installments) { + if (installment.isNotFullyPaidOff() + && overdueUptoDate.isAfter(installment.dueDate())) { + noOfOverdueInstallments++; + totalOverdueAmount = totalOverdueAmount.plus(installment + .getDepositAmountOutstanding(getCurrency())); + } + } + this.recurringDetail.updateOverdueDetails(noOfOverdueInstallments, + totalOverdueAmount); + } + + @Override + public boolean allowWithdrawal() { + return this.recurringDetail.allowWithdrawal(); + } + + public boolean adjustAdvanceTowardsFuturePayments() { + return this.recurringDetail.adjustAdvanceTowardsFuturePayments(); + } + + @Override + public boolean isTransactionsAllowed() { + return isActive() || isAccountMatured(); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java index 3791700c8..2412be611 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java @@ -738,7 +738,7 @@ public class SavingsAccount extends AbstractPersistable { final Money amount = Money.of(this.currency, transactionDTO.getTransactionAmount()); final SavingsAccountTransaction transaction = SavingsAccountTransaction.deposit(this, office(), transactionDTO.getPaymentDetail(), - transactionDTO.getTransactionDate(), amount, transactionDTO.getCreatedDate()); + transactionDTO.getTransactionDate(), amount, transactionDTO.getCreatedDate(), transactionDTO.getAppUser()); this.transactions.add(transaction); this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); @@ -821,21 +821,23 @@ public class SavingsAccount extends AbstractPersistable { final Money transactionAmountMoney = Money.of(this.currency, transactionDTO.getTransactionAmount()); final SavingsAccountTransaction transaction = SavingsAccountTransaction.withdrawal(this, office(), transactionDTO.getPaymentDetail(), transactionDTO.getTransactionDate(), transactionAmountMoney, - transactionDTO.getCreatedDate()); + transactionDTO.getCreatedDate(), transactionDTO.getAppUser()); this.transactions.add(transaction); if (applyWithdrawFee) { // auto pay withdrawal fee - payWithdrawalFee(transactionDTO.getTransactionAmount(), transactionDTO.getTransactionDate()); + payWithdrawalFee(transactionDTO.getTransactionAmount(), transactionDTO.getTransactionDate(), + transactionDTO.getAppUser()); } return transaction; } - private void payWithdrawalFee(final BigDecimal transactionAmoount, final LocalDate transactionDate) { + private void payWithdrawalFee(final BigDecimal transactionAmoount, final LocalDate transactionDate, + final AppUser user) { for (SavingsAccountCharge charge : this.charges()) { if (charge.isWithdrawalFee() && charge.isActive()) { charge.updateWithdralFeeAmount(transactionAmoount); - this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate); + this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate, user); } } } @@ -1817,7 +1819,7 @@ public class SavingsAccount extends AbstractPersistable { } public void processAccountUponActivation(final boolean isSavingsInterestPostingAtCurrentPeriodEnd, - final Integer financialYearBeginningMonth) { + final Integer financialYearBeginningMonth, final AppUser user) { // update annual fee due date for (SavingsAccountCharge charge : this.charges()) { @@ -1825,7 +1827,7 @@ public class SavingsAccount extends AbstractPersistable { } // auto pay the activation time charges - this.payActivationCharges(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + this.payActivationCharges(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, user); // TODO : AA add activation charges to actual changes list } @@ -1848,12 +1850,13 @@ public class SavingsAccount extends AbstractPersistable { this.lockedInUntilDate = calculateDateAccountIsLockedUntil(getActivationLocalDate()); } - private void payActivationCharges(final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { + private void payActivationCharges(final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, + final AppUser user) { boolean isSavingsChargeApplied = false; for (SavingsAccountCharge savingsAccountCharge : this.charges()) { if (savingsAccountCharge.isSavingsActivation()) { isSavingsChargeApplied = true; - payCharge(savingsAccountCharge, savingsAccountCharge.getAmountOutstanding(getCurrency()), getActivationLocalDate()); + payCharge(savingsAccountCharge, savingsAccountCharge.getAmountOutstanding(getCurrency()), getActivationLocalDate(), user); } } @@ -2037,7 +2040,7 @@ public class SavingsAccount extends AbstractPersistable { this.charges.remove(charge); } - public void waiveCharge(final Long savingsAccountChargeId) { + public void waiveCharge(final Long savingsAccountChargeId, final AppUser user) { final List dataValidationErrors = new ArrayList<>(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) @@ -2076,7 +2079,7 @@ public class SavingsAccount extends AbstractPersistable { // waive charge final Money amountWaived = savingsAccountCharge.waive(getCurrency()); - handleWaiverChargeTransactions(savingsAccountCharge, amountWaived); + handleWaiverChargeTransactions(savingsAccountCharge, amountWaived, user); } @@ -2167,7 +2170,7 @@ public class SavingsAccount extends AbstractPersistable { } public void payCharge(final SavingsAccountCharge savingsAccountCharge, final BigDecimal amountPaid, final LocalDate transactionDate, - final DateTimeFormatter formatter) { + final DateTimeFormatter formatter, final AppUser user) { final List dataValidationErrors = new ArrayList<>(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) @@ -2242,32 +2245,34 @@ public class SavingsAccount extends AbstractPersistable { if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - this.payCharge(savingsAccountCharge, chargePaid, transactionDate); + this.payCharge(savingsAccountCharge, chargePaid, transactionDate, user); } - public void payCharge(final SavingsAccountCharge savingsAccountCharge, final Money amountPaid, final LocalDate transactionDate) { + public void payCharge(final SavingsAccountCharge savingsAccountCharge, final Money amountPaid, final LocalDate transactionDate, + final AppUser user) { savingsAccountCharge.pay(getCurrency(), amountPaid); - handlePayChargeTransactions(savingsAccountCharge, amountPaid, transactionDate); + handlePayChargeTransactions(savingsAccountCharge, amountPaid, transactionDate, user); } private void handlePayChargeTransactions(SavingsAccountCharge savingsAccountCharge, Money transactionAmount, - final LocalDate transactionDate) { + final LocalDate transactionDate, final AppUser user) { SavingsAccountTransaction chargeTransaction = null; if (savingsAccountCharge.isWithdrawalFee()) { - chargeTransaction = SavingsAccountTransaction.withdrawalFee(this, office(), transactionDate, transactionAmount); + chargeTransaction = SavingsAccountTransaction.withdrawalFee(this, office(), transactionDate, transactionAmount, user); } else if (savingsAccountCharge.isAnnualFee()) { - chargeTransaction = SavingsAccountTransaction.annualFee(this, office(), transactionDate, transactionAmount); + chargeTransaction = SavingsAccountTransaction.annualFee(this, office(), transactionDate, transactionAmount, user); } else { - chargeTransaction = SavingsAccountTransaction.charge(this, office(), transactionDate, transactionAmount); + chargeTransaction = SavingsAccountTransaction.charge(this, office(), transactionDate, transactionAmount, user); } handleChargeTransactions(savingsAccountCharge, chargeTransaction); } - private void handleWaiverChargeTransactions(SavingsAccountCharge savingsAccountCharge, Money transactionAmount) { + private void handleWaiverChargeTransactions(SavingsAccountCharge savingsAccountCharge, Money transactionAmount, + AppUser user) { final SavingsAccountTransaction chargeTransaction = SavingsAccountTransaction.waiver(this, office(), - DateUtils.getLocalDateOfTenant(), transactionAmount); + DateUtils.getLocalDateOfTenant(), transactionAmount, user); handleChargeTransactions(savingsAccountCharge, chargeTransaction); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java index 321154b9b..b5c7b6a42 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java @@ -17,6 +17,7 @@ import org.joda.time.format.DateTimeFormatter; import org.mifosplatform.accounting.journalentry.service.JournalEntryWritePlatformService; import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService; import org.mifosplatform.infrastructure.core.service.DateUtils; +import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; @@ -24,6 +25,7 @@ import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail; import org.mifosplatform.portfolio.savings.SavingsTransactionBooleanValues; import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionDTO; import org.mifosplatform.portfolio.savings.exception.DepositAccountTransactionNotAllowedException; +import org.mifosplatform.useradministration.domain.AppUser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -31,6 +33,7 @@ import org.springframework.transaction.annotation.Transactional; @Service public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainService { + private final PlatformSecurityContext context; private final SavingsAccountRepositoryWrapper savingsAccountRepository; private final SavingsAccountTransactionRepository savingsAccountTransactionRepository; private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper; @@ -42,12 +45,14 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi final SavingsAccountTransactionRepository savingsAccountTransactionRepository, final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper, final JournalEntryWritePlatformService journalEntryWritePlatformService, - final ConfigurationDomainService configurationDomainService) { + final ConfigurationDomainService configurationDomainService, + final PlatformSecurityContext context) { this.savingsAccountRepository = savingsAccountRepository; this.savingsAccountTransactionRepository = savingsAccountTransactionRepository; this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper; this.journalEntryWritePlatformService = journalEntryWritePlatformService; this.configurationDomainService = configurationDomainService; + this.context = context; } @Transactional @@ -56,6 +61,10 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final SavingsTransactionBooleanValues transactionBooleanValues) { + AppUser user = null; + if (this.context != null) { + user = this.context.authenticatedUser(); + } final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); final Integer financialYearBeginningMonth = this.configurationDomainService @@ -67,7 +76,7 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi final Set existingReversedTransactionIds = new HashSet<>(); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount, - paymentDetail, new Date()); + paymentDetail, new Date(), user); final SavingsAccountTransaction withdrawal = account.withdraw(transactionDTO, transactionBooleanValues.isApplyWithdrawFee()); final MathContext mc = MathContext.DECIMAL64; @@ -97,6 +106,10 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final boolean isAccountTransfer, final boolean isRegularTransaction) { + AppUser user = null; + if (this.context != null) { + user = this.context.authenticatedUser(); + } final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); final Integer financialYearBeginningMonth = this.configurationDomainService @@ -110,7 +123,7 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi final Set existingReversedTransactionIds = new HashSet<>(); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount, - paymentDetail, new Date()); + paymentDetail, new Date(), user); final SavingsAccountTransaction deposit = account.deposit(transactionDTO); final MathContext mc = MathContext.DECIMAL64; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java index 0a7126218..26c137dd8 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java @@ -37,6 +37,7 @@ import org.mifosplatform.portfolio.savings.SavingsAccountTransactionType; import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionEnumData; import org.mifosplatform.portfolio.savings.domain.interest.EndOfDayBalance; import org.mifosplatform.portfolio.savings.service.SavingsEnumerations; +import org.mifosplatform.useradministration.domain.AppUser; import org.springframework.data.jpa.domain.AbstractPersistable; import org.springframework.util.CollectionUtils; @@ -96,6 +97,10 @@ public final class SavingsAccountTransaction extends AbstractPersistable { @Temporal(TemporalType.TIMESTAMP) @Column(name = "created_date", nullable = false) private final Date createdDate; + + @ManyToOne + @JoinColumn(name = "appuser_id", nullable = false) + private AppUser appUser; protected SavingsAccountTransaction() { this.dateOf = null; @@ -104,97 +109,101 @@ public final class SavingsAccountTransaction extends AbstractPersistable { } public static SavingsAccountTransaction deposit(final SavingsAccount savingsAccount, final Office office, - final PaymentDetail paymentDetail, final LocalDate date, final Money amount, Date createdDate) { + final PaymentDetail paymentDetail, final LocalDate date, final Money amount, Date createdDate, final AppUser appUser) { final boolean isReversed = false; return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, SavingsAccountTransactionType.DEPOSIT.getValue(), date, - createdDate, amount, isReversed); + createdDate, amount, isReversed, appUser); } public static SavingsAccountTransaction withdrawal(final SavingsAccount savingsAccount, final Office office, - final PaymentDetail paymentDetail, final LocalDate date, final Money amount, Date createdDate) { + final PaymentDetail paymentDetail, final LocalDate date, final Money amount, Date createdDate, final AppUser appUser) { final boolean isReversed = false; return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, SavingsAccountTransactionType.WITHDRAWAL.getValue(), - date, createdDate, amount, isReversed); + date, createdDate, amount, isReversed, appUser); } public static SavingsAccountTransaction interestPosting(final SavingsAccount savingsAccount, final Office office, final LocalDate date, final Money amount) { final boolean isReversed = false; return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.INTEREST_POSTING.getValue(), date, - amount, isReversed); + amount, isReversed, null); } public static SavingsAccountTransaction withdrawalFee(final SavingsAccount savingsAccount, final Office office, final LocalDate date, - final Money amount) { + final Money amount, final AppUser appUser) { final boolean isReversed = false; return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.WITHDRAWAL_FEE.getValue(), date, amount, - isReversed); + isReversed, appUser); } public static SavingsAccountTransaction annualFee(final SavingsAccount savingsAccount, final Office office, final LocalDate date, - final Money amount) { + final Money amount, final AppUser appUser) { final boolean isReversed = false; return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.ANNUAL_FEE.getValue(), date, amount, - isReversed); + isReversed, appUser); } public static SavingsAccountTransaction charge(final SavingsAccount savingsAccount, final Office office, final LocalDate date, - final Money amount) { + final Money amount, final AppUser appUser) { final boolean isReversed = false; return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.PAY_CHARGE.getValue(), date, amount, - isReversed); + isReversed, appUser); } public static SavingsAccountTransaction waiver(final SavingsAccount savingsAccount, final Office office, final LocalDate date, - final Money amount) { + final Money amount, final AppUser appUser) { final boolean isReversed = false; return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.WAIVE_CHARGES.getValue(), date, amount, - isReversed); + isReversed, appUser); } - public static SavingsAccountTransaction initiateTransfer(final SavingsAccount savingsAccount, final Office office, final LocalDate date) { + public static SavingsAccountTransaction initiateTransfer(final SavingsAccount savingsAccount, final Office office, final LocalDate date, + final AppUser appUser) { final boolean isReversed = false; final PaymentDetail paymentDetail = null; return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, SavingsAccountTransactionType.INITIATE_TRANSFER.getValue(), date, new Date(), savingsAccount.getSummary() - .getAccountBalance(), isReversed); + .getAccountBalance(), isReversed, appUser); } - public static SavingsAccountTransaction approveTransfer(final SavingsAccount savingsAccount, final Office office, final LocalDate date) { + public static SavingsAccountTransaction approveTransfer(final SavingsAccount savingsAccount, final Office office, final LocalDate date, + final AppUser appUser) { final boolean isReversed = false; final PaymentDetail paymentDetail = null; return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, SavingsAccountTransactionType.APPROVE_TRANSFER.getValue(), date, new Date(), savingsAccount.getSummary() - .getAccountBalance(), isReversed); + .getAccountBalance(), isReversed, appUser); } - public static SavingsAccountTransaction withdrawTransfer(final SavingsAccount savingsAccount, final Office office, final LocalDate date) { + public static SavingsAccountTransaction withdrawTransfer(final SavingsAccount savingsAccount, final Office office, final LocalDate date, + final AppUser appUser) { final boolean isReversed = false; final PaymentDetail paymentDetail = null; return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, SavingsAccountTransactionType.WITHDRAW_TRANSFER.getValue(), date, new Date(), savingsAccount.getSummary() - .getAccountBalance(), isReversed); + .getAccountBalance(), isReversed, appUser); } public static SavingsAccountTransaction copyTransaction(SavingsAccountTransaction accountTransaction) { return new SavingsAccountTransaction(accountTransaction.savingsAccount, accountTransaction.office, accountTransaction.paymentDetail, accountTransaction.typeOf, accountTransaction.transactionLocalDate(), - accountTransaction.createdDate, accountTransaction.amount, accountTransaction.reversed); + accountTransaction.createdDate, accountTransaction.amount, accountTransaction.reversed, accountTransaction.appUser); } private SavingsAccountTransaction(final SavingsAccount savingsAccount, final Office office, final Integer typeOf, - final LocalDate transactionLocalDate, final Money amount, final boolean isReversed) { - this(savingsAccount, office, null, typeOf, transactionLocalDate, new Date(), amount, isReversed); + final LocalDate transactionLocalDate, final Money amount, final boolean isReversed, final AppUser appUser) { + this(savingsAccount, office, null, typeOf, transactionLocalDate, new Date(), amount, isReversed, appUser); } private SavingsAccountTransaction(final SavingsAccount savingsAccount, final Office office, final PaymentDetail paymentDetail, - final Integer typeOf, final LocalDate transactionLocalDate, final Date createdDate, final Money amount, final boolean isReversed) { - this(savingsAccount, office, paymentDetail, typeOf, transactionLocalDate, createdDate, amount.getAmount(), isReversed); + final Integer typeOf, final LocalDate transactionLocalDate, final Date createdDate, final Money amount, final boolean isReversed, + final AppUser appUser) { + this(savingsAccount, office, paymentDetail, typeOf, transactionLocalDate, createdDate, amount.getAmount(), isReversed, appUser); } private SavingsAccountTransaction(final SavingsAccount savingsAccount, final Office office, final PaymentDetail paymentDetail, final Integer typeOf, final LocalDate transactionLocalDate, final Date createdDate, final BigDecimal amount, - final boolean isReversed) { + final boolean isReversed, final AppUser appUser) { this.savingsAccount = savingsAccount; this.office = office; this.typeOf = typeOf; @@ -203,6 +212,7 @@ public final class SavingsAccountTransaction extends AbstractPersistable { this.reversed = isReversed; this.paymentDetail = paymentDetail; this.createdDate = createdDate; + this.appUser = appUser; } public LocalDate transactionLocalDate() { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java index 3e68fe2de..254046539 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java @@ -583,6 +583,11 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo @Override public CommandProcessingResult adjustRDTransaction(final Long savingsId, final Long transactionId, final JsonCommand command) { + + AppUser user = null; + if (this.context != null) { + user = this.context.authenticatedUser(); + } final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); @@ -625,11 +630,11 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo SavingsAccountTransaction transaction = null; if (savingsAccountTransaction.isDeposit()) { final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount, - paymentDetail, savingsAccountTransaction.createdDate()); + paymentDetail, savingsAccountTransaction.createdDate(), user); transaction = account.deposit(transactionDTO); } else { final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount, - paymentDetail, savingsAccountTransaction.createdDate()); + paymentDetail, savingsAccountTransaction.createdDate(), user); transaction = account.withdraw(transactionDTO, true); } final Long newtransactionId = saveTransactionToGenerateTransactionId(transaction); @@ -825,6 +830,10 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo public SavingsAccountTransaction initiateSavingsTransfer(final Long accountId, final LocalDate transferDate, final DepositAccountType depositAccountType) { + AppUser user = null; + if (this.context != null) { + user = this.context.authenticatedUser(); + } final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); @@ -836,7 +845,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo updateExistingTransactionsDetails(savingsAccount, existingTransactionIds, existingReversedTransactionIds); final SavingsAccountTransaction newTransferTransaction = SavingsAccountTransaction.initiateTransfer(savingsAccount, - savingsAccount.office(), transferDate); + savingsAccount.office(), transferDate, user); savingsAccount.getTransactions().add(newTransferTransaction); savingsAccount.setStatus(SavingsAccountStatusType.TRANSFER_IN_PROGRESS.getValue()); final MathContext mc = MathContext.DECIMAL64; @@ -855,6 +864,11 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo @Override public SavingsAccountTransaction withdrawSavingsTransfer(final Long accountId, final LocalDate transferDate, final DepositAccountType depositAccountType) { + + AppUser user = null; + if (this.context != null) { + user = this.context.authenticatedUser(); + } final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); @@ -867,7 +881,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo updateExistingTransactionsDetails(savingsAccount, existingTransactionIds, existingReversedTransactionIds); final SavingsAccountTransaction withdrawtransferTransaction = SavingsAccountTransaction.withdrawTransfer(savingsAccount, - savingsAccount.office(), transferDate); + savingsAccount.office(), transferDate, user); savingsAccount.getTransactions().add(withdrawtransferTransaction); savingsAccount.setStatus(SavingsAccountStatusType.ACTIVE.getValue()); final MathContext mc = MathContext.DECIMAL64; @@ -894,6 +908,11 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo public SavingsAccountTransaction acceptSavingsTransfer(final Long accountId, final LocalDate transferDate, final Office acceptedInOffice, final Staff fieldOfficer, final DepositAccountType depositAccountType) { + AppUser user = null; + if (this.context != null) { + user = this.context.authenticatedUser(); + } + final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); @@ -905,7 +924,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo updateExistingTransactionsDetails(savingsAccount, existingTransactionIds, existingReversedTransactionIds); final SavingsAccountTransaction acceptTransferTransaction = SavingsAccountTransaction.approveTransfer(savingsAccount, - acceptedInOffice, transferDate); + acceptedInOffice, transferDate, user); savingsAccount.getTransactions().add(acceptTransferTransaction); savingsAccount.setStatus(SavingsAccountStatusType.ACTIVE.getValue()); if (fieldOfficer != null) { @@ -1038,7 +1057,10 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo public CommandProcessingResult waiveCharge(final Long savingsAccountId, final Long savingsAccountChargeId, @SuppressWarnings("unused") final DepositAccountType depositAccountType) { - this.context.authenticatedUser(); + AppUser user = null; + if (this.context != null) { + user = this.context.authenticatedUser(); + } final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); @@ -1055,7 +1077,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo final Set existingReversedTransactionIds = new HashSet<>(); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); - account.waiveCharge(savingsAccountChargeId); + account.waiveCharge(savingsAccountChargeId, user); boolean isInterestTransfer = false; final MathContext mc = MathContext.DECIMAL64; if (account.isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate())) { @@ -1109,7 +1131,8 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo public CommandProcessingResult payCharge(final Long savingsAccountId, final Long savingsAccountChargeId, final JsonCommand command, @SuppressWarnings("unused") final DepositAccountType depositAccountType) { - this.context.authenticatedUser(); + this.context.authenticatedUser(); + this.savingsAccountChargeDataValidator.validatePayCharge(command.json()); final Locale locale = command.extractLocale(); final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); @@ -1168,6 +1191,11 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo @Transactional private void payCharge(final SavingsAccountCharge savingsAccountCharge, final LocalDate transactionDate, final BigDecimal amountPaid, final DateTimeFormatter formatter) { + + AppUser user = null; + if (this.context != null) { + user = this.context.authenticatedUser(); + } final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); @@ -1179,7 +1207,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo final Set existingTransactionIds = new HashSet<>(); final Set existingReversedTransactionIds = new HashSet<>(); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); - account.payCharge(savingsAccountCharge, amountPaid, transactionDate, formatter); + account.payCharge(savingsAccountCharge, amountPaid, transactionDate, formatter, user); boolean isInterestTransfer = false; final MathContext mc = MathContext.DECIMAL64; if (account.isBeforeLastPostingPeriod(transactionDate)) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java index 923b284d2..e566d9c47 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java @@ -195,6 +195,11 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi public void processPostActiveActions(final SavingsAccount account, final DateTimeFormatter fmt, final Set existingTransactionIds, final Set existingReversedTransactionIds) { + AppUser user = null; + if (this.context != null) { + user = this.context.authenticatedUser(); + } + final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); @@ -207,7 +212,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi null, isAccountTransfer, isRegularTransaction); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); } - account.processAccountUponActivation(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + account.processAccountUponActivation(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, user); account.validateAccountBalanceDoesNotBecomeNegative(SavingsAccountTransactionType.PAY_CHARGE.name()); } @@ -292,12 +297,17 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi @Override public CommandProcessingResult applyAnnualFee(final Long savingsAccountChargeId, final Long accountId) { + AppUser user = null; + if (this.context != null) { + user = this.context.authenticatedUser(); + } + final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection( savingsAccountChargeId, accountId); final DateTimeFormatter fmt = DateTimeFormat.forPattern("dd MM yyyy"); - this.payCharge(savingsAccountCharge, savingsAccountCharge.getDueLocalDate(), savingsAccountCharge.amount(), fmt); + this.payCharge(savingsAccountCharge, savingsAccountCharge.getDueLocalDate(), savingsAccountCharge.amount(), fmt, user); return new CommandProcessingResultBuilder() // .withEntityId(savingsAccountCharge.getId()) // @@ -461,6 +471,11 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi @Override public CommandProcessingResult adjustSavingsTransaction(final Long savingsId, final Long transactionId, final JsonCommand command) { + AppUser user = null; + if (this.context != null) { + user = this.context.authenticatedUser(); + } + final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); @@ -511,7 +526,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi SavingsAccountTransaction transaction = null; boolean isInterestTransfer = false; final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount, - paymentDetail, savingsAccountTransaction.createdDate()); + paymentDetail, savingsAccountTransaction.createdDate(), user); if (savingsAccountTransaction.isDeposit()) { transaction = account.deposit(transactionDTO); } else { @@ -625,6 +640,11 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi @Override public SavingsAccountTransaction initiateSavingsTransfer(final Long accountId, final LocalDate transferDate) { + AppUser user = null; + if (this.context != null) { + user = this.context.authenticatedUser(); + } + final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); @@ -636,7 +656,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi updateExistingTransactionsDetails(savingsAccount, existingTransactionIds, existingReversedTransactionIds); final SavingsAccountTransaction newTransferTransaction = SavingsAccountTransaction.initiateTransfer(savingsAccount, - savingsAccount.office(), transferDate); + savingsAccount.office(), transferDate, user); savingsAccount.getTransactions().add(newTransferTransaction); savingsAccount.setStatus(SavingsAccountStatusType.TRANSFER_IN_PROGRESS.getValue()); final MathContext mc = MathContext.DECIMAL64; @@ -655,6 +675,11 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi @Override public SavingsAccountTransaction withdrawSavingsTransfer(final Long accountId, final LocalDate transferDate) { + AppUser user = null; + if (this.context != null) { + user = this.context.authenticatedUser(); + } + final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); @@ -666,7 +691,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi updateExistingTransactionsDetails(savingsAccount, existingTransactionIds, existingReversedTransactionIds); final SavingsAccountTransaction withdrawtransferTransaction = SavingsAccountTransaction.withdrawTransfer(savingsAccount, - savingsAccount.office(), transferDate); + savingsAccount.office(), transferDate, user); savingsAccount.getTransactions().add(withdrawtransferTransaction); savingsAccount.setStatus(SavingsAccountStatusType.ACTIVE.getValue()); final MathContext mc = MathContext.DECIMAL64; @@ -693,6 +718,11 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi public SavingsAccountTransaction acceptSavingsTransfer(final Long accountId, final LocalDate transferDate, final Office acceptedInOffice, final Staff fieldOfficer) { + AppUser user = null; + if (this.context != null) { + user = this.context.authenticatedUser(); + } + final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); @@ -704,7 +734,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi updateExistingTransactionsDetails(savingsAccount, existingTransactionIds, existingReversedTransactionIds); final SavingsAccountTransaction acceptTransferTransaction = SavingsAccountTransaction.approveTransfer(savingsAccount, - acceptedInOffice, transferDate); + acceptedInOffice, transferDate, user); savingsAccount.getTransactions().add(acceptTransferTransaction); savingsAccount.setStatus(SavingsAccountStatusType.ACTIVE.getValue()); if (fieldOfficer != null) { @@ -836,7 +866,10 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi @Override public CommandProcessingResult waiveCharge(final Long savingsAccountId, final Long savingsAccountChargeId) { - this.context.authenticatedUser(); + AppUser user = null; + if (this.context != null) { + user = this.context.authenticatedUser(); + } final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); @@ -853,7 +886,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final Set existingReversedTransactionIds = new HashSet<>(); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); - account.waiveCharge(savingsAccountChargeId); + account.waiveCharge(savingsAccountChargeId, user); boolean isInterestTransfer = false; final MathContext mc = MathContext.DECIMAL64; if (account.isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate())) { @@ -906,7 +939,11 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi @Override public CommandProcessingResult payCharge(final Long savingsAccountId, final Long savingsAccountChargeId, final JsonCommand command) { - this.context.authenticatedUser(); + AppUser user = null; + if (this.context != null) { + user = this.context.authenticatedUser(); + } + this.savingsAccountChargeDataValidator.validatePayCharge(command.json()); final Locale locale = command.extractLocale(); final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); @@ -935,7 +972,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - this.payCharge(savingsAccountCharge, transactionDate, amountPaid, fmt); + this.payCharge(savingsAccountCharge, transactionDate, amountPaid, fmt, user); return new CommandProcessingResultBuilder() // .withEntityId(savingsAccountCharge.getId()) // .withOfficeId(savingsAccountCharge.savingsAccount().officeId()) // @@ -950,6 +987,11 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi @Override public void applyChargeDue(final Long savingsAccountChargeId, final Long accountId) { // always use current date as transaction date for batch job + AppUser user = null; + if (this.context != null) { + user = this.context.authenticatedUser(); + } + final LocalDate transactionDate = DateUtils.getLocalDateOfTenant(); final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection( savingsAccountChargeId, accountId); @@ -957,13 +999,13 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final DateTimeFormatter fmt = DateTimeFormat.forPattern("dd MM yyyy"); while (transactionDate.isAfter(savingsAccountCharge.getDueLocalDate()) && savingsAccountCharge.isNotFullyPaid()) { - payCharge(savingsAccountCharge, transactionDate, savingsAccountCharge.amoutOutstanding(), fmt); + payCharge(savingsAccountCharge, transactionDate, savingsAccountCharge.amoutOutstanding(), fmt, user); } } @Transactional private void payCharge(final SavingsAccountCharge savingsAccountCharge, final LocalDate transactionDate, final BigDecimal amountPaid, - final DateTimeFormatter formatter) { + final DateTimeFormatter formatter, final AppUser user) { final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); @@ -975,7 +1017,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final Set existingTransactionIds = new HashSet<>(); final Set existingReversedTransactionIds = new HashSet<>(); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); - account.payCharge(savingsAccountCharge, amountPaid, transactionDate, formatter); + account.payCharge(savingsAccountCharge, amountPaid, transactionDate, formatter, user); boolean isInterestTransfer = false; final MathContext mc = MathContext.DECIMAL64; if (account.isBeforeLastPostingPeriod(transactionDate)) {