MIFOSX-172: WIP - update validation and api around loan charges.

This commit is contained in:
Keith Woodlock
2013-02-20 12:18:33 +00:00
parent 9cc9706df1
commit 7743563597
13 changed files with 291 additions and 288 deletions
@@ -732,7 +732,10 @@ DROP TABLE IF EXISTS `m_group`;
CREATE TABLE `m_group` ( CREATE TABLE `m_group` (
`id` bigint(20) NOT NULL AUTO_INCREMENT, `id` bigint(20) NOT NULL AUTO_INCREMENT,
`office_id` bigint(20) NOT NULL, `office_id` bigint(20) NOT NULL,
`loan_officer_id` bigint(20) DEFAULT NULL, `level_Id` int(11) NOT NULL,
`parent_id` bigint(20) DEFAULT NULL,
`hierarchy` varchar(100) DEFAULT NULL,
`staff_id` bigint(20) DEFAULT NULL,
`name` varchar(100) DEFAULT NULL, `name` varchar(100) DEFAULT NULL,
`external_id` varchar(100) DEFAULT NULL, `external_id` varchar(100) DEFAULT NULL,
`is_deleted` tinyint(1) NOT NULL DEFAULT '0', `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
@@ -740,9 +743,13 @@ CREATE TABLE `m_group` (
UNIQUE KEY `name` (`name`), UNIQUE KEY `name` (`name`),
UNIQUE KEY `external_id` (`external_id`), UNIQUE KEY `external_id` (`external_id`),
KEY `office_id` (`office_id`), KEY `office_id` (`office_id`),
KEY `loan_officer_id` (`loan_officer_id`), KEY `loan_officer_id` (`staff_id`),
KEY `Parent_Id_reference` (`parent_id`),
KEY `FK_m_group_level` (`level_Id`),
CONSTRAINT `FK_m_group_level` FOREIGN KEY (`level_Id`) REFERENCES `m_group_level` (`id`),
CONSTRAINT `FK_m_group_m_staff` FOREIGN KEY (`staff_id`) REFERENCES `m_staff` (`id`),
CONSTRAINT `m_group_ibfk_1` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`), CONSTRAINT `m_group_ibfk_1` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`),
CONSTRAINT `FK_m_group_m_staff` FOREIGN KEY (`loan_officer_id`) REFERENCES `m_staff` (`id`) CONSTRAINT `Parent_Id_reference` FOREIGN KEY (`parent_id`) REFERENCES `m_group` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
@@ -781,6 +788,35 @@ LOCK TABLES `m_group_client` WRITE;
/*!40000 ALTER TABLE `m_group_client` ENABLE KEYS */; /*!40000 ALTER TABLE `m_group_client` ENABLE KEYS */;
UNLOCK TABLES; UNLOCK TABLES;
--
-- Table structure for table `m_group_level`
--
DROP TABLE IF EXISTS `m_group_level`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `m_group_level` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`parent_id` int(11) DEFAULT NULL,
`level_name` varchar(100) NOT NULL,
`recursable` tinyint(1) NOT NULL,
`can_have_clients` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
KEY `Parent_levelId_reference` (`parent_id`),
CONSTRAINT `Parent_levelId_reference` FOREIGN KEY (`parent_id`) REFERENCES `m_group_level` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `m_group_level`
--
LOCK TABLES `m_group_level` WRITE;
/*!40000 ALTER TABLE `m_group_level` DISABLE KEYS */;
INSERT INTO `m_group_level` VALUES (1,NULL,'Center',1,0),(2,NULL,'Group',0,1);
/*!40000 ALTER TABLE `m_group_level` ENABLE KEYS */;
UNLOCK TABLES;
-- --
-- Table structure for table `m_guarantor` -- Table structure for table `m_guarantor`
-- --
@@ -1933,4 +1969,4 @@ UNLOCK TABLES;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2013-02-19 0:44:13 -- Dump completed on 2013-02-20 9:57:08
@@ -0,0 +1,43 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.charge.domain;
import org.mifosplatform.portfolio.charge.exception.ChargeIsNotActiveException;
import org.mifosplatform.portfolio.charge.exception.ChargeNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* <p>
* Wrapper for {@link ChargeRepository} that is responsible for checking if
* {@link Charge} is returned when using <code>findOne</code> repository method
* and throwing an appropriate not found exception.
* </p>
*
* <p>
* This is to avoid need for checking and throwing in multiple areas of code
* base where {@link ChargeRepository} is required.
* </p>
*/
@Service
public class ChargeRepositoryWrapper {
private final ChargeRepository repository;
@Autowired
public ChargeRepositoryWrapper(final ChargeRepository repository) {
this.repository = repository;
}
public Charge findOneWithNotFoundDetection(final Long id) {
final Charge chargeDefinition = this.repository.findOne(id);
if (chargeDefinition == null || chargeDefinition.isDeleted()) { throw new ChargeNotFoundException(id); }
if (!chargeDefinition.isActive()) { throw new ChargeIsNotActiveException(id, chargeDefinition.getName()); }
return chargeDefinition;
}
}
@@ -6,16 +6,12 @@
package org.mifosplatform.portfolio.loanaccount.command; package org.mifosplatform.portfolio.loanaccount.command;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.data.ApiParameterError; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
/** /**
* Immutable command for creating and updating loan charges. * Java object representation of {@link LoanCharge} API JSON.
*/ */
public class LoanChargeCommand implements Comparable<LoanChargeCommand> { public class LoanChargeCommand implements Comparable<LoanChargeCommand> {
@@ -23,10 +19,12 @@ public class LoanChargeCommand implements Comparable<LoanChargeCommand> {
private final Long id; private final Long id;
private final Long chargeId; private final Long chargeId;
private final BigDecimal amount; private final BigDecimal amount;
@SuppressWarnings("unused")
private final Integer chargeTimeType; private final Integer chargeTimeType;
@SuppressWarnings("unused")
private final Integer chargeCalculationType; private final Integer chargeCalculationType;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final LocalDate specifiedDueDate; private final LocalDate dueDate;
public LoanChargeCommand(final Long id, final Long chargeId, final BigDecimal amount, final Integer chargeTimeType, public LoanChargeCommand(final Long id, final Long chargeId, final BigDecimal amount, final Integer chargeTimeType,
final Integer chargeCalculationType, final LocalDate specifiedDueDate) { final Integer chargeCalculationType, final LocalDate specifiedDueDate) {
@@ -35,7 +33,7 @@ public class LoanChargeCommand implements Comparable<LoanChargeCommand> {
this.amount = amount; this.amount = amount;
this.chargeTimeType = chargeTimeType; this.chargeTimeType = chargeTimeType;
this.chargeCalculationType = chargeCalculationType; this.chargeCalculationType = chargeCalculationType;
this.specifiedDueDate = specifiedDueDate; this.dueDate = specifiedDueDate;
} }
@Override @Override
@@ -46,18 +44,4 @@ public class LoanChargeCommand implements Comparable<LoanChargeCommand> {
} }
return comparison; return comparison;
} }
@Deprecated
public void validateForUpdate() {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("charge");
baseDataValidator.reset().parameter("amount").value(this.amount).ignoreIfNull().positiveAmount();
baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTimeType).ignoreIfNull().inMinMaxRange(1, 2);
baseDataValidator.reset().parameter("chargeCalculationType").value(this.chargeCalculationType).ignoreIfNull().inMinMaxRange(1, 4);
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
}
} }
@@ -31,7 +31,7 @@ public class LoanChargeData {
private final EnumOptionData chargeTimeType; private final EnumOptionData chargeTimeType;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final LocalDate dueAsOfDate; private final LocalDate dueDate;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final EnumOptionData chargeCalculationType; private final EnumOptionData chargeCalculationType;
@@ -81,7 +81,7 @@ public class LoanChargeData {
public LoanChargeData(final Long id, final Long chargeId, final String name, final CurrencyData currency, final BigDecimal amount, public LoanChargeData(final Long id, final Long chargeId, final String name, final CurrencyData currency, final BigDecimal amount,
final BigDecimal amountPaid, final BigDecimal amountWaived, final BigDecimal amountWrittenOff, final BigDecimal amountPaid, final BigDecimal amountWaived, final BigDecimal amountWrittenOff,
final BigDecimal amountOutstanding, final EnumOptionData chargeTimeType, final LocalDate dueAsOfDate, final BigDecimal amountOutstanding, final EnumOptionData chargeTimeType, final LocalDate dueDate,
final EnumOptionData chargeCalculationType, final BigDecimal percentage, final BigDecimal amountPercentageAppliedTo, final EnumOptionData chargeCalculationType, final BigDecimal percentage, final BigDecimal amountPercentageAppliedTo,
final boolean penalty) { final boolean penalty) {
this.id = id; this.id = id;
@@ -94,7 +94,7 @@ public class LoanChargeData {
this.amountWrittenOff = amountWrittenOff; this.amountWrittenOff = amountWrittenOff;
this.amountOutstanding = amountOutstanding; this.amountOutstanding = amountOutstanding;
this.chargeTimeType = chargeTimeType; this.chargeTimeType = chargeTimeType;
this.dueAsOfDate = dueAsOfDate; this.dueDate = dueDate;
this.chargeCalculationType = chargeCalculationType; this.chargeCalculationType = chargeCalculationType;
this.percentage = percentage; this.percentage = percentage;
this.amountPercentageAppliedTo = amountPercentageAppliedTo; this.amountPercentageAppliedTo = amountPercentageAppliedTo;
@@ -122,7 +122,7 @@ public class LoanChargeData {
this.amountWrittenOff = BigDecimal.ZERO; this.amountWrittenOff = BigDecimal.ZERO;
this.amountOutstanding = amount; this.amountOutstanding = amount;
this.chargeTimeType = chargeTimeType; this.chargeTimeType = chargeTimeType;
this.dueAsOfDate = null; this.dueDate = null;
this.chargeCalculationType = chargeCalculationType; this.chargeCalculationType = chargeCalculationType;
this.percentage = null; this.percentage = null;
this.amountPercentageAppliedTo = null; this.amountPercentageAppliedTo = null;
@@ -150,7 +150,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
for (LoanCharge loanCharge : charges) { for (LoanCharge loanCharge : charges) {
if (loanCharge.isNotFullyPaid() && !loanCharge.isDueAtDisbursement()) { if (loanCharge.isNotFullyPaid() && !loanCharge.isDueAtDisbursement()) {
if (earliestUnpaidCharge == null if (earliestUnpaidCharge == null
|| loanCharge.getDueForCollectionAsOfLocalDate().isBefore(earliestUnpaidCharge.getDueForCollectionAsOfLocalDate())) { || loanCharge.getDueLocalDate().isBefore(earliestUnpaidCharge.getDueLocalDate())) {
earliestUnpaidCharge = loanCharge; earliestUnpaidCharge = loanCharge;
} }
} }
@@ -322,7 +322,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
Money chargesDue = Money.of(getCurrency(), BigDecimal.ZERO); Money chargesDue = Money.of(getCurrency(), BigDecimal.ZERO);
for (LoanCharge charge : getNullPointerSafeLoanCharges()) { for (LoanCharge charge : setOfLoanCharges()) {
if (charge.isDueAtDisbursement()) { if (charge.isDueAtDisbursement()) {
chargesDue = chargesDue.plus(charge.amount()); chargesDue = chargesDue.plus(charge.amount());
} }
@@ -362,7 +362,8 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
validateLoanIsNotClosed(loanCharge); validateLoanIsNotClosed(loanCharge);
if (isDisbursed() && loanCharge.isDueAtDisbursement()) { if (isDisbursed() && loanCharge.isDueAtDisbursement()) {
// Note: added this constraint to restrict adding charges to a loan // Note: added this constraint to restrict adding disbursement
// charges to a loan
// after it is disbursed // after it is disbursed
// if the loan charge payment type is 'Disbursement'. // if the loan charge payment type is 'Disbursement'.
// To undo this constraint would mean resolving how charges due are // To undo this constraint would mean resolving how charges due are
@@ -380,7 +381,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate()); validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate());
loanCharge.update(this); loanCharge.update(this);
getNullPointerSafeLoanCharges().add(loanCharge); setOfLoanCharges().add(loanCharge);
updateTotalChargesDueAtDisbursement(); updateTotalChargesDueAtDisbursement();
@@ -390,11 +391,11 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
if (!loanCharge.isDueAtDisbursement()) { if (!loanCharge.isDueAtDisbursement()) {
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement,
getCurrency(), this.repaymentScheduleInstallments, getNullPointerSafeLoanCharges()); getCurrency(), this.repaymentScheduleInstallments, setOfLoanCharges());
} else { } else {
// just reprocess the loan schedule only for now. // just reprocess the loan schedule only for now.
LoanScheduleWrapper wrapper = new LoanScheduleWrapper(); LoanScheduleWrapper wrapper = new LoanScheduleWrapper();
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, getNullPointerSafeLoanCharges()); wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, setOfLoanCharges());
} }
} }
@@ -435,7 +436,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
// if you want ability to remove loan charges that are waived. // if you want ability to remove loan charges that are waived.
validateLoanChargeIsNotWaived(loanCharge); validateLoanChargeIsNotWaived(loanCharge);
boolean removed = getNullPointerSafeLoanCharges().remove(loanCharge); boolean removed = setOfLoanCharges().remove(loanCharge);
if (removed) { if (removed) {
updateTotalChargesDueAtDisbursement(); updateTotalChargesDueAtDisbursement();
} }
@@ -447,7 +448,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(loanCurrency())) { if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(loanCurrency())) {
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement,
getCurrency(), this.repaymentScheduleInstallments, getNullPointerSafeLoanCharges()); getCurrency(), this.repaymentScheduleInstallments, setOfLoanCharges());
} }
} }
@@ -484,7 +485,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
validateLoanIsNotClosed(loanCharge); validateLoanIsNotClosed(loanCharge);
if (getNullPointerSafeLoanCharges().contains(loanCharge)) { if (setOfLoanCharges().contains(loanCharge)) {
final Map<String, Object> loanChargeChanges = loanCharge.update(command, getPrincpal().getAmount()); final Map<String, Object> loanChargeChanges = loanCharge.update(command, getPrincpal().getAmount());
actualChanges.putAll(loanChargeChanges); actualChanges.putAll(loanChargeChanges);
updateTotalChargesDueAtDisbursement(); updateTotalChargesDueAtDisbursement();
@@ -495,11 +496,11 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
if (!loanCharge.isDueAtDisbursement()) { if (!loanCharge.isDueAtDisbursement()) {
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement,
getCurrency(), this.repaymentScheduleInstallments, getNullPointerSafeLoanCharges()); getCurrency(), this.repaymentScheduleInstallments, setOfLoanCharges());
} else { } else {
// reprocess loan schedule based on charge been waived. // reprocess loan schedule based on charge been waived.
LoanScheduleWrapper wrapper = new LoanScheduleWrapper(); LoanScheduleWrapper wrapper = new LoanScheduleWrapper();
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, getNullPointerSafeLoanCharges()); wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, setOfLoanCharges());
} }
return actualChanges; return actualChanges;
@@ -523,7 +524,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
LocalDate transactionDate = getDisbursementDate(); LocalDate transactionDate = getDisbursementDate();
if (loanCharge.isSpecifiedDueDate()) { if (loanCharge.isSpecifiedDueDate()) {
transactionDate = loanCharge.getDueForCollectionAsOfLocalDate(); transactionDate = loanCharge.getDueLocalDate();
} }
updateTotalChargesDueAtDisbursement(); updateTotalChargesDueAtDisbursement();
@@ -538,11 +539,11 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(loanCurrency())) { if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(loanCurrency())) {
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement,
getCurrency(), this.repaymentScheduleInstallments, getNullPointerSafeLoanCharges()); getCurrency(), this.repaymentScheduleInstallments, setOfLoanCharges());
} else { } else {
// reprocess loan schedule based on charge been waived. // reprocess loan schedule based on charge been waived.
LoanScheduleWrapper wrapper = new LoanScheduleWrapper(); LoanScheduleWrapper wrapper = new LoanScheduleWrapper();
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, getNullPointerSafeLoanCharges()); wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, setOfLoanCharges());
} }
doPostLoanTransactionChecks(waiveLoanChargeTransaction.getTransactionDate(), loanLifecycleStateMachine); doPostLoanTransactionChecks(waiveLoanChargeTransaction.getTransactionDate(), loanLifecycleStateMachine);
@@ -595,8 +596,8 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
} }
public void updateLoanCharges(final Set<LoanCharge> loanCharges) { public void updateLoanCharges(final Set<LoanCharge> loanCharges) {
getNullPointerSafeLoanCharges().clear(); setOfLoanCharges().clear();
getNullPointerSafeLoanCharges().addAll(associateChargesWithThisLoan(loanCharges)); setOfLoanCharges().addAll(associateChargesWithThisLoan(loanCharges));
this.totalChargesDueAtDisbursement = deriveSumTotalOfChargesDueAtDisbursement(); this.totalChargesDueAtDisbursement = deriveSumTotalOfChargesDueAtDisbursement();
} }
@@ -630,7 +631,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessor final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessor
.determineProcessor(this.transactionProcessingStrategy); .determineProcessor(this.transactionProcessingStrategy);
loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), repaymentsOrWaivers, getCurrency(), loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), repaymentsOrWaivers, getCurrency(),
this.repaymentScheduleInstallments, getNullPointerSafeLoanCharges()); this.repaymentScheduleInstallments, setOfLoanCharges());
} }
public void updateLoanScheduleDependentDerivedFields() { public void updateLoanScheduleDependentDerivedFields() {
@@ -785,7 +786,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
final String chargesParamName = "charges"; final String chargesParamName = "charges";
if (command.parameterExists(chargesParamName)) { if (command.parameterExists(chargesParamName)) {
final Set<LoanCharge> existingLoanCharges = getNullPointerSafeLoanCharges(); final Set<LoanCharge> existingLoanCharges = setOfLoanCharges();
if (!possiblyModifedLoanCharges.equals(existingLoanCharges)) { if (!possiblyModifedLoanCharges.equals(existingLoanCharges)) {
actualChanges.put(chargesParamName, getLoanCharges(possiblyModifedLoanCharges)); actualChanges.put(chargesParamName, getLoanCharges(possiblyModifedLoanCharges));
@@ -797,20 +798,6 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate()); validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate());
} }
} }
// final ImmutableSet<LoanCharge> difference =
// Sets.symmetricDifference(existingLoanCharges,
// possiblyModifedLoanCharges)
// .immutableCopy();
// if (!difference.isEmpty()) {
// actualChanges.put(chargesParamName, getLoanCharges(difference));
// actualChanges.put("recalculateLoanSchedule", true);
//
// for (LoanCharge loanCharge : possiblyModifedLoanCharges) {
// validateChargeHasValidSpecifiedDateIfApplicable(loanCharge,
// getDisbursementDate(), getLastRepaymentPeriodDueDate());
// }
// }
} }
final String collateralParamName = "collateral"; final String collateralParamName = "collateral";
@@ -819,22 +806,12 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
if (!possiblyModifedLoanCollateralItems.equals(this.collateral)) { if (!possiblyModifedLoanCollateralItems.equals(this.collateral)) {
actualChanges.put(collateralParamName, listOfLoanCollateralData(possiblyModifedLoanCollateralItems)); actualChanges.put(collateralParamName, listOfLoanCollateralData(possiblyModifedLoanCollateralItems));
} }
//
// final ImmutableSet<LoanCollateral> difference =
// Sets.symmetricDifference(this.collateral,
// possiblyModifedLoanCollateralItems)
// .immutableCopy();
// if (!difference.isEmpty()) {
// actualChanges.put(collateralParamName,
// listOfLoanCollateralData(difference));
// }
} }
return actualChanges; return actualChanges;
} }
private Set<LoanCharge> getNullPointerSafeLoanCharges() { private Set<LoanCharge> setOfLoanCharges() {
Set<LoanCharge> loanCharges = this.charges; Set<LoanCharge> loanCharges = this.charges;
if (this.charges == null) { if (this.charges == null) {
loanCharges = new HashSet<LoanCharge>(); loanCharges = new HashSet<LoanCharge>();
@@ -933,7 +910,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
} }
// charges are optional // charges are optional
for (LoanCharge loanCharge : getNullPointerSafeLoanCharges()) { for (LoanCharge loanCharge : setOfLoanCharges()) {
validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate()); validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate());
} }
} }
@@ -1190,7 +1167,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
final LoanSchedule loanSchedule = new LoanSchedule(loanScheduleGenerator, applicationCurrency, principal, interestRatePerPeriod, final LoanSchedule loanSchedule = new LoanSchedule(loanScheduleGenerator, applicationCurrency, principal, interestRatePerPeriod,
interestRatePeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, interestRatePeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod,
repaymentEvery, repaymentPeriodFrequencyType, numberOfRepayments, amortizationMethod, loanTermFrequency, repaymentEvery, repaymentPeriodFrequencyType, numberOfRepayments, amortizationMethod, loanTermFrequency,
loanTermPeriodFrequencyType, getNullPointerSafeLoanCharges(), this.getDisbursementDate(), loanTermPeriodFrequencyType, setOfLoanCharges(), this.getDisbursementDate(),
this.getExpectedFirstRepaymentOnDate(), this.getInterestChargedFromDate(), inArrearsTolerance); this.getExpectedFirstRepaymentOnDate(), this.getInterestChargedFromDate(), inArrearsTolerance);
final LoanScheduleData generatedData = loanSchedule.generate(); final LoanScheduleData generatedData = loanSchedule.generate();
@@ -1215,7 +1192,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
chargesPayment.updateLoan(this); chargesPayment.updateLoan(this);
this.loanTransactions.add(chargesPayment); this.loanTransactions.add(chargesPayment);
for (LoanCharge charge : getNullPointerSafeLoanCharges()) { for (LoanCharge charge : setOfLoanCharges()) {
if (charge.isDueAtDisbursement()) { if (charge.isDueAtDisbursement()) {
charge.markAsFullyPaid(); charge.markAsFullyPaid();
} }
@@ -1281,7 +1258,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
private void updateLoanToPreDisbursalState() { private void updateLoanToPreDisbursalState() {
this.actualDisbursementDate = null; this.actualDisbursementDate = null;
for (LoanCharge charge : getNullPointerSafeLoanCharges()) { for (LoanCharge charge : setOfLoanCharges()) {
charge.resetToOriginal(loanCurrency()); charge.resetToOriginal(loanCurrency());
} }
@@ -1290,7 +1267,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
} }
LoanScheduleWrapper wrapper = new LoanScheduleWrapper(); LoanScheduleWrapper wrapper = new LoanScheduleWrapper();
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, getNullPointerSafeLoanCharges()); wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, setOfLoanCharges());
} }
public LoanTransaction waiveInterest(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine) { public LoanTransaction waiveInterest(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine) {
@@ -1370,11 +1347,11 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
.determineProcessor(this.transactionProcessingStrategy); .determineProcessor(this.transactionProcessingStrategy);
if (isTransactionChronologicallyLatest && adjustedTransaction == null) { if (isTransactionChronologicallyLatest && adjustedTransaction == null) {
loanRepaymentScheduleTransactionProcessor.handleTransaction(loanTransaction, getCurrency(), this.repaymentScheduleInstallments, loanRepaymentScheduleTransactionProcessor.handleTransaction(loanTransaction, getCurrency(), this.repaymentScheduleInstallments,
getNullPointerSafeLoanCharges()); setOfLoanCharges());
} else { } else {
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement,
getCurrency(), this.repaymentScheduleInstallments, getNullPointerSafeLoanCharges()); getCurrency(), this.repaymentScheduleInstallments, setOfLoanCharges());
} }
doPostLoanTransactionChecks(loanTransaction.getTransactionDate(), loanLifecycleStateMachine); doPostLoanTransactionChecks(loanTransaction.getTransactionDate(), loanLifecycleStateMachine);
@@ -50,7 +50,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
@Temporal(TemporalType.DATE) @Temporal(TemporalType.DATE)
@Column(name = "due_for_collection_as_of_date") @Column(name = "due_for_collection_as_of_date")
private Date dueForCollectionAsOfDate; private Date dueDate;
@Column(name = "charge_calculation_enum") @Column(name = "charge_calculation_enum")
private Integer chargeCalculation; private Integer chargeCalculation;
@@ -85,29 +85,32 @@ public class LoanCharge extends AbstractPersistable<Long> {
public static LoanCharge createNewFromJson(final Loan loan, final Charge chargeDefinition, final JsonCommand command) { public static LoanCharge createNewFromJson(final Loan loan, final Charge chargeDefinition, final JsonCommand command) {
final BigDecimal amount = command.bigDecimalValueOfParameterNamed("amount"); final BigDecimal amount = command.bigDecimalValueOfParameterNamed("amount");
final Integer chargeTimeType = command.integerValueOfParameterNamed("chargeTimeType"); // final Integer chargeTimeType =
final Integer chargeCalculationType = command.integerValueOfParameterNamed("chargeCalculationType"); // command.integerValueOfParameterNamed("chargeTimeType");
final LocalDate specifiedDueDate = command.localDateValueOfParameterNamed("specifiedDueDate"); // final Integer chargeCalculationType =
// command.integerValueOfParameterNamed("chargeCalculationType");
final LocalDate dueDate = command.localDateValueOfParameterNamed("dueDate");
ChargeTimeType chargeTime = null; ChargeTimeType chargeTime = null;
if (chargeTimeType != null) { // if (chargeTimeType != null) {
chargeTime = ChargeTimeType.fromInt(chargeTimeType); // chargeTime = ChargeTimeType.fromInt(chargeTimeType);
} // }
ChargeCalculationType chargeCalculation = null; ChargeCalculationType chargeCalculation = null;
if (chargeCalculationType != null) { // if (chargeCalculationType != null) {
chargeCalculation = ChargeCalculationType.fromInt(chargeCalculationType); // chargeCalculation =
} // ChargeCalculationType.fromInt(chargeCalculationType);
// }
return new LoanCharge(loan, chargeDefinition, loan.getPrincpal().getAmount(), amount, chargeTime, chargeCalculation, return new LoanCharge(loan, chargeDefinition, loan.getPrincpal().getAmount(), amount, chargeTime, chargeCalculation,
specifiedDueDate); dueDate);
} }
/* /*
* loanPrincipal is required for charges that are percentage based * loanPrincipal is required for charges that are percentage based
*/ */
public static LoanCharge createNewWithoutLoan(final Charge chargeDefinition, final BigDecimal loanPrincipal, final BigDecimal amount, public static LoanCharge createNewWithoutLoan(final Charge chargeDefinition, final BigDecimal loanPrincipal, final BigDecimal amount,
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate specifiedDueDate) { final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate) {
return new LoanCharge(null, chargeDefinition, loanPrincipal, amount, chargeTime, chargeCalculation, specifiedDueDate); return new LoanCharge(null, chargeDefinition, loanPrincipal, amount, chargeTime, chargeCalculation, dueDate);
} }
protected LoanCharge() { protected LoanCharge() {
@@ -115,7 +118,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
} }
public LoanCharge(final Loan loan, final Charge chargeDefinition, final BigDecimal loanPrincipal, final BigDecimal amount, public LoanCharge(final Loan loan, final Charge chargeDefinition, final BigDecimal loanPrincipal, final BigDecimal amount,
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate specifiedDueDate) { final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate) {
this.loan = loan; this.loan = loan;
this.charge = chargeDefinition; this.charge = chargeDefinition;
this.penaltyCharge = chargeDefinition.isPenalty(); this.penaltyCharge = chargeDefinition.isPenalty();
@@ -127,15 +130,15 @@ public class LoanCharge extends AbstractPersistable<Long> {
if (ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.SPECIFIED_DUE_DATE)) { if (ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.SPECIFIED_DUE_DATE)) {
if (specifiedDueDate == null) { if (dueDate == null) {
final String defaultUserMessage = "Loan charge is missing specified due date"; final String defaultUserMessage = "Loan charge is missing due date.";
throw new LoanChargeWithoutMandatoryFieldException("loancharge", "specifiedDueDate", defaultUserMessage, throw new LoanChargeWithoutMandatoryFieldException("loanCharge", "dueDate", defaultUserMessage,
chargeDefinition.getId(), chargeDefinition.getName()); chargeDefinition.getId(), chargeDefinition.getName());
} }
this.dueForCollectionAsOfDate = specifiedDueDate.toDate(); this.dueDate = dueDate.toDate();
} else { } else {
this.dueForCollectionAsOfDate = null; this.dueDate = null;
} }
this.chargeCalculation = chargeDefinition.getChargeCalculation(); this.chargeCalculation = chargeDefinition.getChargeCalculation();
@@ -240,7 +243,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
public void update(final BigDecimal amount, final LocalDate specifiedDueDate, final BigDecimal loanPrincipal) { public void update(final BigDecimal amount, final LocalDate specifiedDueDate, final BigDecimal loanPrincipal) {
if (specifiedDueDate != null) { if (specifiedDueDate != null) {
this.dueForCollectionAsOfDate = specifiedDueDate.toDate(); this.dueDate = specifiedDueDate.toDate();
} }
if (amount != null) { if (amount != null) {
@@ -279,31 +282,37 @@ public class LoanCharge extends AbstractPersistable<Long> {
final String dateFormatAsInput = command.dateFormat(); final String dateFormatAsInput = command.dateFormat();
final String localeAsInput = command.locale(); final String localeAsInput = command.locale();
final String chargeTimeParamName = "chargeTime"; // final String chargeTimeParamName = "chargeTime";
if (command.isChangeInIntegerParameterNamed(chargeTimeParamName, this.chargeTime)) { // if (command.isChangeInIntegerParameterNamed(chargeTimeParamName,
final Integer newValue = command.integerValueOfParameterNamed(chargeTimeParamName); // this.chargeTime)) {
actualChanges.put(chargeTimeParamName, newValue); // final Integer newValue =
actualChanges.put("locale", localeAsInput); // command.integerValueOfParameterNamed(chargeTimeParamName);
this.chargeTime = ChargeTimeType.fromInt(newValue).getValue(); // actualChanges.put(chargeTimeParamName, newValue);
} // actualChanges.put("locale", localeAsInput);
// this.chargeTime = ChargeTimeType.fromInt(newValue).getValue();
// }
//
// final String chargeCalculationParamName = "chargeCalculation";
// if
// (command.isChangeInIntegerParameterNamed(chargeCalculationParamName,
// this.chargeCalculation)) {
// final Integer newValue =
// command.integerValueOfParameterNamed(chargeCalculationParamName);
// actualChanges.put(chargeCalculationParamName, newValue);
// actualChanges.put("locale", localeAsInput);
// this.chargeCalculation =
// ChargeCalculationType.fromInt(newValue).getValue();
// }
final String chargeCalculationParamName = "chargeCalculation"; final String specifiedDueDateParamName = "dueDate";
if (command.isChangeInIntegerParameterNamed(chargeCalculationParamName, this.chargeCalculation)) { if (command.isChangeInLocalDateParameterNamed(specifiedDueDateParamName, getDueLocalDate())) {
final Integer newValue = command.integerValueOfParameterNamed(chargeCalculationParamName);
actualChanges.put(chargeCalculationParamName, newValue);
actualChanges.put("locale", localeAsInput);
this.chargeCalculation = ChargeCalculationType.fromInt(newValue).getValue();
}
final String specifiedDueDateParamName = "specifiedDueDate";
if (command.isChangeInLocalDateParameterNamed(specifiedDueDateParamName, getDueForCollectionAsOfLocalDate())) {
final String valueAsInput = command.stringValueOfParameterNamed(specifiedDueDateParamName); final String valueAsInput = command.stringValueOfParameterNamed(specifiedDueDateParamName);
actualChanges.put(specifiedDueDateParamName, valueAsInput); actualChanges.put(specifiedDueDateParamName, valueAsInput);
actualChanges.put("dateFormat", dateFormatAsInput); actualChanges.put("dateFormat", dateFormatAsInput);
actualChanges.put("locale", localeAsInput); actualChanges.put("locale", localeAsInput);
final LocalDate newValue = command.localDateValueOfParameterNamed(specifiedDueDateParamName); final LocalDate newValue = command.localDateValueOfParameterNamed(specifiedDueDateParamName);
this.dueForCollectionAsOfDate = newValue.toDate(); this.dueDate = newValue.toDate();
} }
final String amountParamName = "amount"; final String amountParamName = "amount";
@@ -355,13 +364,13 @@ public class LoanCharge extends AbstractPersistable<Long> {
public LoanChargeCommand toCommand() { public LoanChargeCommand toCommand() {
return new LoanChargeCommand(this.getId(), this.charge.getId(), this.amount, this.chargeTime, this.chargeCalculation, return new LoanChargeCommand(this.getId(), this.charge.getId(), this.amount, this.chargeTime, this.chargeCalculation,
getDueForCollectionAsOfLocalDate()); getDueLocalDate());
} }
public LocalDate getDueForCollectionAsOfLocalDate() { public LocalDate getDueLocalDate() {
LocalDate specifiedDueDate = null; LocalDate specifiedDueDate = null;
if (this.dueForCollectionAsOfDate != null) { if (this.dueDate != null) {
specifiedDueDate = new LocalDate(this.dueForCollectionAsOfDate); specifiedDueDate = new LocalDate(this.dueDate);
} }
return specifiedDueDate; return specifiedDueDate;
} }
@@ -418,7 +427,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
} }
public boolean isDueForCollectionFromAndUpToAndIncluding(final LocalDate fromNotInclusive, final LocalDate upToAndInclusive) { public boolean isDueForCollectionFromAndUpToAndIncluding(final LocalDate fromNotInclusive, final LocalDate upToAndInclusive) {
final LocalDate specifiedDueDate = getDueForCollectionAsOfLocalDate(); final LocalDate specifiedDueDate = getDueLocalDate();
return occursOnDayFromAndUpToAndIncluding(fromNotInclusive, upToAndInclusive, specifiedDueDate); return occursOnDayFromAndUpToAndIncluding(fromNotInclusive, upToAndInclusive, specifiedDueDate);
} }
@@ -508,7 +517,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
.append(this.getId(), rhs.getId()) // .append(this.getId(), rhs.getId()) //
.append(this.charge.getId(), rhs.charge.getId()) // .append(this.charge.getId(), rhs.charge.getId()) //
.append(this.amount, rhs.amount) // .append(this.amount, rhs.amount) //
.append(this.getDueForCollectionAsOfLocalDate(), rhs.getDueForCollectionAsOfLocalDate()) // .append(this.getDueLocalDate(), rhs.getDueLocalDate()) //
.isEquals(); .isEquals();
} }
@@ -517,7 +526,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
return new HashCodeBuilder(3, 5) // return new HashCodeBuilder(3, 5) //
.append(this.getId()) // .append(this.getId()) //
.append(this.charge.getId()) // .append(this.charge.getId()) //
.append(this.amount).append(this.getDueForCollectionAsOfLocalDate()) // .append(this.amount).append(this.getDueLocalDate()) //
.toHashCode(); .toHashCode();
} }
} }
@@ -1,66 +0,0 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.serialization;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
import org.mifosplatform.infrastructure.core.serialization.AbstractFromApiJsonDeserializer;
import org.mifosplatform.infrastructure.core.serialization.FromApiJsonDeserializer;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.google.gson.JsonElement;
import com.google.gson.reflect.TypeToken;
/**
* Implementation of {@link FromApiJsonDeserializer} for
* {@link LoanChargeCommand}'s.
*/
@Component
public final class LoanChargeCommandFromApiJsonDeserializer extends AbstractFromApiJsonDeserializer<LoanChargeCommand> {
/**
* The parameters supported for this command.
*/
final Set<String> supportedParameters = new HashSet<String>(Arrays.asList("id", "chargeId", "amount", "chargeTimeType",
"chargeCalculationType", "specifiedDueDate", "locale", "dateFormat"));
private final FromJsonHelper fromApiJsonHelper;
@Autowired
public LoanChargeCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
this.fromApiJsonHelper = fromApiJsonHelper;
}
@Override
public LoanChargeCommand commandFromApiJson(final String json) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters);
final JsonElement element = fromApiJsonHelper.parse(json);
final Long id = fromApiJsonHelper.extractLongNamed("id", element);
final Long chargeId = fromApiJsonHelper.extractLongNamed("chargeId", element);
final BigDecimal amount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("amount", element);
final Integer chargeTimeType = fromApiJsonHelper.extractIntegerWithLocaleNamed("chargeTimeType", element);
final Integer chargeCalculationType = fromApiJsonHelper.extractIntegerWithLocaleNamed("chargeCalculationType", element);
final LocalDate dueAsOfDate = fromApiJsonHelper.extractLocalDateNamed("specifiedDueDate", element);
return new LoanChargeCommand(id, chargeId, amount, chargeTimeType, chargeCalculationType, dueAsOfDate);
}
}
@@ -111,4 +111,53 @@ public final class LoanEventApiJsonValidator {
throwExceptionIfValidationWarningsExist(dataValidationErrors); throwExceptionIfValidationWarningsExist(dataValidationErrors);
} }
public void validateAddLoanCharge(final String json) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Set<String> disbursementParameters = new HashSet<String>(Arrays.asList("chargeId", "amount", "dueDate", "locale",
"dateFormat"));
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loanCharge");
final JsonElement element = fromApiJsonHelper.parse(json);
final Long chargeId = fromApiJsonHelper.extractLongNamed("chargeId", element);
baseDataValidator.reset().parameter("chargeId").value(chargeId).notNull().integerGreaterThanZero();
final BigDecimal amount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("amount", element);
baseDataValidator.reset().parameter("amount").value(amount).notNull().positiveAmount();
if (fromApiJsonHelper.parameterExists("dueDate", element)) {
fromApiJsonHelper.extractLocalDateNamed("dueDate", element);
}
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateUpdateOfLoanCharge(final String json) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Set<String> disbursementParameters = new HashSet<String>(Arrays.asList("amount", "dueDate", "locale", "dateFormat"));
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loanCharge");
final JsonElement element = fromApiJsonHelper.parse(json);
final BigDecimal amount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("amount", element);
baseDataValidator.reset().parameter("amount").value(amount).notNull().positiveAmount();
if (fromApiJsonHelper.parameterExists("dueDate", element)) {
fromApiJsonHelper.extractLocalDateNamed("dueDate", element);
}
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
} }
@@ -14,10 +14,8 @@ import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.portfolio.charge.domain.Charge; import org.mifosplatform.portfolio.charge.domain.Charge;
import org.mifosplatform.portfolio.charge.domain.ChargeCalculationType; import org.mifosplatform.portfolio.charge.domain.ChargeCalculationType;
import org.mifosplatform.portfolio.charge.domain.ChargeRepository; import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.mifosplatform.portfolio.charge.domain.ChargeTimeType; import org.mifosplatform.portfolio.charge.domain.ChargeTimeType;
import org.mifosplatform.portfolio.charge.exception.ChargeIsNotActiveException;
import org.mifosplatform.portfolio.charge.exception.ChargeNotFoundException;
import org.mifosplatform.portfolio.charge.exception.LoanChargeNotFoundException; import org.mifosplatform.portfolio.charge.exception.LoanChargeNotFoundException;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanChargeRepository; import org.mifosplatform.portfolio.loanaccount.domain.LoanChargeRepository;
@@ -32,11 +30,11 @@ import com.google.gson.JsonObject;
public class LoanChargeAssembler { public class LoanChargeAssembler {
private final FromJsonHelper fromApiJsonHelper; private final FromJsonHelper fromApiJsonHelper;
private final ChargeRepository chargeRepository; private final ChargeRepositoryWrapper chargeRepository;
private final LoanChargeRepository loanChargeRepository; private final LoanChargeRepository loanChargeRepository;
@Autowired @Autowired
public LoanChargeAssembler(final FromJsonHelper fromApiJsonHelper, final ChargeRepository chargeRepository, public LoanChargeAssembler(final FromJsonHelper fromApiJsonHelper, final ChargeRepositoryWrapper chargeRepository,
final LoanChargeRepository loanChargeRepository) { final LoanChargeRepository loanChargeRepository) {
this.fromApiJsonHelper = fromApiJsonHelper; this.fromApiJsonHelper = fromApiJsonHelper;
this.chargeRepository = chargeRepository; this.chargeRepository = chargeRepository;
@@ -69,10 +67,7 @@ public class LoanChargeAssembler {
dateFormat, locale); dateFormat, locale);
if (id == null) { if (id == null) {
final Charge chargeDefinition = this.chargeRepository.findOne(chargeId); final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeId);
if (chargeDefinition == null || chargeDefinition.isDeleted()) { throw new ChargeNotFoundException(chargeId); }
if (!chargeDefinition.isActive()) { throw new ChargeIsNotActiveException(chargeId, chargeDefinition.getName()); }
ChargeTimeType chargeTime = null; ChargeTimeType chargeTime = null;
if (chargeTimeType != null) { if (chargeTimeType != null) {
ChargeTimeType.fromInt(chargeTimeType); ChargeTimeType.fromInt(chargeTimeType);
@@ -23,13 +23,10 @@ import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepository; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepository;
import org.mifosplatform.organisation.staff.domain.Staff; import org.mifosplatform.organisation.staff.domain.Staff;
import org.mifosplatform.portfolio.charge.domain.Charge; import org.mifosplatform.portfolio.charge.domain.Charge;
import org.mifosplatform.portfolio.charge.domain.ChargeRepository; import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.mifosplatform.portfolio.charge.exception.ChargeIsNotActiveException;
import org.mifosplatform.portfolio.charge.exception.ChargeNotFoundException;
import org.mifosplatform.portfolio.charge.exception.LoanChargeNotFoundException; import org.mifosplatform.portfolio.charge.exception.LoanChargeNotFoundException;
import org.mifosplatform.portfolio.client.domain.Note; import org.mifosplatform.portfolio.client.domain.Note;
import org.mifosplatform.portfolio.client.domain.NoteRepository; import org.mifosplatform.portfolio.client.domain.NoteRepository;
import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand;
import org.mifosplatform.portfolio.loanaccount.command.LoanUpdateCommand; import org.mifosplatform.portfolio.loanaccount.command.LoanUpdateCommand;
import org.mifosplatform.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine; import org.mifosplatform.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine;
import org.mifosplatform.portfolio.loanaccount.domain.Loan; import org.mifosplatform.portfolio.loanaccount.domain.Loan;
@@ -44,7 +41,6 @@ import org.mifosplatform.portfolio.loanaccount.exception.LoanNotFoundException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentException; import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerUnassignmentException; import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerUnassignmentException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanTransactionNotFoundException; import org.mifosplatform.portfolio.loanaccount.exception.LoanTransactionNotFoundException;
import org.mifosplatform.portfolio.loanaccount.serialization.LoanChargeCommandFromApiJsonDeserializer;
import org.mifosplatform.portfolio.loanaccount.serialization.LoanEventApiJsonValidator; import org.mifosplatform.portfolio.loanaccount.serialization.LoanEventApiJsonValidator;
import org.mifosplatform.portfolio.loanaccount.serialization.LoanUpdateCommandFromApiJsonDeserializer; import org.mifosplatform.portfolio.loanaccount.serialization.LoanUpdateCommandFromApiJsonDeserializer;
import org.mifosplatform.portfolio.loanproduct.exception.InvalidCurrencyException; import org.mifosplatform.portfolio.loanproduct.exception.InvalidCurrencyException;
@@ -58,13 +54,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final PlatformSecurityContext context; private final PlatformSecurityContext context;
private final LoanEventApiJsonValidator loanEventApiJsonValidator; private final LoanEventApiJsonValidator loanEventApiJsonValidator;
private final LoanChargeCommandFromApiJsonDeserializer loanChargeCommandFromApiJsonDeserializer;
private final LoanUpdateCommandFromApiJsonDeserializer loanUpdateCommandFromApiJsonDeserializer; private final LoanUpdateCommandFromApiJsonDeserializer loanUpdateCommandFromApiJsonDeserializer;
private final LoanRepository loanRepository; private final LoanRepository loanRepository;
private final NoteRepository noteRepository; private final NoteRepository noteRepository;
private final LoanTransactionRepository loanTransactionRepository; private final LoanTransactionRepository loanTransactionRepository;
private final LoanAssembler loanAssembler; private final LoanAssembler loanAssembler;
private final ChargeRepository chargeRepository; private final ChargeRepositoryWrapper chargeRepository;
private final LoanChargeRepository loanChargeRepository; private final LoanChargeRepository loanChargeRepository;
private final ApplicationCurrencyRepository applicationCurrencyRepository; private final ApplicationCurrencyRepository applicationCurrencyRepository;
private final JournalEntryWritePlatformService journalEntryWritePlatformService; private final JournalEntryWritePlatformService journalEntryWritePlatformService;
@@ -72,15 +67,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
@Autowired @Autowired
public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
final LoanEventApiJsonValidator loanEventApiJsonValidator, final LoanEventApiJsonValidator loanEventApiJsonValidator,
final LoanChargeCommandFromApiJsonDeserializer loanChargeCommandFromApiJsonDeserializer,
final LoanUpdateCommandFromApiJsonDeserializer loanUpdateCommandFromApiJsonDeserializer, final LoanAssembler loanAssembler, final LoanUpdateCommandFromApiJsonDeserializer loanUpdateCommandFromApiJsonDeserializer, final LoanAssembler loanAssembler,
final LoanRepository loanRepository, final LoanTransactionRepository loanTransactionRepository, final LoanRepository loanRepository, final LoanTransactionRepository loanTransactionRepository,
final NoteRepository noteRepository, final ChargeRepository chargeRepository, final LoanChargeRepository loanChargeRepository, final NoteRepository noteRepository, final ChargeRepositoryWrapper chargeRepository,
final LoanChargeRepository loanChargeRepository,
final ApplicationCurrencyRepository applicationCurrencyRepository, final ApplicationCurrencyRepository applicationCurrencyRepository,
final JournalEntryWritePlatformService journalEntryWritePlatformService) { final JournalEntryWritePlatformService journalEntryWritePlatformService) {
this.context = context; this.context = context;
this.loanEventApiJsonValidator = loanEventApiJsonValidator; this.loanEventApiJsonValidator = loanEventApiJsonValidator;
this.loanChargeCommandFromApiJsonDeserializer = loanChargeCommandFromApiJsonDeserializer;
this.loanAssembler = loanAssembler; this.loanAssembler = loanAssembler;
this.loanRepository = loanRepository; this.loanRepository = loanRepository;
this.loanTransactionRepository = loanTransactionRepository; this.loanTransactionRepository = loanTransactionRepository;
@@ -472,24 +466,21 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
@Transactional @Transactional
@Override @Override
public CommandProcessingResult addLoanCharge(final Long loanId, final JsonCommand command) { public CommandProcessingResult addLoanCharge(final Long loanId, final JsonCommand command) {
this.context.authenticatedUser(); this.context.authenticatedUser();
// FIXME - KW - update loan charge api validation this.loanEventApiJsonValidator.validateAddLoanCharge(command.json());
final LoanChargeCommand loanChargeCommand = this.loanChargeCommandFromApiJsonDeserializer.commandFromApiJson(command.json());
// loanChargeCommand.validateForCreate();
final Loan loan = retrieveLoanBy(loanId); final Loan loan = retrieveLoanBy(loanId);
final Long chargeDefinitionId = command.longValueOfParameterNamed("chargeId"); final Long chargeDefinitionId = command.longValueOfParameterNamed("chargeId");
final Charge chargeDefinition = this.chargeRepository.findOne(chargeDefinitionId); final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeDefinitionId);
if (chargeDefinition == null || chargeDefinition.isDeleted()) { throw new ChargeNotFoundException(chargeDefinitionId); }
if (!chargeDefinition.isActive()) { throw new ChargeIsNotActiveException(chargeDefinition.getId(), chargeDefinition.getName()); }
final LoanCharge loanCharge = LoanCharge.createNewFromJson(loan, chargeDefinition, command); final LoanCharge loanCharge = LoanCharge.createNewFromJson(loan, chargeDefinition, command);
if (!loan.hasCurrencyCodeOf(chargeDefinition.getCurrencyCode())) { if (!loan.hasCurrencyCodeOf(chargeDefinition.getCurrencyCode())) {
String errorMessage = "Charge and Loan must have the same currency."; final String errorMessage = "Charge and Loan must have the same currency.";
throw new InvalidCurrencyException("charge", "attach.to.loan", errorMessage); throw new InvalidCurrencyException("loanCharge", "attach.to.loan", errorMessage);
} }
this.loanChargeRepository.save(loanCharge); this.loanChargeRepository.save(loanCharge);
@@ -513,8 +504,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.context.authenticatedUser(); this.context.authenticatedUser();
final LoanChargeCommand loanChargeCommand = this.loanChargeCommandFromApiJsonDeserializer.commandFromApiJson(command.json()); this.loanEventApiJsonValidator.validateUpdateOfLoanCharge(command.json());
loanChargeCommand.validateForUpdate();
final Loan loan = retrieveLoanBy(loanId); final Loan loan = retrieveLoanBy(loanId);
final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId); final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId);
@@ -540,9 +530,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.context.authenticatedUser(); this.context.authenticatedUser();
final LoanChargeCommand loanChargeCommand = this.loanChargeCommandFromApiJsonDeserializer.commandFromApiJson(command.json());
loanChargeCommand.validateForUpdate();
final Loan loan = retrieveLoanBy(loanId); final Loan loan = retrieveLoanBy(loanId);
final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId); final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId);
@@ -553,12 +540,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.loanTransactionRepository.save(waiveTransaction); this.loanTransactionRepository.save(waiveTransaction);
this.loanRepository.save(loan); this.loanRepository.save(loan);
final String noteText = ""; // command.getNote();
if (StringUtils.isNotBlank(noteText)) {
final Note note = Note.loanTransactionNote(loan, waiveTransaction, noteText);
this.noteRepository.save(note);
}
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
.withEntityId(loanChargeId) // .withEntityId(loanChargeId) //
@@ -187,10 +187,10 @@ public class DepositAccount extends AbstractAuditableCustom<AppUser, Long> {
if (interestCompoundingAllowed) { if (interestCompoundingAllowed) {
futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityFor(deposit, tenureInMonths, futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityFor(deposit, tenureInMonths,
maturityInterestRate, interestCompoundedEvery, interestCompoundedFrequencyPeriodType); maturityInterestRate, interestCompoundedEvery);
} else { } else {
futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityForSimpleInterest(deposit, futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityForSimpleInterest(deposit,
tenureInMonths, maturityInterestRate, interestCompoundedEvery, interestCompoundedFrequencyPeriodType); tenureInMonths, maturityInterestRate, interestCompoundedEvery);
} }
DepositAccountStatus from = null; DepositAccountStatus from = null;
@@ -278,11 +278,12 @@ public class DepositAccount extends AbstractAuditableCustom<AppUser, Long> {
this.externalId = this.getId() + "_" + this.externalId; this.externalId = this.getId() + "_" + this.externalId;
} }
public Map<String, Object> approve(final LocalDate actualCommencementDate, final DepositLifecycleStateMachine depositLifecycleStateMachine, public Map<String, Object> approve(final LocalDate actualCommencementDate,
final JsonCommand command, final FixedTermDepositInterestCalculator calculator) { final DepositLifecycleStateMachine depositLifecycleStateMachine, final JsonCommand command,
final FixedTermDepositInterestCalculator calculator) {
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>(20); final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>(20);
final String localeAsInput = command.locale(); final String localeAsInput = command.locale();
DepositAccountStatus statusEnum = depositLifecycleStateMachine.transition(DepositAccountEvent.DEPOSIT_APPROVED, DepositAccountStatus statusEnum = depositLifecycleStateMachine.transition(DepositAccountEvent.DEPOSIT_APPROVED,
DepositAccountStatus.fromInt(this.depositStatus)); DepositAccountStatus.fromInt(this.depositStatus));
@@ -337,15 +338,16 @@ public class DepositAccount extends AbstractAuditableCustom<AppUser, Long> {
if (this.interestCompoundingAllowed) { if (this.interestCompoundingAllowed) {
futureValueOnMaturity = calculator.calculateInterestOnMaturityFor(getDeposit(), this.tenureInMonths, this.interestRate, futureValueOnMaturity = calculator.calculateInterestOnMaturityFor(getDeposit(), this.tenureInMonths, this.interestRate,
this.interestCompoundedEvery, getInterestCompoundedFrequencyType()); this.interestCompoundedEvery);
} else { } else {
futureValueOnMaturity = calculator.calculateInterestOnMaturityForSimpleInterest(getDeposit(), this.tenureInMonths, futureValueOnMaturity = calculator.calculateInterestOnMaturityForSimpleInterest(getDeposit(), this.tenureInMonths,
this.interestRate, this.interestCompoundedEvery, getInterestCompoundedFrequencyType()); this.interestRate, this.interestCompoundedEvery);
} }
this.interestAccrued = futureValueOnMaturity.minus(getDeposit()).getAmount(); this.interestAccrued = futureValueOnMaturity.minus(getDeposit()).getAmount();
this.total = futureValueOnMaturity.getAmount(); this.total = futureValueOnMaturity.getAmount();
DepositAccountTransaction depositaccountTransaction = DepositAccountTransaction.deposit(getDeposit(), getActualCommencementDate(), null); DepositAccountTransaction depositaccountTransaction = DepositAccountTransaction.deposit(getDeposit(), getActualCommencementDate(),
null);
depositaccountTransaction.updateAccount(this); depositaccountTransaction.updateAccount(this);
this.depositaccountTransactions.add(depositaccountTransaction); this.depositaccountTransactions.add(depositaccountTransaction);
@@ -592,8 +594,11 @@ public class DepositAccount extends AbstractAuditableCustom<AppUser, Long> {
preClosureInterestRate); preClosureInterestRate);
this.interestAccrued = this.interestAccrued.add(accuredInterestAfterMaturityToTillDate.getAmount()); this.interestAccrued = this.interestAccrued.add(accuredInterestAfterMaturityToTillDate.getAmount());
this.availableInterest = this.availableInterest.add(accuredInterestAfterMaturityToTillDate.getAmount()); this.availableInterest = this.availableInterest.add(accuredInterestAfterMaturityToTillDate.getAmount());
DepositAccountTransaction depositaccountTransaction = DepositAccountTransaction.withdraw(getDeposit(), eventDate,Money.of(currency, this.availableInterest),getDeposit().getAmount().add(this.availableInterest)); DepositAccountTransaction depositaccountTransaction = DepositAccountTransaction.withdraw(getDeposit(), eventDate,
//DepositAccountTransaction depositaccountTransaction = DepositAccountTransaction.withdraw(getDeposit(), eventDate, getAccuredInterest()); Money.of(currency, this.availableInterest), getDeposit().getAmount().add(this.availableInterest));
// DepositAccountTransaction depositaccountTransaction =
// DepositAccountTransaction.withdraw(getDeposit(), eventDate,
// getAccuredInterest());
depositaccountTransaction.updateAccount(this); depositaccountTransaction.updateAccount(this);
this.depositaccountTransactions.add(depositaccountTransaction); this.depositaccountTransactions.add(depositaccountTransaction);
@@ -651,8 +656,11 @@ public class DepositAccount extends AbstractAuditableCustom<AppUser, Long> {
@SuppressWarnings("unused") @SuppressWarnings("unused")
boolean statustest = isActive(); boolean statustest = isActive();
if (isActive()) { if (isActive()) {
//DepositAccountTransaction depositAccountTransaction = DepositAccountTransaction.withdraw(null, new LocalDate(), interest); // DepositAccountTransaction depositAccountTransaction =
DepositAccountTransaction depositAccountTransaction = DepositAccountTransaction.withdraw(null, new LocalDate(), interest,getDeposit().getAmount().add(this.availableInterest).subtract(interest.getAmount())); // DepositAccountTransaction.withdraw(null, new LocalDate(),
// interest);
DepositAccountTransaction depositAccountTransaction = DepositAccountTransaction.withdraw(null, new LocalDate(), interest,
getDeposit().getAmount().add(this.availableInterest).subtract(interest.getAmount()));
depositAccountTransaction.updateAccount(this); depositAccountTransaction.updateAccount(this);
this.depositaccountTransactions.add(depositAccountTransaction); this.depositaccountTransactions.add(depositAccountTransaction);
this.interstPaid = this.interstPaid.add(interest.getAmount()); this.interstPaid = this.interstPaid.add(interest.getAmount());
@@ -667,9 +675,13 @@ public class DepositAccount extends AbstractAuditableCustom<AppUser, Long> {
DepositAccountStatus.fromInt(this.depositStatus)); DepositAccountStatus.fromInt(this.depositStatus));
this.depositStatus = statusEnumForClose.getValue(); this.depositStatus = statusEnumForClose.getValue();
//DepositAccountTransaction depositaccountTransaction = DepositAccountTransaction.withdraw(getDeposit(), new LocalDate(),Money.of(getDeposit().getCurrency(), getTotal()).minus(getDeposit())); // DepositAccountTransaction depositaccountTransaction =
DepositAccountTransaction depositaccountTransaction = DepositAccountTransaction.withdraw(getDeposit(), new LocalDate(),Money.of(getDeposit().getCurrency(), getTotal()).minus(getDeposit()), // DepositAccountTransaction.withdraw(getDeposit(), new
getDeposit().plus(Money.of(getDeposit().getCurrency(), getTotal()).minus(getDeposit())).getAmount()); // LocalDate(),Money.of(getDeposit().getCurrency(),
// getTotal()).minus(getDeposit()));
DepositAccountTransaction depositaccountTransaction = DepositAccountTransaction.withdraw(getDeposit(), new LocalDate(),
Money.of(getDeposit().getCurrency(), getTotal()).minus(getDeposit()),
getDeposit().plus(Money.of(getDeposit().getCurrency(), getTotal()).minus(getDeposit())).getAmount());
depositaccountTransaction.updateAccount(this); depositaccountTransaction.updateAccount(this);
this.depositaccountTransactions.add(depositaccountTransaction); this.depositaccountTransactions.add(depositaccountTransaction);
@@ -687,10 +699,10 @@ public class DepositAccount extends AbstractAuditableCustom<AppUser, Long> {
if (isInterestCompoundingAllowed) { if (isInterestCompoundingAllowed) {
futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityFor(deposit, tenureInMonths, futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityFor(deposit, tenureInMonths,
maturityInterestRate, interestCompoundedEvery, compoundingInterestFrequency); maturityInterestRate, interestCompoundedEvery);
} else { } else {
futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityForSimpleInterest(deposit, futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityForSimpleInterest(deposit,
tenureInMonths, maturityInterestRate, interestCompoundedEvery, compoundingInterestFrequency); tenureInMonths, maturityInterestRate, interestCompoundedEvery);
} }
setExternalId(externalId); setExternalId(externalId);
@@ -747,18 +759,17 @@ public class DepositAccount extends AbstractAuditableCustom<AppUser, Long> {
if (lastInterestPostedDate.isBefore(getMaturityDate())) { if (lastInterestPostedDate.isBefore(getMaturityDate())) {
if (nextInterestPostingDate.isAfter(getMaturityDate())) { if (nextInterestPostingDate.isAfter(getMaturityDate())) {
Integer noofRemainMonthsAfterMaturity = Months.monthsBetween(getMaturityDate(), nextInterestPostingDate).getMonths(); Integer noofRemainMonthsAfterMaturity = Months.monthsBetween(getMaturityDate(), nextInterestPostingDate).getMonths();
Integer noOfMonthsBeforeMaturity = Months.monthsBetween(lastInterestPostedDate, getMaturityDate().plusDays(1)).getMonths(); Integer noOfMonthsBeforeMaturity = Months.monthsBetween(lastInterestPostedDate, getMaturityDate().plusDays(1))
Money futureValueOnMaturity = null; .getMonths();
Money futureValueOnMaturity = Money.zero(this.currency);
if (noOfMonthsBeforeMaturity > 0) { if (noOfMonthsBeforeMaturity > 0) {
if (this.interestCompoundingAllowed) { if (this.interestCompoundingAllowed) {
futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityFor(deposit, futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityFor(deposit,
noOfMonthsBeforeMaturity, this.interestRate, this.interestCompoundedEvery, noOfMonthsBeforeMaturity, this.interestRate, this.interestCompoundedEvery);
getInterestCompoundedFrequencyType());
} else { } else {
futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityForSimpleInterest( futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityForSimpleInterest(
deposit, noOfMonthsBeforeMaturity, this.interestRate, this.interestCompoundedEvery, deposit, noOfMonthsBeforeMaturity, this.interestRate, this.interestCompoundedEvery);
getInterestCompoundedFrequencyType());
} }
this.interestPostedAmount = this.interestPostedAmount.add(futureValueOnMaturity.minus(deposit).getAmount()); this.interestPostedAmount = this.interestPostedAmount.add(futureValueOnMaturity.minus(deposit).getAmount());
this.availableInterest = this.availableInterest.add(futureValueOnMaturity.minus(deposit).getAmount()); this.availableInterest = this.availableInterest.add(futureValueOnMaturity.minus(deposit).getAmount());
@@ -770,12 +781,10 @@ public class DepositAccount extends AbstractAuditableCustom<AppUser, Long> {
if (this.interestCompoundingAllowed) { if (this.interestCompoundingAllowed) {
futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityFor(deposit, futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityFor(deposit,
noofRemainMonthsAfterMaturity, this.preClosureInterestRate, this.interestCompoundedEvery, noofRemainMonthsAfterMaturity, this.preClosureInterestRate, this.interestCompoundedEvery);
getInterestCompoundedFrequencyType());
} else { } else {
futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityForSimpleInterest( futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityForSimpleInterest(
deposit, noofRemainMonthsAfterMaturity, this.preClosureInterestRate, this.interestCompoundedEvery, deposit, noofRemainMonthsAfterMaturity, this.preClosureInterestRate, this.interestCompoundedEvery);
getInterestCompoundedFrequencyType());
} }
this.interestPostedAmount = this.interestPostedAmount.add(futureValueOnMaturity.minus(deposit).getAmount()); this.interestPostedAmount = this.interestPostedAmount.add(futureValueOnMaturity.minus(deposit).getAmount());
this.availableInterest = this.availableInterest.add(futureValueOnMaturity.minus(deposit).getAmount()); this.availableInterest = this.availableInterest.add(futureValueOnMaturity.minus(deposit).getAmount());
@@ -784,8 +793,8 @@ public class DepositAccount extends AbstractAuditableCustom<AppUser, Long> {
} }
// add transaction entry for each interest posting // add transaction entry for each interest posting
DepositAccountTransaction depositaccountTransaction = DepositAccountTransaction.postInterest(null, nextInterestPostingDate,futureValueOnMaturity.minus(deposit),deposit.getAmount().add(this.availableInterest)); DepositAccountTransaction depositaccountTransaction = DepositAccountTransaction.postInterest(null,
//DepositAccountTransaction depositaccountTransaction = DepositAccountTransaction.postInterest(null, nextInterestPostingDate, futureValueOnMaturity.minus(deposit)); nextInterestPostingDate, futureValueOnMaturity.minus(deposit), deposit.getAmount().add(this.availableInterest));
depositaccountTransaction.updateAccount(this); depositaccountTransaction.updateAccount(this);
this.depositaccountTransactions.add(depositaccountTransaction); this.depositaccountTransactions.add(depositaccountTransaction);
@@ -793,12 +802,10 @@ public class DepositAccount extends AbstractAuditableCustom<AppUser, Long> {
Money futureValueOnMaturity = null; Money futureValueOnMaturity = null;
if (this.interestCompoundingAllowed) { if (this.interestCompoundingAllowed) {
futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityFor(deposit, futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityFor(deposit,
this.interestCompoundedEvery, this.interestRate, this.interestCompoundedEvery, this.interestCompoundedEvery, this.interestRate, this.interestCompoundedEvery);
getInterestCompoundedFrequencyType());
} else { } else {
futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityForSimpleInterest(deposit, futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityForSimpleInterest(deposit,
this.interestCompoundedEvery, this.interestRate, this.interestCompoundedEvery, this.interestCompoundedEvery, this.interestRate, this.interestCompoundedEvery);
getInterestCompoundedFrequencyType());
} }
this.interestPostedAmount = this.interestPostedAmount.add(futureValueOnMaturity.minus(deposit).getAmount()); this.interestPostedAmount = this.interestPostedAmount.add(futureValueOnMaturity.minus(deposit).getAmount());
this.availableInterest = this.availableInterest.add(futureValueOnMaturity.minus(deposit).getAmount()); this.availableInterest = this.availableInterest.add(futureValueOnMaturity.minus(deposit).getAmount());
@@ -806,8 +813,8 @@ public class DepositAccount extends AbstractAuditableCustom<AppUser, Long> {
this.nextInterestPostingDate = nextInterestPostingDate.plusMonths(this.interestCompoundedEvery).toDate(); this.nextInterestPostingDate = nextInterestPostingDate.plusMonths(this.interestCompoundedEvery).toDate();
// add transaction entry for each interest posting // add transaction entry for each interest posting
// DepositAccountTransaction depositaccountTransaction = DepositAccountTransaction.postInterest(null, nextInterestPostingDate, futureValueOnMaturity.minus(deposit)); DepositAccountTransaction depositaccountTransaction = DepositAccountTransaction.postInterest(null,
DepositAccountTransaction depositaccountTransaction = DepositAccountTransaction.postInterest(null, nextInterestPostingDate,futureValueOnMaturity.minus(deposit),deposit.getAmount().add(this.availableInterest)); nextInterestPostingDate, futureValueOnMaturity.minus(deposit), deposit.getAmount().add(this.availableInterest));
depositaccountTransaction.updateAccount(this); depositaccountTransaction.updateAccount(this);
this.depositaccountTransactions.add(depositaccountTransaction); this.depositaccountTransactions.add(depositaccountTransaction);
} }
@@ -815,12 +822,10 @@ public class DepositAccount extends AbstractAuditableCustom<AppUser, Long> {
Money futureValueOnMaturity = null; Money futureValueOnMaturity = null;
if (this.interestCompoundingAllowed) { if (this.interestCompoundingAllowed) {
futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityFor(deposit, futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityFor(deposit,
this.interestCompoundedEvery, this.preClosureInterestRate, this.interestCompoundedEvery, this.interestCompoundedEvery, this.preClosureInterestRate, this.interestCompoundedEvery);
getInterestCompoundedFrequencyType());
} else { } else {
futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityForSimpleInterest(deposit, futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityForSimpleInterest(deposit,
this.interestCompoundedEvery, this.preClosureInterestRate, this.interestCompoundedEvery, this.interestCompoundedEvery, this.preClosureInterestRate, this.interestCompoundedEvery);
getInterestCompoundedFrequencyType());
} }
this.interestPostedAmount = this.interestPostedAmount.add(futureValueOnMaturity.minus(deposit).getAmount()); this.interestPostedAmount = this.interestPostedAmount.add(futureValueOnMaturity.minus(deposit).getAmount());
this.availableInterest = this.availableInterest.add(futureValueOnMaturity.minus(deposit).getAmount()); this.availableInterest = this.availableInterest.add(futureValueOnMaturity.minus(deposit).getAmount());
@@ -828,7 +833,8 @@ public class DepositAccount extends AbstractAuditableCustom<AppUser, Long> {
this.nextInterestPostingDate = nextInterestPostingDate.plusMonths(this.interestCompoundedEvery).toDate(); this.nextInterestPostingDate = nextInterestPostingDate.plusMonths(this.interestCompoundedEvery).toDate();
// add transaction entry for each interest posting // add transaction entry for each interest posting
DepositAccountTransaction depositaccountTransaction = DepositAccountTransaction.postInterest(null, nextInterestPostingDate, futureValueOnMaturity.minus(deposit),deposit.getAmount().add(this.availableInterest)); DepositAccountTransaction depositaccountTransaction = DepositAccountTransaction.postInterest(null, nextInterestPostingDate,
futureValueOnMaturity.minus(deposit), deposit.getAmount().add(this.availableInterest));
depositaccountTransaction.updateAccount(this); depositaccountTransaction.updateAccount(this);
this.depositaccountTransactions.add(depositaccountTransaction); this.depositaccountTransactions.add(depositaccountTransaction);
} }
@@ -10,25 +10,14 @@ import java.math.MathContext;
import java.math.RoundingMode; import java.math.RoundingMode;
import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service
public class FixedTermDepositInterestCalculator { public class FixedTermDepositInterestCalculator {
// FIXME - HUGO - Are the unused field here needed?
public Money calculateInterestOnMaturityFor(final Money deposit, final Integer tenureInMonths, final BigDecimal maturityInterestRate, public Money calculateInterestOnMaturityFor(final Money deposit, final Integer tenureInMonths, final BigDecimal maturityInterestRate,
final Integer interestCompoundedEvery, final PeriodFrequencyType interestCompoundedFrequencyPeriodType) { final Integer interestCompoundedEvery) {
// TODO - HUGO - Can change 'interest compounding' fields into something
// simpler like interestCompoundedFrequency {None (Simple Interest),
// Monthly, Quarterly, Half-Yearly, Annually}
// - the 'interestCompoundedEvery' would then be inferred form the
// selected type.
// - the special case of 'simple interest' or 'no compounding' would use
// simple formula rather than 'future value' function.
// FIXME - HUGO - assuming only monthly compounding period for now
MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN); MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN);
Integer monthsInYear = 12; Integer monthsInYear = 12;
@@ -72,8 +61,8 @@ public class FixedTermDepositInterestCalculator {
return retval; return retval;
} }
public Money calculateInterestOnMaturityForSimpleInterest(final Money deposit, final Integer tenureInMonths, final BigDecimal maturityInterestRate, public Money calculateInterestOnMaturityForSimpleInterest(final Money deposit, final Integer tenureInMonths,
final Integer interestCompoundedEvery, final PeriodFrequencyType interestCompoundedFrequencyPeriodType) { final BigDecimal maturityInterestRate, final Integer interestCompoundedEvery) {
MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN); MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN);
Integer monthsInYear = 12; Integer monthsInYear = 12;