From 79d036d5c9adf97bda7e362f6742f46f7402b036 Mon Sep 17 00:00:00 2001 From: Keith Woodlock Date: Tue, 9 Oct 2012 19:16:48 +0100 Subject: [PATCH] squash madhukars pull request into one commit. --- ...ccount-fields_added_for_lock-in_period.sql | 9 ++ .../api/DepositAccountsApiResource.java | 3 +- .../api/DepositProductsApiResource.java | 9 +- .../api/commands/DepositAccountCommand.java | 27 +++++- .../api/commands/DepositProductCommand.java | 35 +++++++- .../platform/api/data/DepositAccountData.java | 88 +++++++++++++++---- .../platform/api/data/DepositProductData.java | 34 ++++++- .../ApiDataConversionServiceImpl.java | 21 +++-- .../GoogleGsonApiJsonSerializerService.java | 6 +- .../DepositAccountTransactionsException.java | 4 +- .../saving/domain/DepositAccount.java | 87 +++++++++++++----- .../FixedTermDepositInterestCalculator.java | 12 +++ .../service/DepositAccountAssembler.java | 58 ++++++++++-- ...DepositAccountReadPlatformServiceImpl.java | 9 +- ...WritePlatformServiceJpaRepositoryImpl.java | 24 +++-- .../savingproduct/domain/DepositProduct.java | 44 +++++++++- ...DepositProductReadPlatformServiceImpl.java | 9 +- ...WritePlatformServiceJpaRepositoryImpl.java | 11 ++- 18 files changed, 416 insertions(+), 74 deletions(-) create mode 100644 mifosng-db/patches/patch-0003-deposit_account-fields_added_for_lock-in_period.sql diff --git a/mifosng-db/patches/patch-0003-deposit_account-fields_added_for_lock-in_period.sql b/mifosng-db/patches/patch-0003-deposit_account-fields_added_for_lock-in_period.sql new file mode 100644 index 000000000..05c6ef65e --- /dev/null +++ b/mifosng-db/patches/patch-0003-deposit_account-fields_added_for_lock-in_period.sql @@ -0,0 +1,9 @@ +ALTER TABLE `m_product_deposit` + ADD COLUMN `is_lock_in_period_allowed` tinyint(1) NOT NULL DEFAULT '0' AFTER `pre_closure_interest_rate`, + ADD COLUMN `lock_in_period` bigint(20) DEFAULT NULL AFTER `is_lock_in_period_allowed`, + ADD COLUMN `lock_in_period_type` smallint(5) NOT NULL DEFAULT '2' AFTER `lock_in_period`; + +ALTER TABLE `m_deposit_account` + ADD COLUMN `is_lock_in_period_allowed` tinyint(1) NOT NULL DEFAULT '0' AFTER `pre_closure_interest_rate`, + ADD COLUMN `lock_in_period` bigint(20) DEFAULT NULL AFTER `is_lock_in_period_allowed`, + ADD COLUMN `lock_in_period_type` smallint(5) NOT NULL DEFAULT '2' AFTER `lock_in_period`; \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/DepositAccountsApiResource.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/DepositAccountsApiResource.java index 095659a26..5e83254fb 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/DepositAccountsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/DepositAccountsApiResource.java @@ -73,7 +73,8 @@ public class DepositAccountsApiResource { "interestCompoundedEveryPeriodType", "renewalAllowed", "preClosureAllowed", "preClosureInterestRate", "withdrawnonDate","rejectedonDate","closedonDate","transactions","interestPaid","isInterestWithdrawable", - "availableInterestForWithdrawal","availableWithdrawalAmount")); + "availableInterestForWithdrawal","availableWithdrawalAmount","todaysDate", + "isLockinPeriodAllowed","lockinPeriod","lockinPeriodType")); @POST diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/DepositProductsApiResource.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/DepositProductsApiResource.java index 5666d6f3f..3430a360d 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/DepositProductsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/DepositProductsApiResource.java @@ -100,7 +100,8 @@ public class DepositProductsApiResource { "currencyCode","digitsAfterDecimal","minimumBalance","maximumBalance","tenureInMonths", "maturityDefaultInterestRate","maturityMinInterestRate","maturityMaxInterestRate", "interestCompoundedEvery", "interestCompoundedEveryPeriodType", - "renewalAllowed","preClosureAllowed","preClosureInterestRate","interestCompoundingAllowed")); + "renewalAllowed","preClosureAllowed","preClosureInterestRate","interestCompoundingAllowed", + "isLockinPeriodAllowed","lockinPeriod","lockinPeriodType")); Set responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters()); if (responseParameters.isEmpty()) { @@ -123,7 +124,8 @@ public class DepositProductsApiResource { "currencyCode","digitsAfterDecimal","minimumBalance","maximumBalance","tenureInMonths", "maturityDefaultInterestRate","maturityMinInterestRate","maturityMaxInterestRate", "interestCompoundedEvery", "interestCompoundedEveryPeriodType", - "renewalAllowed","preClosureAllowed","preClosureInterestRate","interestCompoundingAllowed")); + "renewalAllowed","preClosureAllowed","preClosureInterestRate","interestCompoundingAllowed", + "isLockinPeriodAllowed","lockinPeriod","lockinPeriodType")); Set responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters()); if (responseParameters.isEmpty()) { @@ -153,7 +155,8 @@ public class DepositProductsApiResource { "currencyCode","digitsAfterDecimal","minimumBalance","maximumBalance","tenureInMonths", "maturityDefaultInterestRate","maturityMinInterestRate","maturityMaxInterestRate", "interestCompoundedEvery", "interestCompoundedEveryPeriodType", - "renewalAllowed","preClosureAllowed","preClosureInterestRate")); + "renewalAllowed","preClosureAllowed","preClosureInterestRate", + "isLockinPeriodAllowed","lockinPeriod","lockinPeriodType")); Set responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters()); if (responseParameters.isEmpty()) { diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/commands/DepositAccountCommand.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/commands/DepositAccountCommand.java index 6aecf350e..881a205d1 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/commands/DepositAccountCommand.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/commands/DepositAccountCommand.java @@ -20,6 +20,10 @@ public class DepositAccountCommand { private final BigDecimal preClosureInterestRate; private final Integer tenureInMonths; + private final boolean isLockinPeriodAllowed; + private final Integer lockinPeriod; + private final Integer lockinPeriodType; + private final Integer interestCompoundedEvery; private final Integer interestCompoundedEveryPeriodType; private final LocalDate commencementDate; @@ -39,7 +43,8 @@ public class DepositAccountCommand { final BigDecimal depositAmount, final BigDecimal interestRate, final BigDecimal preClosureInterestRate, final Integer tenureInMonths, final Integer compoundingInterestFrequency, final Integer compoundingInterestFrequencyType, final LocalDate commencementDate, - final boolean renewalAllowed, final boolean preClosureAllowed,final boolean isInterstWithdrawable, final boolean interestCompoundingAllowed) { + final boolean renewalAllowed, final boolean preClosureAllowed,final boolean isInterstWithdrawable, final boolean interestCompoundingAllowed, + final boolean isLockinPeriodAllowed, final Integer lockInPeriod, final Integer lockinPeriodType) { this.id = id; this.clientId = clientId; this.productId = productId; @@ -58,6 +63,10 @@ public class DepositAccountCommand { this.preClosureAllowed = preClosureAllowed; this.isInterestWithdrawable = isInterstWithdrawable; this.interestCompoundingAllowed = interestCompoundingAllowed; + + this.isLockinPeriodAllowed = isLockinPeriodAllowed; + this.lockinPeriod = lockInPeriod; + this.lockinPeriodType = lockinPeriodType; } public Long getId() { @@ -120,6 +129,18 @@ public class DepositAccountCommand { return interestCompoundingAllowed; } + public boolean isLockinPeriodAllowed() { + return isLockinPeriodAllowed; + } + + public Integer getLockinPeriod() { + return lockinPeriod; + } + + public Integer getLockinPeriodType() { + return lockinPeriodType; + } + public boolean isNoFieldChanged() { return this.modifiedParameters.isEmpty(); } @@ -136,6 +157,10 @@ public class DepositAccountCommand { return this.modifiedParameters.contains("tenureInMonths"); } + public boolean isLockinPeriodChanged() { + return this.modifiedParameters.contains("lockinPeriod"); + } + public boolean isMaturityActualInterestRateChanged() { return this.modifiedParameters.contains("maturityInterestRate"); } diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/commands/DepositProductCommand.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/commands/DepositProductCommand.java index e7144d637..90e87dfb9 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/commands/DepositProductCommand.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/commands/DepositProductCommand.java @@ -22,6 +22,9 @@ public class DepositProductCommand { private final BigDecimal maturityMaxInterestRate; private final Integer interestCompoundedEvery; private final Integer interestCompoundedEveryPeriodType; + private final boolean isLockinPeriodAllowed; + private final Integer lockinPeriod; + private final Integer lockinPeriodType; private final boolean renewalAllowed; private final boolean preClosureAllowed; @@ -40,7 +43,8 @@ public class DepositProductCommand { final Integer interestCompoundedEvery, final Integer interestCompoundedEveryPeriodType, final boolean renewalAllowed, final boolean preClosureAllowed, - final BigDecimal preClosureInterestRate, final boolean interestCompoundingAllowed ) { + final BigDecimal preClosureInterestRate, final boolean interestCompoundingAllowed, + final boolean isLockinPeriodAllowed,final Integer lockinPeriod,final Integer lockinPeriodType ) { this.id = id; this.externalId = externalId; this.name = name; @@ -63,6 +67,11 @@ public class DepositProductCommand { this.renewalAllowed = renewalAllowed; this.preClosureAllowed = preClosureAllowed; this.interestCompoundingAllowed = interestCompoundingAllowed; + + this.isLockinPeriodAllowed = isLockinPeriodAllowed; + this.lockinPeriod = lockinPeriod; + this.lockinPeriodType = lockinPeriodType; + this.modifiedParameters = modifiedParameters; } @@ -139,6 +148,18 @@ public class DepositProductCommand { return interestCompoundingAllowed; } + public boolean isLockinPeriodAllowed() { + return isLockinPeriodAllowed; + } + + public Integer getLockinPeriod() { + return lockinPeriod; + } + + public Integer getLockinPeriodType() { + return lockinPeriodType; + } + public boolean isExternalIdChanged() { return this.modifiedParameters.contains("externalId"); } @@ -214,4 +235,16 @@ public class DepositProductCommand { public boolean interestCompoundingAllowedChanged(){ return this.modifiedParameters.contains("interestCompoundingAllowed"); } + + public boolean isLockinPeriodAllowedChanged() { + return this.modifiedParameters.contains("isLockinPeriodAllowed"); + } + + public boolean isLockinPeriodChanged() { + return this.modifiedParameters.contains("lockinPeriod"); + } + + public boolean isLockinPeriodTypeChanged() { + return this.modifiedParameters.contains("lockinPeriodType"); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/data/DepositAccountData.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/data/DepositAccountData.java index 97897156e..a833d3971 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/data/DepositAccountData.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/data/DepositAccountData.java @@ -7,6 +7,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import org.joda.time.Days; import org.joda.time.LocalDate; import org.joda.time.Months; @@ -56,6 +57,11 @@ public class DepositAccountData { private final DepositPermissionData permissions; private final BigDecimal availableInterestForWithdrawal; private final BigDecimal availableWithdrawalAmount; + private final LocalDate todaysDate; + + private final boolean isLockinPeriodAllowed; + private final Integer lockinPeriod; + private final EnumOptionData lockinPeriodType; /* * used when returning account template data but only a clientId is passed, no selected product. @@ -106,6 +112,11 @@ public class DepositAccountData { this.interestCompoundingAllowed = true; this.availableInterestForWithdrawal = new BigDecimal(0); this.availableWithdrawalAmount= new BigDecimal(0); + this.todaysDate = new LocalDate(); + + this.isLockinPeriodAllowed =true; + this.lockinPeriod = null; + this.lockinPeriodType = null; } public DepositAccountData( @@ -150,6 +161,11 @@ public class DepositAccountData { this.interestCompoundingAllowed=account.isInterestCompoundingAllowed(); this.availableInterestForWithdrawal=determineAvailableInterestForWithdrawal(account); this.availableWithdrawalAmount=determineAvalableWithdrawalAmount(account); + this.todaysDate = new LocalDate(); + + this.lockinPeriod = account.getLockinPeriod(); + this.lockinPeriodType = account.getLockinPeriodType(); + this.isLockinPeriodAllowed = account.isLockinPeriodAllowed(); } public DepositAccountData(final DepositAccountData account, final DepositPermissionData permissions, final Collection transactions) { @@ -191,6 +207,11 @@ public class DepositAccountData { this.interestCompoundingAllowed = account.isInterestCompoundingAllowed(); this.availableInterestForWithdrawal=determineAvailableInterestForWithdrawal(account); this.availableWithdrawalAmount=determineAvalableWithdrawalAmount(account); + this.todaysDate = new LocalDate(); + + this.lockinPeriod = account.getLockinPeriod(); + this.lockinPeriodType = account.getLockinPeriodType(); + this.isLockinPeriodAllowed = account.isLockinPeriodAllowed(); } public DepositAccountData( @@ -221,7 +242,10 @@ public class DepositAccountData { final LocalDate closedonDate, final boolean isInterestWithdrawable, final BigDecimal interestPaid, - final boolean interestCompoundingAllowed) { + final boolean interestCompoundingAllowed, + final boolean isLockinPeriodAllowed, + final Integer lockinPeriod, + final EnumOptionData lockinPeriodType) { this.id=id; this.externalId = externalId; this.status = status; @@ -261,6 +285,11 @@ public class DepositAccountData { this.interestCompoundingAllowed = interestCompoundingAllowed; this.availableInterestForWithdrawal = BigDecimal.ZERO; this.availableWithdrawalAmount = BigDecimal.ZERO; + this.todaysDate = new LocalDate(); + + this.isLockinPeriodAllowed = isLockinPeriodAllowed; + this.lockinPeriod =lockinPeriod; + this.lockinPeriodType = lockinPeriodType; } public DepositAccountData( @@ -315,6 +344,12 @@ public class DepositAccountData { this.interestCompoundingAllowed=interestCompoundingAllowed; this.availableInterestForWithdrawal= BigDecimal.ZERO; this.availableWithdrawalAmount = BigDecimal.ZERO; + this.todaysDate = new LocalDate(); + + //need to update in constructor's call method and update it + this.lockinPeriod = Integer.valueOf(0); + this.isLockinPeriodAllowed =true; + this.lockinPeriodType = interestCompoundedEveryPeriodType; } public Long getId() { @@ -457,26 +492,35 @@ public class DepositAccountData { return availableWithdrawalAmount; } + public LocalDate getTodaysDate() { + return todaysDate; + } + + public boolean isLockinPeriodAllowed() { + return isLockinPeriodAllowed; + } + + public Integer getLockinPeriod() { + return lockinPeriod; + } + + public EnumOptionData getLockinPeriodType() { + return lockinPeriodType; + } + private BigDecimal determineAvailableInterestForWithdrawal(final DepositAccountData account) { BigDecimal availableInterestForWithdrawal = BigDecimal.ZERO; if(this.status!=null){ if (account.getStatus().getId() == 300) { - BigDecimal interestGettingForPeriod = BigDecimal.valueOf(account - .getActualInterestAccrued().doubleValue() - / new Double(account.getTenureInMonths())); - Integer noOfMonthsforInterestCal = Months.monthsBetween( - account.getActualCommencementDate(), new LocalDate()) - .getMonths(); - Integer noOfPeriods = noOfMonthsforInterestCal - / account.getInterestCompoundedEvery(); + BigDecimal interestGettingForPeriod = BigDecimal.valueOf(account.getActualInterestAccrued().doubleValue()/ new Double(account.getTenureInMonths())); + LocalDate interestWithdrawingDate= new LocalDate().isAfter(getMaturedOn())?getMaturedOn().plusDays(1):new LocalDate(); + Integer noOfMonthsforInterestCal = Months.monthsBetween(account.getActualCommencementDate(), interestWithdrawingDate).getMonths(); + Integer noOfPeriods = noOfMonthsforInterestCal/ account.getInterestCompoundedEvery(); - availableInterestForWithdrawal = BigDecimal - .valueOf(interestGettingForPeriod.multiply( - new BigDecimal(noOfPeriods)).doubleValue() - - account.getInterestPaid().doubleValue()); + availableInterestForWithdrawal = BigDecimal.valueOf(interestGettingForPeriod.multiply(new BigDecimal(noOfPeriods)).doubleValue()- account.getInterestPaid().doubleValue()); }else { - return new BigDecimal(0); + return BigDecimal.ZERO; } }else return BigDecimal.ZERO; @@ -494,10 +538,12 @@ public class DepositAccountData { BigDecimal interestRateAsFraction = account.getPreClosureInterestRate().divide(BigDecimal.valueOf(100), mc); BigDecimal interestRateForOneMonth = interestRateAsFraction.divide(BigDecimal.valueOf(monthsInYear.doubleValue()), mc); Integer noOfMonthsforInterestCal = Months.monthsBetween(account.getActualCommencementDate(), new LocalDate()).getMonths(); + Integer days = Days.daysBetween(account.getActualCommencementDate().plusMonths(noOfMonthsforInterestCal), new LocalDate()).getDays(); BigDecimal interest = BigDecimal.valueOf(account.getDeposit().doubleValue()*new Double(noOfMonthsforInterestCal)*interestRateForOneMonth.doubleValue()); - avalablePreclosureWithdrawalAmount = interest.add(account.getDeposit()).subtract(account.getInterestPaid()); + avalablePreclosureWithdrawalAmount = interest.add(account.getDeposit()).subtract(account.getInterestPaid()).add(determineRemainInerestAmount(account.getDeposit(), account.getPreClosureInterestRate(), days)); }else if(new LocalDate().isAfter(account.getMaturedOn()) || new LocalDate().isEqual(account.getMaturedOn())){ - avalablePreclosureWithdrawalAmount=determineAvailableInterestForWithdrawal(account).add(account.getDeposit()); + Integer days = Days.daysBetween(account.getMaturedOn(), new LocalDate()).getDays(); + avalablePreclosureWithdrawalAmount=determineAvailableInterestForWithdrawal(account).add(account.getDeposit()).add(determineRemainInerestAmount(account.getDeposit(), account.getPreClosureInterestRate(), days)); } }else{ avalablePreclosureWithdrawalAmount=account.getDeposit(); @@ -505,5 +551,15 @@ public class DepositAccountData { } return avalablePreclosureWithdrawalAmount; } + + private BigDecimal determineRemainInerestAmount(final BigDecimal depsoitAmount, final BigDecimal interstRateApplicable, final Integer days){ + + MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN); + Integer daysInYear = 365; + BigDecimal interestRateAsFraction = interstRateApplicable.divide(BigDecimal.valueOf(100), mc); + BigDecimal interestRateForOneDay = interestRateAsFraction.divide(BigDecimal.valueOf(daysInYear.doubleValue()), mc); + return BigDecimal.valueOf(depsoitAmount.doubleValue()*days.doubleValue()*interestRateForOneDay.doubleValue()); + + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/data/DepositProductData.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/data/DepositProductData.java index 57672977a..1ae947db7 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/data/DepositProductData.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/data/DepositProductData.java @@ -31,6 +31,10 @@ public class DepositProductData { private final BigDecimal preClosureInterestRate; private final boolean interestCompoundingAllowed; + private final boolean isLockinPeriodAllowed; + private final Integer lockinPeriod; + private final EnumOptionData lockinPeriodType; + private final DateTime createdOn; private final DateTime lastModifedOn; @@ -57,7 +61,10 @@ public class DepositProductData { final boolean renewalAllowed, final boolean preClosureAllowed, final BigDecimal preClosureInterestRate, - final boolean interestCompoundingAllowed) { + final boolean interestCompoundingAllowed, + final boolean isLockinPeriodAllowed, + final Integer lockinPeriod, + final EnumOptionData lockinPeriodType) { this.createdOn=createdOn; this.lastModifedOn=lastModifedOn; @@ -81,6 +88,10 @@ public class DepositProductData { this.preClosureInterestRate=preClosureInterestRate; this.interestCompoundingAllowed=interestCompoundingAllowed; + this.lockinPeriod=lockinPeriod; + this.lockinPeriodType=lockinPeriodType; + this.isLockinPeriodAllowed=isLockinPeriodAllowed; + this.currencyOptions = new ArrayList(); this.interestCompoundedEveryPeriodTypeOptions = new ArrayList(); } @@ -112,6 +123,10 @@ public class DepositProductData { this.preClosureInterestRate=BigDecimal.ZERO; this.interestCompoundingAllowed=true; + this.isLockinPeriodAllowed=true; + this.lockinPeriod=Integer.valueOf(0); + this.lockinPeriodType=defaultInterestCompoundedEveryPeriodType; + this.currencyOptions = currencyOptions; this.interestCompoundedEveryPeriodTypeOptions = interestCompoundedEveryPeriodTypeOptions; } @@ -143,6 +158,10 @@ public class DepositProductData { this.preClosureInterestRate=product.getPreClosureInterestRate(); this.interestCompoundingAllowed=product.isInterestCompoundingAllowed(); + this.lockinPeriod=product.getLockinPeriod(); + this.lockinPeriodType=product.getLockinPeriodType(); + this.isLockinPeriodAllowed=product.isLockinPeriodAllowed(); + this.currencyOptions = currencyOptions; this.interestCompoundedEveryPeriodTypeOptions = interestCompoundedEveryPeriodTypeOptions; } @@ -234,4 +253,17 @@ public class DepositProductData { public boolean isInterestCompoundingAllowed() { return interestCompoundingAllowed; } + + public boolean isLockinPeriodAllowed() { + return isLockinPeriodAllowed; + } + + public Integer getLockinPeriod() { + return lockinPeriod; + } + + public EnumOptionData getLockinPeriodType() { + return lockinPeriodType; + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/ApiDataConversionServiceImpl.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/ApiDataConversionServiceImpl.java index ce9e4311d..8184399cf 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/ApiDataConversionServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/ApiDataConversionServiceImpl.java @@ -1262,7 +1262,7 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService { Arrays.asList("clientId", "productId", "externalId", "deposit", "maturityInterestRate", "preClosureInterestRate", "tenureInMonths", "interestCompoundedEvery", "interestCompoundedEveryPeriodType", "commencementDate", "renewalAllowed", "preClosureAllowed","interestCompoundingAllowed", - "locale", "dateFormat","isInterestWithdrawable") + "locale", "dateFormat","isInterestWithdrawable","isLockinPeriodAllowed","lockinPeriod","lockinPeriodType") ); checkForUnsupportedParameters(requestMap, supportedParams); Set modifiedParameters = new HashSet(); @@ -1275,6 +1275,10 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService { BigDecimal preClosureInterestRate = extractBigDecimalParameter("preClosureInterestRate", requestMap, modifiedParameters); Integer tenureInMonths = extractIntegerParameter("tenureInMonths", requestMap, modifiedParameters); + boolean isLockinPeriodAllowed = extractBooleanParameter("isLockinPeriodAllowed", requestMap, modifiedParameters); + Integer lockinPeriod = extractIntegerParameter("lockinPeriod", requestMap, modifiedParameters); + Integer lockinPeriodType = extractIntegerParameter("lockinPeriodType", requestMap, modifiedParameters); + Integer interestCompoundedEvery = extractIntegerParameter("interestCompoundedEvery", requestMap, modifiedParameters); Integer interestCompoundedEveryPeriodType = extractIntegerParameter("interestCompoundedEveryPeriodType", requestMap, modifiedParameters); LocalDate commencementDate = extractLocalDateParameter("commencementDate", requestMap, modifiedParameters); @@ -1285,9 +1289,10 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService { boolean interestCompoundingAllowed = extractBooleanParameter("interestCompoundingAllowed", requestMap, modifiedParameters); return new DepositAccountCommand(modifiedParameters, resourceIdentifier, clientId, productId, - externalId, deposit, interestRate, preClosureInterestRate, tenureInMonths, + externalId, deposit, interestRate, preClosureInterestRate, tenureInMonths, interestCompoundedEvery, interestCompoundedEveryPeriodType, commencementDate, renewalAllowed, - preClosureAllowed, isInterestWithdrawable, interestCompoundingAllowed); + preClosureAllowed, isInterestWithdrawable, interestCompoundingAllowed, + isLockinPeriodAllowed,lockinPeriod,lockinPeriodType); } @Override @@ -1304,7 +1309,7 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService { Arrays.asList("locale", "name", "externalId", "description","currencyCode", "digitsAfterDecimal","minimumBalance","maximumBalance","tenureInMonths", "maturityDefaultInterestRate","maturityMinInterestRate","maturityMaxInterestRate", "interestCompoundedEvery", "interestCompoundedEveryPeriodType", - "renewalAllowed","preClosureAllowed","preClosureInterestRate","interestCompoundingAllowed") + "renewalAllowed","preClosureAllowed","preClosureInterestRate","interestCompoundingAllowed","isLockinPeriodAllowed","lockinPeriod","lockinPeriodType") ); checkForUnsupportedParameters(requestMap, supportedParams); Set modifiedParameters = new HashSet(); @@ -1330,12 +1335,18 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService { boolean canRenew = extractBooleanParameter("renewalAllowed", requestMap, modifiedParameters); boolean canPreClose = extractBooleanParameter("preClosureAllowed", requestMap, modifiedParameters); BigDecimal preClosureInterestRate = extractBigDecimalParameter("preClosureInterestRate", requestMap, modifiedParameters); + + boolean isLockinPeriodAllowed = extractBooleanParameter("isLockinPeriodAllowed", requestMap, modifiedParameters); + Integer lockinPeriod = extractIntegerParameter("lockinPeriod", requestMap, modifiedParameters); + Integer lockinPeriodType = extractIntegerParameter("lockinPeriodType", requestMap, modifiedParameters); + + return new DepositProductCommand(modifiedParameters, resourceIdentifier, externalId, name, description, currencyCode, digitsAfterDecimalValue, minimumBalance,maximumBalance, tenureMonths, maturityDefaultInterestRate, maturityMinInterestRate, maturityMaxInterestRate, interestCompoundedEvery, interestCompoundedEveryPeriodType, - canRenew, canPreClose, preClosureInterestRate, interestCompoundingAllowed); + canRenew, canPreClose, preClosureInterestRate, interestCompoundingAllowed,isLockinPeriodAllowed,lockinPeriod,lockinPeriodType); } @Override diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/GoogleGsonApiJsonSerializerService.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/GoogleGsonApiJsonSerializerService.java index bc2d02300..46db3d9d7 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/GoogleGsonApiJsonSerializerService.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/GoogleGsonApiJsonSerializerService.java @@ -103,7 +103,8 @@ public class GoogleGsonApiJsonSerializerService implements ApiJsonSerializerServ "maturityDefaultInterestRate", "maturityMinInterestRate", "maturityMaxInterestRate", "interestCompoundedEvery", "interestCompoundedEveryPeriodType", "renewalAllowed", - "preClosureAllowed", "preClosureInterestRate", "interestCompoundingAllowed")); + "preClosureAllowed", "preClosureInterestRate", "interestCompoundingAllowed", + "isLockinPeriodAllowed","lockinPeriod","lockinPeriodType")); private static final Set SAVINGS_DEPOSIT_ACCOUNT_DATA_PARAMETERS = new HashSet( Arrays.asList("productOptions", "interestCompoundedEveryPeriodTypeOptions", @@ -113,7 +114,8 @@ public class GoogleGsonApiJsonSerializerService implements ApiJsonSerializerServ "renewalAllowed","preClosureAllowed","preClosureInterestRate", "withdrawnonDate","rejectedonDate","closedonDate","transactions", "permissions","isInterestWithdrawable","interestPaid","interestCompoundingAllowed", - "availableInterestForWithdrawal","availableWithdrawalAmount") + "availableInterestForWithdrawal","availableWithdrawalAmount","todaysDate", + "isLockinPeriodAllowed","lockinPeriod","lockinPeriodType") ); private static final Set CLIENT_DATA_PARAMETERS = new HashSet( diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/exceptions/DepositAccountTransactionsException.java b/mifosng-provider/src/main/java/org/mifosng/platform/exceptions/DepositAccountTransactionsException.java index dc52c1d1a..586831b05 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/exceptions/DepositAccountTransactionsException.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/exceptions/DepositAccountTransactionsException.java @@ -3,8 +3,8 @@ package org.mifosng.platform.exceptions; public class DepositAccountTransactionsException extends AbstractPlatformDomainRuleException { - public DepositAccountTransactionsException(String remainInterestForWithdrawal) { - super("deposit.transaction.interest.withdrawal.exception", "You can Withdraw "+remainInterestForWithdrawal+" only", remainInterestForWithdrawal); + public DepositAccountTransactionsException(String errorcode, String remainInterestForWithdrawal) { + super(errorcode, "You can Withdraw "+remainInterestForWithdrawal+" only", remainInterestForWithdrawal); } } diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/saving/domain/DepositAccount.java b/mifosng-provider/src/main/java/org/mifosng/platform/saving/domain/DepositAccount.java index 853e83b05..2e5ca42dd 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/saving/domain/DepositAccount.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/saving/domain/DepositAccount.java @@ -22,6 +22,7 @@ import javax.persistence.UniqueConstraint; import org.apache.commons.lang.StringUtils; import org.hibernate.annotations.LazyCollection; import org.hibernate.annotations.LazyCollectionOption; +import org.joda.time.Days; import org.joda.time.LocalDate; import org.joda.time.Months; import org.mifosng.platform.api.commands.DepositStateTransitionApprovalCommand; @@ -106,17 +107,14 @@ public class DepositAccount extends AbstractAuditableCustom { @Column(name = "status_enum", nullable = false) private Integer depositStatus; - @SuppressWarnings("unused") @Temporal(TemporalType.DATE) @Column(name = "closedon_date") private Date closedOnDate; - @SuppressWarnings("unused") @Temporal(TemporalType.DATE) @Column(name = "rejectedon_date") private Date rejectedOnDate; - @SuppressWarnings("unused") @Temporal(TemporalType.DATE) @Column(name = "withdrawnon_date") private Date withdrawnOnDate; @@ -137,10 +135,19 @@ public class DepositAccount extends AbstractAuditableCustom { @OneToMany(cascade = CascadeType.ALL, mappedBy = "depositAccount", orphanRemoval = true) private final List depositaccountTransactions = new ArrayList(); - @SuppressWarnings("unused") @OneToOne(optional=true, cascade={CascadeType.PERSIST}) @JoinColumn(name = "renewed_account_id") private DepositAccount renewdAccount; + + @Column(name = "is_lock_in_period_allowed", nullable=false) + private boolean isLockinPeriodAllowed = false; + + @Column(name = "lock_in_period", nullable=false) + private Integer lockinPeriod; + + @Column(name = "lock_in_period_type", nullable=false) + private PeriodFrequencyType lockinPeriodType; + public DepositAccount openNew( final Client client, final DepositProduct product, @@ -152,7 +159,8 @@ public class DepositAccount extends AbstractAuditableCustom { final boolean preClosureAllowed, final FixedTermDepositInterestCalculator fixedTermDepositInterestCalculator, final DepositLifecycleStateMachine depositLifecycleStateMachine, - final boolean isInterstWithdrawable,final boolean interestCompoundingAllowed) { + final boolean isInterstWithdrawable,final boolean interestCompoundingAllowed, + final boolean isLockinPeriodAllowed, final Integer lockinPeriod, final PeriodFrequencyType lockinPeriodType) { Money futureValueOnMaturity =null; @@ -179,7 +187,7 @@ public class DepositAccount extends AbstractAuditableCustom { return new DepositAccount(client, product, externalId, deposit, maturityInterestRate, preClosureInterestRate, tenureInMonths, interestCompoundedEvery, interestCompoundedFrequencyPeriodType, commencementDate, renewalAllowed, preClosureAllowed, - futureValueOnMaturity,depositStatus,isInterstWithdrawable, interestCompoundingAllowed); + futureValueOnMaturity,depositStatus,isInterstWithdrawable, interestCompoundingAllowed,isLockinPeriodAllowed,lockinPeriod,lockinPeriodType); } public DepositAccount() { @@ -197,7 +205,10 @@ public class DepositAccount extends AbstractAuditableCustom { final Money futureValueOnMaturity, final Integer depositStatus, final boolean isInterestWithdrawable, - final boolean interestCompoundingAllowed) { + final boolean interestCompoundingAllowed, + final boolean isLockinPeriodAllowed, + final Integer lockinPeriod, + final PeriodFrequencyType lockinPeriodType) { this.client = client; this.product = product; setExternalId(externalId); @@ -213,7 +224,7 @@ public class DepositAccount extends AbstractAuditableCustom { this.interestCompoundedFrequencyType = interestCompoundedFrequencyPeriodType.getValue(); if (commencementDate != null) { this.projectedCommencementDate = commencementDate.toDate(); - this.maturesOnDate = commencementDate.plusMonths(this.tenureInMonths).toDate(); + this.maturesOnDate = commencementDate.plusMonths(this.tenureInMonths).minusDays(1).toDate(); } this.renewalAllowed = renewalAllowed; @@ -222,12 +233,15 @@ public class DepositAccount extends AbstractAuditableCustom { this.preClosureInterestRate = preClosureInterestRate; this.isInterestWithdrawable = isInterestWithdrawable; this.interestCompoundingAllowed = interestCompoundingAllowed; + this.isLockinPeriodAllowed = isLockinPeriodAllowed; + this.lockinPeriod = lockinPeriod; + this.lockinPeriodType = lockinPeriodType; // derived fields this.projectedInterestAccruedOnMaturity = futureValueOnMaturity.minus(deposit).getAmount(); this.projectedTotalOnMaturity = futureValueOnMaturity.getAmount(); this.depositStatus=depositStatus; - this.interstPaid = new BigDecimal(0); + this.interstPaid = BigDecimal.ZERO; } private void setExternalId(final String externalId) { @@ -275,12 +289,14 @@ public class DepositAccount extends AbstractAuditableCustom { } this.product.validateDepositInRange(this.depositAmount); + this.interestCompoundedEvery = command.getInterestCompoundedEvery(); + if (command.getInterestCompoundedEveryPeriodType() != null) { this.interestCompoundedFrequencyType = PeriodFrequencyType.fromInt(command.getInterestCompoundedEveryPeriodType()).getValue(); } this.actualCommencementDate = actualCommencementDate.toDate(); - this.maturesOnDate = getActualCommencementDate().plusMonths(this.tenureInMonths).toDate(); + this.maturesOnDate = getActualCommencementDate().plusMonths(this.tenureInMonths).minusDays(1).toDate(); Money futureValueOnMaturity =null; @@ -354,6 +370,18 @@ public class DepositAccount extends AbstractAuditableCustom { public boolean isInterestCompoundingAllowed() { return interestCompoundingAllowed; } + + public boolean isLockinPeriodAllowed() { + return isLockinPeriodAllowed; + } + + public Integer getLockinPeriod() { + return lockinPeriod; + } + + public PeriodFrequencyType getLockinPeriodType() { + return lockinPeriodType; + } public void reject(final LocalDate rejectedOn, final DepositLifecycleStateMachine depositLifecycleStateMachine) { @@ -425,7 +453,7 @@ public class DepositAccount extends AbstractAuditableCustom { this.actualCommencementDate = null; this.projectedInterestAccruedOnMaturity=this.interestAccrued; this.projectedTotalOnMaturity = this.depositAmount.add(this.projectedInterestAccruedOnMaturity); - this.maturesOnDate = getProjectedCommencementDate().plusMonths(this.tenureInMonths).toDate(); + this.maturesOnDate = getProjectedCommencementDate().plusMonths(this.tenureInMonths).minusDays(1).toDate(); this.total=null; this.interestAccrued=null; this.depositaccountTransactions.clear(); @@ -509,18 +537,22 @@ public class DepositAccount extends AbstractAuditableCustom { } - public void withdrawDepositAccountMoney(DepositLifecycleStateMachine depositLifecycleStateMachine) { + public void withdrawDepositAccountMoney(DepositLifecycleStateMachine depositLifecycleStateMachine, FixedTermDepositInterestCalculator fixedTermDepositInterestCalculator,LocalDate eventDate) { - if (new LocalDate().isAfter(maturesOnDate()) || new LocalDate().equals(maturesOnDate())) { + if (eventDate.isAfter(maturesOnDate()) || eventDate.equals(maturesOnDate())) { DepositAccountStatus statusEnum = depositLifecycleStateMachine.transition(DepositAccountEvent.DEPOSIT_CLOSED, DepositAccountStatus.fromInt(this.depositStatus)); this.depositStatus = statusEnum.getValue(); - DepositAccountTransaction depositaccountTransaction = DepositAccountTransaction.withdraw(getDeposit(), new LocalDate(),getAccuredInterest()); + Integer days = Days.daysBetween(maturesOnDate(), eventDate).getDays(); + Money accuredInterestAfterMaturityToTillDate = fixedTermDepositInterestCalculator.calculateRemainInterest(getDeposit(), days, preClosureInterestRate); + this.interestAccrued = this.interestAccrued.add(accuredInterestAfterMaturityToTillDate.getAmount()); + + DepositAccountTransaction depositaccountTransaction = DepositAccountTransaction.withdraw(getDeposit(), eventDate,getAccuredInterest()); depositaccountTransaction.updateAccount(this); this.depositaccountTransactions.add(depositaccountTransaction); - this.closedOnDate = new LocalDate().toDate(); + this.closedOnDate = eventDate.toDate(); this.withdrawnOnDate = null; this.rejectedOnDate = null; @@ -529,37 +561,42 @@ public class DepositAccount extends AbstractAuditableCustom { DepositAccountStatus statusEnum = depositLifecycleStateMachine.transition(DepositAccountEvent.DEPOSIT_PRECLOSED, DepositAccountStatus.fromInt(this.depositStatus)); this.depositStatus = statusEnum.getValue(); - DepositAccountTransaction depositaccountTransaction = DepositAccountTransaction.withdraw(getDeposit(), new LocalDate(),getAccuredInterest()); + DepositAccountTransaction depositaccountTransaction = DepositAccountTransaction.withdraw(getDeposit(), eventDate, getAccuredInterest()); depositaccountTransaction.updateAccount(this); this.depositaccountTransactions.add(depositaccountTransaction); - this.closedOnDate = new LocalDate().toDate(); + this.closedOnDate = eventDate.toDate(); this.withdrawnOnDate = null; this.rejectedOnDate = null; } } - public void adjustTotalAmountForPreclosureInterest(DepositAccount account, FixedTermDepositInterestCalculator fixedTermDepositInterestCalculator) { + public void adjustTotalAmountForPreclosureInterest(DepositAccount account, FixedTermDepositInterestCalculator fixedTermDepositInterestCalculator,LocalDate eventDate) { - LocalDate commnencementDate = new LocalDate(this.actualCommencementDate); - LocalDate preClosedDate = new LocalDate(); + LocalDate commnencementDate = getActualCommencementDate(); + LocalDate preClosedDate = eventDate; Integer tenure = Months.monthsBetween(commnencementDate, preClosedDate).getMonths(); + LocalDate actualPrecloseCalculationDate = commnencementDate.plusMonths(tenure); + Integer missedDays = Days.daysBetween(actualPrecloseCalculationDate, preClosedDate).getDays(); Money deposit = Money.of(account.getDeposit().getCurrency(), account.getDeposit().getAmount()); Money accuredtotalAmount = null; + Money remainDaysAmount = fixedTermDepositInterestCalculator.calculateRemainInterest(deposit, missedDays, preClosureInterestRate); if (account.isInterestCompoundingAllowed()) { accuredtotalAmount = fixedTermDepositInterestCalculator .calculateInterestOnMaturityFor(deposit, tenure, preClosureInterestRate, interestCompoundedEvery, this.product.getInterestCompoundedEveryPeriodType()); + accuredtotalAmount = accuredtotalAmount.plus(remainDaysAmount); } else { accuredtotalAmount = fixedTermDepositInterestCalculator .calculateInterestOnMaturityForSimpleInterest(deposit, tenure, preClosureInterestRate, interestCompoundedEvery, this.product.getInterestCompoundedEveryPeriodType()); + accuredtotalAmount = accuredtotalAmount.plus(remainDaysAmount); } this.total = account.isInterestCompoundingAllowed()?accuredtotalAmount.getAmount():BigDecimal.valueOf(accuredtotalAmount.getAmount().doubleValue()-this.interstPaid.doubleValue()); this.interestAccrued = accuredtotalAmount.minus(deposit).getAmount(); @@ -596,7 +633,10 @@ public class DepositAccount extends AbstractAuditableCustom { final boolean renewalAllowed, final boolean preClosureAllowed, final boolean isInterestWithdrawable, final boolean isInterestCompoundingAllowed, final FixedTermDepositInterestCalculator fixedTermDepositInterestCalculator, - final DepositLifecycleStateMachine depositLifecycleStateMachine) { + final DepositLifecycleStateMachine depositLifecycleStateMachine, + final boolean isLockinPeriodAllowed, + final Integer lockinPeriod, + final PeriodFrequencyType lockinPeriodType) { Money futureValueOnMaturity =null; @@ -614,7 +654,7 @@ public class DepositAccount extends AbstractAuditableCustom { setExternalId(externalId); if (commencementDate != null) { this.projectedCommencementDate = commencementDate.toDate(); - this.maturesOnDate = commencementDate.plusMonths(this.tenureInMonths).toDate(); + this.maturesOnDate = commencementDate.plusMonths(this.tenureInMonths).minusDays(1).toDate(); } this.product = product; this.depositAmount = deposit.getAmount(); @@ -630,6 +670,9 @@ public class DepositAccount extends AbstractAuditableCustom { this.interestCompoundingAllowed=isInterestCompoundingAllowed; this.projectedInterestAccruedOnMaturity = futureValueOnMaturity.minus(deposit).getAmount(); this.projectedTotalOnMaturity = futureValueOnMaturity.getAmount(); + this.isLockinPeriodAllowed = isLockinPeriodAllowed; + this.lockinPeriod = lockinPeriod; + this.lockinPeriodType = lockinPeriodType; } diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/saving/domain/FixedTermDepositInterestCalculator.java b/mifosng-provider/src/main/java/org/mifosng/platform/saving/domain/FixedTermDepositInterestCalculator.java index db4dcac2c..2389a9a77 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/saving/domain/FixedTermDepositInterestCalculator.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/saving/domain/FixedTermDepositInterestCalculator.java @@ -82,4 +82,16 @@ public class FixedTermDepositInterestCalculator { return Money.of(deposit.getCurrency(), totalAmount); } + + public Money calculateRemainInterest(Money deposit, Integer days, BigDecimal interestRate){ + + MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN); + Integer daysInYear = 365; + + BigDecimal interestRateAsFraction = interestRate.divide(BigDecimal.valueOf(100), mc); + BigDecimal interestRateForOneDay = interestRateAsFraction.divide(BigDecimal.valueOf(daysInYear.doubleValue()), mc); + BigDecimal interest = BigDecimal.valueOf(deposit.getAmount().doubleValue()*days.doubleValue()*interestRateForOneDay.doubleValue()); + + return Money.of(deposit.getCurrency(), interest); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/saving/service/DepositAccountAssembler.java b/mifosng-provider/src/main/java/org/mifosng/platform/saving/service/DepositAccountAssembler.java index 7cb907103..6146a865f 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/saving/service/DepositAccountAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/saving/service/DepositAccountAssembler.java @@ -101,6 +101,21 @@ public class DepositAccountAssembler { if(command.isInterestCompoundingAllowedChanged()){ interestCompoundingAllowed = command.isInterestCompoundingAllowed(); } + + boolean isLockinPeriodAllowed = product.isLockinPeriodAllowed(); + if(command.isLockinPeriodChanged()){ + isLockinPeriodAllowed = command.isLockinPeriodAllowed(); + } + + Integer lockinPeriod = product.getLockinPeriod(); + if(command.getLockinPeriod() != null){ + lockinPeriod = command.getLockinPeriod(); + } + + PeriodFrequencyType lockinPeriodType = product.getLockinPeriodType(); + if(command.getLockinPeriodType() != null){ + lockinPeriodType = PeriodFrequencyType.fromInt(command.getLockinPeriodType()); + } // end of details allowed to be overriden from product DepositAccount account =new DepositAccount().openNew(client, product, command.getExternalId(), @@ -109,7 +124,8 @@ public class DepositAccountAssembler { tenureInMonths, compoundingInterestEvery, compoundingInterestFrequency, command.getCommencementDate(), renewalAllowed, preClosureAllowed, this.fixedTermDepositInterestCalculator, - defaultDepositLifecycleStateMachine(),isInterestWithdrawable,interestCompoundingAllowed); + defaultDepositLifecycleStateMachine(),isInterestWithdrawable,interestCompoundingAllowed, + isLockinPeriodAllowed,lockinPeriod,lockinPeriodType); return account; } @@ -186,21 +202,36 @@ public class DepositAccountAssembler { isInterestCompoundingAllowed = command.isInterestCompoundingAllowed(); } + boolean isLockinPeriodAllowed = account.isLockinPeriodAllowed(); + if(command.isLockinPeriodChanged()){ + isLockinPeriodAllowed = command.isLockinPeriodAllowed(); + } + + Integer lockinPeriod = account.getLockinPeriod(); + if(command.getLockinPeriod() != null){ + lockinPeriod = command.getLockinPeriod(); + } + + PeriodFrequencyType lockinPeriodType = account.getLockinPeriodType(); + if(command.getLockinPeriodType() != null){ + lockinPeriodType = PeriodFrequencyType.fromInt(command.getLockinPeriodType()); + } + DepositAccount newAccount =new DepositAccount().openNew(client, product, null, deposit, maturityInterestRate, preClosureInterestRate, tenureInMonths, compoundingInterestEvery, compoundingInterestFrequency, - account.maturesOnDate(), + account.maturesOnDate().plusDays(1), renewalAllowed, preClosureAllowed, this.fixedTermDepositInterestCalculator, defaultDepositLifecycleStateMachine(), - isInterestWithdrawable,isInterestCompoundingAllowed); + isInterestWithdrawable,isInterestCompoundingAllowed,isLockinPeriodAllowed,lockinPeriod,lockinPeriodType); newAccount.updateAccount(account); return newAccount; } - public void adjustTotalAmountForPreclosureInterest(DepositAccount account) { - account.adjustTotalAmountForPreclosureInterest(account,this.fixedTermDepositInterestCalculator); + public void adjustTotalAmountForPreclosureInterest(DepositAccount account,LocalDate eventDate) { + account.adjustTotalAmountForPreclosureInterest(account,this.fixedTermDepositInterestCalculator,eventDate); } public void assembleUpdatedDepositAccount(DepositAccount account,DepositAccountCommand command) { @@ -276,8 +307,23 @@ public class DepositAccountAssembler { if(command.isInterestCompoundingAllowedChanged()){ isInterestCompoundingAllowed = command.isInterestCompoundingAllowed(); } + + boolean isLockinPeriodAllowed = account.isLockinPeriodAllowed(); + if(command.isLockinPeriodChanged()){ + isLockinPeriodAllowed = command.isLockinPeriodAllowed(); + } + + Integer lockinPeriod = account.getLockinPeriod(); + if(command.getLockinPeriod() != null){ + lockinPeriod = command.getLockinPeriod(); + } + + PeriodFrequencyType lockinPeriodType = account.getLockinPeriodType(); + if(command.getLockinPeriodType() != null){ + lockinPeriodType = PeriodFrequencyType.fromInt(command.getLockinPeriodType()); + } account.update(product,externalId,commencementDate,deposit,tenureInMonths,maturityInterestRate,preClosureInterestRate,compoundingInterestEvery,compoundingInterestFrequency,renewalAllowed, - preClosureAllowed,isInterestWithdrawable,isInterestCompoundingAllowed,this.fixedTermDepositInterestCalculator,defaultDepositLifecycleStateMachine()); + preClosureAllowed,isInterestWithdrawable,isInterestCompoundingAllowed,this.fixedTermDepositInterestCalculator,defaultDepositLifecycleStateMachine(),isLockinPeriodAllowed,lockinPeriod,lockinPeriodType); } public void updateApprovedDepositAccount(DepositAccount account, DepositAccountCommand command) { diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/saving/service/DepositAccountReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosng/platform/saving/service/DepositAccountReadPlatformServiceImpl.java index 9ff8a00e3..d305392fe 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/saving/service/DepositAccountReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/saving/service/DepositAccountReadPlatformServiceImpl.java @@ -138,6 +138,7 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead + " da.is_renewal_allowed as renewalAllowed, da.is_preclosure_allowed as preClosureAllowed, da.pre_closure_interest_rate as preClosureInterestRate, " + " da.withdrawnon_date as withdrawnonDate, da.rejectedon_date as rejectedonDate, da.closedon_date as closedonDate, " + " da.interest_paid as interestPaid, da.is_interest_withdrawable as isInterestWithdrawable, da.is_compounding_interest_allowed as interestCompoundingAllowed, " + + " da.is_lock_in_period_allowed as isLockinPeriodAllowed, da.lock_in_period as lockinPeriod, da.lock_in_period_type lockinPeriodType, " + " c.firstname as firstname, c.lastname as lastname, pd.name as productName," + " curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, curr.display_symbol as currencyDisplaySymbol " + " from m_deposit_account da " @@ -198,12 +199,18 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead boolean isInterestWithdrawable = rs.getBoolean("isInterestWithdrawable"); BigDecimal interestPaid = rs.getBigDecimal("interestPaid"); + boolean isLockinPeriodAllowed = rs.getBoolean("isLockinPeriodAllowed"); + Integer lockinPeriod = JdbcSupport.getInteger(rs, "lockinPeriod"); + Integer lockinPeriodTypeValue = JdbcSupport.getInteger(rs, "lockinPeriodType"); + EnumOptionData lockinPeriodType = SavingsDepositEnumerations.interestCompoundingPeriodType(lockinPeriodTypeValue); + return new DepositAccountData(id, externalId, status, clientId, clientName, productId, productName, currencyData, depositAmount, interestRate, termInMonths, projectedCommencementDate, actualCommencementDate, maturedOn, projectedInterestAccrued, actualInterestAccrued, projectedMaturityAmount, actualMaturityAmount, interestCompoundedEvery, interestCompoundedEveryPeriodType, renewalAllowed, preClosureAllowed, preClosureInterestRate, - withdrawnonDate,rejectedonDate,closedonDate,isInterestWithdrawable,interestPaid,interestCompoundingAllowed); + withdrawnonDate,rejectedonDate,closedonDate,isInterestWithdrawable,interestPaid,interestCompoundingAllowed, + isLockinPeriodAllowed, lockinPeriod, lockinPeriodType); } } diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/saving/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosng/platform/saving/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java index ab73d7937..ee3712219 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/saving/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/saving/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java @@ -292,12 +292,20 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo throw new DepositAccountNotFoundException(command.getAccountId()); } - if(command.getMaturesOnDate().isBefore(account.maturesOnDate())){ - this.depositAccountAssembler.adjustTotalAmountForPreclosureInterest(account); - } - account.withdrawDepositAccountMoney(defaultDepositLifecycleStateMachine()); - this.depositAccountRepository.save(account); + LocalDate eventDate = command.getMaturesOnDate(); + Integer lockinPeriod = account.getLockinPeriod(); + LocalDate lockinPeriodExpDate = account.getActualCommencementDate().plusMonths(Integer.valueOf(lockinPeriod)); + if(account.isLockinPeriodAllowed()){ + if(eventDate.isBefore(lockinPeriodExpDate)){ + throw new DepositAccountTransactionsException("deposit.transaction.canot.withdraw.before.lockinperiod.reached","You can not withdraw your application before maturity date reached"); + } + } + if(eventDate.isBefore(account.maturesOnDate())){ + this.depositAccountAssembler.adjustTotalAmountForPreclosureInterest(account,eventDate); + } + account.withdrawDepositAccountMoney(defaultDepositLifecycleStateMachine(),fixedTermDepositInterestCalculator,eventDate); + this.depositAccountRepository.save(account); String noteText = command.getNote(); if (StringUtils.isNotBlank(noteText)) { Note note = Note.depositNote(account, noteText); @@ -331,13 +339,13 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo account.withdrawInterest(Money.of(account.getDeposit().getCurrency(), command.getWithdrawInterest())); this.depositAccountRepository.save(account); }else { - throw new DepositAccountTransactionsException("You can Withdraw "+remainInterestForWithdrawal+" only"); + throw new DepositAccountTransactionsException("deposit.transaction.interest.withdrawal.exceed","You can Withdraw "+remainInterestForWithdrawal+" only"); } }else { - throw new DepositAccountTransactionsException("You don't have enough money for withdrawal"); + throw new DepositAccountTransactionsException("deposit.transaction.interest.withdrawal.insufficient.amount","You don't have enough money for withdrawal"); } }else{ - throw new DepositAccountTransactionsException("You can not withdraw interst for this account"); + throw new DepositAccountTransactionsException("deposit.transaction.interest.withdrawal.cannot.withdraw","You can not withdraw interst for this account"); } return new EntityIdentifier(account.getId()); } diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/savingproduct/domain/DepositProduct.java b/mifosng-provider/src/main/java/org/mifosng/platform/savingproduct/domain/DepositProduct.java index 22fc94703..2d6b2f40a 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/savingproduct/domain/DepositProduct.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/savingproduct/domain/DepositProduct.java @@ -9,6 +9,7 @@ import javax.persistence.Table; import javax.persistence.UniqueConstraint; import org.apache.commons.lang.StringUtils; +import org.hibernate.metamodel.binding.CollectionElementNature; import org.mifosng.platform.api.commands.DepositProductCommand; import org.mifosng.platform.currency.domain.MonetaryCurrency; import org.mifosng.platform.exceptions.ValueOutsideRangeException; @@ -75,6 +76,15 @@ public class DepositProduct extends AbstractAuditableCustom { @Column(name = "pre_closure_interest_rate", scale = 6, precision = 19, nullable = false) private BigDecimal preClosureInterestRate; + @Column(name = "is_lock_in_period_allowed", nullable=false) + private boolean isLockinPeriodAllowed = false; + + @Column(name = "lock_in_period", nullable=false) + private Integer lockinPeriod; + + @Column(name = "lock_in_period_type", nullable=false) + private PeriodFrequencyType lockinPeriodType; + protected DepositProduct() { // } @@ -91,7 +101,10 @@ public class DepositProduct extends AbstractAuditableCustom { final boolean canRenew, final boolean canPreClose, final BigDecimal preClosureInterestRate, - final boolean isInterestCompoundingAllowed) { + final boolean isInterestCompoundingAllowed, + final boolean isLockinPeriodAllowed, + final Integer lockinPeriod, + final PeriodFrequencyType lockinPeriodType) { this.name = name.trim(); if (StringUtils.isNotBlank(description)) { this.description = description.trim(); @@ -117,6 +130,9 @@ public class DepositProduct extends AbstractAuditableCustom { this.preClosureAllowed = canPreClose; this.preClosureInterestRate = preClosureInterestRate; this.interestCompoundingAllowed = isInterestCompoundingAllowed; + this.isLockinPeriodAllowed = isLockinPeriodAllowed; + this.lockinPeriod = lockinPeriod; + this.lockinPeriodType = lockinPeriodType; } public MonetaryCurrency getCurrency() { @@ -159,6 +175,18 @@ public class DepositProduct extends AbstractAuditableCustom { return interestCompoundingAllowed; } + public boolean isLockinPeriodAllowed() { + return isLockinPeriodAllowed; + } + + public Integer getLockinPeriod() { + return lockinPeriod; + } + + public PeriodFrequencyType getLockinPeriodType() { + return lockinPeriodType; + } + public boolean isDeleted() { return deleted; } @@ -174,7 +202,7 @@ public class DepositProduct extends AbstractAuditableCustom { this.externalId = this.getId() + "_DELETED_" + this.externalId; } - public void update(final DepositProductCommand command, final PeriodFrequencyType interestCompoundingFrequency){ + public void update(final DepositProductCommand command, final PeriodFrequencyType interestCompoundingFrequency, final PeriodFrequencyType lockinPeriodType){ if (command.isExternalIdChanged()) { this.externalId = command.getExternalId(); @@ -249,6 +277,18 @@ public class DepositProduct extends AbstractAuditableCustom { if(command.interestCompoundingAllowedChanged()){ this.interestCompoundingAllowed = command.isInterestCompoundingAllowed(); } + + if(command.isLockinPeriodAllowedChanged()){ + this.isLockinPeriodAllowed = command.isLockinPeriodAllowed(); + } + + if(command.isLockinPeriodChanged()){ + this.lockinPeriod = command.getLockinPeriod(); + } + + if(command.isLockinPeriodTypeChanged()){ + this.lockinPeriodType = lockinPeriodType; + } } public void validateInterestRateInRange(final BigDecimal interestRate) { diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/savingproduct/service/DepositProductReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosng/platform/savingproduct/service/DepositProductReadPlatformServiceImpl.java index 0efdaa16b..d13e728fa 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/savingproduct/service/DepositProductReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/savingproduct/service/DepositProductReadPlatformServiceImpl.java @@ -74,6 +74,7 @@ public class DepositProductReadPlatformServiceImpl implements DepositProductRead "dp.interest_compounded_every as interestCompoundedEvery, dp.interest_compounded_every_period_enum as interestCompoundedEveryPeriodType, " + "dp.maturity_default_interest_rate as maturityDefaultInterestRate, dp.is_compounding_interest_allowed as interestCompoundingAllowed, " + "dp.maturity_min_interest_rate as maturityMinInterestRate, dp.maturity_max_interest_rate as maturityMaxInterestRate, dp.is_renewal_allowed as canRenew, dp.is_preclosure_allowed as canPreClose, dp.pre_closure_interest_rate as preClosureInterestRate, " + + "dp.is_lock_in_period_allowed as isLockinPeriodAllowed, dp.lock_in_period as lockinPeriod, dp.lock_in_period_type as lockinPeriodType, "+ "curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, curr.display_symbol as currencyDisplaySymbol " + "from m_product_deposit dp join m_currency curr on curr.code = dp.currency_code "; } @@ -111,10 +112,16 @@ public class DepositProductReadPlatformServiceImpl implements DepositProductRead BigDecimal preClosureInterestRate=rs.getBigDecimal("preClosureInterestRate"); + Boolean isLockinPeriodAllowed=rs.getBoolean("isLockinPeriodAllowed"); + Integer lockinPeriod = JdbcSupport.getInteger(rs, "lockinPeriod"); + Integer lockinPeriodTypeValue = JdbcSupport.getInteger(rs, "lockinPeriodType"); + EnumOptionData lockinPeriodType = SavingsDepositEnumerations.interestCompoundingPeriodType(lockinPeriodTypeValue); + + return new DepositProductData(createdOn, lastModifedOn, id, exernalId, name, description, currencyCode, currencyDigits, minimumBalance, maximumBalance, tenureMonths, maturityDefaultInterestRate,maturityMinInterestRate,maturityMaxInterestRate, interestCompoundedEvery, interestCompoundedEveryPeriodType, - canRenew,canPreClose,preClosureInterestRate,interestCompoundingAllowed); + canRenew,canPreClose,preClosureInterestRate,interestCompoundingAllowed,isLockinPeriodAllowed,lockinPeriod,lockinPeriodType); } } diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/savingproduct/service/DepositProductWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosng/platform/savingproduct/service/DepositProductWritePlatformServiceJpaRepositoryImpl.java index b1dc6ccc1..19ead623d 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/savingproduct/service/DepositProductWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/savingproduct/service/DepositProductWritePlatformServiceJpaRepositoryImpl.java @@ -57,12 +57,14 @@ public class DepositProductWritePlatformServiceJpaRepositoryImpl implements Depo validator.validateForCreate(); PeriodFrequencyType interestCompoundingPeriodType = PeriodFrequencyType.fromInt(command.getInterestCompoundedEveryPeriodType()); + PeriodFrequencyType lockinPeriodType = PeriodFrequencyType.fromInt(command.getLockinPeriodType()); MonetaryCurrency currency = new MonetaryCurrency(command.getCurrencyCode(), command.getDigitsAfterDecimal()); DepositProduct product = new DepositProduct(command.getName(), command.getExternalId(), command.getDescription(),currency,command.getMinimumBalance(),command.getMaximumBalance(), command.getTenureInMonths(),command.getMaturityDefaultInterestRate(),command.getMaturityMinInterestRate(),command.getMaturityMaxInterestRate(), command.getInterestCompoundedEvery(), interestCompoundingPeriodType, command.isRenewalAllowed(), command.isPreClosureAllowed(), - command.getPreClosureInterestRate(),command.isInterestCompoundingAllowed()); + command.getPreClosureInterestRate(),command.isInterestCompoundingAllowed(), + command.isLockinPeriodAllowed(),command.getLockinPeriod(),lockinPeriodType); this.depositProductRepository.save(product); return new EntityIdentifier(product.getId()); @@ -91,7 +93,12 @@ public class DepositProductWritePlatformServiceJpaRepositoryImpl implements Depo interestCompoundingFrequency = PeriodFrequencyType.fromInt(command.getInterestCompoundedEveryPeriodType()); } - product.update(command, interestCompoundingFrequency); + PeriodFrequencyType lockinPeriodType = null; + if (command.isLockinPeriodTypeChanged()) { + lockinPeriodType = PeriodFrequencyType.fromInt(command.getLockinPeriodType()); + } + + product.update(command, interestCompoundingFrequency, lockinPeriodType); this.depositProductRepository.save(product); return new EntityIdentifier(Long.valueOf(product.getId())); } catch (DataIntegrityViolationException dve) {