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` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`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,
`external_id` varchar(100) DEFAULT NULL,
`is_deleted` tinyint(1) NOT NULL DEFAULT '0',
@@ -740,9 +743,13 @@ CREATE TABLE `m_group` (
UNIQUE KEY `name` (`name`),
UNIQUE KEY `external_id` (`external_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 `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;
/*!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 */;
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`
--
@@ -1933,4 +1969,4 @@ UNLOCK TABLES;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!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;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
/**
* Immutable command for creating and updating loan charges.
* Java object representation of {@link LoanCharge} API JSON.
*/
public class LoanChargeCommand implements Comparable<LoanChargeCommand> {
@@ -23,10 +19,12 @@ public class LoanChargeCommand implements Comparable<LoanChargeCommand> {
private final Long id;
private final Long chargeId;
private final BigDecimal amount;
@SuppressWarnings("unused")
private final Integer chargeTimeType;
@SuppressWarnings("unused")
private final Integer chargeCalculationType;
@SuppressWarnings("unused")
private final LocalDate specifiedDueDate;
private final LocalDate dueDate;
public LoanChargeCommand(final Long id, final Long chargeId, final BigDecimal amount, final Integer chargeTimeType,
final Integer chargeCalculationType, final LocalDate specifiedDueDate) {
@@ -35,7 +33,7 @@ public class LoanChargeCommand implements Comparable<LoanChargeCommand> {
this.amount = amount;
this.chargeTimeType = chargeTimeType;
this.chargeCalculationType = chargeCalculationType;
this.specifiedDueDate = specifiedDueDate;
this.dueDate = specifiedDueDate;
}
@Override
@@ -46,18 +44,4 @@ public class LoanChargeCommand implements Comparable<LoanChargeCommand> {
}
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;
@SuppressWarnings("unused")
private final LocalDate dueAsOfDate;
private final LocalDate dueDate;
@SuppressWarnings("unused")
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,
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 boolean penalty) {
this.id = id;
@@ -94,7 +94,7 @@ public class LoanChargeData {
this.amountWrittenOff = amountWrittenOff;
this.amountOutstanding = amountOutstanding;
this.chargeTimeType = chargeTimeType;
this.dueAsOfDate = dueAsOfDate;
this.dueDate = dueDate;
this.chargeCalculationType = chargeCalculationType;
this.percentage = percentage;
this.amountPercentageAppliedTo = amountPercentageAppliedTo;
@@ -122,7 +122,7 @@ public class LoanChargeData {
this.amountWrittenOff = BigDecimal.ZERO;
this.amountOutstanding = amount;
this.chargeTimeType = chargeTimeType;
this.dueAsOfDate = null;
this.dueDate = null;
this.chargeCalculationType = chargeCalculationType;
this.percentage = null;
this.amountPercentageAppliedTo = null;
@@ -150,7 +150,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
for (LoanCharge loanCharge : charges) {
if (loanCharge.isNotFullyPaid() && !loanCharge.isDueAtDisbursement()) {
if (earliestUnpaidCharge == null
|| loanCharge.getDueForCollectionAsOfLocalDate().isBefore(earliestUnpaidCharge.getDueForCollectionAsOfLocalDate())) {
|| loanCharge.getDueLocalDate().isBefore(earliestUnpaidCharge.getDueLocalDate())) {
earliestUnpaidCharge = loanCharge;
}
}
@@ -322,7 +322,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
Money chargesDue = Money.of(getCurrency(), BigDecimal.ZERO);
for (LoanCharge charge : getNullPointerSafeLoanCharges()) {
for (LoanCharge charge : setOfLoanCharges()) {
if (charge.isDueAtDisbursement()) {
chargesDue = chargesDue.plus(charge.amount());
}
@@ -362,7 +362,8 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
validateLoanIsNotClosed(loanCharge);
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
// if the loan charge payment type is 'Disbursement'.
// 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());
loanCharge.update(this);
getNullPointerSafeLoanCharges().add(loanCharge);
setOfLoanCharges().add(loanCharge);
updateTotalChargesDueAtDisbursement();
@@ -390,11 +391,11 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
if (!loanCharge.isDueAtDisbursement()) {
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement,
getCurrency(), this.repaymentScheduleInstallments, getNullPointerSafeLoanCharges());
getCurrency(), this.repaymentScheduleInstallments, setOfLoanCharges());
} else {
// just reprocess the loan schedule only for now.
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.
validateLoanChargeIsNotWaived(loanCharge);
boolean removed = getNullPointerSafeLoanCharges().remove(loanCharge);
boolean removed = setOfLoanCharges().remove(loanCharge);
if (removed) {
updateTotalChargesDueAtDisbursement();
}
@@ -447,7 +448,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(loanCurrency())) {
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
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);
if (getNullPointerSafeLoanCharges().contains(loanCharge)) {
if (setOfLoanCharges().contains(loanCharge)) {
final Map<String, Object> loanChargeChanges = loanCharge.update(command, getPrincpal().getAmount());
actualChanges.putAll(loanChargeChanges);
updateTotalChargesDueAtDisbursement();
@@ -495,11 +496,11 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
if (!loanCharge.isDueAtDisbursement()) {
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement,
getCurrency(), this.repaymentScheduleInstallments, getNullPointerSafeLoanCharges());
getCurrency(), this.repaymentScheduleInstallments, setOfLoanCharges());
} else {
// reprocess loan schedule based on charge been waived.
LoanScheduleWrapper wrapper = new LoanScheduleWrapper();
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, getNullPointerSafeLoanCharges());
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, setOfLoanCharges());
}
return actualChanges;
@@ -523,7 +524,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
LocalDate transactionDate = getDisbursementDate();
if (loanCharge.isSpecifiedDueDate()) {
transactionDate = loanCharge.getDueForCollectionAsOfLocalDate();
transactionDate = loanCharge.getDueLocalDate();
}
updateTotalChargesDueAtDisbursement();
@@ -538,11 +539,11 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(loanCurrency())) {
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement,
getCurrency(), this.repaymentScheduleInstallments, getNullPointerSafeLoanCharges());
getCurrency(), this.repaymentScheduleInstallments, setOfLoanCharges());
} else {
// reprocess loan schedule based on charge been waived.
LoanScheduleWrapper wrapper = new LoanScheduleWrapper();
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, getNullPointerSafeLoanCharges());
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, setOfLoanCharges());
}
doPostLoanTransactionChecks(waiveLoanChargeTransaction.getTransactionDate(), loanLifecycleStateMachine);
@@ -595,8 +596,8 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
}
public void updateLoanCharges(final Set<LoanCharge> loanCharges) {
getNullPointerSafeLoanCharges().clear();
getNullPointerSafeLoanCharges().addAll(associateChargesWithThisLoan(loanCharges));
setOfLoanCharges().clear();
setOfLoanCharges().addAll(associateChargesWithThisLoan(loanCharges));
this.totalChargesDueAtDisbursement = deriveSumTotalOfChargesDueAtDisbursement();
}
@@ -630,7 +631,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessor
.determineProcessor(this.transactionProcessingStrategy);
loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), repaymentsOrWaivers, getCurrency(),
this.repaymentScheduleInstallments, getNullPointerSafeLoanCharges());
this.repaymentScheduleInstallments, setOfLoanCharges());
}
public void updateLoanScheduleDependentDerivedFields() {
@@ -785,7 +786,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
final String chargesParamName = "charges";
if (command.parameterExists(chargesParamName)) {
final Set<LoanCharge> existingLoanCharges = getNullPointerSafeLoanCharges();
final Set<LoanCharge> existingLoanCharges = setOfLoanCharges();
if (!possiblyModifedLoanCharges.equals(existingLoanCharges)) {
actualChanges.put(chargesParamName, getLoanCharges(possiblyModifedLoanCharges));
@@ -797,20 +798,6 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
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";
@@ -819,22 +806,12 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
if (!possiblyModifedLoanCollateralItems.equals(this.collateral)) {
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;
}
private Set<LoanCharge> getNullPointerSafeLoanCharges() {
private Set<LoanCharge> setOfLoanCharges() {
Set<LoanCharge> loanCharges = this.charges;
if (this.charges == null) {
loanCharges = new HashSet<LoanCharge>();
@@ -933,7 +910,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
}
// charges are optional
for (LoanCharge loanCharge : getNullPointerSafeLoanCharges()) {
for (LoanCharge loanCharge : setOfLoanCharges()) {
validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate());
}
}
@@ -1190,7 +1167,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
final LoanSchedule loanSchedule = new LoanSchedule(loanScheduleGenerator, applicationCurrency, principal, interestRatePerPeriod,
interestRatePeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod,
repaymentEvery, repaymentPeriodFrequencyType, numberOfRepayments, amortizationMethod, loanTermFrequency,
loanTermPeriodFrequencyType, getNullPointerSafeLoanCharges(), this.getDisbursementDate(),
loanTermPeriodFrequencyType, setOfLoanCharges(), this.getDisbursementDate(),
this.getExpectedFirstRepaymentOnDate(), this.getInterestChargedFromDate(), inArrearsTolerance);
final LoanScheduleData generatedData = loanSchedule.generate();
@@ -1215,7 +1192,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
chargesPayment.updateLoan(this);
this.loanTransactions.add(chargesPayment);
for (LoanCharge charge : getNullPointerSafeLoanCharges()) {
for (LoanCharge charge : setOfLoanCharges()) {
if (charge.isDueAtDisbursement()) {
charge.markAsFullyPaid();
}
@@ -1281,7 +1258,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
private void updateLoanToPreDisbursalState() {
this.actualDisbursementDate = null;
for (LoanCharge charge : getNullPointerSafeLoanCharges()) {
for (LoanCharge charge : setOfLoanCharges()) {
charge.resetToOriginal(loanCurrency());
}
@@ -1290,7 +1267,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
}
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) {
@@ -1370,11 +1347,11 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
.determineProcessor(this.transactionProcessingStrategy);
if (isTransactionChronologicallyLatest && adjustedTransaction == null) {
loanRepaymentScheduleTransactionProcessor.handleTransaction(loanTransaction, getCurrency(), this.repaymentScheduleInstallments,
getNullPointerSafeLoanCharges());
setOfLoanCharges());
} else {
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement,
getCurrency(), this.repaymentScheduleInstallments, getNullPointerSafeLoanCharges());
getCurrency(), this.repaymentScheduleInstallments, setOfLoanCharges());
}
doPostLoanTransactionChecks(loanTransaction.getTransactionDate(), loanLifecycleStateMachine);
@@ -50,7 +50,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
@Temporal(TemporalType.DATE)
@Column(name = "due_for_collection_as_of_date")
private Date dueForCollectionAsOfDate;
private Date dueDate;
@Column(name = "charge_calculation_enum")
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) {
final BigDecimal amount = command.bigDecimalValueOfParameterNamed("amount");
final Integer chargeTimeType = command.integerValueOfParameterNamed("chargeTimeType");
final Integer chargeCalculationType = command.integerValueOfParameterNamed("chargeCalculationType");
final LocalDate specifiedDueDate = command.localDateValueOfParameterNamed("specifiedDueDate");
// final Integer chargeTimeType =
// command.integerValueOfParameterNamed("chargeTimeType");
// final Integer chargeCalculationType =
// command.integerValueOfParameterNamed("chargeCalculationType");
final LocalDate dueDate = command.localDateValueOfParameterNamed("dueDate");
ChargeTimeType chargeTime = null;
if (chargeTimeType != null) {
chargeTime = ChargeTimeType.fromInt(chargeTimeType);
}
// if (chargeTimeType != null) {
// chargeTime = ChargeTimeType.fromInt(chargeTimeType);
// }
ChargeCalculationType chargeCalculation = null;
if (chargeCalculationType != null) {
chargeCalculation = ChargeCalculationType.fromInt(chargeCalculationType);
}
// if (chargeCalculationType != null) {
// chargeCalculation =
// ChargeCalculationType.fromInt(chargeCalculationType);
// }
return new LoanCharge(loan, chargeDefinition, loan.getPrincpal().getAmount(), amount, chargeTime, chargeCalculation,
specifiedDueDate);
dueDate);
}
/*
* loanPrincipal is required for charges that are percentage based
*/
public static LoanCharge createNewWithoutLoan(final Charge chargeDefinition, final BigDecimal loanPrincipal, final BigDecimal amount,
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate specifiedDueDate) {
return new LoanCharge(null, chargeDefinition, loanPrincipal, amount, chargeTime, chargeCalculation, specifiedDueDate);
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate) {
return new LoanCharge(null, chargeDefinition, loanPrincipal, amount, chargeTime, chargeCalculation, dueDate);
}
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,
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate specifiedDueDate) {
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate) {
this.loan = loan;
this.charge = chargeDefinition;
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 (specifiedDueDate == null) {
final String defaultUserMessage = "Loan charge is missing specified due date";
throw new LoanChargeWithoutMandatoryFieldException("loancharge", "specifiedDueDate", defaultUserMessage,
if (dueDate == null) {
final String defaultUserMessage = "Loan charge is missing due date.";
throw new LoanChargeWithoutMandatoryFieldException("loanCharge", "dueDate", defaultUserMessage,
chargeDefinition.getId(), chargeDefinition.getName());
}
this.dueForCollectionAsOfDate = specifiedDueDate.toDate();
this.dueDate = dueDate.toDate();
} else {
this.dueForCollectionAsOfDate = null;
this.dueDate = null;
}
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) {
if (specifiedDueDate != null) {
this.dueForCollectionAsOfDate = specifiedDueDate.toDate();
this.dueDate = specifiedDueDate.toDate();
}
if (amount != null) {
@@ -279,31 +282,37 @@ public class LoanCharge extends AbstractPersistable<Long> {
final String dateFormatAsInput = command.dateFormat();
final String localeAsInput = command.locale();
final String chargeTimeParamName = "chargeTime";
if (command.isChangeInIntegerParameterNamed(chargeTimeParamName, this.chargeTime)) {
final Integer newValue = command.integerValueOfParameterNamed(chargeTimeParamName);
actualChanges.put(chargeTimeParamName, newValue);
actualChanges.put("locale", localeAsInput);
this.chargeTime = ChargeTimeType.fromInt(newValue).getValue();
}
// final String chargeTimeParamName = "chargeTime";
// if (command.isChangeInIntegerParameterNamed(chargeTimeParamName,
// this.chargeTime)) {
// final Integer newValue =
// command.integerValueOfParameterNamed(chargeTimeParamName);
// 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";
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 specifiedDueDateParamName = "specifiedDueDate";
if (command.isChangeInLocalDateParameterNamed(specifiedDueDateParamName, getDueForCollectionAsOfLocalDate())) {
final String specifiedDueDateParamName = "dueDate";
if (command.isChangeInLocalDateParameterNamed(specifiedDueDateParamName, getDueLocalDate())) {
final String valueAsInput = command.stringValueOfParameterNamed(specifiedDueDateParamName);
actualChanges.put(specifiedDueDateParamName, valueAsInput);
actualChanges.put("dateFormat", dateFormatAsInput);
actualChanges.put("locale", localeAsInput);
final LocalDate newValue = command.localDateValueOfParameterNamed(specifiedDueDateParamName);
this.dueForCollectionAsOfDate = newValue.toDate();
this.dueDate = newValue.toDate();
}
final String amountParamName = "amount";
@@ -355,13 +364,13 @@ public class LoanCharge extends AbstractPersistable<Long> {
public LoanChargeCommand toCommand() {
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;
if (this.dueForCollectionAsOfDate != null) {
specifiedDueDate = new LocalDate(this.dueForCollectionAsOfDate);
if (this.dueDate != null) {
specifiedDueDate = new LocalDate(this.dueDate);
}
return specifiedDueDate;
}
@@ -418,7 +427,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
}
public boolean isDueForCollectionFromAndUpToAndIncluding(final LocalDate fromNotInclusive, final LocalDate upToAndInclusive) {
final LocalDate specifiedDueDate = getDueForCollectionAsOfLocalDate();
final LocalDate specifiedDueDate = getDueLocalDate();
return occursOnDayFromAndUpToAndIncluding(fromNotInclusive, upToAndInclusive, specifiedDueDate);
}
@@ -508,7 +517,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
.append(this.getId(), rhs.getId()) //
.append(this.charge.getId(), rhs.charge.getId()) //
.append(this.amount, rhs.amount) //
.append(this.getDueForCollectionAsOfLocalDate(), rhs.getDueForCollectionAsOfLocalDate()) //
.append(this.getDueLocalDate(), rhs.getDueLocalDate()) //
.isEquals();
}
@@ -517,7 +526,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
return new HashCodeBuilder(3, 5) //
.append(this.getId()) //
.append(this.charge.getId()) //
.append(this.amount).append(this.getDueForCollectionAsOfLocalDate()) //
.append(this.amount).append(this.getDueLocalDate()) //
.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);
}
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.portfolio.charge.domain.Charge;
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.exception.ChargeIsNotActiveException;
import org.mifosplatform.portfolio.charge.exception.ChargeNotFoundException;
import org.mifosplatform.portfolio.charge.exception.LoanChargeNotFoundException;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanChargeRepository;
@@ -32,11 +30,11 @@ import com.google.gson.JsonObject;
public class LoanChargeAssembler {
private final FromJsonHelper fromApiJsonHelper;
private final ChargeRepository chargeRepository;
private final ChargeRepositoryWrapper chargeRepository;
private final LoanChargeRepository loanChargeRepository;
@Autowired
public LoanChargeAssembler(final FromJsonHelper fromApiJsonHelper, final ChargeRepository chargeRepository,
public LoanChargeAssembler(final FromJsonHelper fromApiJsonHelper, final ChargeRepositoryWrapper chargeRepository,
final LoanChargeRepository loanChargeRepository) {
this.fromApiJsonHelper = fromApiJsonHelper;
this.chargeRepository = chargeRepository;
@@ -69,10 +67,7 @@ public class LoanChargeAssembler {
dateFormat, locale);
if (id == null) {
final Charge chargeDefinition = this.chargeRepository.findOne(chargeId);
if (chargeDefinition == null || chargeDefinition.isDeleted()) { throw new ChargeNotFoundException(chargeId); }
if (!chargeDefinition.isActive()) { throw new ChargeIsNotActiveException(chargeId, chargeDefinition.getName()); }
final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeId);
ChargeTimeType chargeTime = null;
if (chargeTimeType != null) {
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.staff.domain.Staff;
import org.mifosplatform.portfolio.charge.domain.Charge;
import org.mifosplatform.portfolio.charge.domain.ChargeRepository;
import org.mifosplatform.portfolio.charge.exception.ChargeIsNotActiveException;
import org.mifosplatform.portfolio.charge.exception.ChargeNotFoundException;
import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.mifosplatform.portfolio.charge.exception.LoanChargeNotFoundException;
import org.mifosplatform.portfolio.client.domain.Note;
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.domain.DefaultLoanLifecycleStateMachine;
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.LoanOfficerUnassignmentException;
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.LoanUpdateCommandFromApiJsonDeserializer;
import org.mifosplatform.portfolio.loanproduct.exception.InvalidCurrencyException;
@@ -58,13 +54,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final PlatformSecurityContext context;
private final LoanEventApiJsonValidator loanEventApiJsonValidator;
private final LoanChargeCommandFromApiJsonDeserializer loanChargeCommandFromApiJsonDeserializer;
private final LoanUpdateCommandFromApiJsonDeserializer loanUpdateCommandFromApiJsonDeserializer;
private final LoanRepository loanRepository;
private final NoteRepository noteRepository;
private final LoanTransactionRepository loanTransactionRepository;
private final LoanAssembler loanAssembler;
private final ChargeRepository chargeRepository;
private final ChargeRepositoryWrapper chargeRepository;
private final LoanChargeRepository loanChargeRepository;
private final ApplicationCurrencyRepository applicationCurrencyRepository;
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
@@ -72,15 +67,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
@Autowired
public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
final LoanEventApiJsonValidator loanEventApiJsonValidator,
final LoanChargeCommandFromApiJsonDeserializer loanChargeCommandFromApiJsonDeserializer,
final LoanUpdateCommandFromApiJsonDeserializer loanUpdateCommandFromApiJsonDeserializer, final LoanAssembler loanAssembler,
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 JournalEntryWritePlatformService journalEntryWritePlatformService) {
this.context = context;
this.loanEventApiJsonValidator = loanEventApiJsonValidator;
this.loanChargeCommandFromApiJsonDeserializer = loanChargeCommandFromApiJsonDeserializer;
this.loanAssembler = loanAssembler;
this.loanRepository = loanRepository;
this.loanTransactionRepository = loanTransactionRepository;
@@ -472,24 +466,21 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
@Transactional
@Override
public CommandProcessingResult addLoanCharge(final Long loanId, final JsonCommand command) {
this.context.authenticatedUser();
// FIXME - KW - update loan charge api validation
final LoanChargeCommand loanChargeCommand = this.loanChargeCommandFromApiJsonDeserializer.commandFromApiJson(command.json());
// loanChargeCommand.validateForCreate();
this.loanEventApiJsonValidator.validateAddLoanCharge(command.json());
final Loan loan = retrieveLoanBy(loanId);
final Long chargeDefinitionId = command.longValueOfParameterNamed("chargeId");
final Charge chargeDefinition = this.chargeRepository.findOne(chargeDefinitionId);
if (chargeDefinition == null || chargeDefinition.isDeleted()) { throw new ChargeNotFoundException(chargeDefinitionId); }
if (!chargeDefinition.isActive()) { throw new ChargeIsNotActiveException(chargeDefinition.getId(), chargeDefinition.getName()); }
final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeDefinitionId);
final LoanCharge loanCharge = LoanCharge.createNewFromJson(loan, chargeDefinition, command);
if (!loan.hasCurrencyCodeOf(chargeDefinition.getCurrencyCode())) {
String errorMessage = "Charge and Loan must have the same currency.";
throw new InvalidCurrencyException("charge", "attach.to.loan", errorMessage);
final String errorMessage = "Charge and Loan must have the same currency.";
throw new InvalidCurrencyException("loanCharge", "attach.to.loan", errorMessage);
}
this.loanChargeRepository.save(loanCharge);
@@ -513,8 +504,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.context.authenticatedUser();
final LoanChargeCommand loanChargeCommand = this.loanChargeCommandFromApiJsonDeserializer.commandFromApiJson(command.json());
loanChargeCommand.validateForUpdate();
this.loanEventApiJsonValidator.validateUpdateOfLoanCharge(command.json());
final Loan loan = retrieveLoanBy(loanId);
final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId);
@@ -540,9 +530,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.context.authenticatedUser();
final LoanChargeCommand loanChargeCommand = this.loanChargeCommandFromApiJsonDeserializer.commandFromApiJson(command.json());
loanChargeCommand.validateForUpdate();
final Loan loan = retrieveLoanBy(loanId);
final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId);
@@ -553,12 +540,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.loanTransactionRepository.save(waiveTransaction);
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() //
.withCommandId(command.commandId()) //
.withEntityId(loanChargeId) //
@@ -187,10 +187,10 @@ public class DepositAccount extends AbstractAuditableCustom<AppUser, Long> {
if (interestCompoundingAllowed) {
futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityFor(deposit, tenureInMonths,
maturityInterestRate, interestCompoundedEvery, interestCompoundedFrequencyPeriodType);
maturityInterestRate, interestCompoundedEvery);
} else {
futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityForSimpleInterest(deposit,
tenureInMonths, maturityInterestRate, interestCompoundedEvery, interestCompoundedFrequencyPeriodType);
tenureInMonths, maturityInterestRate, interestCompoundedEvery);
}
DepositAccountStatus from = null;
@@ -278,11 +278,12 @@ public class DepositAccount extends AbstractAuditableCustom<AppUser, Long> {
this.externalId = this.getId() + "_" + this.externalId;
}
public Map<String, Object> approve(final LocalDate actualCommencementDate, final DepositLifecycleStateMachine depositLifecycleStateMachine,
final JsonCommand command, final FixedTermDepositInterestCalculator calculator) {
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>(20);
final String localeAsInput = command.locale();
public Map<String, Object> approve(final LocalDate actualCommencementDate,
final DepositLifecycleStateMachine depositLifecycleStateMachine, final JsonCommand command,
final FixedTermDepositInterestCalculator calculator) {
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>(20);
final String localeAsInput = command.locale();
DepositAccountStatus statusEnum = depositLifecycleStateMachine.transition(DepositAccountEvent.DEPOSIT_APPROVED,
DepositAccountStatus.fromInt(this.depositStatus));
@@ -295,7 +296,7 @@ public class DepositAccount extends AbstractAuditableCustom<AppUser, Long> {
actualChanges.put("locale", localeAsInput);
this.tenureInMonths = newValue;
}
final String maturityInterestRateParamName = "maturityInterestRate";
if (command.isChangeInBigDecimalParameterNamed(maturityInterestRateParamName, this.interestRate)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(maturityInterestRateParamName);
@@ -313,7 +314,7 @@ public class DepositAccount extends AbstractAuditableCustom<AppUser, Long> {
this.depositAmount = newValue;
}
this.product.validateDepositInRange(this.depositAmount);
final String interestCompoundedEveryParamName = "interestCompoundedEvery";
if (command.isChangeInIntegerParameterNamed(interestCompoundedEveryParamName, this.interestCompoundedEvery)) {
final Integer newValue = command.integerValueOfParameterNamed(interestCompoundedEveryParamName);
@@ -337,15 +338,16 @@ public class DepositAccount extends AbstractAuditableCustom<AppUser, Long> {
if (this.interestCompoundingAllowed) {
futureValueOnMaturity = calculator.calculateInterestOnMaturityFor(getDeposit(), this.tenureInMonths, this.interestRate,
this.interestCompoundedEvery, getInterestCompoundedFrequencyType());
this.interestCompoundedEvery);
} else {
futureValueOnMaturity = calculator.calculateInterestOnMaturityForSimpleInterest(getDeposit(), this.tenureInMonths,
this.interestRate, this.interestCompoundedEvery, getInterestCompoundedFrequencyType());
this.interestRate, this.interestCompoundedEvery);
}
this.interestAccrued = futureValueOnMaturity.minus(getDeposit()).getAmount();
this.total = futureValueOnMaturity.getAmount();
DepositAccountTransaction depositaccountTransaction = DepositAccountTransaction.deposit(getDeposit(), getActualCommencementDate(), null);
DepositAccountTransaction depositaccountTransaction = DepositAccountTransaction.deposit(getDeposit(), getActualCommencementDate(),
null);
depositaccountTransaction.updateAccount(this);
this.depositaccountTransactions.add(depositaccountTransaction);
@@ -368,7 +370,7 @@ public class DepositAccount extends AbstractAuditableCustom<AppUser, Long> {
throw new InvalidDepositStateTransitionException("approval", "cannot.be.a.future.date", errorMessage,
getActualCommencementDate());
}
return actualChanges;
return actualChanges;
}
public LocalDate getActualCommencementDate() {
@@ -592,8 +594,11 @@ public class DepositAccount extends AbstractAuditableCustom<AppUser, Long> {
preClosureInterestRate);
this.interestAccrued = this.interestAccrued.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, getAccuredInterest());
DepositAccountTransaction depositaccountTransaction = DepositAccountTransaction.withdraw(getDeposit(), eventDate,
Money.of(currency, this.availableInterest), getDeposit().getAmount().add(this.availableInterest));
// DepositAccountTransaction depositaccountTransaction =
// DepositAccountTransaction.withdraw(getDeposit(), eventDate,
// getAccuredInterest());
depositaccountTransaction.updateAccount(this);
this.depositaccountTransactions.add(depositaccountTransaction);
@@ -651,8 +656,11 @@ public class DepositAccount extends AbstractAuditableCustom<AppUser, Long> {
@SuppressWarnings("unused")
boolean statustest = isActive();
if (isActive()) {
//DepositAccountTransaction depositAccountTransaction = DepositAccountTransaction.withdraw(null, new LocalDate(), interest);
DepositAccountTransaction depositAccountTransaction = DepositAccountTransaction.withdraw(null, new LocalDate(), interest,getDeposit().getAmount().add(this.availableInterest).subtract(interest.getAmount()));
// DepositAccountTransaction depositAccountTransaction =
// 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);
this.depositaccountTransactions.add(depositAccountTransaction);
this.interstPaid = this.interstPaid.add(interest.getAmount());
@@ -667,9 +675,13 @@ public class DepositAccount extends AbstractAuditableCustom<AppUser, Long> {
DepositAccountStatus.fromInt(this.depositStatus));
this.depositStatus = statusEnumForClose.getValue();
//DepositAccountTransaction depositaccountTransaction = DepositAccountTransaction.withdraw(getDeposit(), new 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 depositaccountTransaction =
// DepositAccountTransaction.withdraw(getDeposit(), new
// 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);
this.depositaccountTransactions.add(depositaccountTransaction);
@@ -687,10 +699,10 @@ public class DepositAccount extends AbstractAuditableCustom<AppUser, Long> {
if (isInterestCompoundingAllowed) {
futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityFor(deposit, tenureInMonths,
maturityInterestRate, interestCompoundedEvery, compoundingInterestFrequency);
maturityInterestRate, interestCompoundedEvery);
} else {
futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityForSimpleInterest(deposit,
tenureInMonths, maturityInterestRate, interestCompoundedEvery, compoundingInterestFrequency);
tenureInMonths, maturityInterestRate, interestCompoundedEvery);
}
setExternalId(externalId);
@@ -747,18 +759,17 @@ public class DepositAccount extends AbstractAuditableCustom<AppUser, Long> {
if (lastInterestPostedDate.isBefore(getMaturityDate())) {
if (nextInterestPostingDate.isAfter(getMaturityDate())) {
Integer noofRemainMonthsAfterMaturity = Months.monthsBetween(getMaturityDate(), nextInterestPostingDate).getMonths();
Integer noOfMonthsBeforeMaturity = Months.monthsBetween(lastInterestPostedDate, getMaturityDate().plusDays(1)).getMonths();
Money futureValueOnMaturity = null;
Integer noOfMonthsBeforeMaturity = Months.monthsBetween(lastInterestPostedDate, getMaturityDate().plusDays(1))
.getMonths();
Money futureValueOnMaturity = Money.zero(this.currency);
if (noOfMonthsBeforeMaturity > 0) {
if (this.interestCompoundingAllowed) {
futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityFor(deposit,
noOfMonthsBeforeMaturity, this.interestRate, this.interestCompoundedEvery,
getInterestCompoundedFrequencyType());
noOfMonthsBeforeMaturity, this.interestRate, this.interestCompoundedEvery);
} else {
futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityForSimpleInterest(
deposit, noOfMonthsBeforeMaturity, this.interestRate, this.interestCompoundedEvery,
getInterestCompoundedFrequencyType());
deposit, noOfMonthsBeforeMaturity, this.interestRate, this.interestCompoundedEvery);
}
this.interestPostedAmount = this.interestPostedAmount.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) {
futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityFor(deposit,
noofRemainMonthsAfterMaturity, this.preClosureInterestRate, this.interestCompoundedEvery,
getInterestCompoundedFrequencyType());
noofRemainMonthsAfterMaturity, this.preClosureInterestRate, this.interestCompoundedEvery);
} else {
futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityForSimpleInterest(
deposit, noofRemainMonthsAfterMaturity, this.preClosureInterestRate, this.interestCompoundedEvery,
getInterestCompoundedFrequencyType());
deposit, noofRemainMonthsAfterMaturity, this.preClosureInterestRate, this.interestCompoundedEvery);
}
this.interestPostedAmount = this.interestPostedAmount.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
DepositAccountTransaction depositaccountTransaction = DepositAccountTransaction.postInterest(null, nextInterestPostingDate,futureValueOnMaturity.minus(deposit),deposit.getAmount().add(this.availableInterest));
//DepositAccountTransaction depositaccountTransaction = DepositAccountTransaction.postInterest(null, nextInterestPostingDate, futureValueOnMaturity.minus(deposit));
DepositAccountTransaction depositaccountTransaction = DepositAccountTransaction.postInterest(null,
nextInterestPostingDate, futureValueOnMaturity.minus(deposit), deposit.getAmount().add(this.availableInterest));
depositaccountTransaction.updateAccount(this);
this.depositaccountTransactions.add(depositaccountTransaction);
@@ -793,12 +802,10 @@ public class DepositAccount extends AbstractAuditableCustom<AppUser, Long> {
Money futureValueOnMaturity = null;
if (this.interestCompoundingAllowed) {
futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityFor(deposit,
this.interestCompoundedEvery, this.interestRate, this.interestCompoundedEvery,
getInterestCompoundedFrequencyType());
this.interestCompoundedEvery, this.interestRate, this.interestCompoundedEvery);
} else {
futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityForSimpleInterest(deposit,
this.interestCompoundedEvery, this.interestRate, this.interestCompoundedEvery,
getInterestCompoundedFrequencyType());
this.interestCompoundedEvery, this.interestRate, this.interestCompoundedEvery);
}
this.interestPostedAmount = this.interestPostedAmount.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();
// add transaction entry for each interest posting
// DepositAccountTransaction depositaccountTransaction = DepositAccountTransaction.postInterest(null, nextInterestPostingDate, futureValueOnMaturity.minus(deposit));
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);
this.depositaccountTransactions.add(depositaccountTransaction);
}
@@ -815,12 +822,10 @@ public class DepositAccount extends AbstractAuditableCustom<AppUser, Long> {
Money futureValueOnMaturity = null;
if (this.interestCompoundingAllowed) {
futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityFor(deposit,
this.interestCompoundedEvery, this.preClosureInterestRate, this.interestCompoundedEvery,
getInterestCompoundedFrequencyType());
this.interestCompoundedEvery, this.preClosureInterestRate, this.interestCompoundedEvery);
} else {
futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityForSimpleInterest(deposit,
this.interestCompoundedEvery, this.preClosureInterestRate, this.interestCompoundedEvery,
getInterestCompoundedFrequencyType());
this.interestCompoundedEvery, this.preClosureInterestRate, this.interestCompoundedEvery);
}
this.interestPostedAmount = this.interestPostedAmount.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();
// 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);
this.depositaccountTransactions.add(depositaccountTransaction);
}
@@ -10,25 +10,14 @@ import java.math.MathContext;
import java.math.RoundingMode;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
import org.springframework.stereotype.Service;
@Service
public class FixedTermDepositInterestCalculator {
// FIXME - HUGO - Are the unused field here needed?
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);
Integer monthsInYear = 12;
@@ -72,8 +61,8 @@ public class FixedTermDepositInterestCalculator {
return retval;
}
public Money calculateInterestOnMaturityForSimpleInterest(final Money deposit, final Integer tenureInMonths, final BigDecimal maturityInterestRate,
final Integer interestCompoundedEvery, final PeriodFrequencyType interestCompoundedFrequencyPeriodType) {
public Money calculateInterestOnMaturityForSimpleInterest(final Money deposit, final Integer tenureInMonths,
final BigDecimal maturityInterestRate, final Integer interestCompoundedEvery) {
MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN);
Integer monthsInYear = 12;