From 013276d9e4a6e2d609f146ad73ce573e32d63b6f Mon Sep 17 00:00:00 2001 From: Keith Woodlock Date: Tue, 18 Dec 2012 20:41:12 +0000 Subject: [PATCH] MIFOSX-97: support audit for loan application creation, loan application modification, approval, undo approval, disbursal and undo of disbursal. --- .../commands/domain/CommandWrapper.java | 28 + .../SynchronousCommandProcessingService.java | 23 + ...GsonPortfolioApiJsonSerializerService.java | 18 +- .../infrastructure/core/api/JsonCommand.java | 4 + .../infrastructure/core/api/JsonQuery.java | 261 ++++ .../PortfolioApiDataConversionService.java | 6 - ...PortfolioApiDataConversionServiceImpl.java | 126 +- .../PortfolioApiJsonSerializerService.java | 4 - .../core/serialization/FromJsonHelper.java | 24 +- .../loanaccount/api/LoansApiResource.java | 117 +- .../command/LoanApplicationCommand.java | 70 +- .../LoanApplicationCommandValidator.java | 78 -- .../command/LoanChargeCommand.java | 48 +- .../command/LoanStateTransitionCommand.java | 82 +- .../LoanStateTransitionCommandValidator.java | 32 - .../portfolio/loanaccount/domain/Loan.java | 532 ++++++-- .../loanaccount/domain/LoanCharge.java | 82 ++ .../loanaccount/domain/LoanStatus.java | 168 +-- ...rawsFromLoanApplicationCommandHandler.java | 27 + .../ApproveLoanApplicationCommandHandler.java | 27 + .../CreateLoanApplicationCommandHandler.java | 27 + .../DeleteLoanApplicationCommandHandler.java | 27 + .../handler/DisburseLoanCommandHandler.java | 27 + .../RejectLoanApplicationCommandHandler.java | 27 + ...ApprovalLoanApplicationCommandHandler.java | 27 + .../UndoDisbursalLoanCommandHandler.java | 27 + .../UpdateLoanApplicationCommandHandler.java | 27 + .../command/CalculateLoanScheduleCommand.java | 125 -- ...CalculateLoanScheduleCommandValidator.java | 101 -- .../loanschedule/data/LoanScheduleData.java | 551 ++++---- .../loanschedule/domain/AprCalculator.java | 40 +- ...ingBalanceMethodLoanScheduleGenerator.java | 85 +- .../DefaultLoanScheduleGeneratorFactory.java | 37 +- .../FlatMethodLoanScheduleGenerator.java | 223 ++- .../loanschedule/domain/LoanSchedule.java | 105 ++ .../domain/LoanScheduleGenerator.java | 8 +- .../query/CalculateLoanScheduleQuery.java | 207 +++ .../service/CalculationPlatformService.java | 9 - .../CalculationPlatformServiceImpl.java | 71 - .../service/LoanScheduleAssembler.java | 108 ++ ...oanScheduleCalculationPlatformService.java | 9 + ...cheduleCalculationPlatformServiceImpl.java | 38 + ...nScheduleQueryFromApiJsonDeserializer.java | 114 ++ ...icationCommandFromApiJsonDeserializer.java | 142 ++ ...nsitionCommandFromApiJsonDeserializer.java | 61 + .../loanaccount/service/LoanAssembler.java | 293 ++-- .../service/LoanChargeAssembler.java | 138 +- .../LoanProductRelatedDetailAssembler.java | 83 +- .../service/LoanWritePlatformService.java | 81 +- ...WritePlatformServiceJpaRepositoryImpl.java | 1201 ++++++++--------- .../loanproduct/domain/LoanProduct.java | 4 +- .../domain/LoanProductRelatedDetail.java | 43 +- ...WritePlatformServiceJpaRepositoryImpl.java | 2 +- 53 files changed, 3469 insertions(+), 2356 deletions(-) create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JsonQuery.java delete mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/command/LoanApplicationCommandValidator.java delete mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/command/LoanStateTransitionCommandValidator.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/ApplicantWithdrawsFromLoanApplicationCommandHandler.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/ApproveLoanApplicationCommandHandler.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/CreateLoanApplicationCommandHandler.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/DeleteLoanApplicationCommandHandler.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/DisburseLoanCommandHandler.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/RejectLoanApplicationCommandHandler.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/UndoApprovalLoanApplicationCommandHandler.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/UndoDisbursalLoanCommandHandler.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/UpdateLoanApplicationCommandHandler.java delete mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/command/CalculateLoanScheduleCommand.java delete mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/command/CalculateLoanScheduleCommandValidator.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanSchedule.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/query/CalculateLoanScheduleQuery.java delete mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/CalculationPlatformService.java delete mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/CalculationPlatformServiceImpl.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformService.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonDeserializer.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonDeserializer.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanStateTransitionCommandFromApiJsonDeserializer.java diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java index bc3076a9f..7a5315acf 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java @@ -163,4 +163,32 @@ public class CommandWrapper { public boolean isIdentifiersSubResource() { return "IDENTIFIERS".equalsIgnoreCase(this.subResource); } + + public boolean isLoanResource() { + return this.resource.equalsIgnoreCase("LOANS"); + } + + public boolean isApproveLoanApplication() { + return this.actionName.equalsIgnoreCase("APPROVE") && this.entityName.equalsIgnoreCase("LOAN"); + } + + public boolean isUndoApprovalOfLoanApplication() { + return this.actionName.equalsIgnoreCase("APPROVALUNDO") && this.entityName.equalsIgnoreCase("LOAN"); + } + + public boolean isApplicantWithdrawalFromLoanApplication() { + return this.actionName.equalsIgnoreCase("WITHDRAW") && this.entityName.equalsIgnoreCase("LOAN"); + } + + public boolean isRejectionOfLoanApplication() { + return this.actionName.equalsIgnoreCase("REJECT") && this.entityName.equalsIgnoreCase("LOAN"); + } + + public boolean isDisbursementOfLoan() { + return this.actionName.equalsIgnoreCase("DISBURSE") && this.entityName.equalsIgnoreCase("LOAN"); + } + + public boolean isUndoDisbursementOfLoan() { + return this.actionName.equalsIgnoreCase("DISBURSALUNDO") && this.entityName.equalsIgnoreCase("LOAN"); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java index 82f977fdb..d662f620b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java @@ -210,6 +210,29 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer } else { throw new UnsupportedCommandException(wrapper.commandName()); } + } else if (wrapper.isLoanResource()) { + + if (wrapper.isApproveLoanApplication()) { + handler = applicationContext.getBean("approveLoanApplicationCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isUndoApprovalOfLoanApplication()) { + handler = applicationContext.getBean("undoApprovalLoanApplicationCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isApplicantWithdrawalFromLoanApplication()) { + handler = applicationContext.getBean("applicantWithdrawsFromLoanApplicationCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isRejectionOfLoanApplication()) { + handler = applicationContext.getBean("rejectLoanApplicationCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isDisbursementOfLoan()) { + handler = applicationContext.getBean("disburseLoanCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isUndoDisbursementOfLoan()) { + handler = applicationContext.getBean("undoDisbursalLoanCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isCreate()) { + handler = applicationContext.getBean("createLoanApplicationCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isUpdate()) { + handler = applicationContext.getBean("updateLoanApplicationCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isDelete()) { + handler = applicationContext.getBean("deleteLoanApplicationCommandHandler", NewCommandSourceHandler.class); + } else { + throw new UnsupportedCommandException(wrapper.commandName()); + } } else { throw new UnsupportedCommandException(wrapper.commandName()); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/GoogleGsonPortfolioApiJsonSerializerService.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/GoogleGsonPortfolioApiJsonSerializerService.java index da9554ceb..365f8cc19 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/GoogleGsonPortfolioApiJsonSerializerService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/GoogleGsonPortfolioApiJsonSerializerService.java @@ -18,7 +18,6 @@ import org.mifosplatform.portfolio.group.data.GroupData; import org.mifosplatform.portfolio.loanaccount.data.LoanChargeData; import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionData; import org.mifosplatform.portfolio.loanaccount.gaurantor.data.GuarantorData; -import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData; import org.mifosplatform.portfolio.savingsaccount.data.SavingAccountData; import org.mifosplatform.portfolio.savingsaccount.data.SavingScheduleData; import org.mifosplatform.portfolio.savingsaccountproduct.data.SavingProductData; @@ -59,8 +58,8 @@ public class GoogleGsonPortfolioApiJsonSerializerService implements PortfolioApi "interestCompoundedEveryPeriodType", "renewalAllowed", "preClosureAllowed", "preClosureInterestRate", "withdrawnonDate", "rejectedonDate", "closedonDate", "transactions", "permissions", "isInterestWithdrawable", "interestPaid", "interestCompoundingAllowed", "availableInterestForWithdrawal", "availableWithdrawalAmount", "todaysDate", - "isLockinPeriodAllowed", "lockinPeriod", "lockinPeriodType", "printFDdetailsLocation","availableInterest", "interestPostedAmount", - "lastInterestPostedDate", "nextInterestPostedDate","fatherName","address","imageKey")); + "isLockinPeriodAllowed", "lockinPeriod", "lockinPeriodType", "printFDdetailsLocation", "availableInterest", + "interestPostedAmount", "lastInterestPostedDate", "nextInterestPostedDate", "fatherName", "address", "imageKey")); private static final Set DOCUMENT_DATA_PARAMETERS = new HashSet(Arrays.asList("id", "parentEntityType", "parentEntityId", "name", "fileName", "type", "size", "description")); @@ -79,9 +78,6 @@ public class GoogleGsonPortfolioApiJsonSerializerService implements PortfolioApi private static final Set GROUP_DATA_PARAMETERS = new HashSet(Arrays.asList("id", "officeId", "name", "externalId", "clientMembers", "allowedClients", "allowedOffices")); - private static final Set LOAN_SCHEDULE_DATA_PARAMETERS = new HashSet(Arrays.asList("periods", - "cumulativePrincipalDisbursed")); - private static final Set LOAN_TRANSACTION_NEW_DATA_PARAMETERS = new HashSet(Arrays.asList("id", "type", "date", "currency", "amount")); @@ -103,7 +99,7 @@ public class GoogleGsonPortfolioApiJsonSerializerService implements PortfolioApi private static final Set GUARANTOR_DATA_PARAMETERS = new HashSet(Arrays.asList("externalGuarantor", "existingClientId", "firstname", "lastname", "addressLine1", "addressLine2", "city", "state", "zip", "country", "mobileNumber", "housePhoneNumber", "comment", "dob")); - + private static final Set SAVING_SCHEDULE_DATA_PARAMETERS = new HashSet(Arrays.asList("periods", "cumulativeDepositDue")); private final GoogleGsonSerializerHelper helper; @@ -210,14 +206,6 @@ public class GoogleGsonPortfolioApiJsonSerializerService implements PortfolioApi return helper.serializedJsonFrom(gsonDeserializer, group); } - @Override - public String serializeLoanScheduleDataToJson(final boolean prettyPrint, final Set responseParameters, - final LoanScheduleData loanSchedule) { - final Gson gsonDeserializer = helper.createGsonBuilderWithParameterExclusionSerializationStrategy(LOAN_SCHEDULE_DATA_PARAMETERS, - prettyPrint, responseParameters); - return helper.serializedJsonFrom(gsonDeserializer, loanSchedule); - } - @Override public String serializeLoanTransactionDataToJson(final boolean prettyPrint, final Set responseParameters, final LoanTransactionData transaction) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JsonCommand.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JsonCommand.java index e9c2b8bd3..e68715ac1 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JsonCommand.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JsonCommand.java @@ -55,6 +55,10 @@ public final class JsonCommand { return this.jsonCommand; } + public JsonElement parsedJson() { + return this.parsedCommand; + } + public Long commandId() { return this.commandId; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JsonQuery.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JsonQuery.java new file mode 100644 index 000000000..bdfedd1a1 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JsonQuery.java @@ -0,0 +1,261 @@ +package org.mifosplatform.infrastructure.core.api; + +import java.lang.reflect.Type; +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.Map; + +import org.apache.commons.lang.ObjectUtils; +import org.apache.commons.lang.StringUtils; +import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; +import org.mifosplatform.infrastructure.security.domain.BasicPasswordEncodablePlatformUser; +import org.mifosplatform.infrastructure.security.domain.PlatformUser; +import org.mifosplatform.infrastructure.security.service.PlatformPasswordEncoder; + +import com.google.gson.JsonElement; +import com.google.gson.reflect.TypeToken; + +/** + * Immutable representation of a query. + * + * Wraps the provided JSON with convenience functions for extracting parameter + * values. + */ +public final class JsonQuery { + + private final String jsonQuery; + private final JsonElement parsedQuery; + private final FromJsonHelper fromApiJsonHelper; + + public static JsonQuery from(final String jsonCommand, final JsonElement parsedQuery, final FromJsonHelper fromApiJsonHelper) { + return new JsonQuery(jsonCommand, parsedQuery, fromApiJsonHelper); + } + + public JsonQuery(final String jsonCommand, final JsonElement parsedCommand, final FromJsonHelper fromApiJsonHelper) { + this.jsonQuery = jsonCommand; + this.parsedQuery = parsedCommand; + this.fromApiJsonHelper = fromApiJsonHelper; + } + + public String json() { + return this.jsonQuery; + } + + public JsonElement parsedJson() { + return this.parsedQuery; + } + + private boolean differenceExists(final LocalDate baseValue, final LocalDate workingCopyValue) { + boolean differenceExists = false; + + if (baseValue != null) { + differenceExists = !baseValue.equals(workingCopyValue); + } else { + differenceExists = workingCopyValue != null; + } + + return differenceExists; + } + + private boolean differenceExists(final String baseValue, final String workingCopyValue) { + boolean differenceExists = false; + + if (StringUtils.isNotBlank(baseValue)) { + differenceExists = !baseValue.equals(workingCopyValue); + } else { + differenceExists = StringUtils.isNotBlank(workingCopyValue); + } + + return differenceExists; + } + + private boolean differenceExists(final String[] baseValue, final String[] workingCopyValue) { + Arrays.sort(baseValue); + Arrays.sort(workingCopyValue); + return !Arrays.equals(baseValue, workingCopyValue); + } + + private boolean differenceExists(final Number baseValue, final Number workingCopyValue) { + boolean differenceExists = false; + + if (baseValue != null) { + differenceExists = !baseValue.equals(workingCopyValue); + } else { + differenceExists = workingCopyValue != null; + } + + return differenceExists; + } + + private boolean differenceExists(final BigDecimal baseValue, final BigDecimal workingCopyValue) { + boolean differenceExists = false; + + if (baseValue != null) { + differenceExists = baseValue.compareTo(workingCopyValue) != 0; + } else { + differenceExists = workingCopyValue != null; + } + + return differenceExists; + } + + private boolean differenceExists(final Boolean baseValue, final Boolean workingCopyValue) { + boolean differenceExists = false; + + if (baseValue != null) { + differenceExists = !baseValue.equals(workingCopyValue); + } else { + differenceExists = workingCopyValue != null; + } + + return differenceExists; + } + + private boolean parameterExists(final String parameterName) { + return this.fromApiJsonHelper.parameterExists(parameterName, parsedQuery); + } + + public boolean hasParameter(final String parameterName) { + return parameterExists(parameterName); + } + + public String dateFormat() { + return stringValueOfParameterNamed("dateFormat"); + } + + public String locale() { + return stringValueOfParameterNamed("locale"); + } + + public Map mapValueOfParameterNamed(final String parameterName) { + final Type typeOfMap = new TypeToken>() {}.getType(); + + if (parsedQuery.getAsJsonObject().has(parameterName)) { + parsedQuery.getAsJsonObject().get(parameterName); + } + + return this.fromApiJsonHelper.extractMap(typeOfMap, jsonQuery); + } + + public boolean isChangeInLongParameterNamed(final String parameterName, final Long existingValue) { + boolean isChanged = false; + if (parameterExists(parameterName)) { + final Long workingValue = longValueOfParameterNamed(parameterName); + isChanged = differenceExists(existingValue, workingValue); + } + return isChanged; + } + + public Long longValueOfParameterNamed(final String parameterName) { + return this.fromApiJsonHelper.extractLongNamed(parameterName, parsedQuery); + } + + public boolean isChangeInLocalDateParameterNamed(final String parameterName, final LocalDate existingValue) { + boolean isChanged = false; + if (parameterExists(parameterName)) { + final LocalDate workingValue = localDateValueOfParameterNamed(parameterName); + isChanged = differenceExists(existingValue, workingValue); + } + return isChanged; + } + + public LocalDate localDateValueOfParameterNamed(final String parameterName) { + return this.fromApiJsonHelper.extractLocalDateNamed(parameterName, parsedQuery); + } + + public boolean isChangeInStringParameterNamed(final String parameterName, final String existingValue) { + boolean isChanged = false; + if (parameterExists(parameterName)) { + final String workingValue = stringValueOfParameterNamed(parameterName); + isChanged = differenceExists(existingValue, workingValue); + } + return isChanged; + } + + public String stringValueOfParameterNamed(final String parameterName) { + final String value = this.fromApiJsonHelper.extractStringNamed(parameterName, parsedQuery); + return StringUtils.defaultIfEmpty(value, ""); + } + + public boolean isChangeInBigDecimalParameterNamed(final String parameterName, final BigDecimal existingValue) { + boolean isChanged = false; + if (parameterExists(parameterName)) { + final BigDecimal workingValue = bigDecimalValueOfParameterNamed(parameterName); + isChanged = differenceExists(existingValue, workingValue); + } + return isChanged; + } + + public BigDecimal bigDecimalValueOfParameterNamed(final String parameterName) { + return this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(parameterName, parsedQuery); + } + + public boolean isChangeInIntegerParameterNamed(final String parameterName, final Integer existingValue) { + boolean isChanged = false; + if (parameterExists(parameterName)) { + final Integer workingValue = integerValueOfParameterNamed(parameterName); + isChanged = differenceExists(existingValue, workingValue); + } + return isChanged; + } + + public Integer integerValueOfParameterNamed(final String parameterName) { + return this.fromApiJsonHelper.extractIntegerWithLocaleNamed(parameterName, parsedQuery); + } + + public boolean isChangeInBooleanParameterNamed(final String parameterName, final Boolean existingValue) { + boolean isChanged = false; + if (parameterExists(parameterName)) { + final Boolean workingValue = booleanObjectValueOfParameterNamed(parameterName); + isChanged = differenceExists(existingValue, workingValue); + } + return isChanged; + } + + /** + * Returns {@link Boolean} that could possibly be null. + */ + public Boolean booleanObjectValueOfParameterNamed(final String parameterName) { + return this.fromApiJsonHelper.extractBooleanNamed(parameterName, parsedQuery); + } + + /** + * always returns true or false + */ + public boolean booleanPrimitiveValueOfParameterNamed(final String parameterName) { + final Boolean value = this.fromApiJsonHelper.extractBooleanNamed(parameterName, parsedQuery); + return (Boolean) ObjectUtils.defaultIfNull(value, Boolean.FALSE); + } + + public boolean isChangeInArrayParameterNamed(final String parameterName, final String[] existingValue) { + boolean isChanged = false; + if (parameterExists(parameterName)) { + final String[] workingValue = arrayValueOfParameterNamed(parameterName); + isChanged = differenceExists(existingValue, workingValue); + } + return isChanged; + } + + public String[] arrayValueOfParameterNamed(final String parameterName) { + return this.fromApiJsonHelper.extractArrayNamed(parameterName, parsedQuery); + } + + public boolean isChangeInPasswordParameterNamed(final String parameterName, final String existingValue, + final PlatformPasswordEncoder platformPasswordEncoder, final Long saltValue) { + boolean isChanged = false; + if (parameterExists(parameterName)) { + final String workingValue = passwordValueOfParameterNamed(parameterName, platformPasswordEncoder, saltValue); + isChanged = differenceExists(existingValue, workingValue); + } + return isChanged; + } + + public String passwordValueOfParameterNamed(final String parameterName, final PlatformPasswordEncoder platformPasswordEncoder, + final Long saltValue) { + final String passwordPlainText = stringValueOfParameterNamed(parameterName); + + final PlatformUser dummyPlatformUser = new BasicPasswordEncodablePlatformUser(saltValue, "", passwordPlainText); + return platformPasswordEncoder.encode(dummyPlatformUser); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/PortfolioApiDataConversionService.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/PortfolioApiDataConversionService.java index 4175f757c..86d8e5330 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/PortfolioApiDataConversionService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/PortfolioApiDataConversionService.java @@ -5,9 +5,7 @@ import org.mifosplatform.portfolio.client.command.NoteCommand; import org.mifosplatform.portfolio.client.data.ClientData; import org.mifosplatform.portfolio.group.command.GroupCommand; import org.mifosplatform.portfolio.loanaccount.command.AdjustLoanTransactionCommand; -import org.mifosplatform.portfolio.loanaccount.command.LoanApplicationCommand; import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand; -import org.mifosplatform.portfolio.loanaccount.command.LoanStateTransitionCommand; import org.mifosplatform.portfolio.loanaccount.command.LoanTransactionCommand; import org.mifosplatform.portfolio.loanaccount.gaurantor.command.GuarantorCommand; import org.mifosplatform.portfolio.savingsaccount.command.SavingAccountCommand; @@ -29,12 +27,8 @@ public interface PortfolioApiDataConversionService { GroupCommand convertJsonToGroupCommand(Long resourceIdentifier, String json); - LoanApplicationCommand convertApiRequestJsonToLoanApplicationCommand(Long resourceIdentifier, String json); - LoanChargeCommand convertJsonToLoanChargeCommand(Long loanChargeId, Long loanId, String json); - LoanStateTransitionCommand convertJsonToLoanStateTransitionCommand(Long resourceIdentifier, String json); - LoanTransactionCommand convertJsonToLoanTransactionCommand(Long resourceIdentifier, String json); AdjustLoanTransactionCommand convertJsonToAdjustLoanTransactionCommand(Long loanId, Long transactionId, String json); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/PortfolioApiDataConversionServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/PortfolioApiDataConversionServiceImpl.java index efd3d2c53..d711c41ab 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/PortfolioApiDataConversionServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/PortfolioApiDataConversionServiceImpl.java @@ -21,7 +21,6 @@ import org.mifosplatform.infrastructure.core.data.ApiParameterError; import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; import org.mifosplatform.infrastructure.core.exception.UnsupportedParameterException; -import org.mifosplatform.infrastructure.core.serialization.JsonParserHelper; import org.mifosplatform.organisation.staff.command.BulkTransferLoanOfficerCommand; import org.mifosplatform.portfolio.client.command.ClientCommand; import org.mifosplatform.portfolio.client.command.NoteCommand; @@ -29,9 +28,7 @@ import org.mifosplatform.portfolio.client.data.ClientData; import org.mifosplatform.portfolio.client.serialization.ClientCommandFromApiJsonDeserializer; import org.mifosplatform.portfolio.group.command.GroupCommand; import org.mifosplatform.portfolio.loanaccount.command.AdjustLoanTransactionCommand; -import org.mifosplatform.portfolio.loanaccount.command.LoanApplicationCommand; import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand; -import org.mifosplatform.portfolio.loanaccount.command.LoanStateTransitionCommand; import org.mifosplatform.portfolio.loanaccount.command.LoanTransactionCommand; import org.mifosplatform.portfolio.loanaccount.gaurantor.command.GuarantorCommand; import org.mifosplatform.portfolio.savingsaccount.command.SavingAccountCommand; @@ -178,106 +175,10 @@ public class PortfolioApiDataConversionServiceImpl implements PortfolioApiDataCo return new GroupCommand(modifiedParameters, resourceIdentifier, externalId, name, officeId, clientMembers); } - @Override - public LoanApplicationCommand convertApiRequestJsonToLoanApplicationCommand(final Long resourceIdentifier, final String json) { - - if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } - - final Type typeOfMap = new TypeToken>() {}.getType(); - final Map requestMap = gsonConverter.fromJson(json, typeOfMap); - - final Set supportedParams = new HashSet(Arrays.asList("clientId", "groupId", "productId", "externalId", "fundId", - "transactionProcessingStrategyId", "principal", "inArrearsTolerance", "interestRatePerPeriod", "repaymentEvery", - "numberOfRepayments", "loanTermFrequency", "loanTermFrequencyType", "charges", "repaymentFrequencyType", - "interestRateFrequencyType", "amortizationType", "interestType", "interestCalculationPeriodType", - "expectedDisbursementDate", "repaymentsStartingFromDate", "interestChargedFromDate", "submittedOnDate", "submittedOnNote", - "locale", "dateFormat", "loanOfficerId", "id")); - - checkForUnsupportedParameters(requestMap, supportedParams); - - final Set parametersPassedInCommand = new HashSet(); - - final JsonParser parser = new JsonParser(); - final JsonElement element = parser.parse(json); - final JsonParserHelper helper = new JsonParserHelper(); - - final Long clientId = helper.extractLongNamed("clientId", element, parametersPassedInCommand); - final Long groupId = helper.extractLongNamed("groupId", element, parametersPassedInCommand); - final Long productId = helper.extractLongNamed("productId", element, parametersPassedInCommand); - final Long fundId = helper.extractLongNamed("fundId", element, parametersPassedInCommand); - final Long loanOfficerId = helper.extractLongNamed("loanOfficerId", element, parametersPassedInCommand); - final Long transactionProcessingStrategyId = helper.extractLongNamed("transactionProcessingStrategyId", element, - parametersPassedInCommand); - final String externalId = helper.extractStringNamed("externalId", element, parametersPassedInCommand); - final BigDecimal principal = helper.extractBigDecimalWithLocaleNamed("principal", element, parametersPassedInCommand); - final BigDecimal inArrearsToleranceValue = helper.extractBigDecimalWithLocaleNamed("inArrearsTolerance", element, - parametersPassedInCommand); - final BigDecimal interestRatePerPeriod = helper.extractBigDecimalWithLocaleNamed("interestRatePerPeriod", element, - parametersPassedInCommand); - - final Integer repaymentEvery = helper.extractIntegerWithLocaleNamed("repaymentEvery", element, parametersPassedInCommand); - final Integer numberOfRepayments = helper.extractIntegerWithLocaleNamed("numberOfRepayments", element, parametersPassedInCommand); - final Integer repaymentFrequencyType = helper.extractIntegerWithLocaleNamed("repaymentFrequencyType", element, - parametersPassedInCommand); - final Integer loanTermFrequency = helper.extractIntegerWithLocaleNamed("loanTermFrequency", element, parametersPassedInCommand); - final Integer loanTermFrequencyType = helper.extractIntegerWithLocaleNamed("loanTermFrequencyType", element, - parametersPassedInCommand); - final Integer interestRateFrequencyType = helper.extractIntegerWithLocaleNamed("interestRateFrequencyType", element, - parametersPassedInCommand); - final Integer amortizationType = helper.extractIntegerWithLocaleNamed("amortizationType", element, parametersPassedInCommand); - final Integer interestType = helper.extractIntegerWithLocaleNamed("interestType", element, parametersPassedInCommand); - final Integer interestCalculationPeriodType = helper.extractIntegerWithLocaleNamed("interestCalculationPeriodType", element, - parametersPassedInCommand); - - final LocalDate expectedDisbursementDate = helper.extractLocalDateNamed("expectedDisbursementDate", element, - parametersPassedInCommand); - final LocalDate repaymentsStartingFromDate = helper.extractLocalDateNamed("repaymentsStartingFromDate", element, - parametersPassedInCommand); - final LocalDate interestChargedFromDate = helper.extractLocalDateNamed("interestChargedFromDate", element, - parametersPassedInCommand); - final LocalDate submittedOnDate = helper.extractLocalDateNamed("submittedOnDate", element, parametersPassedInCommand); - - final String submittedOnNote = helper.extractStringNamed("submittedOnNote", element, parametersPassedInCommand); - - LoanChargeCommand[] charges = null; - if (element.isJsonObject()) { - final JsonObject topLevelJsonElement = element.getAsJsonObject(); - final String dateFormat = helper.extractDateFormatParameter(topLevelJsonElement); - final Locale locale = helper.extractLocaleParameter(topLevelJsonElement); - if (topLevelJsonElement.has("charges") && topLevelJsonElement.get("charges").isJsonArray()) { - - parametersPassedInCommand.add("charges"); - final JsonArray array = topLevelJsonElement.get("charges").getAsJsonArray(); - charges = new LoanChargeCommand[array.size()]; - for (int i = 0; i < array.size(); i++) { - - final JsonObject loanChargeElement = array.get(i).getAsJsonObject(); - final Set parametersPassedInForChargesCommand = new HashSet(); - - final Long id = helper.extractLongNamed("id", loanChargeElement, parametersPassedInForChargesCommand); - final Long chargeId = helper.extractLongNamed("chargeId", loanChargeElement, parametersPassedInForChargesCommand); - final BigDecimal amount = helper.extractBigDecimalNamed("amount", loanChargeElement, locale, - parametersPassedInForChargesCommand); - final Integer chargeTimeType = helper.extractIntegerNamed("chargeTimeType", loanChargeElement, locale, - parametersPassedInForChargesCommand); - final Integer chargeCalculationType = helper.extractIntegerNamed("chargeCalculationType", loanChargeElement, locale, - parametersPassedInForChargesCommand); - final LocalDate specifiedDueDate = helper.extractLocalDateNamed("specifiedDueDate", loanChargeElement, dateFormat, - locale, parametersPassedInForChargesCommand); - - charges[i] = new LoanChargeCommand(parametersPassedInForChargesCommand, id, null, chargeId, amount, chargeTimeType, - chargeCalculationType, specifiedDueDate); - } - } - } - - return new LoanApplicationCommand(parametersPassedInCommand, resourceIdentifier, clientId, groupId, productId, externalId, fundId, - transactionProcessingStrategyId, submittedOnDate, submittedOnNote, expectedDisbursementDate, repaymentsStartingFromDate, - interestChargedFromDate, principal, interestRatePerPeriod, interestRateFrequencyType, interestType, - interestCalculationPeriodType, repaymentEvery, repaymentFrequencyType, numberOfRepayments, amortizationType, - loanTermFrequency, loanTermFrequencyType, inArrearsToleranceValue, charges, loanOfficerId); - } - + /** + * @deprecated - to be removed soon + */ + @Deprecated @Override public LoanChargeCommand convertJsonToLoanChargeCommand(final Long loanChargeId, final Long loanId, final String json) { if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } @@ -302,25 +203,6 @@ public class PortfolioApiDataConversionServiceImpl implements PortfolioApiDataCo submittedOnDate); } - @Override - public LoanStateTransitionCommand convertJsonToLoanStateTransitionCommand(final Long resourceIdentifier, final String json) { - if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } - - Type typeOfMap = new TypeToken>() {}.getType(); - Map requestMap = gsonConverter.fromJson(json, typeOfMap); - - Set supportedParams = new HashSet(Arrays.asList("eventDate", "note", "locale", "dateFormat")); - - checkForUnsupportedParameters(requestMap, supportedParams); - - Set modifiedParameters = new HashSet(); - - LocalDate eventDate = extractLocalDateParameter("eventDate", requestMap, modifiedParameters); - String note = extractStringParameter("note", requestMap, modifiedParameters); - - return new LoanStateTransitionCommand(resourceIdentifier, eventDate, note); - } - @Override public LoanTransactionCommand convertJsonToLoanTransactionCommand(final Long resourceIdentifier, final String json) { if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/PortfolioApiJsonSerializerService.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/PortfolioApiJsonSerializerService.java index cda91c926..3e8428326 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/PortfolioApiJsonSerializerService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/PortfolioApiJsonSerializerService.java @@ -15,7 +15,6 @@ import org.mifosplatform.portfolio.group.data.GroupData; import org.mifosplatform.portfolio.loanaccount.data.LoanChargeData; import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionData; import org.mifosplatform.portfolio.loanaccount.gaurantor.data.GuarantorData; -import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData; import org.mifosplatform.portfolio.savingsaccount.data.SavingAccountData; import org.mifosplatform.portfolio.savingsaccount.data.SavingScheduleData; import org.mifosplatform.portfolio.savingsaccountproduct.data.SavingProductData; @@ -52,8 +51,6 @@ public interface PortfolioApiJsonSerializerService { String serializeGroupDataToJson(boolean prettyPrint, Set responseParameters, GroupData group); - String serializeLoanScheduleDataToJson(boolean prettyPrint, Set responseParameters, LoanScheduleData loanSchedule); - String serializeLoanTransactionDataToJson(boolean prettyPrint, Set responseParameters, LoanTransactionData newTransactionData); String serializeLoanReassignmentDataToJson(boolean prettyPrint, Set responseParameters, @@ -77,5 +74,4 @@ public interface PortfolioApiJsonSerializerService { String serializeSavingScheduleDataToJson(boolean prettyPrint, Set responseParameters, SavingScheduleData savingScheduleData); - } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/FromJsonHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/FromJsonHelper.java index c2a09deb4..dee76e78f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/FromJsonHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/FromJsonHelper.java @@ -17,6 +17,7 @@ import org.springframework.stereotype.Component; import com.google.gson.Gson; import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.google.gson.JsonParser; @Component @@ -98,6 +99,11 @@ public class FromJsonHelper { public LocalDate extractLocalDateNamed(final String parameterName, JsonElement element) { return helperDelegator.extractLocalDateNamed(parameterName, element, new HashSet()); } + + public LocalDate extractLocalDateNamed(final String parameterName, final JsonElement element, final String dateFormat, + final Locale locale) { + return helperDelegator.extractLocalDateNamed(parameterName, element.getAsJsonObject(), dateFormat, locale, new HashSet()); + } public LocalDate extractLocalDateNamed(final String parameterName, final JsonElement element, final Set parametersPassedInRequest) { @@ -118,11 +124,15 @@ public class FromJsonHelper { return helperDelegator.extractBigDecimalWithLocaleNamed(parameterName, element, parametersPassedInRequest); } + public BigDecimal extractBigDecimalNamed(final String parameterName, final JsonElement element, final Locale locale) { + return helperDelegator.extractBigDecimalNamed(parameterName, element.getAsJsonObject(), locale, new HashSet()); + } + public BigDecimal extractBigDecimalNamed(final String parameterName, final JsonElement element, final Set parametersPassedInRequest) { return helperDelegator.extractBigDecimalNamed(parameterName, element.getAsJsonObject(), Locale.US, parametersPassedInRequest); } - + public Integer extractIntegerWithLocaleNamed(final String parameterName, final JsonElement element) { return helperDelegator.extractIntegerWithLocaleNamed(parameterName, element.getAsJsonObject(), new HashSet()); } @@ -131,9 +141,21 @@ public class FromJsonHelper { final Set parametersPassedInRequest) { return helperDelegator.extractIntegerWithLocaleNamed(parameterName, element.getAsJsonObject(), parametersPassedInRequest); } + + public Integer extractIntegerNamed(final String parameterName, final JsonElement element, final Locale locale) { + return helperDelegator.extractIntegerNamed(parameterName, element.getAsJsonObject(), locale, new HashSet()); + } public Integer extractIntegerNamed(final String parameterName, final JsonElement element, final Set parametersPassedInRequest) { return helperDelegator.extractIntegerNamed(parameterName, element.getAsJsonObject(), Locale.US, parametersPassedInRequest); } + + public Locale extractLocaleParameter(final JsonObject element) { + return helperDelegator.extractLocaleParameter(element); + } + + public String extractDateFormatParameter(final JsonObject element) { + return helperDelegator.extractDateFormatParameter(element); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java index 92595dbf1..521974cfc 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java @@ -21,8 +21,10 @@ import javax.ws.rs.core.UriInfo; import org.apache.commons.lang.StringUtils; import org.joda.time.LocalDate; +import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService; import org.mifosplatform.infrastructure.core.api.ApiParameterHelper; import org.mifosplatform.infrastructure.core.api.ApiRequestParameterHelper; +import org.mifosplatform.infrastructure.core.api.JsonQuery; import org.mifosplatform.infrastructure.core.api.PortfolioApiDataConversionService; import org.mifosplatform.infrastructure.core.api.PortfolioApiJsonSerializerService; import org.mifosplatform.infrastructure.core.data.EntityIdentifier; @@ -30,6 +32,7 @@ import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.infrastructure.core.exception.UnrecognizedQueryParamException; import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer; +import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.Money; @@ -45,11 +48,8 @@ import org.mifosplatform.portfolio.charge.service.ChargeReadPlatformService; import org.mifosplatform.portfolio.fund.data.FundData; import org.mifosplatform.portfolio.fund.service.FundReadPlatformService; import org.mifosplatform.portfolio.loanaccount.command.AdjustLoanTransactionCommand; -import org.mifosplatform.portfolio.loanaccount.command.LoanApplicationCommand; import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand; -import org.mifosplatform.portfolio.loanaccount.command.LoanStateTransitionCommand; import org.mifosplatform.portfolio.loanaccount.command.LoanTransactionCommand; -import org.mifosplatform.portfolio.loanaccount.command.UndoStateTransitionCommand; import org.mifosplatform.portfolio.loanaccount.data.DisbursementData; import org.mifosplatform.portfolio.loanaccount.data.LoanAccountData; import org.mifosplatform.portfolio.loanaccount.data.LoanBasicDetailsData; @@ -58,9 +58,8 @@ import org.mifosplatform.portfolio.loanaccount.data.LoanPermissionData; import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionData; import org.mifosplatform.portfolio.loanaccount.gaurantor.data.GuarantorData; import org.mifosplatform.portfolio.loanaccount.gaurantor.service.GuarantorReadPlatformService; -import org.mifosplatform.portfolio.loanaccount.loanschedule.command.CalculateLoanScheduleCommand; import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData; -import org.mifosplatform.portfolio.loanaccount.loanschedule.service.CalculationPlatformService; +import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleCalculationPlatformService; import org.mifosplatform.portfolio.loanaccount.service.LoanReadPlatformService; import org.mifosplatform.portfolio.loanaccount.service.LoanWritePlatformService; import org.mifosplatform.portfolio.loanproduct.data.LoanProductData; @@ -72,6 +71,8 @@ import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; +import com.google.gson.JsonElement; + @Path("/loans") @Component @Scope("singleton") @@ -88,7 +89,7 @@ public class LoansApiResource { "interestRateFrequencyTypeOptions", "fundOptions", "repaymentStrategyOptions", "chargeOptions", "loanOfficerId", "loanOfficerName", "loanOfficerOptions", "chargeTemplate")); - // private final String resourceNameForPermissions = "OFFICE"; + private final String resourceNameForPermissions = "LOAN"; private final PlatformSecurityContext context; private final LoanReadPlatformService loanReadPlatformService; @@ -97,25 +98,31 @@ public class LoansApiResource { private final LoanDropdownReadPlatformService dropdownReadPlatformService; private final FundReadPlatformService fundReadPlatformService; private final ChargeReadPlatformService chargeReadPlatformService; - private final CalculationPlatformService calculationPlatformService; + private final LoanScheduleCalculationPlatformService calculationPlatformService; private final StaffReadPlatformService staffReadPlatformService; private final OfficeReadPlatformService officeReadPlatformService; private final GuarantorReadPlatformService guarantorReadPlatformService; private final PortfolioApiJsonSerializerService apiJsonSerializerService; private final PortfolioApiDataConversionService apiDataConversionService; private final DefaultToApiJsonSerializer toApiJsonSerializer; + private final DefaultToApiJsonSerializer loanScheduleToApiJsonSerializer; private final ApiRequestParameterHelper apiRequestParameterHelper; + private final FromJsonHelper fromJsonHelper; + private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; @Autowired public LoansApiResource(final PlatformSecurityContext context, final LoanReadPlatformService loanReadPlatformService, final LoanWritePlatformService loanWritePlatformService, final LoanProductReadPlatformService loanProductReadPlatformService, final LoanDropdownReadPlatformService dropdownReadPlatformService, final FundReadPlatformService fundReadPlatformService, - final ChargeReadPlatformService chargeReadPlatformService, final CalculationPlatformService calculationPlatformService, + final ChargeReadPlatformService chargeReadPlatformService, + final LoanScheduleCalculationPlatformService calculationPlatformService, final StaffReadPlatformService staffReadPlatformService, final OfficeReadPlatformService officeReadPlatformService, final GuarantorReadPlatformService guarantorReadPlatformService, final DefaultToApiJsonSerializer toApiJsonSerializer, + final DefaultToApiJsonSerializer loanScheduleToApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper, final PortfolioApiJsonSerializerService apiJsonSerializerService, - final PortfolioApiDataConversionService apiDataConversionService) { + final PortfolioApiDataConversionService apiDataConversionService, final FromJsonHelper fromJsonHelper, + final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) { this.context = context; this.loanReadPlatformService = loanReadPlatformService; this.loanWritePlatformService = loanWritePlatformService; @@ -128,9 +135,12 @@ public class LoansApiResource { this.officeReadPlatformService = officeReadPlatformService; this.guarantorReadPlatformService = guarantorReadPlatformService; this.toApiJsonSerializer = toApiJsonSerializer; + this.loanScheduleToApiJsonSerializer = loanScheduleToApiJsonSerializer; this.apiRequestParameterHelper = apiRequestParameterHelper; this.apiJsonSerializerService = apiJsonSerializerService; this.apiDataConversionService = apiDataConversionService; + this.fromJsonHelper = fromJsonHelper; + this.commandsSourceWritePlatformService = commandsSourceWritePlatformService; } @GET @@ -140,7 +150,7 @@ public class LoansApiResource { public String retrieveDetailsForNewLoanApplicationStepOne(@QueryParam("clientId") final Long clientId, @QueryParam("groupId") final Long groupId, @QueryParam("productId") final Long productId, @Context final UriInfo uriInfo) { - context.authenticatedUser().validateHasReadPermission("LOAN"); + context.authenticatedUser().validateHasReadPermission(resourceNameForPermissions); // tempate related Collection productOptions = this.loanProductReadPlatformService.retrieveAllLoanProductsForLookup(); @@ -193,7 +203,7 @@ public class LoansApiResource { @Produces({ MediaType.APPLICATION_JSON }) public String retrieveLoanAccountDetails(@PathParam("loanId") final Long loanId, @Context final UriInfo uriInfo) { - context.authenticatedUser().validateHasReadPermission("LOAN"); + context.authenticatedUser().validateHasReadPermission(resourceNameForPermissions); final LoanBasicDetailsData loanBasicDetails = this.loanReadPlatformService.retrieveLoanAccountDetails(loanId); @@ -319,94 +329,83 @@ public class LoansApiResource { @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) public String calculateLoanScheduleOrSubmitLoanApplication(@QueryParam("command") final String commandParam, - @Context final UriInfo uriInfo, final String jsonRequestBody) { - - final LoanApplicationCommand command = this.apiDataConversionService.convertApiRequestJsonToLoanApplicationCommand(null, - jsonRequestBody); + @Context final UriInfo uriInfo, final String apiRequestBodyAsJson) { if (is(commandParam, "calculateLoanSchedule")) { - CalculateLoanScheduleCommand calculateLoanScheduleCommand = command.toCalculateLoanScheduleCommand(); - return calculateLoanSchedule(uriInfo, calculateLoanScheduleCommand); + + final JsonElement parsedQuery = this.fromJsonHelper.parse(apiRequestBodyAsJson); + final JsonQuery query = JsonQuery.from(apiRequestBodyAsJson, parsedQuery, this.fromJsonHelper); + + final LoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanSchedule(query); + + final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + return this.loanScheduleToApiJsonSerializer.serialize(settings, loanSchedule, new HashSet()); } - final EntityIdentifier identifier = this.loanWritePlatformService.submitLoanApplication(command); + final EntityIdentifier result = this.commandsSourceWritePlatformService.logCommandSource("CREATE", "LOAN", "CREATE", "loans", null, + apiRequestBodyAsJson); - return this.toApiJsonSerializer.serialize(identifier); - } - - private String calculateLoanSchedule(final UriInfo uriInfo, final CalculateLoanScheduleCommand command) { - - final LoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanSchedule(command); - - final Set responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters()); - boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters()); - - return this.apiJsonSerializerService.serializeLoanScheduleDataToJson(prettyPrint, responseParameters, loanSchedule); + return this.toApiJsonSerializer.serialize(result); } @PUT @Path("{loanId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - public String modifyLoanApplication(@PathParam("loanId") final Long loanId, final String jsonRequestBody) { + public String modifyLoanApplication(@PathParam("loanId") final Long loanId, final String apiRequestBodyAsJson) { - final LoanApplicationCommand command = this.apiDataConversionService.convertApiRequestJsonToLoanApplicationCommand(loanId, - jsonRequestBody); + final EntityIdentifier result = this.commandsSourceWritePlatformService.logCommandSource("UPDATE", "LOAN", "UPDATE", "loans", + loanId, apiRequestBodyAsJson); - final EntityIdentifier identifier = this.loanWritePlatformService.modifyLoanApplication(command); - - return this.toApiJsonSerializer.serialize(identifier); + return this.toApiJsonSerializer.serialize(result); } @DELETE @Path("{loanId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - public Response deleteLoanApplication(@PathParam("loanId") final Long loanId) { + public String deleteLoanApplication(@PathParam("loanId") final Long loanId) { - EntityIdentifier identifier = this.loanWritePlatformService.deleteLoan(loanId); + final EntityIdentifier result = this.commandsSourceWritePlatformService.logCommandSource("DELETE", "LOAN", "DELETE", "loans", + loanId, "{}"); - return Response.ok().entity(identifier).build(); + return this.toApiJsonSerializer.serialize(result); } @POST @Path("{loanId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - public Response stateTransitions(@PathParam("loanId") final Long loanId, @QueryParam("command") final String commandParam, - final String jsonRequestBody) { + public String stateTransitions(@PathParam("loanId") final Long loanId, @QueryParam("command") final String commandParam, + final String apiRequestBodyAsJson) { - LoanStateTransitionCommand command = this.apiDataConversionService.convertJsonToLoanStateTransitionCommand(loanId, jsonRequestBody); - - Response response = null; + EntityIdentifier result = null; if (is(commandParam, "reject")) { - EntityIdentifier identifier = this.loanWritePlatformService.rejectLoan(command); - response = Response.ok().entity(identifier).build(); + result = this.commandsSourceWritePlatformService.logCommandSource("REJECT", "LOAN", "N/A", "loans", loanId, + apiRequestBodyAsJson); } else if (is(commandParam, "withdrewbyclient")) { - EntityIdentifier identifier = this.loanWritePlatformService.withdrawLoan(command); - response = Response.ok().entity(identifier).build(); + result = this.commandsSourceWritePlatformService.logCommandSource("WITHDRAW", "LOAN", "N/A", "loans", loanId, + apiRequestBodyAsJson); } else if (is(commandParam, "approve")) { - EntityIdentifier identifier = this.loanWritePlatformService.approveLoanApplication(command); - response = Response.ok().entity(identifier).build(); + result = this.commandsSourceWritePlatformService.logCommandSource("APPROVE", "LOAN", "N/A", "loans", loanId, + apiRequestBodyAsJson); } else if (is(commandParam, "disburse")) { - EntityIdentifier identifier = this.loanWritePlatformService.disburseLoan(command); - response = Response.ok().entity(identifier).build(); + result = this.commandsSourceWritePlatformService.logCommandSource("DISBURSE", "LOAN", "N/A", "loans", loanId, + apiRequestBodyAsJson); } - UndoStateTransitionCommand undoCommand = new UndoStateTransitionCommand(loanId, command.getNote()); - if (is(commandParam, "undoapproval")) { - EntityIdentifier identifier = this.loanWritePlatformService.undoLoanApproval(undoCommand); - response = Response.ok().entity(identifier).build(); + result = this.commandsSourceWritePlatformService.logCommandSource("APPROVALUNDO", "LOAN", "N/A", "loans", loanId, + apiRequestBodyAsJson); } else if (is(commandParam, "undodisbursal")) { - EntityIdentifier identifier = this.loanWritePlatformService.undoLoanDisbursal(undoCommand); - response = Response.ok().entity(identifier).build(); + result = this.commandsSourceWritePlatformService.logCommandSource("DISBURSALUNDO", "LOAN", "N/A", "loans", loanId, + apiRequestBodyAsJson); } - if (response == null) { throw new UnrecognizedQueryParamException("command", commandParam); } + if (result == null) { throw new UnrecognizedQueryParamException("command", commandParam); } - return response; + return this.toApiJsonSerializer.serialize(result); } private boolean is(final String commandParam, final String commandValue) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/command/LoanApplicationCommand.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/command/LoanApplicationCommand.java index 37e7a0d98..1026cdc52 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/command/LoanApplicationCommand.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/command/LoanApplicationCommand.java @@ -1,11 +1,15 @@ package org.mifosplatform.portfolio.loanaccount.command; import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; import java.util.Set; import org.joda.time.LocalDate; -import org.mifosplatform.portfolio.loanaccount.loanschedule.command.CalculateLoanScheduleCommand; -import org.mifosplatform.portfolio.loanproduct.command.LoanProductCommand; +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.loanschedule.query.CalculateLoanScheduleQuery; /** * Immutable command for submitting new loan application. @@ -90,8 +94,8 @@ public class LoanApplicationCommand { this.charges = charges; } - public CalculateLoanScheduleCommand toCalculateLoanScheduleCommand() { - return new CalculateLoanScheduleCommand(this.productId, this.principal, this.interestRatePerPeriod, this.interestRateFrequencyType, + public CalculateLoanScheduleQuery toCalculateLoanScheduleCommand() { + return new CalculateLoanScheduleQuery(this.productId, this.principal, this.interestRatePerPeriod, this.interestRateFrequencyType, this.interestType, this.interestCalculationPeriodType, this.repaymentEvery, this.repaymentFrequencyType, this.numberOfRepayments, this.amortizationType, this.loanTermFrequency, this.loanTermFrequencyType, this.expectedDisbursementDate, this.repaymentsStartingFromDate, this.interestChargedFromDate, this.charges); @@ -197,16 +201,6 @@ public class LoanApplicationCommand { return charges; } - public LoanProductCommand toLoanProductCommand() { - - // FIXME - KW - CHARGE - FIX UP FOR CHARGES - take look at loan product - // charges also and align usage with other loan areas. - return new LoanProductCommand(null, null, this.fundId, this.transactionProcessingStrategyId, null, null, this.principal, - this.inArrearsTolerance, this.numberOfRepayments, this.repaymentEvery, this.interestRatePerPeriod, - this.repaymentFrequencyType, this.interestRateFrequencyType, this.amortizationType, this.interestType, - this.interestCalculationPeriodType, null); - } - public boolean isClientChanged() { return this.modifiedParameters.contains("clientId"); } @@ -258,4 +252,52 @@ public class LoanApplicationCommand { public Long getLoanOfficerId() { return loanOfficerId; } + + public void validate() { + List dataValidationErrors = new ArrayList(); + + DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan"); + + if (this.groupId != null) { + baseDataValidator.reset().parameter("clientId").value(this.clientId).mustBeBlankWhenParameterProvided("groupId", this.groupId) + .longGreaterThanZero(); + } + if (this.clientId != null) { + baseDataValidator.reset().parameter("groupId").value(this.groupId).mustBeBlankWhenParameterProvided("clientId", this.clientId) + .longGreaterThanZero(); + } + + if (this.submittedOnDate == null) { + ApiParameterError error = ApiParameterError.parameterError("validation.msg.loan.submitted.on.date.cannot.be.blank", + "The parameter submittedOnDate cannot be empty.", "submittedOnDate"); + dataValidationErrors.add(error); + } else { + if (this.submittedOnDate.isAfter(this.expectedDisbursementDate)) { + ApiParameterError error = ApiParameterError.parameterError( + "validation.msg.loan.submitted.on.date.cannot.be.after.expectedDisbursementDate", + "The date of parameter submittedOnDate cannot fall after the date given for expectedDisbursementDate.", + "submittedOnDate", this.submittedOnDate, this.expectedDisbursementDate); + dataValidationErrors.add(error); + } + } + + if (this.transactionProcessingStrategyId == null) { + baseDataValidator.reset().parameter("transactionProcessingStrategyId").value(this.transactionProcessingStrategyId).notNull() + .inMinMaxRange(2, 2); + } + + if (this.charges != null) { + for (LoanChargeCommand loanChargeCommand : this.charges) { + try { + LoanChargeCommandValidator validator = new LoanChargeCommandValidator(loanChargeCommand); + validator.validateForCreate(); + } catch (PlatformApiDataValidationException e) { + dataValidationErrors.addAll(e.getErrors()); + } + } + } + + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", + "Validation errors exist.", dataValidationErrors); } + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/command/LoanApplicationCommandValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/command/LoanApplicationCommandValidator.java deleted file mode 100644 index 96acfa5ef..000000000 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/command/LoanApplicationCommandValidator.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.mifosplatform.portfolio.loanaccount.command; - -import java.util.ArrayList; -import java.util.List; - -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.loanschedule.command.CalculateLoanScheduleCommand; -import org.mifosplatform.portfolio.loanaccount.loanschedule.command.CalculateLoanScheduleCommandValidator; - -public class LoanApplicationCommandValidator { - - private final LoanApplicationCommand command; - - public LoanApplicationCommandValidator(LoanApplicationCommand command) { - this.command = command; - } - - public void validate() { - List dataValidationErrors = new ArrayList(); - -// if (command.getLoanSchedule() == null) { -// ApiParameterError error = ApiParameterError.parameterError("validation.msg.submit.loan.loan.schedule.cannot.be.blank", -// "The parameter loanSchedule cannot be empty.", "loanSchedule"); -// dataValidationErrors.add(error); -// } - DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan"); - - if (command.getGroupId() != null){ - baseDataValidator.reset().parameter("clientId").value(command.getClientId()).mustBeBlankWhenParameterProvided("groupId", command.getGroupId()).longGreaterThanZero(); - } - if (command.getClientId() != null){ - baseDataValidator.reset().parameter("groupId").value(command.getGroupId()).mustBeBlankWhenParameterProvided("clientId", command.getClientId()).longGreaterThanZero(); - } - - if (command.getSubmittedOnDate() == null) { - ApiParameterError error = ApiParameterError.parameterError("validation.msg.loan.submitted.on.date.cannot.be.blank", - "The parameter submittedOnDate cannot be empty.", "submittedOnDate"); - dataValidationErrors.add(error); - } else { - if (command.getSubmittedOnDate().isAfter(command.getExpectedDisbursementDate())) { - ApiParameterError error = ApiParameterError.parameterError("validation.msg.loan.submitted.on.date.cannot.be.after.expectedDisbursementDate", - "The date of parameter submittedOnDate cannot fall after the date given for expectedDisbursementDate.", "submittedOnDate", - command.getSubmittedOnDate(), command.getExpectedDisbursementDate()); - dataValidationErrors.add(error); - } - } - - if (command.getTransactionProcessingStrategyId() == null) { - baseDataValidator.reset().parameter("transactionProcessingStrategyId").value(command.getTransactionProcessingStrategyId()).notNull().inMinMaxRange(2, 2); - } - - try { - // reuse calculate loan schedule validator for now - CalculateLoanScheduleCommand calculateLoanScheduleCommand = this.command.toCalculateLoanScheduleCommand(); - CalculateLoanScheduleCommandValidator validator = new CalculateLoanScheduleCommandValidator(calculateLoanScheduleCommand); - validator.validate(); - } catch (PlatformApiDataValidationException e) { - dataValidationErrors.addAll(e.getErrors()); - } - - if (this.command.getCharges() != null){ - for (LoanChargeCommand loanChargeCommand : this.command.getCharges()){ - try { - LoanChargeCommandValidator validator = new LoanChargeCommandValidator(loanChargeCommand); - validator.validateForCreate(); - } catch (PlatformApiDataValidationException e) { - dataValidationErrors.addAll(e.getErrors()); - } - } - } - - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors); - } - } -} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/command/LoanChargeCommand.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/command/LoanChargeCommand.java index 82525da5a..07f5071b5 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/command/LoanChargeCommand.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/command/LoanChargeCommand.java @@ -21,25 +21,19 @@ public class LoanChargeCommand { private final Integer chargeCalculationType; /** - * Used to capture what parameters were passed in the json api request. - * It does not indicate that these values are modified from their original values when tyring to update. + * Used to capture what parameters were passed in the json api request. It + * does not indicate that these values are modified from their original + * values when tyring to update. */ private final Set requestParameters; - - public static LoanChargeCommand forWaiver(final Long id, final Long loanId) { - final Set parametersPassedInCommand = new HashSet(); - return new LoanChargeCommand(parametersPassedInCommand, id, loanId, null, null, null, null, null); - } - public LoanChargeCommand( - final Set parametersPassedInCommand, - final Long id, - final Long loanId, - final Long chargeId, - final BigDecimal amount, - final Integer chargeTimeType, - final Integer chargeCalculationType, - final LocalDate specifiedDueDate) { + public static LoanChargeCommand forWaiver(final Long id, final Long loanId) { + final Set parametersPassedInCommand = new HashSet(); + return new LoanChargeCommand(parametersPassedInCommand, id, loanId, null, null, null, null, null); + } + + public LoanChargeCommand(final Set parametersPassedInCommand, final Long id, final Long loanId, final Long chargeId, + final BigDecimal amount, final Integer chargeTimeType, final Integer chargeCalculationType, final LocalDate specifiedDueDate) { this.requestParameters = parametersPassedInCommand; this.id = id; this.chargeId = chargeId; @@ -47,7 +41,7 @@ public class LoanChargeCommand { this.amount = amount; this.chargeTimeType = chargeTimeType; this.chargeCalculationType = chargeCalculationType; - this.specifiedDueDate = specifiedDueDate; + this.specifiedDueDate = specifiedDueDate; } public Long getId() { @@ -69,28 +63,28 @@ public class LoanChargeCommand { public Integer getChargeTimeType() { return chargeTimeType; } - - public LocalDate getSpecifiedDueDate() { - return specifiedDueDate; - } - public Integer getChargeCalculationType() { + public LocalDate getSpecifiedDueDate() { + return specifiedDueDate; + } + + public Integer getChargeCalculationType() { return chargeCalculationType; } - public boolean isAmountChanged(){ + public boolean isAmountChanged() { return this.requestParameters.contains("amount"); } - public boolean isChargeTimeTypeChanged(){ + public boolean isChargeTimeTypeChanged() { return this.requestParameters.contains("chargeTimeType"); } - - public boolean isSpecifiedDueDateChanged(){ + + public boolean isSpecifiedDueDateChanged() { return this.requestParameters.contains("specifiedDueDate"); } - public boolean isChargeCalculationTypeChanged(){ + public boolean isChargeCalculationTypeChanged() { return this.requestParameters.contains("chargeCalculationType"); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/command/LoanStateTransitionCommand.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/command/LoanStateTransitionCommand.java index eaca07029..7211580e7 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/command/LoanStateTransitionCommand.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/command/LoanStateTransitionCommand.java @@ -1,31 +1,77 @@ package org.mifosplatform.portfolio.loanaccount.command; +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; /** - * Immutable command for any of the loan state transitions eg. reject, withdrawn by client, approve, disburse + * Immutable command for any of the loan state transitions eg. reject, withdrawn + * by client, approve, disburse */ public class LoanStateTransitionCommand { - private final Long loanId; - private final LocalDate eventDate; - private final String note; + private final LocalDate eventDate; + private final String note; + private final LocalDate approvedOnDate; + private final LocalDate rejectedOnDate; + private final LocalDate withdrawnOnDate; + private final LocalDate disbursedOnDate; - public LoanStateTransitionCommand(final Long loanId, final LocalDate eventDate, final String note) { - this.loanId = loanId; - this.eventDate = eventDate; - this.note = note; - } + public LoanStateTransitionCommand(final LocalDate eventDate, final String note, final LocalDate approvedOnDate, + final LocalDate rejectedOnDate, final LocalDate withdrawnOnDate, final LocalDate disbursedOnDate) { + this.eventDate = eventDate; + this.note = note; + this.approvedOnDate = approvedOnDate; + this.rejectedOnDate = rejectedOnDate; + this.withdrawnOnDate = withdrawnOnDate; + this.disbursedOnDate = disbursedOnDate; + } - public Long getLoanId() { - return loanId; - } + public LocalDate getApprovedOnDate() { + LocalDate dateValue = this.approvedOnDate; + if (this.approvedOnDate == null) { + dateValue = eventDate; + } + return dateValue; + } - public LocalDate getEventDate() { - return eventDate; - } + public LocalDate getRejectedOnDate() { + LocalDate dateValue = this.rejectedOnDate; + if (this.rejectedOnDate == null) { + dateValue = eventDate; + } + return dateValue; + } - public String getNote() { - return note; - } + public LocalDate getWithdrawnOnDate() { + LocalDate dateValue = this.withdrawnOnDate; + if (this.withdrawnOnDate == null) { + dateValue = eventDate; + } + return dateValue; + } + + public LocalDate getDisbursedOnDate() { + LocalDate dateValue = this.disbursedOnDate; + if (this.disbursedOnDate == null) { + dateValue = eventDate; + } + return dateValue; + } + + public void validate() { + List dataValidationErrors = new ArrayList(); + + DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transition"); + + baseDataValidator.reset().parameter("eventDate").value(this.eventDate).notNull(); + baseDataValidator.reset().parameter("note").value(this.note).notExceedingLengthOf(1000); + + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", + "Validation errors exist.", dataValidationErrors); } + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/command/LoanStateTransitionCommandValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/command/LoanStateTransitionCommandValidator.java deleted file mode 100644 index 0b74da7fc..000000000 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/command/LoanStateTransitionCommandValidator.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.mifosplatform.portfolio.loanaccount.command; - -import java.util.ArrayList; -import java.util.List; - -import org.mifosplatform.infrastructure.core.data.ApiParameterError; -import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; -import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; - -public class LoanStateTransitionCommandValidator { - - private final LoanStateTransitionCommand command; - - public LoanStateTransitionCommandValidator(LoanStateTransitionCommand command) { - this.command = command; - } - - public void validate() { - List dataValidationErrors = new ArrayList(); - - DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transition"); - - baseDataValidator.reset().parameter("loanId").value(command.getLoanId()).notNull().integerGreaterThanZero(); - baseDataValidator.reset().parameter("eventDate").value(command.getEventDate()).notNull(); - baseDataValidator.reset().parameter("note").value(command.getNote()).notExceedingLengthOf(1000); - - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors); - } - } - -} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java index 9c27826fe..c8e538cbb 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java @@ -2,9 +2,13 @@ package org.mifosplatform.portfolio.loanaccount.domain; import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Date; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; +import java.util.Map; import java.util.Set; import javax.persistence.CascadeType; @@ -26,7 +30,11 @@ import org.apache.commons.lang.StringUtils; import org.hibernate.annotations.LazyCollection; import org.hibernate.annotations.LazyCollectionOption; import org.joda.time.LocalDate; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; +import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.domain.AbstractAuditableCustom; +import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.organisation.staff.domain.Staff; @@ -34,17 +42,24 @@ import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeAddedExcep import org.mifosplatform.portfolio.client.domain.Client; import org.mifosplatform.portfolio.fund.domain.Fund; import org.mifosplatform.portfolio.group.domain.Group; -import org.mifosplatform.portfolio.loanaccount.command.LoanApplicationCommand; import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand; import org.mifosplatform.portfolio.loanaccount.exception.InvalidLoanStateTransitionException; import org.mifosplatform.portfolio.loanaccount.exception.InvalidLoanTransactionTypeException; import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentException; import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData; import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData; +import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator; +import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.DefaultLoanScheduleGeneratorFactory; +import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanSchedule; +import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator; +import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod; +import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; +import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct; import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy; import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType; +import org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations; import org.mifosplatform.useradministration.domain.AppUser; @Entity @@ -68,7 +83,6 @@ public class Loan extends AbstractAuditableCustom { @JoinColumn(name = "product_id") private LoanProduct loanProduct; - @SuppressWarnings("unused") @ManyToOne @JoinColumn(name = "fund_id", nullable = true) private Fund fund; @@ -87,11 +101,9 @@ public class Loan extends AbstractAuditableCustom { @Embedded private LoanProductRelatedDetail loanRepaymentScheduleDetail; - @SuppressWarnings("unused") @Column(name = "term_frequency", nullable = false) private Integer termFrequency; - @SuppressWarnings("unused") @Column(name = "term_period_frequency_enum", nullable = false) private Integer termPeriodFrequencyType; @@ -178,6 +190,7 @@ public class Loan extends AbstractAuditableCustom { @Transient private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessor = new LoanRepaymentScheduleTransactionProcessorFactory(); + @Deprecated public static Loan createNew(final Fund fund, final Staff loanOfficer, final LoanTransactionProcessingStrategy transactionProcessingStrategy, final LoanProduct loanProduct, final Client client, final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set loanCharges) { @@ -185,6 +198,7 @@ public class Loan extends AbstractAuditableCustom { loanCharges); } + @Deprecated public static Loan createNew(final Fund fund, final Staff loanOfficer, final LoanTransactionProcessingStrategy transactionProcessingStrategy, final LoanProduct loanProduct, final Group group, final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set loanCharges) { @@ -192,13 +206,24 @@ public class Loan extends AbstractAuditableCustom { loanCharges); } + public static Loan newIndividualLoanApplication(final Client client, final LoanProduct loanProduct, final Fund fund, + final Staff officer, final LoanTransactionProcessingStrategy transactionProcessingStrategy, final LoanSchedule loanSchedule, + final Set loanCharges) { + final LoanStatus status = null; + LoanProductRelatedDetail loanRepaymentScheduleDetail = loanSchedule.loanProductRelatedDetail(); + return new Loan(client, fund, officer, transactionProcessingStrategy, loanProduct, loanRepaymentScheduleDetail, status, loanCharges); + } + + public static Loan newGroupLoanApplication(final Group group, final LoanProduct loanProduct, final Fund fund, final Staff officer, + final LoanTransactionProcessingStrategy transactionProcessingStrategy, final LoanSchedule loanSchedule, + final Set loanCharges) { + final LoanStatus status = null; + LoanProductRelatedDetail loanRepaymentScheduleDetail = loanSchedule.loanProductRelatedDetail(); + return new Loan(group, fund, officer, transactionProcessingStrategy, loanProduct, loanRepaymentScheduleDetail, status, loanCharges); + } + protected Loan() { - this.client = null; - this.group = null; - this.loanProduct = null; - this.loanRepaymentScheduleDetail = null; - this.charges = null; - this.loanOfficerHistory = null; + // } private Loan(final Client client, Fund fund, Staff loanOfficer, final LoanTransactionProcessingStrategy transactionProcessingStrategy, @@ -472,79 +497,31 @@ public class Loan extends AbstractAuditableCustom { return this.loanRepaymentScheduleDetail; } - public void modifyLoanApplication(final LoanApplicationCommand command, final Client client, final LoanProduct loanProduct, - final Fund fund, final LoanTransactionProcessingStrategy strategy, final LoanScheduleData modifiedLoanSchedule, - final Set charges, final LoanLifecycleStateMachine loanLifecycleStateMachine) { + public void updateClient(final Client client) { + this.client = client; + } - // FIXME - KW - whilst the methods are named isXXXChanged, this really - // only is asking has the parameter for that field been passed in - // request, - // an additional check to see if the value passed is different to the - // current value may bee need for all these like it is for - // expectedDisbursementDate - if (command.isClientChanged()) { - this.client = client; - } - if (command.isProductChanged()) { - this.loanProduct = loanProduct; - } - if (command.isFundChanged()) { - this.fund = fund; - } - if (command.isTransactionStrategyChanged()) { - this.transactionProcessingStrategy = strategy; - } + public void updateLoanProduct(final LoanProduct loanProduct) { + this.loanProduct = loanProduct; + } - if (command.isTermFrequencyChanged()) { - this.termFrequency = command.getLoanTermFrequency(); - } + public void updateFund(final Fund fund) { + this.fund = fund; + } - if (command.isTermFrequencyTypeChanged()) { - this.termPeriodFrequencyType = PeriodFrequencyType.fromInt(command.getLoanTermFrequencyType()).getValue(); - } + public void updateTransactionProcessingStrategy(final LoanTransactionProcessingStrategy strategy) { + this.transactionProcessingStrategy = strategy; + } - if (command.isSubmittedOnDateChanged()) { - this.submittedOnDate = command.getSubmittedOnDate().toDate(); - } + public void updateLoanCharges(final Set loanCharges) { + this.charges.clear(); + this.charges.addAll(associateChargesWithThisLoan(loanCharges)); + this.totalChargesDueAtDisbursement = deriveSumTotalOfChargesDueAtDisbursement(); + } - if (command.isExpectedDisbursementDatePassed()) { - if (dateHasChanged(getExpectedDisbursedOnLocalDate(), command.getExpectedDisbursementDate())) { - this.expectedDisbursedOnDate = command.getExpectedDisbursementDate().toDate(); - removeFirstDisbursementTransaction(); - disburse(command.getExpectedDisbursementDate(), loanLifecycleStateMachine, false); - } - } - - if (command.isRepaymentsStartingFromDateChanged()) { - if (command.getRepaymentsStartingFromDate() != null) { - this.expectedFirstRepaymentOnDate = command.getRepaymentsStartingFromDate().toDate(); - } else { - this.expectedFirstRepaymentOnDate = null; - } - } - - if (command.isInterestChargedFromDateChanged()) { - if (command.getInterestChargedFromDate() != null) { - this.interestChargedFromDate = command.getInterestChargedFromDate().toDate(); - } else { - this.interestChargedFromDate = null; - } - } - - if (command.isChargesChanged()) { - this.charges.clear(); - this.charges.addAll(associateChargesWithThisLoan(charges)); - this.totalChargesDueAtDisbursement = deriveSumTotalOfChargesDueAtDisbursement(); - } - - // FIXME - kw - break modify loan application at present. - // this.loanRepaymentScheduleDetail.update(command.toLoanProductCommand()); - - // FIXME - rewrite over loan schedule by default for now but worth - // putting in check to see if required - // i.e. only a client change wouldn't require it, only if one of - // parameters related to loan schedule calculation is changed. + public void updateLoanSchedule(final LoanScheduleData modifiedLoanSchedule) { this.repaymentScheduleInstallments.clear(); + for (LoanSchedulePeriodData scheduledLoanInstallment : modifiedLoanSchedule.getPeriods()) { if (scheduledLoanInstallment.isRepaymentPeriod()) { @@ -567,17 +544,147 @@ public class Loan extends AbstractAuditableCustom { this.repaymentScheduleInstallments, this.charges); } - private boolean dateHasChanged(final LocalDate originalLocalDate, final LocalDate providedLocalDate) { + public void updateLoanScheduleDependentDerivedFields() { + this.expectedMaturityDate = determineExpectedMaturityDate().toDate(); + } - boolean dateHasChanged = false; + public Map modifyLoanApplication(final JsonCommand command, final LoanChargeCommand[] loanChargeCommands, + final AprCalculator aprCalculator) { - if (originalLocalDate != null) { - dateHasChanged = !originalLocalDate.equals(providedLocalDate); - } else { - dateHasChanged = (providedLocalDate != null); + final Map actualChanges = this.loanRepaymentScheduleDetail.updateLoanApplicationAttributes(command, aprCalculator); + if (!actualChanges.isEmpty()) { + boolean recalculateLoanSchedule = !(actualChanges.size() == 1 && actualChanges.containsKey("inArrearsTolerance")); + actualChanges.put("recalculateLoanSchedule", recalculateLoanSchedule); } - return dateHasChanged; + final String dateFormatAsInput = command.dateFormat(); + final String localeAsInput = command.locale(); + + final String externalIdParamName = "externalId"; + if (command.isChangeInStringParameterNamed(externalIdParamName, this.externalId)) { + final String newValue = command.stringValueOfParameterNamed(externalIdParamName); + actualChanges.put(externalIdParamName, newValue); + this.externalId = StringUtils.defaultIfEmpty(newValue, null); + } + + final String clientIdParamName = "clientId"; + if (command.isChangeInLongParameterNamed(clientIdParamName, this.client.getId())) { + final Long newValue = command.longValueOfParameterNamed(clientIdParamName); + actualChanges.put(clientIdParamName, newValue); + } + + final String productIdParamName = "productId"; + if (command.isChangeInLongParameterNamed(productIdParamName, this.loanProduct.getId())) { + final Long newValue = command.longValueOfParameterNamed(productIdParamName); + actualChanges.put(productIdParamName, newValue); + actualChanges.put("recalculateLoanSchedule", true); + } + + Long existingFundId = null; + if (this.fund != null) { + existingFundId = this.fund.getId(); + } + final String fundIdParamName = "fundId"; + if (command.isChangeInLongParameterNamed(fundIdParamName, existingFundId)) { + final Long newValue = command.longValueOfParameterNamed(fundIdParamName); + actualChanges.put(fundIdParamName, newValue); + } + + final String strategyIdParamName = "transactionProcessingStrategyId"; + if (command.isChangeInLongParameterNamed(strategyIdParamName, this.transactionProcessingStrategy.getId())) { + final Long newValue = command.longValueOfParameterNamed(strategyIdParamName); + actualChanges.put(strategyIdParamName, newValue); + } + + final String chargesParamName = "charges"; + if (command.parameterExists(chargesParamName)) { + LoanChargeCommand[] existingLoanChargeData = getLoanCharges(); + if (differenceExistsBetween(existingLoanChargeData, loanChargeCommands)) { + actualChanges.put(chargesParamName, loanChargeCommands); + actualChanges.put("recalculateLoanSchedule", true); + } + } + + final String submittedOnDateParamName = "submittedOnDate"; + if (command.isChangeInLocalDateParameterNamed(submittedOnDateParamName, getSubmittedOnDate())) { + final String valueAsInput = command.stringValueOfParameterNamed(submittedOnDateParamName); + actualChanges.put(submittedOnDateParamName, valueAsInput); + actualChanges.put("dateFormat", dateFormatAsInput); + actualChanges.put("locale", localeAsInput); + + final LocalDate newValue = command.localDateValueOfParameterNamed(submittedOnDateParamName); + this.submittedOnDate = newValue.toDate(); + } + + final String expectedDisbursementDateParamName = "expectedDisbursementDate"; + if (command.isChangeInLocalDateParameterNamed(expectedDisbursementDateParamName, getExpectedDisbursedOnLocalDate())) { + final String valueAsInput = command.stringValueOfParameterNamed(expectedDisbursementDateParamName); + actualChanges.put(expectedDisbursementDateParamName, valueAsInput); + actualChanges.put("dateFormat", dateFormatAsInput); + actualChanges.put("locale", localeAsInput); + actualChanges.put("recalculateLoanSchedule", true); + + final LocalDate newValue = command.localDateValueOfParameterNamed(expectedDisbursementDateParamName); + this.expectedDisbursedOnDate = newValue.toDate(); + removeFirstDisbursementTransaction(); + disburse(getExpectedDisbursedOnLocalDate()); + } + + final String repaymentsStartingFromDateParamName = "repaymentsStartingFromDate"; + if (command.isChangeInLocalDateParameterNamed(repaymentsStartingFromDateParamName, getExpectedFirstRepaymentOnDate())) { + final String valueAsInput = command.stringValueOfParameterNamed(repaymentsStartingFromDateParamName); + actualChanges.put(repaymentsStartingFromDateParamName, valueAsInput); + actualChanges.put("dateFormat", dateFormatAsInput); + actualChanges.put("locale", localeAsInput); + actualChanges.put("recalculateLoanSchedule", true); + + final LocalDate newValue = command.localDateValueOfParameterNamed(repaymentsStartingFromDateParamName); + if (newValue != null) { + this.expectedFirstRepaymentOnDate = newValue.toDate(); + } else { + this.expectedFirstRepaymentOnDate = null; + } + } + + final String interestChargedFromDateParamName = "interestChargedFromDate"; + if (command.isChangeInLocalDateParameterNamed(interestChargedFromDateParamName, getInterestChargedFromDate())) { + final String valueAsInput = command.stringValueOfParameterNamed(interestChargedFromDateParamName); + actualChanges.put(interestChargedFromDateParamName, valueAsInput); + actualChanges.put("dateFormat", dateFormatAsInput); + actualChanges.put("locale", localeAsInput); + actualChanges.put("recalculateLoanSchedule", true); + + final LocalDate newValue = command.localDateValueOfParameterNamed(interestChargedFromDateParamName); + if (newValue != null) { + this.interestChargedFromDate = newValue.toDate(); + } else { + this.interestChargedFromDate = null; + } + } + + return actualChanges; + } + + private boolean differenceExistsBetween(final LoanChargeCommand[] existingLoanChargeData, final LoanChargeCommand[] newLoanChargeData) { + Arrays.sort(existingLoanChargeData); + Arrays.sort(newLoanChargeData); + return !Arrays.equals(existingLoanChargeData, newLoanChargeData); + } + + private LoanChargeCommand[] getLoanCharges() { + + LoanChargeCommand[] existingLoanCharges = null; + + if (this.charges != null && !this.charges.isEmpty()) { + List loanChargesList = new ArrayList(); + for (LoanCharge loanCharge : this.charges) { + loanChargesList.add(loanCharge.toCommand()); + } + + existingLoanCharges = loanChargesList.toArray(new LoanChargeCommand[loanChargesList.size()]); + } + + return existingLoanCharges; } private void removeFirstDisbursementTransaction() { @@ -589,9 +696,11 @@ public class Loan extends AbstractAuditableCustom { } } - public void submitApplication(final Integer loanTermFrequency, final PeriodFrequencyType loanTermFrequencyType, - final LocalDate submittedOn, final LocalDate expectedDisbursementDate, final LocalDate repaymentsStartingFromDate, - final LocalDate interestChargedFromDate, final LoanLifecycleStateMachine lifecycleStateMachine) { + public void loanApplicationSubmittal(final LoanSchedule loanSchedule, final LoanLifecycleStateMachine lifecycleStateMachine, + final LocalDate submittedOn, final String externalId) { + + final LoanScheduleData loanScheduleData = loanSchedule.generate(); + updateLoanSchedule(loanScheduleData); LoanStatus from = null; if (this.loanStatus != null) { @@ -601,22 +710,22 @@ public class Loan extends AbstractAuditableCustom { LoanStatus statusEnum = lifecycleStateMachine.transition(LoanEvent.LOAN_CREATED, from); this.loanStatus = statusEnum.getValue(); - this.termFrequency = loanTermFrequency; - this.termPeriodFrequencyType = loanTermFrequencyType.getValue(); + this.termFrequency = loanSchedule.getLoanTermFrequency(); + this.termPeriodFrequencyType = loanSchedule.getLoanTermPeriodFrequencyType().getValue(); this.submittedOnDate = submittedOn.toDate(); this.expectedMaturityDate = determineExpectedMaturityDate().toDate(); - if (expectedDisbursementDate != null) { - // can be null during bulk upload of loans - this.expectedDisbursedOnDate = expectedDisbursementDate.toDate(); + + if (loanSchedule.getDisbursementDate() != null) { + this.expectedDisbursedOnDate = loanSchedule.getDisbursementDate().toDate(); } - if (repaymentsStartingFromDate != null) { - this.expectedFirstRepaymentOnDate = repaymentsStartingFromDate.toDate(); + if (loanSchedule.getRepaymentStartFromDate() != null) { + this.expectedFirstRepaymentOnDate = loanSchedule.getRepaymentStartFromDate().toDate(); } - if (interestChargedFromDate != null) { - this.interestChargedFromDate = interestChargedFromDate.toDate(); + if (loanSchedule.getInterestChargedFromDate() != null) { + this.interestChargedFromDate = loanSchedule.getInterestChargedFromDate().toDate(); } if (submittedOn.isAfter(new LocalDate())) { @@ -637,6 +746,8 @@ public class Loan extends AbstractAuditableCustom { validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate()); } } + + this.externalId = externalId; } private LocalDate determineExpectedMaturityDate() { @@ -644,13 +755,30 @@ public class Loan extends AbstractAuditableCustom { return this.repaymentScheduleInstallments.get(numberOfInstallments - 1).getDueDate(); } - public void reject(final LocalDate rejectedOn, final LoanLifecycleStateMachine loanLifecycleStateMachine) { + public Map loanApplicationRejection(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine) { - LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_REJECTED, LoanStatus.fromInt(this.loanStatus)); - this.loanStatus = statusEnum.getValue(); + final Map actualChanges = new LinkedHashMap(); - this.rejectedOnDate = rejectedOn.toDateTimeAtCurrentTime().toDate(); - this.closedOnDate = rejectedOn.toDateTimeAtCurrentTime().toDate(); + final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_REJECTED, LoanStatus.fromInt(this.loanStatus)); + if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) { + this.loanStatus = statusEnum.getValue(); + actualChanges.put("status", LoanEnumerations.status(this.loanStatus)); + } + + LocalDate rejectedOn = command.localDateValueOfParameterNamed("rejectedOnDate"); + if (rejectedOn == null) { + rejectedOn = command.localDateValueOfParameterNamed("eventDate"); + } + + final Locale locale = new Locale(command.locale()); + final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); + + this.rejectedOnDate = rejectedOn.toDate(); + this.closedOnDate = rejectedOn.toDate(); + actualChanges.put("locale", command.locale()); + actualChanges.put("dateFormat", command.dateFormat()); + actualChanges.put("rejectedOnDate", rejectedOn.toString(fmt)); + actualChanges.put("closedOnDate", rejectedOn.toString(fmt)); if (rejectedOn.isBefore(getSubmittedOnDate())) { final String errorMessage = "The date on which a loan is rejected cannot be before its submittal date: " @@ -662,34 +790,73 @@ public class Loan extends AbstractAuditableCustom { final String errorMessage = "The date on which a loan is rejected cannot be in the future."; throw new InvalidLoanStateTransitionException("reject", "cannot.be.a.future.date", errorMessage, rejectedOn); } + + return actualChanges; } - public void withdraw(final LocalDate withdrawnOn, final LoanLifecycleStateMachine loanLifecycleStateMachine) { + public Map loanApplicationWithdrawnByApplicant(final JsonCommand command, + final LoanLifecycleStateMachine loanLifecycleStateMachine) { - LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_WITHDRAWN, LoanStatus.fromInt(this.loanStatus)); - this.loanStatus = statusEnum.getValue(); + final Map actualChanges = new LinkedHashMap(); - this.withdrawnOnDate = withdrawnOn.toDateTimeAtCurrentTime().toDate(); - this.closedOnDate = withdrawnOn.toDateTimeAtCurrentTime().toDate(); + final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_WITHDRAWN, LoanStatus.fromInt(this.loanStatus)); + if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) { + this.loanStatus = statusEnum.getValue(); + actualChanges.put("status", LoanEnumerations.status(this.loanStatus)); + } + + LocalDate withdrawnOn = command.localDateValueOfParameterNamed("withdrawnOnDate"); + if (withdrawnOn == null) { + withdrawnOn = command.localDateValueOfParameterNamed("eventDate"); + } + + final Locale locale = new Locale(command.locale()); + final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); + + this.withdrawnOnDate = withdrawnOn.toDate(); + this.closedOnDate = withdrawnOn.toDate(); + actualChanges.put("locale", command.locale()); + actualChanges.put("dateFormat", command.dateFormat()); + actualChanges.put("withdrawnOnDate", withdrawnOn.toString(fmt)); + actualChanges.put("closedOnDate", withdrawnOn.toString(fmt)); if (withdrawnOn.isBefore(getSubmittedOnDate())) { final String errorMessage = "The date on which a loan is withdrawn cannot be before its submittal date: " + getSubmittedOnDate().toString(); - throw new InvalidLoanStateTransitionException("reject", "cannot.be.before.submittal.date", errorMessage, withdrawnOn, + throw new InvalidLoanStateTransitionException("reject", "cannot.be.before.submittal.date", errorMessage, command, getSubmittedOnDate()); } + if (withdrawnOn.isAfter(new LocalDate())) { final String errorMessage = "The date on which a loan is withdrawn cannot be in the future."; - throw new InvalidLoanStateTransitionException("reject", "cannot.be.a.future.date", errorMessage, withdrawnOn); + throw new InvalidLoanStateTransitionException("reject", "cannot.be.a.future.date", errorMessage, command); } + + return actualChanges; } - public void approve(final LocalDate approvedOn, final LoanLifecycleStateMachine loanLifecycleStateMachine) { + public Map loanApplicationApproval(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine) { + + final Map actualChanges = new LinkedHashMap(); final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_APPROVED, LoanStatus.fromInt(this.loanStatus)); - this.loanStatus = statusEnum.getValue(); + if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) { + this.loanStatus = statusEnum.getValue(); + actualChanges.put("status", LoanEnumerations.status(this.loanStatus)); + } - this.approvedOnDate = approvedOn.toDateTimeAtCurrentTime().toDate(); + LocalDate approvedOn = command.localDateValueOfParameterNamed("approvedOnDate"); + if (approvedOn == null) { + approvedOn = command.localDateValueOfParameterNamed("eventDate"); + } + + final Locale locale = new Locale(command.locale()); + final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); + + this.approvedOnDate = approvedOn.toDate(); + actualChanges.put("locale", command.locale()); + actualChanges.put("dateFormat", command.dateFormat()); + actualChanges.put("approvedOnDate", approvedOn.toString(fmt)); final LocalDate submittalDate = new LocalDate(this.submittedOnDate); if (approvedOn.isBefore(submittalDate)) { @@ -708,47 +875,112 @@ public class Loan extends AbstractAuditableCustom { this.loanofficer, approvedOn); this.loanOfficerHistory.add(loanOfficerAssignmentHistory); } + + return actualChanges; } - public void undoApproval(LoanLifecycleStateMachine loanLifecycleStateMachine) { + public Map undoApproval(final LoanLifecycleStateMachine loanLifecycleStateMachine) { - final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_APPROVAL_UNDO, - LoanStatus.fromInt(this.loanStatus)); - this.loanStatus = statusEnum.getValue(); + final Map actualChanges = new LinkedHashMap(); + + final LoanStatus currentStatus = LoanStatus.fromInt(this.loanStatus); + final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_APPROVAL_UNDO, currentStatus); + if (!statusEnum.hasStateOf(currentStatus)) { + this.loanStatus = statusEnum.getValue(); + actualChanges.put("status", LoanEnumerations.status(this.loanStatus)); + } this.approvedOnDate = null; + actualChanges.put("approvedOnDate", ""); this.loanOfficerHistory.clear(); + + return actualChanges; } - public void disburseWithModifiedRepaymentSchedule(final LocalDate disbursedOn, - final List modifiedLoanRepaymentSchedule, LoanLifecycleStateMachine loanLifecycleStateMachine) { - this.repaymentScheduleInstallments.clear(); - for (LoanRepaymentScheduleInstallment modifiedInstallment : modifiedLoanRepaymentSchedule) { - modifiedInstallment.updateLoan(this); - this.repaymentScheduleInstallments.add(modifiedInstallment); - } - disburse(disbursedOn, loanLifecycleStateMachine, true); + private void disburse(final LocalDate expectedDisbursedOnLocalDate) { + this.disbursedOnDate = expectedDisbursedOnLocalDate.toDate(); + this.expectedMaturityDate = determineExpectedMaturityDate().toDate(); + + handleDisbursementTransaction(expectedDisbursedOnLocalDate); } - public void disburse(final LocalDate disbursedOn, final LoanLifecycleStateMachine loanLifecycleStateMachine, - final boolean statusTransition) { + public Map disburse(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine, + final ApplicationCurrency currency) { - if (statusTransition) { - // ensure loan is in pre-disbursal state - updateLoanToPreDisbursalState(); + final Map actualChanges = new LinkedHashMap(); - LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED, LoanStatus.fromInt(this.loanStatus)); + updateLoanToPreDisbursalState(); + + final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED, LoanStatus.fromInt(this.loanStatus)); + if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) { this.loanStatus = statusEnum.getValue(); + actualChanges.put("status", LoanEnumerations.status(this.loanStatus)); } - this.disbursedOnDate = disbursedOn.toDateTimeAtCurrentTime().toDate(); - this.expectedMaturityDate = this.repaymentScheduleInstallments.get(this.repaymentScheduleInstallments.size() - 1).getDueDate() - .toDateMidnight().toDate(); + LocalDate disbursedOn = command.localDateValueOfParameterNamed("disbursedOnDate"); + if (disbursedOn == null) { + disbursedOn = command.localDateValueOfParameterNamed("eventDate"); + } - // add disbursement transaction to track outgoing money from mfi to - // client - LoanTransaction loanTransaction = LoanTransaction.disbursement(this.loanRepaymentScheduleDetail.getPrincipal(), disbursedOn); + final Locale locale = new Locale(command.locale()); + final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); + + this.disbursedOnDate = disbursedOn.toDate(); + this.expectedMaturityDate = determineExpectedMaturityDate().toDate(); + + actualChanges.put("locale", command.locale()); + actualChanges.put("dateFormat", command.dateFormat()); + actualChanges.put("disbursedOnDate", disbursedOn.toString(fmt)); + actualChanges.put("expectedMaturityDate", LocalDate.fromDateFields(this.expectedDisbursedOnDate).toString(fmt)); + + handleDisbursementTransaction(disbursedOn); + + if (isRepaymentScheduleRegenerationRequiredForDisbursement(disbursedOn)) { + regenerateRepaymentSchedule(currency); + } + + return actualChanges; + } + + /* + * Ability to regenerate the repayment schedule based on the loans current + * details/state. + */ + private void regenerateRepaymentSchedule(final ApplicationCurrency applicationCurrency) { + + final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod(); + final LoanScheduleGenerator loanScheduleGenerator = new DefaultLoanScheduleGeneratorFactory().create(interestMethod); + + final BigDecimal principal = this.loanRepaymentScheduleDetail.getPrincipal().getAmount(); + final BigDecimal inArrearsTolerance = this.loanRepaymentScheduleDetail.getInArrearsTolerance().getAmount(); + final BigDecimal interestRatePerPeriod = this.loanRepaymentScheduleDetail.getNominalInterestRatePerPeriod(); + final PeriodFrequencyType interestRatePeriodFrequencyType = this.loanRepaymentScheduleDetail.getInterestPeriodFrequencyType(); + final BigDecimal defaultAnnualNominalInterestRate = this.loanRepaymentScheduleDetail.getAnnualNominalInterestRate(); + + final InterestCalculationPeriodMethod interestCalculationPeriodMethod = this.loanRepaymentScheduleDetail + .getInterestCalculationPeriodMethod(); + final Integer repaymentEvery = this.loanRepaymentScheduleDetail.getRepayEvery(); + final PeriodFrequencyType repaymentPeriodFrequencyType = this.loanRepaymentScheduleDetail.getRepaymentPeriodFrequencyType(); + final Integer numberOfRepayments = this.loanRepaymentScheduleDetail.getNumberOfRepayments(); + final AmortizationMethod amortizationMethod = this.loanRepaymentScheduleDetail.getAmortizationMethod(); + final Integer loanTermFrequency = this.termFrequency; + final PeriodFrequencyType loanTermPeriodFrequencyType = PeriodFrequencyType.fromInt(this.termPeriodFrequencyType); + + final LoanSchedule loanSchedule = new LoanSchedule(loanScheduleGenerator, applicationCurrency, principal, interestRatePerPeriod, + interestRatePeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, + repaymentEvery, repaymentPeriodFrequencyType, numberOfRepayments, amortizationMethod, loanTermFrequency, + loanTermPeriodFrequencyType, this.charges, this.getDisbursementDate(), this.getExpectedFirstRepaymentOnDate(), + this.getInterestChargedFromDate(), inArrearsTolerance); + + final LoanScheduleData generatedData = loanSchedule.generate(); + + updateLoanSchedule(generatedData); + } + + private void handleDisbursementTransaction(LocalDate disbursedOn) { + // track disbursement transaction + final LoanTransaction loanTransaction = LoanTransaction.disbursement(this.loanRepaymentScheduleDetail.getPrincipal(), disbursedOn); loanTransaction.updateLoan(this); this.loanTransactions.add(loanTransaction); @@ -769,7 +1001,7 @@ public class Loan extends AbstractAuditableCustom { } } - if (disbursedOn.isBefore(getApprovedOnDate())) { + if (getApprovedOnDate() != null && disbursedOn.isBefore(getApprovedOnDate())) { final String errorMessage = "The date on which a loan is disbursed cannot be before its approval date: " + getApprovedOnDate().toString(); throw new InvalidLoanStateTransitionException("disbursal", "cannot.be.before.approval.date", errorMessage, disbursedOn, @@ -790,13 +1022,23 @@ public class Loan extends AbstractAuditableCustom { } } - public void undoDisbursal(final LoanLifecycleStateMachine loanLifecycleStateMachine) { + public Map undoDisbursal(final LoanLifecycleStateMachine loanLifecycleStateMachine) { - final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSAL_UNDO, - LoanStatus.fromInt(this.loanStatus)); - this.loanStatus = statusEnum.getValue(); + final Map actualChanges = new LinkedHashMap(); + + final LoanStatus currentStatus = LoanStatus.fromInt(this.loanStatus); + final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSAL_UNDO, currentStatus); + if (!statusEnum.hasStateOf(currentStatus)) { + this.loanStatus = statusEnum.getValue(); + actualChanges.put("status", LoanEnumerations.status(this.loanStatus)); + } + + this.approvedOnDate = null; + actualChanges.put("disbursedOnDate", ""); updateLoanToPreDisbursalState(); + + return actualChanges; } private void updateLoanToPreDisbursalState() { @@ -1489,14 +1731,7 @@ public class Loan extends AbstractAuditableCustom { } public boolean isRepaymentScheduleRegenerationRequiredForDisbursement(final LocalDate actualDisbursementDate) { - - boolean regenerationRequired = false; - - if (isActualDisbursedOnDateEarlierOrLaterThanExpected(actualDisbursementDate)) { - regenerationRequired = true; - } - - return regenerationRequired; + return isActualDisbursedOnDateEarlierOrLaterThanExpected(actualDisbursementDate); } private Money getTotalPaidInRepayments() { @@ -1655,5 +1890,4 @@ public class Loan extends AbstractAuditableCustom { public Client getClient() { return client; } - } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java index 143a1cd25..66542c2a9 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java @@ -86,6 +86,14 @@ public class LoanCharge extends AbstractPersistable { return new LoanCharge(chargeDefinition, command, loanPrincipal); } + /* + * 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(chargeDefinition, loanPrincipal, amount, chargeTime, chargeCalculation, specifiedDueDate); + } + public static LoanCharge createNew(final Charge chargeDefinition) { return new LoanCharge(null, chargeDefinition); } @@ -94,6 +102,45 @@ public class LoanCharge extends AbstractPersistable { // } + public LoanCharge(final Charge chargeDefinition, final BigDecimal loanPrincipal, final BigDecimal amount, + final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate specifiedDueDate) { + this.loan = null; + this.charge = chargeDefinition; + this.penaltyCharge = chargeDefinition.isPenalty(); + + this.chargeTime = chargeDefinition.getChargeTime(); + if (chargeTime != null) { + this.chargeTime = chargeTime.getValue(); + } + + 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, + chargeDefinition.getId(), chargeDefinition.getName()); + } + + this.dueForCollectionAsOfDate = specifiedDueDate.toDate(); + } else { + this.dueForCollectionAsOfDate = null; + } + + this.chargeCalculation = chargeDefinition.getChargeCalculation(); + if (chargeCalculation != null) { + this.chargeCalculation = chargeCalculation.getValue(); + } + + BigDecimal chargeAmount = chargeDefinition.getAmount(); + if (amount != null) { + chargeAmount = amount; + } + + populateDerivedFields(loanPrincipal, chargeAmount); + this.paid = determineIfFullyPaid(); + } + + @Deprecated public LoanCharge(final Charge chargeDefinition, final LoanChargeCommand command, final BigDecimal loanPrincipal) { this.loan = null; this.charge = chargeDefinition; @@ -133,6 +180,7 @@ public class LoanCharge extends AbstractPersistable { this.paid = determineIfFullyPaid(); } + @Deprecated private LoanCharge(final Loan loan, final Charge chargeDefinition, final LoanChargeCommand command) { this.loan = loan; this.charge = chargeDefinition; @@ -270,6 +318,40 @@ public class LoanCharge extends AbstractPersistable { public void update(final Loan loan) { this.loan = loan; } + + public void update(final BigDecimal amount, final LocalDate specifiedDueDate, final BigDecimal loanPrincipal) { + if (specifiedDueDate != null) { + this.dueForCollectionAsOfDate = specifiedDueDate.toDate(); + } + + if (amount != null) { + switch (ChargeCalculationType.fromInt(this.chargeCalculation)) { + case INVALID: + break; + case FLAT: + this.amount = amount; + break; + case PERCENT_OF_AMOUNT: + this.percentage = amount; + this.amountPercentageAppliedTo = loanPrincipal; + this.amount = percentageOf(this.amountPercentageAppliedTo, this.percentage); + this.amountOutstanding = calculateOutstanding(); + break; + case PERCENT_OF_AMOUNT_AND_INTEREST: + this.percentage = amount; + this.amount = null; + this.amountPercentageAppliedTo = null; + this.amountOutstanding = null; + break; + case PERCENT_OF_INTEREST: + this.percentage = amount; + this.amount = null; + this.amountPercentageAppliedTo = null; + this.amountOutstanding = null; + break; + } + } + } public void update(final LoanChargeCommand command, final BigDecimal loanPrincipal) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanStatus.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanStatus.java index ba677de53..f684934c8 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanStatus.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanStatus.java @@ -4,106 +4,106 @@ package org.mifosplatform.portfolio.loanaccount.domain; * Enum representation of loan status states. */ public enum LoanStatus { - - INVALID(0, "loanStatusType.invalid"), // - SUBMITED_AND_PENDING_APPROVAL(100, "loanStatusType.submitted.and.pending.approval"), // - APPROVED(200, "loanStatusType.approved"), // - ACTIVE(300, "loanStatusType.active"), // - WITHDRAWN_BY_CLIENT(400, "loanStatusType.withdrawn.by.client"), // - REJECTED(500, "loanStatusType.rejected"), // - CLOSED_OBLIGATIONS_MET(600, "loanStatusType.closed.obligations.met"), // - CLOSED_WRITTEN_OFF(601, "loanStatusType.closed.written.off"), // - CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT(602, "loanStatusType.closed.reschedule.outstanding.amount"), // - OVERPAID(700, "loanStatusType.overpaid"); + + INVALID(0, "loanStatusType.invalid"), // + SUBMITED_AND_PENDING_APPROVAL(100, "loanStatusType.submitted.and.pending.approval"), // + APPROVED(200, "loanStatusType.approved"), // + ACTIVE(300, "loanStatusType.active"), // + WITHDRAWN_BY_CLIENT(400, "loanStatusType.withdrawn.by.client"), // + REJECTED(500, "loanStatusType.rejected"), // + CLOSED_OBLIGATIONS_MET(600, "loanStatusType.closed.obligations.met"), // + CLOSED_WRITTEN_OFF(601, "loanStatusType.closed.written.off"), // + CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT(602, "loanStatusType.closed.reschedule.outstanding.amount"), // + OVERPAID(700, "loanStatusType.overpaid"); private final Integer value; - private final String code; - - public static LoanStatus fromInt(final Integer statusValue) { + private final String code; - LoanStatus enumeration = LoanStatus.INVALID; - switch (statusValue) { - case 100: - enumeration = LoanStatus.SUBMITED_AND_PENDING_APPROVAL; - break; - case 200: - enumeration = LoanStatus.APPROVED; - break; - case 300: - enumeration = LoanStatus.ACTIVE; - break; - case 400: - enumeration = LoanStatus.WITHDRAWN_BY_CLIENT; - break; - case 500: - enumeration = LoanStatus.REJECTED; - break; - case 600: - enumeration = LoanStatus.CLOSED_OBLIGATIONS_MET; - break; - case 601: - enumeration = LoanStatus.CLOSED_WRITTEN_OFF; - break; - case 602: - enumeration = LoanStatus.CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT; - break; - case 700: - enumeration = LoanStatus.OVERPAID; - break; - } - return enumeration; + public static LoanStatus fromInt(final Integer statusValue) { + + LoanStatus enumeration = LoanStatus.INVALID; + switch (statusValue) { + case 100: + enumeration = LoanStatus.SUBMITED_AND_PENDING_APPROVAL; + break; + case 200: + enumeration = LoanStatus.APPROVED; + break; + case 300: + enumeration = LoanStatus.ACTIVE; + break; + case 400: + enumeration = LoanStatus.WITHDRAWN_BY_CLIENT; + break; + case 500: + enumeration = LoanStatus.REJECTED; + break; + case 600: + enumeration = LoanStatus.CLOSED_OBLIGATIONS_MET; + break; + case 601: + enumeration = LoanStatus.CLOSED_WRITTEN_OFF; + break; + case 602: + enumeration = LoanStatus.CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT; + break; + case 700: + enumeration = LoanStatus.OVERPAID; + break; + } + return enumeration; } private LoanStatus(final Integer value, final String code) { this.value = value; - this.code = code; + this.code = code; } - + public boolean hasStateOf(final LoanStatus state) { - return this.value.equals(state.getValue()); - } + return this.value.equals(state.getValue()); + } public Integer getValue() { return this.value; } - + public String getCode() { - return code; - } + return code; + } - public boolean isSubmittedAndPendingApproval() { - return this.value.equals(LoanStatus.SUBMITED_AND_PENDING_APPROVAL.getValue()); - } - - public boolean isApproved() { - return this.value.equals(LoanStatus.APPROVED.getValue()); - } - - public boolean isActive() { - return this.value.equals(LoanStatus.ACTIVE.getValue()); - } + public boolean isSubmittedAndPendingApproval() { + return this.value.equals(LoanStatus.SUBMITED_AND_PENDING_APPROVAL.getValue()); + } - public boolean isClosed() { - return isClosedObligationsMet() || isClosedWrittenOff() || isClosedWithOutsandingAmountMarkedForReschedule(); - } - - public boolean isClosedObligationsMet() { - return this.value.equals(LoanStatus.CLOSED_OBLIGATIONS_MET.getValue()); - } - - public boolean isClosedWrittenOff() { - return this.value.equals(LoanStatus.CLOSED_WRITTEN_OFF.getValue()); - } + public boolean isApproved() { + return this.value.equals(LoanStatus.APPROVED.getValue()); + } - public boolean isClosedWithOutsandingAmountMarkedForReschedule() { - return this.value.equals(LoanStatus.CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT.getValue()); - } - - public boolean isWithdrawnByClient() { - return this.value.equals(LoanStatus.WITHDRAWN_BY_CLIENT.getValue()); - } + public boolean isActive() { + return this.value.equals(LoanStatus.ACTIVE.getValue()); + } - public boolean isRejected() { - return this.value.equals(LoanStatus.REJECTED.getValue()); - } + public boolean isClosed() { + return isClosedObligationsMet() || isClosedWrittenOff() || isClosedWithOutsandingAmountMarkedForReschedule(); + } + + public boolean isClosedObligationsMet() { + return this.value.equals(LoanStatus.CLOSED_OBLIGATIONS_MET.getValue()); + } + + public boolean isClosedWrittenOff() { + return this.value.equals(LoanStatus.CLOSED_WRITTEN_OFF.getValue()); + } + + public boolean isClosedWithOutsandingAmountMarkedForReschedule() { + return this.value.equals(LoanStatus.CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT.getValue()); + } + + public boolean isWithdrawnByClient() { + return this.value.equals(LoanStatus.WITHDRAWN_BY_CLIENT.getValue()); + } + + public boolean isRejected() { + return this.value.equals(LoanStatus.REJECTED.getValue()); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/ApplicantWithdrawsFromLoanApplicationCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/ApplicantWithdrawsFromLoanApplicationCommandHandler.java new file mode 100644 index 000000000..37076f270 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/ApplicantWithdrawsFromLoanApplicationCommandHandler.java @@ -0,0 +1,27 @@ +package org.mifosplatform.portfolio.loanaccount.handler; + +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.EntityIdentifier; +import org.mifosplatform.portfolio.loanaccount.service.LoanWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class ApplicantWithdrawsFromLoanApplicationCommandHandler implements NewCommandSourceHandler { + + private final LoanWritePlatformService writePlatformService; + + @Autowired + public ApplicantWithdrawsFromLoanApplicationCommandHandler(final LoanWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public EntityIdentifier processCommand(final JsonCommand command) { + + return this.writePlatformService.applicantWithdrawsFromLoanApplication(command.resourceId(), command); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/ApproveLoanApplicationCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/ApproveLoanApplicationCommandHandler.java new file mode 100644 index 000000000..bdd2f307b --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/ApproveLoanApplicationCommandHandler.java @@ -0,0 +1,27 @@ +package org.mifosplatform.portfolio.loanaccount.handler; + +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.EntityIdentifier; +import org.mifosplatform.portfolio.loanaccount.service.LoanWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class ApproveLoanApplicationCommandHandler implements NewCommandSourceHandler { + + private final LoanWritePlatformService writePlatformService; + + @Autowired + public ApproveLoanApplicationCommandHandler(final LoanWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public EntityIdentifier processCommand(final JsonCommand command) { + + return this.writePlatformService.approveLoanApplication(command.resourceId(), command); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/CreateLoanApplicationCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/CreateLoanApplicationCommandHandler.java new file mode 100644 index 000000000..7798eab7d --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/CreateLoanApplicationCommandHandler.java @@ -0,0 +1,27 @@ +package org.mifosplatform.portfolio.loanaccount.handler; + +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.EntityIdentifier; +import org.mifosplatform.portfolio.loanaccount.service.LoanWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class CreateLoanApplicationCommandHandler implements NewCommandSourceHandler { + + private final LoanWritePlatformService writePlatformService; + + @Autowired + public CreateLoanApplicationCommandHandler(final LoanWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public EntityIdentifier processCommand(final JsonCommand command) { + + return this.writePlatformService.submitLoanApplication(command); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/DeleteLoanApplicationCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/DeleteLoanApplicationCommandHandler.java new file mode 100644 index 000000000..8d529064c --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/DeleteLoanApplicationCommandHandler.java @@ -0,0 +1,27 @@ +package org.mifosplatform.portfolio.loanaccount.handler; + +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.EntityIdentifier; +import org.mifosplatform.portfolio.loanaccount.service.LoanWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class DeleteLoanApplicationCommandHandler implements NewCommandSourceHandler { + + private final LoanWritePlatformService writePlatformService; + + @Autowired + public DeleteLoanApplicationCommandHandler(final LoanWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public EntityIdentifier processCommand(final JsonCommand command) { + + return this.writePlatformService.deleteLoan(command.resourceId()); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/DisburseLoanCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/DisburseLoanCommandHandler.java new file mode 100644 index 000000000..0ea2e4797 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/DisburseLoanCommandHandler.java @@ -0,0 +1,27 @@ +package org.mifosplatform.portfolio.loanaccount.handler; + +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.EntityIdentifier; +import org.mifosplatform.portfolio.loanaccount.service.LoanWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class DisburseLoanCommandHandler implements NewCommandSourceHandler { + + private final LoanWritePlatformService writePlatformService; + + @Autowired + public DisburseLoanCommandHandler(final LoanWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public EntityIdentifier processCommand(final JsonCommand command) { + + return this.writePlatformService.disburseLoan(command.resourceId(), command); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/RejectLoanApplicationCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/RejectLoanApplicationCommandHandler.java new file mode 100644 index 000000000..379806b1e --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/RejectLoanApplicationCommandHandler.java @@ -0,0 +1,27 @@ +package org.mifosplatform.portfolio.loanaccount.handler; + +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.EntityIdentifier; +import org.mifosplatform.portfolio.loanaccount.service.LoanWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class RejectLoanApplicationCommandHandler implements NewCommandSourceHandler { + + private final LoanWritePlatformService writePlatformService; + + @Autowired + public RejectLoanApplicationCommandHandler(final LoanWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public EntityIdentifier processCommand(final JsonCommand command) { + + return this.writePlatformService.rejectLoanApplication(command.resourceId(), command); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/UndoApprovalLoanApplicationCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/UndoApprovalLoanApplicationCommandHandler.java new file mode 100644 index 000000000..096481a6b --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/UndoApprovalLoanApplicationCommandHandler.java @@ -0,0 +1,27 @@ +package org.mifosplatform.portfolio.loanaccount.handler; + +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.EntityIdentifier; +import org.mifosplatform.portfolio.loanaccount.service.LoanWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class UndoApprovalLoanApplicationCommandHandler implements NewCommandSourceHandler { + + private final LoanWritePlatformService writePlatformService; + + @Autowired + public UndoApprovalLoanApplicationCommandHandler(final LoanWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public EntityIdentifier processCommand(final JsonCommand command) { + + return this.writePlatformService.undoLoanApplicationApproval(command.resourceId(), command); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/UndoDisbursalLoanCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/UndoDisbursalLoanCommandHandler.java new file mode 100644 index 000000000..7463bd8c1 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/UndoDisbursalLoanCommandHandler.java @@ -0,0 +1,27 @@ +package org.mifosplatform.portfolio.loanaccount.handler; + +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.EntityIdentifier; +import org.mifosplatform.portfolio.loanaccount.service.LoanWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class UndoDisbursalLoanCommandHandler implements NewCommandSourceHandler { + + private final LoanWritePlatformService writePlatformService; + + @Autowired + public UndoDisbursalLoanCommandHandler(final LoanWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public EntityIdentifier processCommand(final JsonCommand command) { + + return this.writePlatformService.undoLoanDisbursal(command.resourceId(), command); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/UpdateLoanApplicationCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/UpdateLoanApplicationCommandHandler.java new file mode 100644 index 000000000..8bb78f822 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/UpdateLoanApplicationCommandHandler.java @@ -0,0 +1,27 @@ +package org.mifosplatform.portfolio.loanaccount.handler; + +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.EntityIdentifier; +import org.mifosplatform.portfolio.loanaccount.service.LoanWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class UpdateLoanApplicationCommandHandler implements NewCommandSourceHandler { + + private final LoanWritePlatformService writePlatformService; + + @Autowired + public UpdateLoanApplicationCommandHandler(final LoanWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public EntityIdentifier processCommand(final JsonCommand command) { + + return this.writePlatformService.modifyLoanApplication(command.resourceId(), command); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/command/CalculateLoanScheduleCommand.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/command/CalculateLoanScheduleCommand.java deleted file mode 100644 index 525258d1e..000000000 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/command/CalculateLoanScheduleCommand.java +++ /dev/null @@ -1,125 +0,0 @@ -package org.mifosplatform.portfolio.loanaccount.loanschedule.command; - -import java.math.BigDecimal; - -import org.joda.time.LocalDate; -import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand; - -/** - * Immutable command used when auto-calculating loan schedules based on loan terms. - */ -public class CalculateLoanScheduleCommand { - - private final Long productId; - private final BigDecimal principal; - - private final Integer loanTermFrequency; - private final Integer loanTermFrequencyType; - - private final Integer repaymentEvery; - private final Integer repaymentFrequencyType; - private final Integer numberOfRepayments; - - private final BigDecimal interestRatePerPeriod; - private final Integer interestRateFrequencyType; - private final Integer amortizationType; - private final Integer interestType; - private final Integer interestCalculationPeriodType; - - private final LocalDate expectedDisbursementDate; - private final LocalDate repaymentsStartingFromDate; - private final LocalDate interestChargedFromDate; - private final LoanChargeCommand[] charges; - - public CalculateLoanScheduleCommand( - final Long productId, - final BigDecimal principal, - final BigDecimal interestRatePerPeriod, Integer interestRateFrequencyMethod, final Integer interestMethod, final Integer interestCalculationPeriodMethod, - final Integer repaymentEvery, final Integer repaymentFrequency, final Integer numberOfRepayments, Integer amortizationMethod, - final Integer loanTermFrequency, final Integer loanTermFrequencyType, - final LocalDate expectedDisbursementDate, - final LocalDate repaymentsStartingFromDate, final LocalDate interestCalculatedFromDate, final LoanChargeCommand[] charges) { - - this.productId = productId; - this.principal = principal; - this.interestRatePerPeriod = interestRatePerPeriod; - this.interestRateFrequencyType = interestRateFrequencyMethod; - this.interestType = interestMethod; - this.interestCalculationPeriodType = interestCalculationPeriodMethod; - this.repaymentEvery = repaymentEvery; - this.repaymentFrequencyType = repaymentFrequency; - this.numberOfRepayments = numberOfRepayments; - this.amortizationType = amortizationMethod; - this.loanTermFrequency = loanTermFrequency; - this.loanTermFrequencyType = loanTermFrequencyType; - - this.expectedDisbursementDate = expectedDisbursementDate; - this.repaymentsStartingFromDate = repaymentsStartingFromDate; - this.interestChargedFromDate= interestCalculatedFromDate; - this.charges = charges; - } - - public Long getProductId() { - return productId; - } - - public BigDecimal getPrincipal() { - return principal; - } - - public Integer getLoanTermFrequency() { - return loanTermFrequency; - } - - public Integer getLoanTermFrequencyType() { - return loanTermFrequencyType; - } - - public Integer getRepaymentEvery() { - return repaymentEvery; - } - - public Integer getRepaymentFrequencyType() { - return repaymentFrequencyType; - } - - public Integer getNumberOfRepayments() { - return numberOfRepayments; - } - - public BigDecimal getInterestRatePerPeriod() { - return interestRatePerPeriod; - } - - public Integer getInterestRateFrequencyType() { - return interestRateFrequencyType; - } - - public Integer getAmortizationType() { - return amortizationType; - } - - public Integer getInterestType() { - return interestType; - } - - public Integer getInterestCalculationPeriodType() { - return interestCalculationPeriodType; - } - - public LocalDate getExpectedDisbursementDate() { - return expectedDisbursementDate; - } - - public LocalDate getRepaymentsStartingFromDate() { - return repaymentsStartingFromDate; - } - - public LocalDate getInterestChargedFromDate() { - return interestChargedFromDate; - } - - public LoanChargeCommand[] getCharges() { - return charges; - } -} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/command/CalculateLoanScheduleCommandValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/command/CalculateLoanScheduleCommandValidator.java deleted file mode 100644 index 63c1d7f7d..000000000 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/command/CalculateLoanScheduleCommandValidator.java +++ /dev/null @@ -1,101 +0,0 @@ -package org.mifosplatform.portfolio.loanaccount.loanschedule.command; - -import java.util.ArrayList; -import java.util.List; - -import org.mifosplatform.infrastructure.core.data.ApiParameterError; -import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; -import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; - -public class CalculateLoanScheduleCommandValidator { - - private final CalculateLoanScheduleCommand command; - - public CalculateLoanScheduleCommandValidator(CalculateLoanScheduleCommand command) { - this.command = command; - } - - public void validate() { - - List dataValidationErrors = new ArrayList(); - - DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan"); - - baseDataValidator.reset().parameter("principal").value(command.getPrincipal()).notNull().positiveAmount(); - - baseDataValidator.reset().parameter("loanTermFrequency").value(command.getLoanTermFrequency()).notNull().integerGreaterThanZero(); - baseDataValidator.reset().parameter("loanTermFrequencyType").value(command.getLoanTermFrequencyType()).notNull().inMinMaxRange(0, 4); - - baseDataValidator.reset().parameter("numberOfRepayments").value(command.getNumberOfRepayments()).notNull().integerGreaterThanZero(); - baseDataValidator.reset().parameter("repaymentEvery").value(command.getRepaymentEvery()).notNull().integerGreaterThanZero(); - baseDataValidator.reset().parameter("repaymentFrequencyType").value(command.getRepaymentFrequencyType()).notNull().inMinMaxRange(0, 3); - - // FIXME - this constraint doesnt really need to be here. should be possible to express loan term as say 12 months whilst also saying - // - that the repayment structure is 6 repayments every bi-monthly. - if (command.getLoanTermFrequencyType() != null && !command.getLoanTermFrequencyType().equals(command.getRepaymentFrequencyType())) { - ApiParameterError error = ApiParameterError.parameterError("validation.msg.loan.loanTermFrequencyType.not.the.same.as.repaymentFrequencyType", - "The parameters loanTermFrequencyType and repaymentFrequencyType must be the same.", "loanTermFrequencyType", - command.getLoanTermFrequencyType(), command.getRepaymentFrequencyType()); - dataValidationErrors.add(error); - } else { - if (command.getLoanTermFrequency() != null && command.getRepaymentEvery() != null && command.getNumberOfRepayments() != null) { - int suggestsedLoanTerm = command.getRepaymentEvery() * command.getNumberOfRepayments(); - if (command.getLoanTermFrequency().intValue() < suggestsedLoanTerm) { - ApiParameterError error = ApiParameterError.parameterError("validation.msg.loan.loanTermFrequency.less.than.repayment.structure.suggests", - "The parameter loanTermFrequency is less than the suggest loan term as indicated by numberOfRepayments and repaymentEvery.", "loanTermFrequency", - command.getLoanTermFrequency(), command.getNumberOfRepayments(), command.getRepaymentEvery()); - dataValidationErrors.add(error); - } - } - } - - baseDataValidator.reset().parameter("interestRatePerPeriod").value(command.getInterestRatePerPeriod()).notNull(); - baseDataValidator.reset().parameter("interestRateFrequencyType").value(command.getInterestRateFrequencyType()).notNull().inMinMaxRange(0, 3); - baseDataValidator.reset().parameter("amortizationType").value(command.getAmortizationType()).notNull().inMinMaxRange(0, 1); - baseDataValidator.reset().parameter("interestType").value(command.getInterestType()).notNull().inMinMaxRange(0, 1); - baseDataValidator.reset().parameter("interestCalculationPeriodType").value(command.getInterestCalculationPeriodType()).notNull().inMinMaxRange(0, 1); - - baseDataValidator.reset().parameter("expectedDisbursementDate").value(command.getExpectedDisbursementDate()).notNull(); - - if (command.getExpectedDisbursementDate() != null) { - if (command.getRepaymentsStartingFromDate() != null - && command.getExpectedDisbursementDate().isAfter(command.getRepaymentsStartingFromDate())) { - ApiParameterError error = ApiParameterError.parameterError("validation.msg.loan.expectedDisbursementDate.cannot.be.after.first.repayment.date", - "The parameter expectedDisbursementDate has a date which falls after the given first repayment date.", "expectedDisbursementDate", - command.getExpectedDisbursementDate(), command.getRepaymentsStartingFromDate()); - dataValidationErrors.add(error); - } - } - - if (command.getRepaymentsStartingFromDate() != null && command.getInterestChargedFromDate() == null) { - - ApiParameterError error = ApiParameterError.parameterError("validation.msg.loan.interestCalculatedFromDate.must.be.entered.when.using.repayments.startfrom.field", - "The parameter interestCalculatedFromDate cannot be empty when first repayment date is provided.", "interestChargedFromDate", command.getRepaymentsStartingFromDate()); - dataValidationErrors.add(error); - } else if (command.getRepaymentsStartingFromDate() == null && command.getInterestChargedFromDate() != null) { - - // validate interestCalculatedFromDate is after or on repaymentsStartingFromDate - if (command.getExpectedDisbursementDate().isAfter(command.getInterestChargedFromDate())) { - ApiParameterError error = ApiParameterError.parameterError("validation.msg.loan.interestChargedFromDate.cannot.be.before.disbursement.date", - "The parameter interestCalculatedFromDate cannot be before the date given for expected disbursement.", "interestChargedFromDate", command.getInterestChargedFromDate(), command.getExpectedDisbursementDate()); - dataValidationErrors.add(error); - } - } - -// if (command.getCharges() != null) { -// for (LoanChargeCommand loanCharge : command.getCharges()) { -// try { -// LoanChargeCommandValidator validator = new LoanChargeCommandValidator(loanCharge); -// validator.validateForCreate(); -// } catch (PlatformApiDataValidationException e) { -// dataValidationErrors.addAll(e.getErrors()); -// } -// } -// } - - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors); - } - - } -} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/data/LoanScheduleData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/data/LoanScheduleData.java index 265b8b4a3..07067f716 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/data/LoanScheduleData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/data/LoanScheduleData.java @@ -5,301 +5,284 @@ import java.util.Collection; import org.mifosplatform.organisation.monetary.data.CurrencyData; - /** * Immutable data object to represent aspects of a loan schedule such as: * *
    - *
  • summary information - the totals for each part of schedule monitored
  • - *
  • repayment schedule - the principal due, outstanding balance and cost of loan items such as interest and charges
  • + *
  • summary information - the totals for each part of schedule monitored
  • + *
  • repayment schedule - the principal due, outstanding balance and cost of + * loan items such as interest and charges
  • *
*/ public class LoanScheduleData { - @SuppressWarnings("unused") - private final Integer loanTermInDays; - - /** - * The currency associated with all monetary values in loan schedule. - */ - @SuppressWarnings("unused") - private final CurrencyData currency; - - /** - * The cumulative total of all money (principal) disbursed to the loan applicant. - */ - @SuppressWarnings("unused") - private final BigDecimal cumulativePrincipalDisbursed; - - /** - * The cumulative total of all principalDue for each period of loan schedule. (originalPrincipal) - */ - @SuppressWarnings("unused") - private final BigDecimal cumulativePrincipalDue; - @SuppressWarnings("unused") - private final BigDecimal cumulativePrincipalPaid; - @SuppressWarnings("unused") - private final BigDecimal cumulativePrincipalWrittenOff; - @SuppressWarnings("unused") - private final BigDecimal cumulativePrincipalOutstanding; - - /** - * The cumulative total of interest expected on any principal disbursed. (originalInterest) - */ - @SuppressWarnings("unused") - private final BigDecimal cumulativeInterestExpected; - @SuppressWarnings("unused") - private final BigDecimal cumulativeInterestPaid; - @SuppressWarnings("unused") - private final BigDecimal cumulativeInterestWaived; - @SuppressWarnings("unused") - private final BigDecimal cumulativeInterestWrittenOff; - @SuppressWarnings("unused") - private final BigDecimal cumulativeInterestOutstanding; - - /** - * The cumulative total of all fee charges applied on the loan to date. - */ - @SuppressWarnings("unused") - private final BigDecimal cumulativeFeeChargesToDate; - @SuppressWarnings("unused") - private final BigDecimal cumulativeFeeChargesPaid; - @SuppressWarnings("unused") - private final BigDecimal cumulativeFeeChargesWaived; - @SuppressWarnings("unused") - private final BigDecimal cumulativeFeeChargesWrittenOff; - @SuppressWarnings("unused") - private final BigDecimal cumulativeFeeChargesOutstanding; - - @SuppressWarnings("unused") - private final BigDecimal cumulativePenaltyChargesToDate; - @SuppressWarnings("unused") - private final BigDecimal cumulativePenaltyChargesPaid; - @SuppressWarnings("unused") - private final BigDecimal cumulativePenaltyChargesWaived; - @SuppressWarnings("unused") - private final BigDecimal cumulativePenaltyChargesWrittenOff; - @SuppressWarnings("unused") - private final BigDecimal cumulativePenaltyChargesOutstanding; - - /** - * The cumulative total of all costs on the loan. - * Costs tracked are: - *
    - *
  • Interest
  • - *
  • Charges (fees and penalties)
  • - *
- */ - @SuppressWarnings("unused") - private final BigDecimal totalCostOfLoan; - - /** - * The sum of principalDisbursed and totalCostOfLoan. (originalTotal) - */ - @SuppressWarnings("unused") - private final BigDecimal totalExpectedRepayment; - - /** - * The cumulative sum of all repayments to date. (totalPaid) - */ - @SuppressWarnings("unused") - private final BigDecimal totalPaidToDate; - - /** - * The cumulative sum of all waivers to date. - */ - @SuppressWarnings("unused") - private final BigDecimal totalWaivedToDate; - - /** - * The cumulative sum of all waivers to date. - */ - @SuppressWarnings("unused") - private final BigDecimal totalWrittenOffToDate; - - /** - * The cumulative sum of all principal, interest & charges outstanding. - */ - private final BigDecimal totalOutstanding; - - /** - * The amount by which this loan is is arrears. - * - * Different to outstanding as arrears is based on whether a portion of the total outstanding is late and above any tolerance setting. - */ - @SuppressWarnings("unused") - private final BigDecimal totalInArrears; - - /** - * periods is collection of data objects containing specific - * information to each period of the loan schedule including disbursement and - * repayment information. - */ - private final Collection periods; + @SuppressWarnings("unused") + private final Integer loanTermInDays; - public LoanScheduleData() { - this.cumulativePrincipalDisbursed = null; - this.cumulativePrincipalPaid = null; - this.cumulativePrincipalWrittenOff = null; - this.cumulativePrincipalDue = null; - this.cumulativePrincipalOutstanding = null; - - this.cumulativeInterestExpected = null; - this.cumulativeInterestPaid = null; - this.cumulativeInterestWaived = null; - this.cumulativeInterestWrittenOff = null; - this.cumulativeInterestOutstanding = null; - - this.cumulativeFeeChargesToDate = null; - this.cumulativeFeeChargesPaid = null; - this.cumulativeFeeChargesWaived = null; - this.cumulativeFeeChargesWrittenOff = null; - this.cumulativeFeeChargesOutstanding = null; - - this.cumulativePenaltyChargesToDate = null; - this.cumulativePenaltyChargesPaid = null; - this.cumulativePenaltyChargesWaived = null; - this.cumulativePenaltyChargesWrittenOff = null; - this.cumulativePenaltyChargesOutstanding = null; - - this.totalCostOfLoan = null; - - this.totalExpectedRepayment = null; - this.totalPaidToDate = null; - this.totalWaivedToDate = null; - this.totalWrittenOffToDate = null; - this.totalOutstanding = null; - this.totalInArrears = null; - - this.currency = null; - this.periods = null; - this.loanTermInDays = null; - } + /** + * The currency associated with all monetary values in loan schedule. + */ + @SuppressWarnings("unused") + private final CurrencyData currency; - public LoanScheduleData( - final CurrencyData currency, - final Collection periods, - final Integer loanTermInDays, - final BigDecimal cumulativePrincipalDisbursed, - final BigDecimal cumulativePrincipalDue, - final BigDecimal cumulativePrincipalOutstanding, - final BigDecimal cumulativeInterestExpected, - final BigDecimal cumulativeFeeChargesToDate, - final BigDecimal totalExpectedRepayment) { - this.currency = currency; - this.periods = periods; - this.loanTermInDays = loanTermInDays; - - this.cumulativePrincipalDisbursed = cumulativePrincipalDisbursed; - this.cumulativePrincipalDue = cumulativePrincipalDue; - this.cumulativePrincipalPaid = null; - this.cumulativePrincipalWrittenOff = null; - this.cumulativePrincipalOutstanding = cumulativePrincipalOutstanding; - - this.cumulativeInterestExpected = cumulativeInterestExpected; - this.cumulativeInterestPaid = null; - this.cumulativeInterestWaived = null; - this.cumulativeInterestWrittenOff = null; - this.cumulativeInterestOutstanding = null; - - this.cumulativeFeeChargesToDate = cumulativeFeeChargesToDate; - this.cumulativeFeeChargesPaid = null; - this.cumulativeFeeChargesWaived = null; - this.cumulativeFeeChargesWrittenOff = null; - this.cumulativeFeeChargesOutstanding = null; - - this.cumulativePenaltyChargesToDate = null; - this.cumulativePenaltyChargesPaid = null; - this.cumulativePenaltyChargesWaived = null; - this.cumulativePenaltyChargesWrittenOff = null; - this.cumulativePenaltyChargesOutstanding = null; - - if (cumulativeFeeChargesToDate != null) { - this.totalCostOfLoan = cumulativeInterestExpected.add(cumulativeFeeChargesToDate); - } else if (cumulativeInterestExpected != null) { - this.totalCostOfLoan = cumulativeInterestExpected; - } else { - this.totalCostOfLoan = null; - } - - this.totalExpectedRepayment = totalExpectedRepayment; - this.totalPaidToDate = BigDecimal.ZERO; - this.totalWaivedToDate = BigDecimal.ZERO; - this.totalWrittenOffToDate = BigDecimal.ZERO; - this.totalOutstanding = totalExpectedRepayment; - this.totalInArrears = BigDecimal.ZERO; - } + /** + * The cumulative total of all money (principal) disbursed to the loan + * applicant. + */ + @SuppressWarnings("unused") + private final BigDecimal cumulativePrincipalDisbursed; - /* - * Used when fully populating - */ - public LoanScheduleData(final CurrencyData currency, final Collection periods, - final Integer loanTermInDays, - final BigDecimal cumulativePrincipalDisbursed, - final BigDecimal cumulativePrincipalDue, - final BigDecimal cumulativePrincipalPaid, - final BigDecimal cumulativePrincipalWrittenOff, - final BigDecimal cumulativePrincipalOutstanding, - final BigDecimal cumulativeInterestExpected, - final BigDecimal cumulativeInterestPaid, - final BigDecimal cumulativeInterestWaived, - final BigDecimal cumulativeInterestWrittenOff, - final BigDecimal cumulativeInterestOutstanding, - final BigDecimal cumulativeFeeChargesToDate, - final BigDecimal cumulativeFeeChargesPaid, - final BigDecimal cumulativeFeeChargesWaived, - final BigDecimal cumulativeFeeChargesWrittenOff, - final BigDecimal cumulativeFeeChargesOutstanding, - final BigDecimal cumulativePenaltyChargesToDate, - final BigDecimal cumulativePenaltyChargesPaid, - final BigDecimal cumulativePenaltyChargesWaived, - final BigDecimal cumulativePenaltyChargesWrittenOff, - final BigDecimal cumulativePenaltyChargesOutstanding, - final BigDecimal totalCostOfLoan, - final BigDecimal totalExpectedRepayment, - final BigDecimal totalPaidToDate, - final BigDecimal totalWaivedToDate, - final BigDecimal totalWrittenOffToDate, - final BigDecimal totalOutstanding, - final BigDecimal totalInArrears) { - this.currency = currency; - this.periods = periods; - this.loanTermInDays = loanTermInDays; - - this.cumulativePrincipalDisbursed = cumulativePrincipalDisbursed; - this.cumulativePrincipalDue = cumulativePrincipalDue; - this.cumulativePrincipalPaid = cumulativePrincipalPaid; - this.cumulativePrincipalWrittenOff = cumulativePrincipalWrittenOff; - this.cumulativePrincipalOutstanding = cumulativePrincipalOutstanding; - this.cumulativeInterestExpected = cumulativeInterestExpected; - this.cumulativeInterestPaid = cumulativeInterestPaid; - this.cumulativeInterestWrittenOff = cumulativeInterestWrittenOff; - this.cumulativeInterestWaived = cumulativeInterestWaived; - this.cumulativeInterestOutstanding = cumulativeInterestOutstanding; - this.cumulativeFeeChargesToDate = cumulativeFeeChargesToDate; - this.cumulativeFeeChargesPaid = cumulativeFeeChargesPaid; - this.cumulativeFeeChargesWaived = cumulativeFeeChargesWaived; - this.cumulativeFeeChargesWrittenOff = cumulativeFeeChargesWrittenOff; - this.cumulativeFeeChargesOutstanding = cumulativeFeeChargesOutstanding; - this.cumulativePenaltyChargesToDate = cumulativePenaltyChargesToDate; - this.cumulativePenaltyChargesPaid = cumulativePenaltyChargesPaid; - this.cumulativePenaltyChargesWaived = cumulativePenaltyChargesWaived; - this.cumulativePenaltyChargesWrittenOff = cumulativePenaltyChargesWrittenOff; - this.cumulativePenaltyChargesOutstanding = cumulativePenaltyChargesOutstanding; - this.totalCostOfLoan = totalCostOfLoan; - this.totalExpectedRepayment = totalExpectedRepayment; - this.totalPaidToDate = totalPaidToDate; - this.totalWaivedToDate = totalWaivedToDate; - this.totalWrittenOffToDate = totalWrittenOffToDate; - this.totalOutstanding = totalOutstanding; - this.totalInArrears = totalInArrears; - } + /** + * The cumulative total of all principalDue for each period of + * loan schedule. (originalPrincipal) + */ + @SuppressWarnings("unused") + private final BigDecimal cumulativePrincipalDue; + @SuppressWarnings("unused") + private final BigDecimal cumulativePrincipalPaid; + @SuppressWarnings("unused") + private final BigDecimal cumulativePrincipalWrittenOff; + @SuppressWarnings("unused") + private final BigDecimal cumulativePrincipalOutstanding; - public Collection getPeriods() { - return periods; - } + /** + * The cumulative total of interest expected on any principal disbursed. + * (originalInterest) + */ + @SuppressWarnings("unused") + private final BigDecimal cumulativeInterestExpected; + @SuppressWarnings("unused") + private final BigDecimal cumulativeInterestPaid; + @SuppressWarnings("unused") + private final BigDecimal cumulativeInterestWaived; + @SuppressWarnings("unused") + private final BigDecimal cumulativeInterestWrittenOff; + @SuppressWarnings("unused") + private final BigDecimal cumulativeInterestOutstanding; - public BigDecimal totalOutstanding() { - return this.totalOutstanding; - } + /** + * The cumulative total of all fee charges applied on the loan to date. + */ + @SuppressWarnings("unused") + private final BigDecimal cumulativeFeeChargesToDate; + @SuppressWarnings("unused") + private final BigDecimal cumulativeFeeChargesPaid; + @SuppressWarnings("unused") + private final BigDecimal cumulativeFeeChargesWaived; + @SuppressWarnings("unused") + private final BigDecimal cumulativeFeeChargesWrittenOff; + @SuppressWarnings("unused") + private final BigDecimal cumulativeFeeChargesOutstanding; + + @SuppressWarnings("unused") + private final BigDecimal cumulativePenaltyChargesToDate; + @SuppressWarnings("unused") + private final BigDecimal cumulativePenaltyChargesPaid; + @SuppressWarnings("unused") + private final BigDecimal cumulativePenaltyChargesWaived; + @SuppressWarnings("unused") + private final BigDecimal cumulativePenaltyChargesWrittenOff; + @SuppressWarnings("unused") + private final BigDecimal cumulativePenaltyChargesOutstanding; + + /** + * The cumulative total of all costs on the loan. Costs tracked are: + *
    + *
  • Interest
  • + *
  • Charges (fees and penalties)
  • + *
+ */ + @SuppressWarnings("unused") + private final BigDecimal totalCostOfLoan; + + /** + * The sum of principalDisbursed and + * totalCostOfLoan. (originalTotal) + */ + @SuppressWarnings("unused") + private final BigDecimal totalExpectedRepayment; + + /** + * The cumulative sum of all repayments to date. (totalPaid) + */ + @SuppressWarnings("unused") + private final BigDecimal totalPaidToDate; + + /** + * The cumulative sum of all waivers to date. + */ + @SuppressWarnings("unused") + private final BigDecimal totalWaivedToDate; + + /** + * The cumulative sum of all waivers to date. + */ + @SuppressWarnings("unused") + private final BigDecimal totalWrittenOffToDate; + + /** + * The cumulative sum of all principal, interest & charges outstanding. + */ + private final BigDecimal totalOutstanding; + + /** + * The amount by which this loan is is arrears. + * + * Different to outstanding as arrears is based on whether a portion of the + * total outstanding is late and above any tolerance setting. + */ + @SuppressWarnings("unused") + private final BigDecimal totalInArrears; + + /** + * periods is collection of data objects containing specific + * information to each period of the loan schedule including disbursement + * and repayment information. + */ + private final Collection periods; + + public LoanScheduleData() { + this.cumulativePrincipalDisbursed = null; + this.cumulativePrincipalPaid = null; + this.cumulativePrincipalWrittenOff = null; + this.cumulativePrincipalDue = null; + this.cumulativePrincipalOutstanding = null; + + this.cumulativeInterestExpected = null; + this.cumulativeInterestPaid = null; + this.cumulativeInterestWaived = null; + this.cumulativeInterestWrittenOff = null; + this.cumulativeInterestOutstanding = null; + + this.cumulativeFeeChargesToDate = null; + this.cumulativeFeeChargesPaid = null; + this.cumulativeFeeChargesWaived = null; + this.cumulativeFeeChargesWrittenOff = null; + this.cumulativeFeeChargesOutstanding = null; + + this.cumulativePenaltyChargesToDate = null; + this.cumulativePenaltyChargesPaid = null; + this.cumulativePenaltyChargesWaived = null; + this.cumulativePenaltyChargesWrittenOff = null; + this.cumulativePenaltyChargesOutstanding = null; + + this.totalCostOfLoan = null; + + this.totalExpectedRepayment = null; + this.totalPaidToDate = null; + this.totalWaivedToDate = null; + this.totalWrittenOffToDate = null; + this.totalOutstanding = null; + this.totalInArrears = null; + + this.currency = null; + this.periods = null; + this.loanTermInDays = null; + } + + public LoanScheduleData(final CurrencyData currency, final Collection periods, final Integer loanTermInDays, + final BigDecimal cumulativePrincipalDisbursed, final BigDecimal cumulativePrincipalDue, + final BigDecimal cumulativePrincipalOutstanding, final BigDecimal cumulativeInterestExpected, + final BigDecimal cumulativeFeeChargesToDate, final BigDecimal totalExpectedRepayment) { + this.currency = currency; + this.periods = periods; + this.loanTermInDays = loanTermInDays; + + this.cumulativePrincipalDisbursed = cumulativePrincipalDisbursed; + this.cumulativePrincipalDue = cumulativePrincipalDue; + this.cumulativePrincipalPaid = null; + this.cumulativePrincipalWrittenOff = null; + this.cumulativePrincipalOutstanding = cumulativePrincipalOutstanding; + + this.cumulativeInterestExpected = cumulativeInterestExpected; + this.cumulativeInterestPaid = null; + this.cumulativeInterestWaived = null; + this.cumulativeInterestWrittenOff = null; + this.cumulativeInterestOutstanding = null; + + this.cumulativeFeeChargesToDate = cumulativeFeeChargesToDate; + this.cumulativeFeeChargesPaid = null; + this.cumulativeFeeChargesWaived = null; + this.cumulativeFeeChargesWrittenOff = null; + this.cumulativeFeeChargesOutstanding = null; + + this.cumulativePenaltyChargesToDate = null; + this.cumulativePenaltyChargesPaid = null; + this.cumulativePenaltyChargesWaived = null; + this.cumulativePenaltyChargesWrittenOff = null; + this.cumulativePenaltyChargesOutstanding = null; + + if (cumulativeFeeChargesToDate != null) { + this.totalCostOfLoan = cumulativeInterestExpected.add(cumulativeFeeChargesToDate); + } else if (cumulativeInterestExpected != null) { + this.totalCostOfLoan = cumulativeInterestExpected; + } else { + this.totalCostOfLoan = null; + } + + this.totalExpectedRepayment = totalExpectedRepayment; + this.totalPaidToDate = BigDecimal.ZERO; + this.totalWaivedToDate = BigDecimal.ZERO; + this.totalWrittenOffToDate = BigDecimal.ZERO; + this.totalOutstanding = totalExpectedRepayment; + this.totalInArrears = BigDecimal.ZERO; + } + + /* + * Used when fully populating + */ + public LoanScheduleData(final CurrencyData currency, final Collection periods, final Integer loanTermInDays, + final BigDecimal cumulativePrincipalDisbursed, final BigDecimal cumulativePrincipalDue, + final BigDecimal cumulativePrincipalPaid, final BigDecimal cumulativePrincipalWrittenOff, + final BigDecimal cumulativePrincipalOutstanding, final BigDecimal cumulativeInterestExpected, + final BigDecimal cumulativeInterestPaid, final BigDecimal cumulativeInterestWaived, + final BigDecimal cumulativeInterestWrittenOff, final BigDecimal cumulativeInterestOutstanding, + final BigDecimal cumulativeFeeChargesToDate, final BigDecimal cumulativeFeeChargesPaid, + final BigDecimal cumulativeFeeChargesWaived, final BigDecimal cumulativeFeeChargesWrittenOff, + final BigDecimal cumulativeFeeChargesOutstanding, final BigDecimal cumulativePenaltyChargesToDate, + final BigDecimal cumulativePenaltyChargesPaid, final BigDecimal cumulativePenaltyChargesWaived, + final BigDecimal cumulativePenaltyChargesWrittenOff, final BigDecimal cumulativePenaltyChargesOutstanding, + final BigDecimal totalCostOfLoan, final BigDecimal totalExpectedRepayment, final BigDecimal totalPaidToDate, + final BigDecimal totalWaivedToDate, final BigDecimal totalWrittenOffToDate, final BigDecimal totalOutstanding, + final BigDecimal totalInArrears) { + this.currency = currency; + this.periods = periods; + this.loanTermInDays = loanTermInDays; + + this.cumulativePrincipalDisbursed = cumulativePrincipalDisbursed; + this.cumulativePrincipalDue = cumulativePrincipalDue; + this.cumulativePrincipalPaid = cumulativePrincipalPaid; + this.cumulativePrincipalWrittenOff = cumulativePrincipalWrittenOff; + this.cumulativePrincipalOutstanding = cumulativePrincipalOutstanding; + this.cumulativeInterestExpected = cumulativeInterestExpected; + this.cumulativeInterestPaid = cumulativeInterestPaid; + this.cumulativeInterestWrittenOff = cumulativeInterestWrittenOff; + this.cumulativeInterestWaived = cumulativeInterestWaived; + this.cumulativeInterestOutstanding = cumulativeInterestOutstanding; + this.cumulativeFeeChargesToDate = cumulativeFeeChargesToDate; + this.cumulativeFeeChargesPaid = cumulativeFeeChargesPaid; + this.cumulativeFeeChargesWaived = cumulativeFeeChargesWaived; + this.cumulativeFeeChargesWrittenOff = cumulativeFeeChargesWrittenOff; + this.cumulativeFeeChargesOutstanding = cumulativeFeeChargesOutstanding; + this.cumulativePenaltyChargesToDate = cumulativePenaltyChargesToDate; + this.cumulativePenaltyChargesPaid = cumulativePenaltyChargesPaid; + this.cumulativePenaltyChargesWaived = cumulativePenaltyChargesWaived; + this.cumulativePenaltyChargesWrittenOff = cumulativePenaltyChargesWrittenOff; + this.cumulativePenaltyChargesOutstanding = cumulativePenaltyChargesOutstanding; + this.totalCostOfLoan = totalCostOfLoan; + this.totalExpectedRepayment = totalExpectedRepayment; + this.totalPaidToDate = totalPaidToDate; + this.totalWaivedToDate = totalWaivedToDate; + this.totalWrittenOffToDate = totalWrittenOffToDate; + this.totalOutstanding = totalOutstanding; + this.totalInArrears = totalInArrears; + } + + public Collection getPeriods() { + return periods; + } + + public BigDecimal totalOutstanding() { + return this.totalOutstanding; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AprCalculator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AprCalculator.java index cf28a36d9..55a16356f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AprCalculator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AprCalculator.java @@ -8,25 +8,25 @@ import org.springframework.stereotype.Component; @Component public class AprCalculator { - public BigDecimal calculateFrom(PeriodFrequencyType interestPeriodFrequencyType, BigDecimal interestRatePerPeriod) { - BigDecimal defaultAnnualNominalInterestRate = BigDecimal.ZERO; - switch (interestPeriodFrequencyType) { - case DAYS: - break; - case WEEKS: - defaultAnnualNominalInterestRate = interestRatePerPeriod.multiply(BigDecimal.valueOf(52)); - break; - case MONTHS: - defaultAnnualNominalInterestRate = interestRatePerPeriod.multiply(BigDecimal.valueOf(12)); - break; - case YEARS: - defaultAnnualNominalInterestRate = interestRatePerPeriod.multiply(BigDecimal.valueOf(1)); - break; - case INVALID: - break; - } - - return defaultAnnualNominalInterestRate; - } + public BigDecimal calculateFrom(PeriodFrequencyType interestPeriodFrequencyType, BigDecimal interestRatePerPeriod) { + BigDecimal defaultAnnualNominalInterestRate = BigDecimal.ZERO; + switch (interestPeriodFrequencyType) { + case DAYS: + break; + case WEEKS: + defaultAnnualNominalInterestRate = interestRatePerPeriod.multiply(BigDecimal.valueOf(52)); + break; + case MONTHS: + defaultAnnualNominalInterestRate = interestRatePerPeriod.multiply(BigDecimal.valueOf(12)); + break; + case YEARS: + defaultAnnualNominalInterestRate = interestRatePerPeriod.multiply(BigDecimal.valueOf(1)); + break; + case INVALID: + break; + } + + return defaultAnnualNominalInterestRate; + } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DecliningBalanceMethodLoanScheduleGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DecliningBalanceMethodLoanScheduleGenerator.java index 2ba15fbaa..a33de5fd8 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DecliningBalanceMethodLoanScheduleGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DecliningBalanceMethodLoanScheduleGenerator.java @@ -14,55 +14,56 @@ import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType; /** *

- * Declining balance can amortized (see {@link AmortizationMethod}) in two ways at present: - *

  1. Equal principal payments
  2. Equal installment payments
+ * Declining balance can be amortized (see {@link AmortizationMethod}) in two ways + * at present: + *
    + *
  1. Equal principal payments
  2. + *
  3. Equal installment payments
  4. + *
*

* *

- * When amortized using equal principal payments, the principal component of each installment is fixed and interest due - * is calculated from the outstanding principal balance resulting in a different total payment due for each installment. + * When amortized using equal principal payments, the principal + * component of each installment is fixed and interest due is + * calculated from the outstanding principal balance resulting in a + * different total payment due for each installment. *

* *

- * When amortized using equal installments, the total payment due for each installment is fixed and - * is calculated using the excel like pmt function. The interest due is calculated from the outstanding principal balance - * which results in a principal component that is total payment due minus interest due. - *

+ * When amortized using equal installments, the total payment due + * for each installment is fixed and is calculated using the excel like + * pmt function. The interest due is calculated from the + * outstanding principal balance which results in a principal + * component that is total payment due minus interest due. + *

*/ public class DecliningBalanceMethodLoanScheduleGenerator implements LoanScheduleGenerator { - private final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator(); - private final PeriodicInterestRateCalculator periodicInterestRateCalculator = new PeriodicInterestRateCalculator(); - private final AmortizationLoanScheduleGeneratorFactory amortizationLoanScheduleGeneratorFactory = new AmortizationLoanScheduleGeneratorFactory(); - - @Override - public LoanScheduleData generate( - final ApplicationCurrency applicationCurrency, - final LoanProductRelatedDetail loanScheduleInfo, - final Integer loanTermFrequency, - final PeriodFrequencyType loanTermFrequencyType, - final LocalDate disbursementDate, - final LocalDate firstRepaymentDate, - final LocalDate interestCalculatedFrom, - final Set loanCharges) { - - // 1. generate valid set of 'due dates' based on some of the 'loan attributes' - final List scheduledDates = this.scheduledDateGenerator.generate(loanScheduleInfo, disbursementDate, firstRepaymentDate); - - final LocalDate idealDisbursementDateBasedOnFirstRepaymentDate = this.scheduledDateGenerator.idealDisbursementDateBasedOnFirstRepaymentDate(loanScheduleInfo, scheduledDates); - - // 2. determine the 'periodic' interest rate based on the 'repayment periods' so we can use - final BigDecimal periodInterestRateForRepaymentPeriod = this.periodicInterestRateCalculator.calculateFrom(loanScheduleInfo); - - // Determine with 'amortisation' approach to use - final AmortizationLoanScheduleGenerator generator = this.amortizationLoanScheduleGeneratorFactory.createGenerator(loanScheduleInfo.getAmortizationMethod()); - - return generator.generate(applicationCurrency, - loanScheduleInfo, - disbursementDate, - interestCalculatedFrom, - periodInterestRateForRepaymentPeriod, - idealDisbursementDateBasedOnFirstRepaymentDate, - scheduledDates, loanCharges); - } + private final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator(); + private final PeriodicInterestRateCalculator periodicInterestRateCalculator = new PeriodicInterestRateCalculator(); + private final AmortizationLoanScheduleGeneratorFactory amortizationLoanScheduleGeneratorFactory = new AmortizationLoanScheduleGeneratorFactory(); + + @Override + public LoanScheduleData generate(final ApplicationCurrency applicationCurrency, final LoanProductRelatedDetail loanScheduleInfo, + final Integer loanTermFrequency, final PeriodFrequencyType loanTermFrequencyType, final LocalDate disbursementDate, + final LocalDate firstRepaymentDate, final LocalDate interestCalculatedFrom, final Set loanCharges) { + + // 1. generate valid set of 'due dates' based on some of the 'loan + // attributes' + final List scheduledDates = this.scheduledDateGenerator.generate(loanScheduleInfo, disbursementDate, firstRepaymentDate); + + final LocalDate idealDisbursementDateBasedOnFirstRepaymentDate = this.scheduledDateGenerator + .idealDisbursementDateBasedOnFirstRepaymentDate(loanScheduleInfo, scheduledDates); + + // 2. determine the 'periodic' interest rate based on the 'repayment + // periods' so we can use + final BigDecimal periodInterestRateForRepaymentPeriod = this.periodicInterestRateCalculator.calculateFrom(loanScheduleInfo); + + // Determine with 'amortisation' approach to use + final AmortizationLoanScheduleGenerator generator = this.amortizationLoanScheduleGeneratorFactory.createGenerator(loanScheduleInfo + .getAmortizationMethod()); + + return generator.generate(applicationCurrency, loanScheduleInfo, disbursementDate, interestCalculatedFrom, + periodInterestRateForRepaymentPeriod, idealDisbursementDateBasedOnFirstRepaymentDate, scheduledDates, loanCharges); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DefaultLoanScheduleGeneratorFactory.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DefaultLoanScheduleGeneratorFactory.java index f0692608d..9604efb82 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DefaultLoanScheduleGeneratorFactory.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DefaultLoanScheduleGeneratorFactory.java @@ -1,26 +1,27 @@ package org.mifosplatform.portfolio.loanaccount.loanschedule.domain; import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; +import org.springframework.stereotype.Component; -public class DefaultLoanScheduleGeneratorFactory implements - LoanScheduleGeneratorFactory { +@Component +public class DefaultLoanScheduleGeneratorFactory implements LoanScheduleGeneratorFactory { - @Override - public LoanScheduleGenerator create(final InterestMethod interestMethod) { + @Override + public LoanScheduleGenerator create(final InterestMethod interestMethod) { - LoanScheduleGenerator loanScheduleGenerator = null; + LoanScheduleGenerator loanScheduleGenerator = null; - switch (interestMethod) { - case FLAT: - loanScheduleGenerator = new FlatMethodLoanScheduleGenerator(); - break; - case DECLINING_BALANCE: - loanScheduleGenerator = new DecliningBalanceMethodLoanScheduleGenerator(); - break; - case INVALID: - break; - } - - return loanScheduleGenerator; - } + switch (interestMethod) { + case FLAT: + loanScheduleGenerator = new FlatMethodLoanScheduleGenerator(); + break; + case DECLINING_BALANCE: + loanScheduleGenerator = new DecliningBalanceMethodLoanScheduleGenerator(); + break; + case INVALID: + break; + } + + return loanScheduleGenerator; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/FlatMethodLoanScheduleGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/FlatMethodLoanScheduleGenerator.java index e383db13c..437669528 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/FlatMethodLoanScheduleGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/FlatMethodLoanScheduleGenerator.java @@ -22,130 +22,117 @@ import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType; public class FlatMethodLoanScheduleGenerator implements LoanScheduleGenerator { - private final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator(); - private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator = new DefaultPaymentPeriodsInOneYearCalculator(); - - @Override - public LoanScheduleData generate( - final ApplicationCurrency currency, - final LoanProductRelatedDetail loanScheduleInfo, - final Integer loanTermFrequency, - final PeriodFrequencyType loanTermFrequencyType, - final LocalDate disbursementDate, - final LocalDate firstRepaymentDate, - final LocalDate interestCalculatedFrom, - final Set loanCharges) { - - final Collection periods = new ArrayList(); - - final List scheduledDates = this.scheduledDateGenerator.generate(loanScheduleInfo, disbursementDate, firstRepaymentDate); + private final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator(); + private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator = new DefaultPaymentPeriodsInOneYearCalculator(); - MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN); - - BigDecimal loanTermPeriodsInYear = BigDecimal.valueOf(this.paymentPeriodsInOneYearCalculator.calculate(loanTermFrequencyType)); - BigDecimal interestRateForLoanTerm = loanScheduleInfo - .getAnnualNominalInterestRate() - .divide(loanTermPeriodsInYear, mc) - .divide(BigDecimal.valueOf(Double.valueOf("100.0")), mc) - .multiply(BigDecimal.valueOf(loanTermFrequency)); - - final MonetaryCurrency monetaryCurrency = loanScheduleInfo.getPrincipal().getCurrency(); - Money totalInterestForLoanTerm = loanScheduleInfo.getPrincipal().multiplyRetainScale(interestRateForLoanTerm, RoundingMode.HALF_EVEN); + @Override + public LoanScheduleData generate(final ApplicationCurrency currency, final LoanProductRelatedDetail loanScheduleInfo, + final Integer loanTermFrequency, final PeriodFrequencyType loanTermFrequencyType, final LocalDate disbursementDate, + final LocalDate firstRepaymentDate, final LocalDate interestCalculatedFrom, final Set loanCharges) { - Money interestPerInstallment = totalInterestForLoanTerm.dividedBy(Long.valueOf(loanScheduleInfo.getNumberOfRepayments()), RoundingMode.HALF_EVEN); - - Money principalPerInstallment = loanScheduleInfo.getPrincipal() - .dividedBy(loanScheduleInfo.getNumberOfRepayments(), - RoundingMode.HALF_EVEN); + final Collection periods = new ArrayList(); - Money outstandingBalance = loanScheduleInfo.getPrincipal(); - Money principalDisbursed = loanScheduleInfo.getPrincipal(); - Money totalPrincipal = Money.zero(outstandingBalance.getCurrency()); - Money totalInterest = Money.zero(outstandingBalance.getCurrency()); - - BigDecimal chargesDueAtTimeOfDisbursement = BigDecimal.ZERO; - for (LoanCharge loanCharge : loanCharges) { - if (loanCharge.isDueAtDisbursement()) { - chargesDueAtTimeOfDisbursement = chargesDueAtTimeOfDisbursement.add(loanCharge.amount()); - } - } - - BigDecimal cumulativeChargesToDate = chargesDueAtTimeOfDisbursement; - - // create entries of disbursement period on loan schedule - final LoanSchedulePeriodData disbursementPeriod = LoanSchedulePeriodData.disbursementOnlyPeriod(disbursementDate, principalDisbursed.getAmount(), chargesDueAtTimeOfDisbursement, false); - periods.add(disbursementPeriod); - - int loanTermInDays = Integer.valueOf(0); - BigDecimal cumulativePrincipalDisbursed = principalDisbursed.getAmount(); - BigDecimal cumulativePrincipalDue = BigDecimal.ZERO; - BigDecimal cumulativeInterestExpected = BigDecimal.ZERO; - BigDecimal totalExpectedRepayment = chargesDueAtTimeOfDisbursement; - - LocalDate startDate = disbursementDate; - int periodNumber = 1; - for (LocalDate scheduledDueDate : scheduledDates) { - totalPrincipal = totalPrincipal.plus(principalPerInstallment); - totalInterest = totalInterest.plus(interestPerInstallment); - - // number of days from startDate to this scheduledDate - int daysInPeriod = Days.daysBetween(startDate.toDateMidnight().toDateTime(), scheduledDueDate.toDateMidnight().toDateTime()).getDays(); + final List scheduledDates = this.scheduledDateGenerator.generate(loanScheduleInfo, disbursementDate, firstRepaymentDate); - if (periodNumber == loanScheduleInfo.getNumberOfRepayments()) { - final Money difference = totalPrincipal.minus(loanScheduleInfo.getPrincipal()); - if (difference.isLessThanZero()) { - principalPerInstallment = principalPerInstallment.plus(difference.abs()); - } else if (difference.isGreaterThanZero()) { - principalPerInstallment = principalPerInstallment.minus(difference.abs()); - } - - final Money interestDifference = totalInterest.minus(totalInterestForLoanTerm); - if (interestDifference.isLessThanZero()) { - interestPerInstallment = interestPerInstallment.plus(interestDifference.abs()); - } else if (interestDifference.isGreaterThanZero()) { - interestPerInstallment = interestPerInstallment.minus(interestDifference.abs()); - } - } + MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN); - outstandingBalance = outstandingBalance.minus(principalPerInstallment); + BigDecimal loanTermPeriodsInYear = BigDecimal.valueOf(this.paymentPeriodsInOneYearCalculator.calculate(loanTermFrequencyType)); + BigDecimal interestRateForLoanTerm = loanScheduleInfo.getAnnualNominalInterestRate().divide(loanTermPeriodsInYear, mc) + .divide(BigDecimal.valueOf(Double.valueOf("100.0")), mc).multiply(BigDecimal.valueOf(loanTermFrequency)); - Money feeChargesForInstallment = Money.zero(monetaryCurrency); - Money penaltyChargesForInstallment = Money.zero(monetaryCurrency); - - Money totalInstallmentDue = principalPerInstallment.plus(interestPerInstallment).plus(feeChargesForInstallment).plus(penaltyChargesForInstallment); - cumulativeChargesToDate = cumulativeChargesToDate.add(feeChargesForInstallment.getAmount()).add(penaltyChargesForInstallment.getAmount()); - - LoanSchedulePeriodData installment = LoanSchedulePeriodData.repaymentOnlyPeriod(periodNumber, startDate, - scheduledDueDate, - principalPerInstallment.getAmount(), - outstandingBalance.getAmount(), - interestPerInstallment.getAmount(), - feeChargesForInstallment.getAmount(), - penaltyChargesForInstallment.getAmount(), - totalInstallmentDue.getAmount()); + final MonetaryCurrency monetaryCurrency = loanScheduleInfo.getPrincipal().getCurrency(); + Money totalInterestForLoanTerm = loanScheduleInfo.getPrincipal().multiplyRetainScale(interestRateForLoanTerm, + RoundingMode.HALF_EVEN); - periods.add(installment); - - // handle cumulative fields - loanTermInDays += daysInPeriod; - cumulativePrincipalDue = cumulativePrincipalDue.add(principalPerInstallment.getAmount()); - cumulativeInterestExpected = cumulativeInterestExpected.add(interestPerInstallment.getAmount()); - totalExpectedRepayment = totalExpectedRepayment.add(totalInstallmentDue.getAmount()); - startDate = scheduledDueDate; + Money interestPerInstallment = totalInterestForLoanTerm.dividedBy(Long.valueOf(loanScheduleInfo.getNumberOfRepayments()), + RoundingMode.HALF_EVEN); - periodNumber++; - } - - final BigDecimal cumulativePrincipalOutstanding = cumulativePrincipalDisbursed.subtract(cumulativePrincipalDue); - - CurrencyData currencyData = new CurrencyData( - currency.getCode(), - currency.getName(), - monetaryCurrency.getDigitsAfterDecimal(), - currency.getDisplaySymbol(), - currency.getNameCode()); - - return new LoanScheduleData(currencyData, periods, loanTermInDays, cumulativePrincipalDisbursed, cumulativePrincipalDue, - cumulativePrincipalOutstanding, cumulativeInterestExpected, cumulativeChargesToDate, totalExpectedRepayment); - } + Money principalPerInstallment = loanScheduleInfo.getPrincipal().dividedBy(loanScheduleInfo.getNumberOfRepayments(), + RoundingMode.HALF_EVEN); + + Money outstandingBalance = loanScheduleInfo.getPrincipal(); + Money principalDisbursed = loanScheduleInfo.getPrincipal(); + Money totalPrincipal = Money.zero(outstandingBalance.getCurrency()); + Money totalInterest = Money.zero(outstandingBalance.getCurrency()); + + BigDecimal chargesDueAtTimeOfDisbursement = BigDecimal.ZERO; + for (LoanCharge loanCharge : loanCharges) { + if (loanCharge.isDueAtDisbursement()) { + chargesDueAtTimeOfDisbursement = chargesDueAtTimeOfDisbursement.add(loanCharge.amount()); + } + } + + BigDecimal cumulativeChargesToDate = chargesDueAtTimeOfDisbursement; + + // create entries of disbursement period on loan schedule + final LoanSchedulePeriodData disbursementPeriod = LoanSchedulePeriodData.disbursementOnlyPeriod(disbursementDate, + principalDisbursed.getAmount(), chargesDueAtTimeOfDisbursement, false); + periods.add(disbursementPeriod); + + int loanTermInDays = Integer.valueOf(0); + BigDecimal cumulativePrincipalDisbursed = principalDisbursed.getAmount(); + BigDecimal cumulativePrincipalDue = BigDecimal.ZERO; + BigDecimal cumulativeInterestExpected = BigDecimal.ZERO; + BigDecimal totalExpectedRepayment = chargesDueAtTimeOfDisbursement; + + LocalDate startDate = disbursementDate; + int periodNumber = 1; + for (LocalDate scheduledDueDate : scheduledDates) { + totalPrincipal = totalPrincipal.plus(principalPerInstallment); + totalInterest = totalInterest.plus(interestPerInstallment); + + // number of days from startDate to this scheduledDate + int daysInPeriod = Days.daysBetween(startDate.toDateMidnight().toDateTime(), scheduledDueDate.toDateMidnight().toDateTime()) + .getDays(); + + if (periodNumber == loanScheduleInfo.getNumberOfRepayments()) { + final Money difference = totalPrincipal.minus(loanScheduleInfo.getPrincipal()); + if (difference.isLessThanZero()) { + principalPerInstallment = principalPerInstallment.plus(difference.abs()); + } else if (difference.isGreaterThanZero()) { + principalPerInstallment = principalPerInstallment.minus(difference.abs()); + } + + final Money interestDifference = totalInterest.minus(totalInterestForLoanTerm); + if (interestDifference.isLessThanZero()) { + interestPerInstallment = interestPerInstallment.plus(interestDifference.abs()); + } else if (interestDifference.isGreaterThanZero()) { + interestPerInstallment = interestPerInstallment.minus(interestDifference.abs()); + } + } + + outstandingBalance = outstandingBalance.minus(principalPerInstallment); + + Money feeChargesForInstallment = Money.zero(monetaryCurrency); + Money penaltyChargesForInstallment = Money.zero(monetaryCurrency); + + Money totalInstallmentDue = principalPerInstallment.plus(interestPerInstallment).plus(feeChargesForInstallment) + .plus(penaltyChargesForInstallment); + cumulativeChargesToDate = cumulativeChargesToDate.add(feeChargesForInstallment.getAmount()).add( + penaltyChargesForInstallment.getAmount()); + + LoanSchedulePeriodData installment = LoanSchedulePeriodData.repaymentOnlyPeriod(periodNumber, startDate, scheduledDueDate, + principalPerInstallment.getAmount(), outstandingBalance.getAmount(), interestPerInstallment.getAmount(), + feeChargesForInstallment.getAmount(), penaltyChargesForInstallment.getAmount(), totalInstallmentDue.getAmount()); + + periods.add(installment); + + // handle cumulative fields + loanTermInDays += daysInPeriod; + cumulativePrincipalDue = cumulativePrincipalDue.add(principalPerInstallment.getAmount()); + cumulativeInterestExpected = cumulativeInterestExpected.add(interestPerInstallment.getAmount()); + totalExpectedRepayment = totalExpectedRepayment.add(totalInstallmentDue.getAmount()); + startDate = scheduledDueDate; + + periodNumber++; + } + + final BigDecimal cumulativePrincipalOutstanding = cumulativePrincipalDisbursed.subtract(cumulativePrincipalDue); + + CurrencyData currencyData = new CurrencyData(currency.getCode(), currency.getName(), monetaryCurrency.getDigitsAfterDecimal(), + currency.getDisplaySymbol(), currency.getNameCode()); + + return new LoanScheduleData(currencyData, periods, loanTermInDays, cumulativePrincipalDisbursed, cumulativePrincipalDue, + cumulativePrincipalOutstanding, cumulativeInterestExpected, cumulativeChargesToDate, totalExpectedRepayment); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanSchedule.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanSchedule.java new file mode 100644 index 000000000..06aecdfd6 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanSchedule.java @@ -0,0 +1,105 @@ +package org.mifosplatform.portfolio.loanaccount.loanschedule.domain; + +import java.math.BigDecimal; +import java.util.Set; + +import org.joda.time.LocalDate; +import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; +import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; +import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; +import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData; +import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod; +import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; +import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; +import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail; +import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType; + +/** + * Domain representation of a Loan Schedule (not used for persistence) + */ +public final class LoanSchedule { + + private final LoanScheduleGenerator loanScheduleGenerator; + private final ApplicationCurrency applicationCurrency; + private final BigDecimal principal; + private final BigDecimal nominalInterestRatePerPeriod; + private final PeriodFrequencyType interestRatePeriodFrequencyType; + private final BigDecimal nominalAnnualInterestRate; + private final InterestMethod interestMethod; + private final InterestCalculationPeriodMethod interestCalculationPeriodMethod; + private final Integer repaymentEvery; + private final PeriodFrequencyType repaymentPeriodFrequencyType; + private final Integer numberOfRepayments; + private final AmortizationMethod amortizationMethod; + private final Integer loanTermFrequency; + private final PeriodFrequencyType loanTermPeriodFrequencyType; + private final LocalDate disbursementDate; + private final LocalDate repaymentStartFromDate; + private final LocalDate interestChargedFromDate; + private final BigDecimal inArrearsTolerance; + + private final Set loanCharges; + + public LoanSchedule(final LoanScheduleGenerator loanScheduleGenerator, final ApplicationCurrency applicationCurrency, + final BigDecimal principal, final BigDecimal nominalInterestRatePerPeriod, + final PeriodFrequencyType interestRatePeriodFrequencyType, final BigDecimal nominalAnnualInterestRate, + final InterestMethod interestMethod, final InterestCalculationPeriodMethod interestCalculationPeriodMethod, + final Integer repaymentEvery, final PeriodFrequencyType repaymentPeriodFrequencyType, final Integer numberOfRepayments, + final AmortizationMethod amortizationMethod, final Integer loanTermFrequency, + final PeriodFrequencyType loanTermPeriodFrequencyType, final Set loanCharges, final LocalDate disbursementDate, + final LocalDate repaymentStartFromDate, final LocalDate interestChargedFromDate, + final BigDecimal inArrearsTolerance) { + this.loanScheduleGenerator = loanScheduleGenerator; + this.applicationCurrency = applicationCurrency; + this.principal = principal; + this.nominalInterestRatePerPeriod = nominalInterestRatePerPeriod; + this.interestRatePeriodFrequencyType = interestRatePeriodFrequencyType; + this.nominalAnnualInterestRate = nominalAnnualInterestRate; + this.interestMethod = interestMethod; + this.interestCalculationPeriodMethod = interestCalculationPeriodMethod; + this.repaymentEvery = repaymentEvery; + this.repaymentPeriodFrequencyType = repaymentPeriodFrequencyType; + this.numberOfRepayments = numberOfRepayments; + this.amortizationMethod = amortizationMethod; + this.loanTermFrequency = loanTermFrequency; + this.loanTermPeriodFrequencyType = loanTermPeriodFrequencyType; + this.loanCharges = loanCharges; + this.disbursementDate = disbursementDate; + this.repaymentStartFromDate = repaymentStartFromDate; + this.interestChargedFromDate = interestChargedFromDate; + this.inArrearsTolerance = inArrearsTolerance; + } + + public LoanScheduleData generate() { + return loanScheduleGenerator.generate(applicationCurrency, loanProductRelatedDetail(), loanTermFrequency, + loanTermPeriodFrequencyType, disbursementDate, repaymentStartFromDate, interestChargedFromDate, loanCharges); + } + + public LoanProductRelatedDetail loanProductRelatedDetail() { + final MonetaryCurrency currency = new MonetaryCurrency(applicationCurrency.getCode(), applicationCurrency.getDecimalPlaces()); + + return LoanProductRelatedDetail.createFrom(currency, principal, nominalInterestRatePerPeriod, interestRatePeriodFrequencyType, + nominalAnnualInterestRate, interestMethod, interestCalculationPeriodMethod, repaymentEvery, repaymentPeriodFrequencyType, + numberOfRepayments, amortizationMethod, this.inArrearsTolerance); + } + + public Integer getLoanTermFrequency() { + return this.loanTermFrequency; + } + + public PeriodFrequencyType getLoanTermPeriodFrequencyType() { + return this.loanTermPeriodFrequencyType; + } + + public LocalDate getDisbursementDate() { + return this.disbursementDate; + } + + public LocalDate getRepaymentStartFromDate() { + return this.repaymentStartFromDate; + } + + public LocalDate getInterestChargedFromDate() { + return this.interestChargedFromDate; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleGenerator.java index d66314aab..b0661b01a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleGenerator.java @@ -11,10 +11,8 @@ import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType; public interface LoanScheduleGenerator { - LoanScheduleData generate(ApplicationCurrency applicationCurrency, - LoanProductRelatedDetail loanScheduleInfo, - Integer loanTermFrequency, - PeriodFrequencyType loanTermFrequencyType, - LocalDate disbursementDate, LocalDate firstRepaymentDate, LocalDate interestCalculatedFrom, Set loanCharges); + LoanScheduleData generate(ApplicationCurrency applicationCurrency, LoanProductRelatedDetail loanScheduleInfo, + Integer loanTermFrequency, PeriodFrequencyType loanTermFrequencyType, LocalDate disbursementDate, LocalDate firstRepaymentDate, + LocalDate interestCalculatedFrom, Set loanCharges); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/query/CalculateLoanScheduleQuery.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/query/CalculateLoanScheduleQuery.java new file mode 100644 index 000000000..d59cb9a62 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/query/CalculateLoanScheduleQuery.java @@ -0,0 +1,207 @@ +package org.mifosplatform.portfolio.loanaccount.loanschedule.query; + +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.command.LoanChargeCommand; + +/** + * Immutable query used when auto-calculating loan schedules based on loan + * terms. + */ +public class CalculateLoanScheduleQuery { + + private final Long productId; + private final BigDecimal principal; + private final Integer loanTermFrequency; + private final Integer loanTermFrequencyType; + private final Integer repaymentEvery; + private final Integer repaymentFrequencyType; + private final Integer numberOfRepayments; + private final BigDecimal interestRatePerPeriod; + private final Integer interestRateFrequencyType; + private final Integer amortizationType; + private final Integer interestType; + private final Integer interestCalculationPeriodType; + private final LocalDate expectedDisbursementDate; + private final LocalDate repaymentsStartingFromDate; + private final LocalDate interestChargedFromDate; + private final LoanChargeCommand[] charges; + + public CalculateLoanScheduleQuery(final Long productId, final BigDecimal principal, final BigDecimal interestRatePerPeriod, + Integer interestRateFrequencyMethod, final Integer interestMethod, final Integer interestCalculationPeriodMethod, + final Integer repaymentEvery, final Integer repaymentFrequency, final Integer numberOfRepayments, Integer amortizationMethod, + final Integer loanTermFrequency, final Integer loanTermFrequencyType, final LocalDate expectedDisbursementDate, + final LocalDate repaymentsStartingFromDate, final LocalDate interestCalculatedFromDate, final LoanChargeCommand[] charges) { + + this.productId = productId; + this.principal = principal; + this.interestRatePerPeriod = interestRatePerPeriod; + this.interestRateFrequencyType = interestRateFrequencyMethod; + this.interestType = interestMethod; + this.interestCalculationPeriodType = interestCalculationPeriodMethod; + this.repaymentEvery = repaymentEvery; + this.repaymentFrequencyType = repaymentFrequency; + this.numberOfRepayments = numberOfRepayments; + this.amortizationType = amortizationMethod; + this.loanTermFrequency = loanTermFrequency; + this.loanTermFrequencyType = loanTermFrequencyType; + + this.expectedDisbursementDate = expectedDisbursementDate; + this.repaymentsStartingFromDate = repaymentsStartingFromDate; + this.interestChargedFromDate = interestCalculatedFromDate; + this.charges = charges; + } + + public Long getProductId() { + return productId; + } + + public BigDecimal getPrincipal() { + return principal; + } + + public Integer getLoanTermFrequency() { + return loanTermFrequency; + } + + public Integer getLoanTermFrequencyType() { + return loanTermFrequencyType; + } + + public Integer getRepaymentEvery() { + return repaymentEvery; + } + + public Integer getRepaymentFrequencyType() { + return repaymentFrequencyType; + } + + public Integer getNumberOfRepayments() { + return numberOfRepayments; + } + + public BigDecimal getInterestRatePerPeriod() { + return interestRatePerPeriod; + } + + public Integer getInterestRateFrequencyType() { + return interestRateFrequencyType; + } + + public Integer getAmortizationType() { + return amortizationType; + } + + public Integer getInterestType() { + return interestType; + } + + public Integer getInterestCalculationPeriodType() { + return interestCalculationPeriodType; + } + + public LocalDate getExpectedDisbursementDate() { + return expectedDisbursementDate; + } + + public LocalDate getRepaymentsStartingFromDate() { + return repaymentsStartingFromDate; + } + + public LocalDate getInterestChargedFromDate() { + return interestChargedFromDate; + } + + public LoanChargeCommand[] getCharges() { + return charges; + } + + public void validate() { + + List dataValidationErrors = new ArrayList(); + + DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan"); + + baseDataValidator.reset().parameter("principal").value(this.principal).notNull().positiveAmount(); + + baseDataValidator.reset().parameter("loanTermFrequency").value(this.loanTermFrequency).notNull().integerGreaterThanZero(); + baseDataValidator.reset().parameter("loanTermFrequencyType").value(this.loanTermFrequencyType).notNull().inMinMaxRange(0, 4); + + baseDataValidator.reset().parameter("numberOfRepayments").value(this.numberOfRepayments).notNull().integerGreaterThanZero(); + baseDataValidator.reset().parameter("repaymentEvery").value(this.repaymentEvery).notNull().integerGreaterThanZero(); + baseDataValidator.reset().parameter("repaymentFrequencyType").value(this.repaymentFrequencyType).notNull().inMinMaxRange(0, 3); + + // FIXME - this constraint doesnt really need to be here. should be + // possible to express loan term as say 12 months whilst also saying + // - that the repayment structure is 6 repayments every bi-monthly. + if (this.loanTermFrequencyType != null && !this.loanTermFrequencyType.equals(this.repaymentFrequencyType)) { + ApiParameterError error = ApiParameterError.parameterError( + "validation.msg.loan.loanTermFrequencyType.not.the.same.as.repaymentFrequencyType", + "The parameters loanTermFrequencyType and repaymentFrequencyType must be the same.", "loanTermFrequencyType", + this.loanTermFrequencyType, this.repaymentFrequencyType); + dataValidationErrors.add(error); + } else { + if (this.loanTermFrequency != null && this.repaymentEvery != null && this.numberOfRepayments != null) { + int suggestsedLoanTerm = this.repaymentEvery * this.numberOfRepayments; + if (this.loanTermFrequency.intValue() < suggestsedLoanTerm) { + ApiParameterError error = ApiParameterError + .parameterError( + "validation.msg.loan.loanTermFrequency.less.than.repayment.structure.suggests", + "The parameter loanTermFrequency is less than the suggest loan term as indicated by numberOfRepayments and repaymentEvery.", + "loanTermFrequency", this.loanTermFrequency, this.numberOfRepayments, this.repaymentEvery); + dataValidationErrors.add(error); + } + } + } + + baseDataValidator.reset().parameter("interestRatePerPeriod").value(this.interestRatePerPeriod).notNull(); + baseDataValidator.reset().parameter("interestRateFrequencyType").value(this.interestRateFrequencyType).notNull() + .inMinMaxRange(0, 3); + baseDataValidator.reset().parameter("amortizationType").value(this.amortizationType).notNull().inMinMaxRange(0, 1); + baseDataValidator.reset().parameter("interestType").value(this.interestType).notNull().inMinMaxRange(0, 1); + baseDataValidator.reset().parameter("interestCalculationPeriodType").value(this.interestCalculationPeriodType).notNull() + .inMinMaxRange(0, 1); + + baseDataValidator.reset().parameter("expectedDisbursementDate").value(this.expectedDisbursementDate).notNull(); + + if (this.expectedDisbursementDate != null) { + if (this.repaymentsStartingFromDate != null && this.expectedDisbursementDate.isAfter(this.repaymentsStartingFromDate)) { + ApiParameterError error = ApiParameterError.parameterError( + "validation.msg.loan.expectedDisbursementDate.cannot.be.after.first.repayment.date", + "The parameter expectedDisbursementDate has a date which falls after the given first repayment date.", + "expectedDisbursementDate", this.expectedDisbursementDate, this.repaymentsStartingFromDate); + dataValidationErrors.add(error); + } + } + + if (this.repaymentsStartingFromDate != null && this.interestChargedFromDate == null) { + + ApiParameterError error = ApiParameterError.parameterError( + "validation.msg.loan.interestCalculatedFromDate.must.be.entered.when.using.repayments.startfrom.field", + "The parameter interestCalculatedFromDate cannot be empty when first repayment date is provided.", + "interestChargedFromDate", this.repaymentsStartingFromDate); + dataValidationErrors.add(error); + } else if (this.repaymentsStartingFromDate == null && this.interestChargedFromDate != null) { + + // validate interestCalculatedFromDate is after or on + // repaymentsStartingFromDate + if (this.expectedDisbursementDate.isAfter(this.interestChargedFromDate)) { + ApiParameterError error = ApiParameterError.parameterError( + "validation.msg.loan.interestChargedFromDate.cannot.be.before.disbursement.date", + "The parameter interestCalculatedFromDate cannot be before the date given for expected disbursement.", + "interestChargedFromDate", this.interestChargedFromDate, this.expectedDisbursementDate); + dataValidationErrors.add(error); + } + } + + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", + "Validation errors exist.", dataValidationErrors); } + + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/CalculationPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/CalculationPlatformService.java deleted file mode 100644 index dbee57ef0..000000000 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/CalculationPlatformService.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.mifosplatform.portfolio.loanaccount.loanschedule.service; - -import org.mifosplatform.portfolio.loanaccount.loanschedule.command.CalculateLoanScheduleCommand; -import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData; - -public interface CalculationPlatformService { - - LoanScheduleData calculateLoanSchedule(CalculateLoanScheduleCommand command); -} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/CalculationPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/CalculationPlatformServiceImpl.java deleted file mode 100644 index df4950f22..000000000 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/CalculationPlatformServiceImpl.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.mifosplatform.portfolio.loanaccount.loanschedule.service; - -import java.util.Set; - -import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; -import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; -import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepository; -import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; -import org.mifosplatform.portfolio.loanaccount.loanschedule.command.CalculateLoanScheduleCommand; -import org.mifosplatform.portfolio.loanaccount.loanschedule.command.CalculateLoanScheduleCommandValidator; -import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData; -import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.DefaultLoanScheduleGeneratorFactory; -import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator; -import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory; -import org.mifosplatform.portfolio.loanaccount.service.LoanChargeAssembler; -import org.mifosplatform.portfolio.loanaccount.service.LoanProductRelatedDetailAssembler; -import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; -import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail; -import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -@Service -public class CalculationPlatformServiceImpl implements CalculationPlatformService { - - private final LoanScheduleGeneratorFactory loanScheduleFactory; - private final ApplicationCurrencyRepository applicationCurrencyRepository; - private final PlatformSecurityContext context; - private final LoanProductRelatedDetailAssembler loanProductRelatedDetailAssembler; - private final LoanChargeAssembler loanChargeAssembler; - - @Autowired - public CalculationPlatformServiceImpl( - final PlatformSecurityContext context, - final ApplicationCurrencyRepository applicationCurrencyRepository, - final LoanProductRelatedDetailAssembler loanProductRelatedDetailAssembler, - final LoanChargeAssembler loanChargeAssembler) { - this.context = context; - this.applicationCurrencyRepository = applicationCurrencyRepository; - this.loanScheduleFactory = new DefaultLoanScheduleGeneratorFactory(); - this.loanProductRelatedDetailAssembler = loanProductRelatedDetailAssembler; - this.loanChargeAssembler = loanChargeAssembler; - } - - @Override - public LoanScheduleData calculateLoanSchedule(final CalculateLoanScheduleCommand command) { - - context.authenticatedUser(); - - final CalculateLoanScheduleCommandValidator validator = new CalculateLoanScheduleCommandValidator(command); - validator.validate(); - - final LoanProductRelatedDetail loanScheduleRelatedDetails = this.loanProductRelatedDetailAssembler.assembleFrom(command); - - final Integer loanTermFrequency = command.getLoanTermFrequency(); - final PeriodFrequencyType loanTermFrequencyType = PeriodFrequencyType.fromInt(command.getLoanTermFrequencyType()); - final InterestMethod interestMethod = InterestMethod.fromInt(command.getInterestType()); - - final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(interestMethod); - - final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneByCode(loanScheduleRelatedDetails.getCurrency().getCode()); - - final Set loanCharges = this.loanChargeAssembler.assembleFrom(command.getCharges(), loanScheduleRelatedDetails.getPrincipal().getAmount()); - - return loanScheduleGenerator.generate(applicationCurrency, - loanScheduleRelatedDetails, loanTermFrequency, - loanTermFrequencyType, command.getExpectedDisbursementDate(), - command.getRepaymentsStartingFromDate(), - command.getInterestChargedFromDate(), loanCharges); - } -} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java new file mode 100644 index 000000000..3a21e6c20 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java @@ -0,0 +1,108 @@ +package org.mifosplatform.portfolio.loanaccount.loanschedule.service; + +import java.math.BigDecimal; +import java.util.Set; + +import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; +import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; +import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepository; +import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; +import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; +import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator; +import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanSchedule; +import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator; +import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory; +import org.mifosplatform.portfolio.loanaccount.service.LoanChargeAssembler; +import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod; +import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; +import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; +import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct; +import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRepository; +import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType; +import org.mifosplatform.portfolio.loanproduct.exception.LoanProductNotFoundException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.google.gson.JsonElement; + +@Service +public class LoanScheduleAssembler { + + private final FromJsonHelper fromApiJsonHelper; + private final LoanProductRepository loanProductRepository; + private final ApplicationCurrencyRepository applicationCurrencyRepository; + private final LoanChargeAssembler loanChargeAssembler; + private final LoanScheduleGeneratorFactory loanScheduleFactory; + private final AprCalculator aprCalculator; + + @Autowired + public LoanScheduleAssembler(final FromJsonHelper fromApiJsonHelper, final LoanProductRepository loanProductRepository, + final ApplicationCurrencyRepository applicationCurrencyRepository, final LoanScheduleGeneratorFactory loanScheduleFactory, + final AprCalculator aprCalculator, final LoanChargeAssembler loanChargeAssembler) { + this.fromApiJsonHelper = fromApiJsonHelper; + this.loanProductRepository = loanProductRepository; + this.applicationCurrencyRepository = applicationCurrencyRepository; + this.loanScheduleFactory = loanScheduleFactory; + this.aprCalculator = aprCalculator; + this.loanChargeAssembler = loanChargeAssembler; + } + + public LoanSchedule fromJson(final String json) { + final JsonElement element = fromApiJsonHelper.parse(json); + return fromJson(element); + } + + public LoanSchedule fromJson(final JsonElement element) { + return fromJson(element, null); + } + + public LoanSchedule fromJson(final JsonElement element, final BigDecimal inArrearsTolerance) { + final Long loanProductId = fromApiJsonHelper.extractLongNamed("productId", element); + + final LoanProduct loanProduct = this.loanProductRepository.findOne(loanProductId); + if (loanProduct == null) { throw new LoanProductNotFoundException(loanProductId); } + + final MonetaryCurrency currency = loanProduct.getCurrency(); + final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneByCode(currency.getCode()); + + final BigDecimal principal = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("principal", element); + final BigDecimal interestRatePerPeriod = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRatePerPeriod", element); + final Integer interestRateFrequencyType = fromApiJsonHelper.extractIntegerWithLocaleNamed("interestRateFrequencyType", element); + final Integer interestType = fromApiJsonHelper.extractIntegerWithLocaleNamed("interestType", element); + final Integer interestCalculationPeriodType = fromApiJsonHelper.extractIntegerWithLocaleNamed("interestCalculationPeriodType", + element); + + final InterestMethod interestMethod = InterestMethod.fromInt(interestType); + final PeriodFrequencyType interestRatePeriodFrequencyType = PeriodFrequencyType.fromInt(interestRateFrequencyType); + final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod + .fromInt(interestCalculationPeriodType); + + final BigDecimal defaultAnnualNominalInterestRate = this.aprCalculator.calculateFrom(interestRatePeriodFrequencyType, + interestRatePerPeriod); + + final Integer repaymentEvery = fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentEvery", element); + final Integer repaymentFrequencyType = fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentFrequencyType", element); + final Integer numberOfRepayments = fromApiJsonHelper.extractIntegerWithLocaleNamed("numberOfRepayments", element); + final Integer amortizationType = fromApiJsonHelper.extractIntegerWithLocaleNamed("amortizationType", element); + final PeriodFrequencyType repaymentPeriodFrequencyType = PeriodFrequencyType.fromInt(repaymentFrequencyType); + final AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(amortizationType); + + final Integer loanTermFrequency = fromApiJsonHelper.extractIntegerWithLocaleNamed("loanTermFrequency", element); + final Integer loanTermFrequencyType = fromApiJsonHelper.extractIntegerWithLocaleNamed("loanTermFrequencyType", element); + final PeriodFrequencyType loanTermPeriodFrequencyType = PeriodFrequencyType.fromInt(loanTermFrequencyType); + + final LocalDate expectedDisbursementDate = fromApiJsonHelper.extractLocalDateNamed("expectedDisbursementDate", element); + final LocalDate repaymentsStartingFromDate = fromApiJsonHelper.extractLocalDateNamed("repaymentsStartingFromDate", element); + final LocalDate interestChargedFromDate = fromApiJsonHelper.extractLocalDateNamed("interestChargedFromDate", element); + + final Set loanCharges = this.loanChargeAssembler.fromParsedJson(element); + + final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(interestMethod); + + return new LoanSchedule(loanScheduleGenerator, applicationCurrency, principal, interestRatePerPeriod, + interestRatePeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, + repaymentEvery, repaymentPeriodFrequencyType, numberOfRepayments, amortizationMethod, loanTermFrequency, + loanTermPeriodFrequencyType, loanCharges, expectedDisbursementDate, repaymentsStartingFromDate, interestChargedFromDate, inArrearsTolerance); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformService.java new file mode 100644 index 000000000..9c427f3eb --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformService.java @@ -0,0 +1,9 @@ +package org.mifosplatform.portfolio.loanaccount.loanschedule.service; + +import org.mifosplatform.infrastructure.core.api.JsonQuery; +import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData; + +public interface LoanScheduleCalculationPlatformService { + + LoanScheduleData calculateLoanSchedule(JsonQuery query); +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java new file mode 100644 index 000000000..9a511d3b6 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java @@ -0,0 +1,38 @@ +package org.mifosplatform.portfolio.loanaccount.loanschedule.service; + +import org.mifosplatform.infrastructure.core.api.JsonQuery; +import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; +import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData; +import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanSchedule; +import org.mifosplatform.portfolio.loanaccount.loanschedule.query.CalculateLoanScheduleQuery; +import org.mifosplatform.portfolio.loanaccount.serialization.CalculateLoanScheduleQueryFromApiJsonDeserializer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleCalculationPlatformService { + + private final PlatformSecurityContext context; + private final CalculateLoanScheduleQueryFromApiJsonDeserializer fromApiJsonDeserializer; + private final LoanScheduleAssembler loanScheduleAssembler; + + @Autowired + public LoanScheduleCalculationPlatformServiceImpl(final PlatformSecurityContext context, + final CalculateLoanScheduleQueryFromApiJsonDeserializer fromApiJsonDeserializer, + final LoanScheduleAssembler loanScheduleAssembler) { + this.context = context; + this.fromApiJsonDeserializer = fromApiJsonDeserializer; + this.loanScheduleAssembler = loanScheduleAssembler; + } + + @Override + public LoanScheduleData calculateLoanSchedule(final JsonQuery query) { + context.authenticatedUser(); + + CalculateLoanScheduleQuery calculateLoanScheduleQuery = this.fromApiJsonDeserializer.commandFromApiJson(query.json()); + calculateLoanScheduleQuery.validate(); + + final LoanSchedule loanSchedule = this.loanScheduleAssembler.fromJson(query.parsedJson()); + return loanSchedule.generate(); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonDeserializer.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonDeserializer.java new file mode 100644 index 000000000..b95788559 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonDeserializer.java @@ -0,0 +1,114 @@ +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.Locale; +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.mifosplatform.portfolio.loanaccount.loanschedule.query.CalculateLoanScheduleQuery; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; + +/** + * Implementation of {@link FromApiJsonDeserializer} for + * {@link CalculateLoanScheduleQuery}'s. + */ +@Component +public final class CalculateLoanScheduleQueryFromApiJsonDeserializer extends AbstractFromApiJsonDeserializer { + + /** + * The parameters supported for this command. + */ + final Set supportedParameters = new HashSet(Arrays.asList("clientId", "groupId", "productId", "externalId", "fundId", + "transactionProcessingStrategyId", "principal", "inArrearsTolerance", "interestRatePerPeriod", "repaymentEvery", + "numberOfRepayments", "loanTermFrequency", "loanTermFrequencyType", "charges", "repaymentFrequencyType", + "interestRateFrequencyType", "amortizationType", "interestType", "interestCalculationPeriodType", "expectedDisbursementDate", + "repaymentsStartingFromDate", "interestChargedFromDate", "submittedOnDate", "submittedOnNote", "locale", "dateFormat", + "loanOfficerId", "id")); + + private final FromJsonHelper fromApiJsonHelper; + + @Autowired + public CalculateLoanScheduleQueryFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) { + this.fromApiJsonHelper = fromApiJsonHelper; + } + + @Override + public CalculateLoanScheduleQuery commandFromApiJson(final String json) { + + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters); + + final JsonElement element = fromApiJsonHelper.parse(json); + final Long productId = fromApiJsonHelper.extractLongNamed("productId", element); + final BigDecimal principal = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("principal", element); + final BigDecimal interestRatePerPeriod = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRatePerPeriod", element); + final Integer interestRateFrequencyType = fromApiJsonHelper.extractIntegerWithLocaleNamed("interestRateFrequencyType", element); + final Integer interestType = fromApiJsonHelper.extractIntegerWithLocaleNamed("interestType", element); + final Integer interestCalculationPeriodType = fromApiJsonHelper.extractIntegerWithLocaleNamed("interestCalculationPeriodType", + element); + + final Integer repaymentEvery = fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentEvery", element); + final Integer repaymentFrequencyType = fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentFrequencyType", element); + final Integer numberOfRepayments = fromApiJsonHelper.extractIntegerWithLocaleNamed("numberOfRepayments", element); + final Integer amortizationType = fromApiJsonHelper.extractIntegerWithLocaleNamed("amortizationType", element); + final Integer loanTermFrequency = fromApiJsonHelper.extractIntegerWithLocaleNamed("loanTermFrequency", element); + final Integer loanTermFrequencyType = fromApiJsonHelper.extractIntegerWithLocaleNamed("loanTermFrequencyType", element); + final LocalDate expectedDisbursementDate = fromApiJsonHelper.extractLocalDateNamed("expectedDisbursementDate", element); + final LocalDate repaymentsStartingFromDate = fromApiJsonHelper.extractLocalDateNamed("repaymentsStartingFromDate", element); + final LocalDate interestChargedFromDate = fromApiJsonHelper.extractLocalDateNamed("interestChargedFromDate", element); + + // /// + LoanChargeCommand[] charges = null; + if (element.isJsonObject()) { + final JsonObject topLevelJsonElement = element.getAsJsonObject(); + final String dateFormat = fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement); + final Locale locale = fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement); + if (topLevelJsonElement.has("charges") && topLevelJsonElement.get("charges").isJsonArray()) { + + final JsonArray array = topLevelJsonElement.get("charges").getAsJsonArray(); + charges = new LoanChargeCommand[array.size()]; + for (int i = 0; i < array.size(); i++) { + + final JsonObject loanChargeElement = array.get(i).getAsJsonObject(); + final Set parametersPassedInForChargesCommand = new HashSet(); + + final Long id = fromApiJsonHelper.extractLongNamed("id", loanChargeElement); + final Long chargeId = fromApiJsonHelper.extractLongNamed("chargeId", loanChargeElement); + final BigDecimal amount = fromApiJsonHelper.extractBigDecimalNamed("amount", loanChargeElement, locale); + final Integer chargeTimeType = fromApiJsonHelper.extractIntegerNamed("chargeTimeType", loanChargeElement, locale); + final Integer chargeCalculationType = fromApiJsonHelper.extractIntegerNamed("chargeCalculationType", loanChargeElement, + locale); + final LocalDate specifiedDueDate = fromApiJsonHelper.extractLocalDateNamed("specifiedDueDate", loanChargeElement, + dateFormat, locale); + + charges[i] = new LoanChargeCommand(parametersPassedInForChargesCommand, id, null, chargeId, amount, chargeTimeType, + chargeCalculationType, specifiedDueDate); + } + } + } + // / + + return new CalculateLoanScheduleQuery(productId, principal, interestRatePerPeriod, interestRateFrequencyType, interestType, + interestCalculationPeriodType, repaymentEvery, repaymentFrequencyType, numberOfRepayments, amortizationType, + loanTermFrequency, loanTermFrequencyType, expectedDisbursementDate, repaymentsStartingFromDate, interestChargedFromDate, + charges); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonDeserializer.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonDeserializer.java new file mode 100644 index 000000000..461676c40 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonDeserializer.java @@ -0,0 +1,142 @@ +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.Locale; +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.LoanApplicationCommand; +import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; + +/** + * Implementation of {@link FromApiJsonDeserializer} for + * {@link LoanApplicationCommand}'s. + */ +@Component +public final class LoanApplicationCommandFromApiJsonDeserializer extends AbstractFromApiJsonDeserializer { + + /** + * The parameters supported for this command. + */ + final Set supportedParameters = new HashSet(Arrays.asList("clientId", "groupId", "productId", "externalId", "fundId", + "transactionProcessingStrategyId", "principal", "inArrearsTolerance", "interestRatePerPeriod", "repaymentEvery", + "numberOfRepayments", "loanTermFrequency", "loanTermFrequencyType", "charges", "repaymentFrequencyType", + "interestRateFrequencyType", "amortizationType", "interestType", "interestCalculationPeriodType", "expectedDisbursementDate", + "repaymentsStartingFromDate", "interestChargedFromDate", "submittedOnDate", "submittedOnNote", "locale", "dateFormat", + "loanOfficerId", "id")); + + private final FromJsonHelper fromApiJsonHelper; + + @Autowired + public LoanApplicationCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) { + this.fromApiJsonHelper = fromApiJsonHelper; + } + + @Override + public LoanApplicationCommand commandFromApiJson(final String json) { + + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters); + + final JsonElement element = fromApiJsonHelper.parse(json); + final Long clientId = fromApiJsonHelper.extractLongNamed("clientId", element); + final Long groupId = fromApiJsonHelper.extractLongNamed("groupId", element); + final Long productId = fromApiJsonHelper.extractLongNamed("productId", element); + final String externalId = fromApiJsonHelper.extractStringNamed("externalId", element); + final Long fundId = fromApiJsonHelper.extractLongNamed("fundId", element); + final Long loanOfficerId = fromApiJsonHelper.extractLongNamed("loanOfficerId", element); + final Long transactionProcessingStrategyId = fromApiJsonHelper.extractLongNamed("transactionProcessingStrategyId", element); + + final LocalDate submittedOnDate = fromApiJsonHelper.extractLocalDateNamed("submittedOnDate", element); + final String submittedOnNote = fromApiJsonHelper.extractStringNamed("submittedOnNote", element); + + final BigDecimal principal = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("principal", element); + final BigDecimal interestRatePerPeriod = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRatePerPeriod", element); + final Integer interestRateFrequencyType = fromApiJsonHelper.extractIntegerWithLocaleNamed("interestRateFrequencyType", element); + final Integer interestType = fromApiJsonHelper.extractIntegerWithLocaleNamed("interestType", element); + final Integer interestCalculationPeriodType = fromApiJsonHelper.extractIntegerWithLocaleNamed("interestCalculationPeriodType", + element); + + // final InterestMethod interestMethod = + // InterestMethod.fromInt(interestType); + // final PeriodFrequencyType interestRatePeriodFrequencyType = + // PeriodFrequencyType.fromInt(interestRateFrequencyType); + // final InterestCalculationPeriodMethod interestCalculationPeriodMethod + // = InterestCalculationPeriodMethod + // .fromInt(interestCalculationPeriodType); + + final Integer amortizationType = fromApiJsonHelper.extractIntegerWithLocaleNamed("amortizationType", element); + final Integer repaymentEvery = fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentEvery", element); + final Integer repaymentFrequencyType = fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentFrequencyType", element); + final Integer numberOfRepayments = fromApiJsonHelper.extractIntegerWithLocaleNamed("numberOfRepayments", element); + // final PeriodFrequencyType repaymentPeriodFrequencyType = + // PeriodFrequencyType.fromInt(repaymentFrequencyType); + // final AmortizationMethod amortizationMethod = + // AmortizationMethod.fromInt(amortizationType); + + final Integer loanTermFrequency = fromApiJsonHelper.extractIntegerWithLocaleNamed("loanTermFrequency", element); + final Integer loanTermFrequencyType = fromApiJsonHelper.extractIntegerWithLocaleNamed("loanTermFrequencyType", element); + // final PeriodFrequencyType loanTermPeriodFrequencyType = + // PeriodFrequencyType.fromInt(loanTermFrequencyType); + + final LocalDate expectedDisbursementDate = fromApiJsonHelper.extractLocalDateNamed("expectedDisbursementDate", element); + final LocalDate repaymentsStartingFromDate = fromApiJsonHelper.extractLocalDateNamed("repaymentsStartingFromDate", element); + final LocalDate interestChargedFromDate = fromApiJsonHelper.extractLocalDateNamed("interestChargedFromDate", element); + + final BigDecimal inArrearsTolerance = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("inArrearsTolerance", element); + + // /// + LoanChargeCommand[] charges = null; + if (element.isJsonObject()) { + final JsonObject topLevelJsonElement = element.getAsJsonObject(); + final String dateFormat = fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement); + final Locale locale = fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement); + if (topLevelJsonElement.has("charges") && topLevelJsonElement.get("charges").isJsonArray()) { + + final JsonArray array = topLevelJsonElement.get("charges").getAsJsonArray(); + charges = new LoanChargeCommand[array.size()]; + for (int i = 0; i < array.size(); i++) { + + final JsonObject loanChargeElement = array.get(i).getAsJsonObject(); + final Set parametersPassedInForChargesCommand = new HashSet(); + + final Long id = fromApiJsonHelper.extractLongNamed("id", loanChargeElement); + final Long chargeId = fromApiJsonHelper.extractLongNamed("chargeId", loanChargeElement); + final BigDecimal amount = fromApiJsonHelper.extractBigDecimalNamed("amount", loanChargeElement, locale); + final Integer chargeTimeType = fromApiJsonHelper.extractIntegerNamed("chargeTimeType", loanChargeElement, locale); + final Integer chargeCalculationType = fromApiJsonHelper.extractIntegerNamed("chargeCalculationType", loanChargeElement, + locale); + final LocalDate specifiedDueDate = fromApiJsonHelper.extractLocalDateNamed("specifiedDueDate", loanChargeElement, + dateFormat, locale); + + charges[i] = new LoanChargeCommand(parametersPassedInForChargesCommand, id, null, chargeId, amount, chargeTimeType, + chargeCalculationType, specifiedDueDate); + } + } + } + + return new LoanApplicationCommand(new HashSet(), null, clientId, groupId, productId, externalId, fundId, + transactionProcessingStrategyId, submittedOnDate, submittedOnNote, expectedDisbursementDate, repaymentsStartingFromDate, + interestChargedFromDate, principal, interestRatePerPeriod, interestRateFrequencyType, interestType, + interestCalculationPeriodType, repaymentEvery, repaymentFrequencyType, numberOfRepayments, amortizationType, + loanTermFrequency, loanTermFrequencyType, inArrearsTolerance, charges, loanOfficerId); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanStateTransitionCommandFromApiJsonDeserializer.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanStateTransitionCommandFromApiJsonDeserializer.java new file mode 100644 index 000000000..e4285931a --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanStateTransitionCommandFromApiJsonDeserializer.java @@ -0,0 +1,61 @@ +package org.mifosplatform.portfolio.loanaccount.serialization; + +import java.lang.reflect.Type; +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.LoanStateTransitionCommand; +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 LoanStateTransitionCommand}'s. + */ +@Component +public final class LoanStateTransitionCommandFromApiJsonDeserializer extends AbstractFromApiJsonDeserializer { + + /** + * The parameters supported for this command. + */ + final Set supportedParameters = new HashSet(Arrays.asList("eventDate", "note", "locale", "dateFormat", "status", + "approvedOnDate", "rejectedOnDate", "withdrawnOnDate", "disbursedOnDate", "expectedMaturityDate")); + + private final FromJsonHelper fromApiJsonHelper; + + @Autowired + public LoanStateTransitionCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) { + this.fromApiJsonHelper = fromApiJsonHelper; + } + + @Override + public LoanStateTransitionCommand commandFromApiJson(final String json) { + + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters); + + final JsonElement element = fromApiJsonHelper.parse(json); + final LocalDate eventDate = fromApiJsonHelper.extractLocalDateNamed("eventDate", element); + final String note = fromApiJsonHelper.extractStringNamed("note", element); + + final LocalDate approvedOnDate = fromApiJsonHelper.extractLocalDateNamed("approvedOnDate", element); + final LocalDate rejectedOnDate = fromApiJsonHelper.extractLocalDateNamed("rejectedOnDate", element); + final LocalDate withdrawnOnDate = fromApiJsonHelper.extractLocalDateNamed("withdrawnOnDate", element); + final LocalDate disbursedOnDate = fromApiJsonHelper.extractLocalDateNamed("disbursedOnDate", element); + + return new LoanStateTransitionCommand(eventDate, note, approvedOnDate, rejectedOnDate, withdrawnOnDate, disbursedOnDate); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java index a6fb7c51a..67c868c49 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java @@ -5,7 +5,9 @@ import java.util.Arrays; import java.util.List; import java.util.Set; -import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; +import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; import org.mifosplatform.organisation.staff.domain.Staff; import org.mifosplatform.organisation.staff.domain.StaffRepository; import org.mifosplatform.organisation.staff.exception.StaffNotFoundException; @@ -19,207 +21,140 @@ import org.mifosplatform.portfolio.fund.exception.FundNotFoundException; import org.mifosplatform.portfolio.group.domain.Group; import org.mifosplatform.portfolio.group.domain.GroupRepository; import org.mifosplatform.portfolio.group.exception.GroupNotFoundException; -import org.mifosplatform.portfolio.loanaccount.command.LoanApplicationCommand; import org.mifosplatform.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine; import org.mifosplatform.portfolio.loanaccount.domain.Loan; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; import org.mifosplatform.portfolio.loanaccount.domain.LoanLifecycleStateMachine; -import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionProcessingStrategyRepository; import org.mifosplatform.portfolio.loanaccount.exception.LoanTransactionProcessingStrategyNotFoundException; -import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData; -import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData; -import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator; -import org.mifosplatform.portfolio.loanaccount.loanschedule.service.CalculationPlatformService; -import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod; -import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; -import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; +import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanSchedule; +import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler; import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct; -import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRepository; import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy; -import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.loanproduct.exception.LoanProductNotFoundException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import com.google.gson.JsonElement; + @Service public class LoanAssembler { - private final LoanProductRepository loanProductRepository; - private final ClientRepository clientRepository; - private final GroupRepository groupRepository; - private final AprCalculator aprCalculator = new AprCalculator(); - private final FundRepository fundRepository; - private final LoanTransactionProcessingStrategyRepository loanTransactionProcessingStrategyRepository; - private final StaffRepository staffRepository; - private final CalculationPlatformService calculationPlatformService; - private final LoanChargeAssembler loanChargeAssembler; - - @Autowired - public LoanAssembler( - final LoanProductRepository loanProductRepository, - final ClientRepository clientRepository, - final GroupRepository groupRepository, - final FundRepository fundRepository, - final LoanTransactionProcessingStrategyRepository loanTransactionProcessingStrategyRepository, - final StaffRepository staffRepository, - final CalculationPlatformService calculationPlatformService, - final LoanChargeAssembler loanChargeAssembler) { - this.loanProductRepository = loanProductRepository; - this.clientRepository = clientRepository; - this.groupRepository = groupRepository; - this.fundRepository = fundRepository; - this.loanTransactionProcessingStrategyRepository = loanTransactionProcessingStrategyRepository; - this.staffRepository = staffRepository; - this.calculationPlatformService = calculationPlatformService; - this.loanChargeAssembler = loanChargeAssembler; - } - - public Loan assembleFrom(final LoanApplicationCommand command) { - - LoanProduct loanProduct = this.loanProductRepository.findOne(command.getProductId()); - if (loanProduct == null) { - throw new LoanProductNotFoundException(command.getProductId()); - } + private final LoanProductRepository loanProductRepository; + private final ClientRepository clientRepository; + private final GroupRepository groupRepository; + private final FundRepository fundRepository; + private final LoanTransactionProcessingStrategyRepository loanTransactionProcessingStrategyRepository; + private final StaffRepository staffRepository; + private final LoanChargeAssembler loanChargeAssembler; + private final LoanScheduleAssembler loanScheduleAssembler; + private final FromJsonHelper fromApiJsonHelper; - Client client = null; - if (command.getClientId() != null) { - client = this.clientRepository.findOne(command.getClientId()); - if (client == null || client.isDeleted()) { - throw new ClientNotFoundException(command.getClientId()); - } - } - - Group group = null; - if (command.getGroupId() != null) { - group = this.groupRepository.findOne(command.getGroupId()); - if (group == null || group.isDeleted()) { - throw new GroupNotFoundException(command.getGroupId()); - } - } - - final MonetaryCurrency currency = loanProduct.getCurrency(); - final Integer loanTermFrequency = command.getLoanTermFrequency(); - final PeriodFrequencyType loanTermFrequencyType = PeriodFrequencyType.fromInt(command.getLoanTermFrequencyType()); - - final LoanProductRelatedDetail loanRepaymentScheduleDetail = assembleLoanProductRelatedDetailFrom(command, currency); + @Autowired + public LoanAssembler(final FromJsonHelper fromApiJsonHelper, final LoanProductRepository loanProductRepository, + final ClientRepository clientRepository, final GroupRepository groupRepository, final FundRepository fundRepository, + final LoanTransactionProcessingStrategyRepository loanTransactionProcessingStrategyRepository, + final StaffRepository staffRepository, final LoanScheduleAssembler loanScheduleAssembler, + final LoanChargeAssembler loanChargeAssembler) { + this.fromApiJsonHelper = fromApiJsonHelper; + this.loanProductRepository = loanProductRepository; + this.clientRepository = clientRepository; + this.groupRepository = groupRepository; + this.fundRepository = fundRepository; + this.loanTransactionProcessingStrategyRepository = loanTransactionProcessingStrategyRepository; + this.staffRepository = staffRepository; + this.loanScheduleAssembler = loanScheduleAssembler; + this.loanChargeAssembler = loanChargeAssembler; + } - // associating fund with loan product at creation is optional for now. - final Fund fund = findFundByIdIfProvided(command.getFundId()); - final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy = findStrategyByIdIfProvided(command.getTransactionProcessingStrategyId()); + public Loan assembleFrom(final JsonCommand command) { + final JsonElement element = command.parsedJson(); - //optionally associate a loan officer to the loan - final Staff loanOfficer= findLoanOfficerByIdIfProvided(command.getLoanOfficerId()); - - // optionally, see if charges are associated with loan on creation (through loan product or by being directly added) - final Set loanCharges = this.loanChargeAssembler.assembleFrom(command.getCharges(), loanProduct.getCharges(), command.getPrincipal()); + final Long clientId = fromApiJsonHelper.extractLongNamed("clientId", element); + final Long groupId = fromApiJsonHelper.extractLongNamed("groupId", element); - Loan loan = null; - if (group != null) { - loan = Loan.createNew(fund,loanOfficer, loanTransactionProcessingStrategy, loanProduct, group, loanRepaymentScheduleDetail, loanCharges); - loan.setExternalId(command.getExternalId()); - } - - if (client != null) { - loan = Loan.createNew(fund,loanOfficer, loanTransactionProcessingStrategy, loanProduct, client, loanRepaymentScheduleDetail, loanCharges); - loan.setExternalId(command.getExternalId()); - } - - if (loan == null) { - // FIXME - kw - put in appropriate exception here. - throw new RuntimeException(); - } + return assembleApplication(element, clientId, groupId); + } - final LoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanSchedule(command.toCalculateLoanScheduleCommand()); - - for (LoanSchedulePeriodData scheduledLoanInstallment : loanSchedule.getPeriods()) { - if (scheduledLoanInstallment.isRepaymentPeriod()) { - - final LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment( - loan, - scheduledLoanInstallment.periodNumber(), - scheduledLoanInstallment.periodFromDate(), - scheduledLoanInstallment.periodDueDate(), - scheduledLoanInstallment.principalDue(), - scheduledLoanInstallment.interestDue(), - scheduledLoanInstallment.feeChargesDue(), - scheduledLoanInstallment.penaltyChargesDue()); - - loan.addRepaymentScheduleInstallment(installment); - } - } + private Loan assembleApplication(final JsonElement element, final Long clientId, final Long groupId) { - loan.submitApplication(loanTermFrequency, loanTermFrequencyType, - command.getSubmittedOnDate(), command.getExpectedDisbursementDate(), - command.getRepaymentsStartingFromDate(), command.getInterestChargedFromDate(), - defaultLoanLifecycleStateMachine()); - - return loan; - } + final Long productId = fromApiJsonHelper.extractLongNamed("productId", element); + final Long fundId = fromApiJsonHelper.extractLongNamed("fundId", element); + final Long loanOfficerId = fromApiJsonHelper.extractLongNamed("loanOfficerId", element); + final Long transactionProcessingStrategyId = fromApiJsonHelper.extractLongNamed("transactionProcessingStrategyId", element); - public LoanProductRelatedDetail assembleLoanProductRelatedDetailFrom(final LoanApplicationCommand command, MonetaryCurrency currency) { - final BigDecimal defaultNominalInterestRatePerPeriod = command.getInterestRatePerPeriod(); - final PeriodFrequencyType interestPeriodFrequencyType = PeriodFrequencyType.fromInt(command.getInterestRateFrequencyType()); - - BigDecimal defaultAnnualNominalInterestRate = aprCalculator.calculateFrom(interestPeriodFrequencyType, command.getInterestRatePerPeriod()); - - final InterestMethod interestMethod = InterestMethod.fromInt(command.getInterestType()); - final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod.fromInt(command.getInterestCalculationPeriodType()); - - - final Integer defaultNumberOfInstallments = command.getNumberOfRepayments(); - final Integer repayEvery = command.getRepaymentEvery(); - final PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.fromInt(command.getRepaymentFrequencyType()); - - final AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(command.getAmortizationType()); - - LoanProductRelatedDetail loanRepaymentScheduleDetail = new LoanProductRelatedDetail(currency, - command.getPrincipal(), defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType, defaultAnnualNominalInterestRate, - interestMethod, interestCalculationPeriodMethod, - repayEvery, repaymentFrequencyType, defaultNumberOfInstallments, amortizationMethod, command.getInArrearsTolerance()); - return loanRepaymentScheduleDetail; - } + final LoanProduct loanProduct = this.loanProductRepository.findOne(productId); + if (loanProduct == null) { throw new LoanProductNotFoundException(productId); } - private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { - List allowedLoanStatuses = Arrays.asList(LoanStatus.values()); - return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses); - } - - public Fund findFundByIdIfProvided(final Long fundId) { - Fund fund = null; - if (fundId != null) { - fund = this.fundRepository.findOne(fundId); - if (fund == null) { - throw new FundNotFoundException(fundId); - } - } - return fund; - } - - public Staff findLoanOfficerByIdIfProvided(final Long loanOfficerId) { - Staff staff = null; - if (loanOfficerId != null) { - staff = this.staffRepository.findOne(loanOfficerId); - if (staff == null) { - throw new StaffNotFoundException(loanOfficerId); - } else if (staff.isNotLoanOfficer()) { - throw new StaffRoleException(loanOfficerId, - StaffRoleException.STAFF_ROLE.LOAN_OFFICER); - } - } - return staff; - } - - public LoanTransactionProcessingStrategy findStrategyByIdIfProvided(final Long transactionProcessingStrategyId) { - LoanTransactionProcessingStrategy strategy = null; - if (transactionProcessingStrategyId != null) { - strategy = this.loanTransactionProcessingStrategyRepository.findOne(transactionProcessingStrategyId); - if (strategy == null) { - throw new LoanTransactionProcessingStrategyNotFoundException(transactionProcessingStrategyId); - } - } - return strategy; - } + final Fund fund = findFundByIdIfProvided(fundId); + final Staff loanOfficer = findLoanOfficerByIdIfProvided(loanOfficerId); + final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy = findStrategyByIdIfProvided(transactionProcessingStrategyId); + + final Set loanCharges = this.loanChargeAssembler.fromParsedJson(element); + + final BigDecimal inArrearsTolerance = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("inArrearsTolerance", element); + LoanSchedule loanSchedule = this.loanScheduleAssembler.fromJson(element, inArrearsTolerance); + + Loan loanApplication = null; + if (clientId != null) { + final Client client = this.clientRepository.findOne(clientId); + if (client == null || client.isDeleted()) { throw new ClientNotFoundException(clientId); } + + loanApplication = Loan.newIndividualLoanApplication(client, loanProduct, fund, loanOfficer, loanTransactionProcessingStrategy, + loanSchedule, loanCharges); + } + + if (groupId != null) { + final Group group = this.groupRepository.findOne(groupId); + if (group == null || group.isDeleted()) { throw new GroupNotFoundException(groupId); } + + loanApplication = Loan.newGroupLoanApplication(group, loanProduct, fund, loanOfficer, loanTransactionProcessingStrategy, + loanSchedule, loanCharges); + } + + final String externalId = fromApiJsonHelper.extractStringNamed("externalId", element); + final LocalDate submittedOnDate = fromApiJsonHelper.extractLocalDateNamed("submittedOnDate", element); + + if (loanApplication == null) { throw new IllegalStateException("No loan application exists for either a client or group (or both)."); } + + loanApplication.loanApplicationSubmittal(loanSchedule, defaultLoanLifecycleStateMachine(), submittedOnDate, externalId); + + return loanApplication; + } + + private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { + List allowedLoanStatuses = Arrays.asList(LoanStatus.values()); + return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses); + } + + public Fund findFundByIdIfProvided(final Long fundId) { + Fund fund = null; + if (fundId != null) { + fund = this.fundRepository.findOne(fundId); + if (fund == null) { throw new FundNotFoundException(fundId); } + } + return fund; + } + + public Staff findLoanOfficerByIdIfProvided(final Long loanOfficerId) { + Staff staff = null; + if (loanOfficerId != null) { + staff = this.staffRepository.findOne(loanOfficerId); + if (staff == null) { + throw new StaffNotFoundException(loanOfficerId); + } else if (staff.isNotLoanOfficer()) { throw new StaffRoleException(loanOfficerId, StaffRoleException.STAFF_ROLE.LOAN_OFFICER); } + } + return staff; + } + + public LoanTransactionProcessingStrategy findStrategyByIdIfProvided(final Long transactionProcessingStrategyId) { + LoanTransactionProcessingStrategy strategy = null; + if (transactionProcessingStrategyId != null) { + strategy = this.loanTransactionProcessingStrategyRepository.findOne(transactionProcessingStrategyId); + if (strategy == null) { throw new LoanTransactionProcessingStrategyNotFoundException(transactionProcessingStrategyId); } + } + return strategy; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanChargeAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanChargeAssembler.java index 8308c3730..eaa718934 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanChargeAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanChargeAssembler.java @@ -2,81 +2,99 @@ package org.mifosplatform.portfolio.loanaccount.service; import java.math.BigDecimal; import java.util.HashSet; +import java.util.Locale; import java.util.Set; +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.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.command.LoanChargeCommand; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; import org.mifosplatform.portfolio.loanaccount.domain.LoanChargeRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + @Service public class LoanChargeAssembler { - private final ChargeRepository chargeRepository; - private final LoanChargeRepository loanChargeRepository; + private final FromJsonHelper fromApiJsonHelper; + private final ChargeRepository chargeRepository; + private final LoanChargeRepository loanChargeRepository; - @Autowired - public LoanChargeAssembler(final ChargeRepository chargeRepository, final LoanChargeRepository loanChargeRepository) { - this.chargeRepository = chargeRepository; - this.loanChargeRepository = loanChargeRepository; - } - - public Set assembleFrom(final LoanChargeCommand[] charges, final BigDecimal loanPrincipal) { - - final Set loanCharges = new HashSet(); - - if (charges != null) { - for (LoanChargeCommand loanChargeCommand : charges) { - if (loanChargeCommand.getId() == null) { - final Long chargeDefinitionId = loanChargeCommand.getChargeId(); - final Charge chargeDefinition = this.chargeRepository.findOne(chargeDefinitionId); - if (chargeDefinition == null || chargeDefinition.isDeleted()) { - throw new ChargeNotFoundException(chargeDefinitionId); - } - - if (!chargeDefinition.isActive()) { - throw new ChargeIsNotActiveException(chargeDefinitionId, chargeDefinition.getName()); - } - final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, loanChargeCommand, loanPrincipal); - loanCharges.add(loanCharge); - } else { - final Long loanChargeId = loanChargeCommand.getId(); - final LoanCharge loanCharge = this.loanChargeRepository.findOne(loanChargeId); - if (loanCharge == null) { - throw new LoanChargeNotFoundException(loanChargeId); - } - - loanCharge.update(loanChargeCommand, loanPrincipal); - - loanCharges.add(loanCharge); - } - } - } - - return loanCharges; - } + @Autowired + public LoanChargeAssembler(final FromJsonHelper fromApiJsonHelper, final ChargeRepository chargeRepository, + final LoanChargeRepository loanChargeRepository) { + this.fromApiJsonHelper = fromApiJsonHelper; + this.chargeRepository = chargeRepository; + this.loanChargeRepository = loanChargeRepository; + } - public Set assembleFrom(final LoanChargeCommand[] charges, final Set chargesInheritedFromProduct, final BigDecimal loanPrincipal) { - - Set loanCharges = new HashSet(); - - if (charges != null) { - loanCharges = assembleFrom(charges, loanPrincipal); - } - // TODO - kw - dont inherit charges from product by default, user may remove charges (that were default) during creation - expect - // charges to be passed along in request at time of creation -// else { -// for (Charge productCharge : chargesInheritedFromProduct) { -// loanCharges.add(LoanCharge.createNew(productCharge)); -// } -// } - - return loanCharges; - } + public Set fromParsedJson(final JsonElement element) { + + final Set loanCharges = new HashSet(); + + final BigDecimal principal = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("principal", element); + + if (element.isJsonObject()) { + final JsonObject topLevelJsonElement = element.getAsJsonObject(); + final String dateFormat = fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement); + final Locale locale = fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement); + if (topLevelJsonElement.has("charges") && topLevelJsonElement.get("charges").isJsonArray()) { + final JsonArray array = topLevelJsonElement.get("charges").getAsJsonArray(); + for (int i = 0; i < array.size(); i++) { + + final JsonObject loanChargeElement = array.get(i).getAsJsonObject(); + + final Long id = fromApiJsonHelper.extractLongNamed("id", loanChargeElement); + final Long chargeId = fromApiJsonHelper.extractLongNamed("chargeId", loanChargeElement); + final BigDecimal amount = fromApiJsonHelper.extractBigDecimalNamed("amount", loanChargeElement, locale); + final Integer chargeTimeType = fromApiJsonHelper.extractIntegerNamed("chargeTimeType", loanChargeElement, locale); + final Integer chargeCalculationType = fromApiJsonHelper.extractIntegerNamed("chargeCalculationType", loanChargeElement, + locale); + final LocalDate specifiedDueDate = fromApiJsonHelper.extractLocalDateNamed("specifiedDueDate", loanChargeElement, + 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()); } + ChargeTimeType chargeTime = null; + if (chargeTimeType != null) { + ChargeTimeType.fromInt(chargeTimeType); + } + ChargeCalculationType chargeCalculation = null; + if (chargeCalculationType != null) { + ChargeCalculationType.fromInt(chargeCalculationType); + } + final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, principal, amount, chargeTime, + chargeCalculation, specifiedDueDate); + loanCharges.add(loanCharge); + } else { + final Long loanChargeId = id; + final LoanCharge loanCharge = this.loanChargeRepository.findOne(loanChargeId); + if (loanCharge == null) { throw new LoanChargeNotFoundException(loanChargeId); } + + loanCharge.update(amount, specifiedDueDate, principal); + + loanCharges.add(loanCharge); + } + } + } else { + // no charges passed, use existing ones against loan + } + + } + + return loanCharges; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanProductRelatedDetailAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanProductRelatedDetailAssembler.java index 1c1b15a0e..6ba2b9909 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanProductRelatedDetailAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanProductRelatedDetailAssembler.java @@ -3,8 +3,8 @@ package org.mifosplatform.portfolio.loanaccount.service; import java.math.BigDecimal; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; -import org.mifosplatform.portfolio.loanaccount.loanschedule.command.CalculateLoanScheduleCommand; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator; +import org.mifosplatform.portfolio.loanaccount.loanschedule.query.CalculateLoanScheduleQuery; import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; @@ -18,48 +18,45 @@ import org.springframework.stereotype.Service; @Service public class LoanProductRelatedDetailAssembler { - - private final LoanProductRepository loanProductRepository; - private final AprCalculator aprCalculator; - @Autowired - public LoanProductRelatedDetailAssembler( - final LoanProductRepository loanProductRepository, - final AprCalculator aprCalculator) { - this.loanProductRepository = loanProductRepository; - this.aprCalculator = aprCalculator; - } + private final LoanProductRepository loanProductRepository; + private final AprCalculator aprCalculator; - public LoanProductRelatedDetail assembleFrom(final CalculateLoanScheduleCommand command) { - - final BigDecimal principalAmount = command.getPrincipal(); - final BigDecimal defaultNominalInterestRatePerPeriod = command.getInterestRatePerPeriod(); - final PeriodFrequencyType interestPeriodFrequencyType = PeriodFrequencyType.fromInt(command.getInterestRateFrequencyType()); - final InterestMethod interestMethod = InterestMethod.fromInt(command.getInterestType()); - final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod.fromInt(command.getInterestCalculationPeriodType()); - - final Integer defaultNumberOfInstallments = command.getNumberOfRepayments(); - final Integer repayEvery = command.getRepaymentEvery(); - final PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.fromInt(command.getRepaymentFrequencyType()); - - final AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(command.getAmortizationType()); - - final BigDecimal defaultAnnualNominalInterestRate = this.aprCalculator.calculateFrom(interestPeriodFrequencyType, defaultNominalInterestRatePerPeriod); - - final LoanProduct loanProduct = this.loanProductRepository.findOne(command.getProductId()); - if (loanProduct == null) { - throw new LoanProductNotFoundException(command.getProductId()); - } - - final MonetaryCurrency currency = loanProduct.getCurrency(); - - // in arrerars tolerance isnt relevant when auto-calculating a loan schedule - final BigDecimal inArrearsTolerance = BigDecimal.ZERO; - - return new LoanProductRelatedDetail(currency, principalAmount, - defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType, defaultAnnualNominalInterestRate, - interestMethod, interestCalculationPeriodMethod, - repayEvery, repaymentFrequencyType, defaultNumberOfInstallments, amortizationMethod, - inArrearsTolerance); - } + @Autowired + public LoanProductRelatedDetailAssembler(final LoanProductRepository loanProductRepository, final AprCalculator aprCalculator) { + this.loanProductRepository = loanProductRepository; + this.aprCalculator = aprCalculator; + } + + public LoanProductRelatedDetail assembleFrom(final CalculateLoanScheduleQuery command) { + + final BigDecimal principalAmount = command.getPrincipal(); + final BigDecimal defaultNominalInterestRatePerPeriod = command.getInterestRatePerPeriod(); + final PeriodFrequencyType interestPeriodFrequencyType = PeriodFrequencyType.fromInt(command.getInterestRateFrequencyType()); + final InterestMethod interestMethod = InterestMethod.fromInt(command.getInterestType()); + final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod.fromInt(command + .getInterestCalculationPeriodType()); + + final Integer defaultNumberOfInstallments = command.getNumberOfRepayments(); + final Integer repayEvery = command.getRepaymentEvery(); + final PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.fromInt(command.getRepaymentFrequencyType()); + + final AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(command.getAmortizationType()); + + final BigDecimal defaultAnnualNominalInterestRate = this.aprCalculator.calculateFrom(interestPeriodFrequencyType, + defaultNominalInterestRatePerPeriod); + + final LoanProduct loanProduct = this.loanProductRepository.findOne(command.getProductId()); + if (loanProduct == null) { throw new LoanProductNotFoundException(command.getProductId()); } + + final MonetaryCurrency currency = loanProduct.getCurrency(); + + // in arrerars tolerance isnt relevant when auto-calculating a loan + // schedule + final BigDecimal inArrearsTolerance = BigDecimal.ZERO; + + return new LoanProductRelatedDetail(currency, principalAmount, defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType, + defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery, repaymentFrequencyType, + defaultNumberOfInstallments, amortizationMethod, inArrearsTolerance); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformService.java index d87dcbf0a..80d0c3d49 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformService.java @@ -1,73 +1,62 @@ package org.mifosplatform.portfolio.loanaccount.service; +import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.EntityIdentifier; import org.mifosplatform.organisation.staff.command.BulkTransferLoanOfficerCommand; import org.mifosplatform.portfolio.loanaccount.command.AdjustLoanTransactionCommand; -import org.mifosplatform.portfolio.loanaccount.command.LoanApplicationCommand; import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand; -import org.mifosplatform.portfolio.loanaccount.command.LoanStateTransitionCommand; import org.mifosplatform.portfolio.loanaccount.command.LoanTransactionCommand; -import org.mifosplatform.portfolio.loanaccount.command.UndoStateTransitionCommand; import org.springframework.security.access.prepost.PreAuthorize; public interface LoanWritePlatformService { - - @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'CREATE_LOAN', 'CREATEHISTORIC_LOAN')") - EntityIdentifier submitLoanApplication(LoanApplicationCommand command); - - @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'UPDATE_LOAN', 'UPDATEHISTORIC_LOAN')") - EntityIdentifier modifyLoanApplication(LoanApplicationCommand command); - - @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'DELETE_LOAN')") - EntityIdentifier deleteLoan(Long loanId); - - @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'APPROVE_LOAN', 'APPROVEINPAST_LOAN')") - EntityIdentifier approveLoanApplication(LoanStateTransitionCommand command); - @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'APPROVALUNDO_LOAN')") - EntityIdentifier undoLoanApproval(UndoStateTransitionCommand command); + EntityIdentifier submitLoanApplication(JsonCommand command); - @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'REJECT_LOAN', 'REJECTINPAST_LOAN')") - EntityIdentifier rejectLoan(LoanStateTransitionCommand command); + EntityIdentifier modifyLoanApplication(Long loanId, JsonCommand command); - @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'WITHDRAW_LOAN', 'WITHDRAWINPAST_LOAN')") - EntityIdentifier withdrawLoan(LoanStateTransitionCommand command); - - @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'DISBURSE_LOAN', 'DISBURSEINPAST_LOAN')") - EntityIdentifier disburseLoan(LoanStateTransitionCommand command); + EntityIdentifier deleteLoan(Long loanId); - @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'DISBURSALUNDO_LOAN')") - public EntityIdentifier undoLoanDisbursal(UndoStateTransitionCommand command); - - @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'REPAYMENT_LOAN', 'REPAYMENTINPAST_LOAN')") - public EntityIdentifier makeLoanRepayment(LoanTransactionCommand command); + EntityIdentifier approveLoanApplication(Long loanId, JsonCommand command); - @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'ADJUST_LOAN')") - EntityIdentifier adjustLoanTransaction(AdjustLoanTransactionCommand command); + EntityIdentifier undoLoanApplicationApproval(Long loanId, JsonCommand command); - @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'WAIVEINTERESTPORTION_LOAN')") - EntityIdentifier waiveInterestOnLoan(LoanTransactionCommand command); - - @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'WRITEOFF_LOAN')") - EntityIdentifier writeOff(LoanTransactionCommand command); - - @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'CLOSE_LOAN')") - EntityIdentifier closeLoan(LoanTransactionCommand command); + EntityIdentifier rejectLoanApplication(Long loanId, JsonCommand command); - @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'CLOSEASRESCHEDULED_LOAN')") - EntityIdentifier closeAsRescheduled(LoanTransactionCommand command); - - @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'CREATE_LOANCHARGE')") + EntityIdentifier applicantWithdrawsFromLoanApplication(Long loanId, JsonCommand command); + + EntityIdentifier disburseLoan(Long loanId, JsonCommand command); + + public EntityIdentifier undoLoanDisbursal(Long loanId, JsonCommand command); + + @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'REPAYMENT_LOAN', 'REPAYMENTINPAST_LOAN')") + public EntityIdentifier makeLoanRepayment(LoanTransactionCommand command); + + @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'ADJUST_LOAN')") + EntityIdentifier adjustLoanTransaction(AdjustLoanTransactionCommand command); + + @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'WAIVEINTERESTPORTION_LOAN')") + EntityIdentifier waiveInterestOnLoan(LoanTransactionCommand command); + + @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'WRITEOFF_LOAN')") + EntityIdentifier writeOff(LoanTransactionCommand command); + + @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'CLOSE_LOAN')") + EntityIdentifier closeLoan(LoanTransactionCommand command); + + @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'CLOSEASRESCHEDULED_LOAN')") + EntityIdentifier closeAsRescheduled(LoanTransactionCommand command); + + @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'CREATE_LOANCHARGE')") EntityIdentifier addLoanCharge(LoanChargeCommand command); - @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'UPDATE_LOANCHARGE')") + @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'UPDATE_LOANCHARGE')") EntityIdentifier updateLoanCharge(LoanChargeCommand command); @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'DELETE_LOANCHARGE')") EntityIdentifier deleteLoanCharge(final Long loanId, final Long loanChargeId); - + @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'WAIVE_LOANCHARGE')") - EntityIdentifier waiveLoanCharge(LoanChargeCommand command); + EntityIdentifier waiveLoanCharge(LoanChargeCommand command); @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'BULKREASSIGN_LOAN')") EntityIdentifier bulkLoanReassignment(final BulkTransferLoanOfficerCommand command); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 6525193c7..f2c2f5680 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -1,16 +1,20 @@ package org.mifosplatform.portfolio.loanaccount.service; -import java.math.BigDecimal; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.Set; import org.apache.commons.lang.StringUtils; import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.api.JsonQuery; import org.mifosplatform.infrastructure.core.data.EntityIdentifier; +import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; import org.mifosplatform.infrastructure.security.exception.NoAuthorizationException; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; +import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; +import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepository; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.organisation.staff.command.BulkTransferLoanOfficerCommand; @@ -30,20 +34,16 @@ import org.mifosplatform.portfolio.fund.domain.Fund; import org.mifosplatform.portfolio.loanaccount.command.AdjustLoanTransactionCommand; import org.mifosplatform.portfolio.loanaccount.command.AdjustLoanTransactionCommandValidator; import org.mifosplatform.portfolio.loanaccount.command.LoanApplicationCommand; -import org.mifosplatform.portfolio.loanaccount.command.LoanApplicationCommandValidator; import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand; import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommandValidator; import org.mifosplatform.portfolio.loanaccount.command.LoanStateTransitionCommand; -import org.mifosplatform.portfolio.loanaccount.command.LoanStateTransitionCommandValidator; import org.mifosplatform.portfolio.loanaccount.command.LoanTransactionCommand; import org.mifosplatform.portfolio.loanaccount.command.LoanTransactionCommandValidator; -import org.mifosplatform.portfolio.loanaccount.command.UndoStateTransitionCommand; import org.mifosplatform.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine; import org.mifosplatform.portfolio.loanaccount.domain.Loan; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; import org.mifosplatform.portfolio.loanaccount.domain.LoanChargeRepository; import org.mifosplatform.portfolio.loanaccount.domain.LoanLifecycleStateMachine; -import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepository; import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction; @@ -52,570 +52,542 @@ import org.mifosplatform.portfolio.loanaccount.exception.LoanNotFoundException; import org.mifosplatform.portfolio.loanaccount.exception.LoanNotInSubmittedAndPendingApprovalStateCannotBeDeleted; import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentException; import org.mifosplatform.portfolio.loanaccount.exception.LoanTransactionNotFoundException; -import org.mifosplatform.portfolio.loanaccount.loanschedule.command.CalculateLoanScheduleCommand; import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData; -import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData; -import org.mifosplatform.portfolio.loanaccount.loanschedule.service.CalculationPlatformService; +import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator; +import org.mifosplatform.portfolio.loanaccount.loanschedule.query.CalculateLoanScheduleQuery; +import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleCalculationPlatformService; +import org.mifosplatform.portfolio.loanaccount.serialization.CalculateLoanScheduleQueryFromApiJsonDeserializer; +import org.mifosplatform.portfolio.loanaccount.serialization.LoanApplicationCommandFromApiJsonDeserializer; +import org.mifosplatform.portfolio.loanaccount.serialization.LoanStateTransitionCommandFromApiJsonDeserializer; import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct; import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRepository; import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy; -import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.loanproduct.exception.InvalidCurrencyException; import org.mifosplatform.portfolio.loanproduct.exception.LoanProductNotFoundException; import org.mifosplatform.useradministration.domain.AppUser; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import com.google.gson.JsonElement; + @Service public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatformService { - private final PlatformSecurityContext context; - private final LoanRepository loanRepository; - private final NoteRepository noteRepository; - private final CalculationPlatformService calculationPlatformService; - private final LoanTransactionRepository loanTransactionRepository; - private final LoanAssembler loanAssembler; - private final ClientRepository clientRepository; - private final LoanProductRepository loanProductRepository; + private final PlatformSecurityContext context; + private final FromJsonHelper fromJsonHelper; + private final LoanApplicationCommandFromApiJsonDeserializer fromApiJsonDeserializer; + private final CalculateLoanScheduleQueryFromApiJsonDeserializer calculateLoanScheduleQueryFromApiJsonDeserializer; + private final LoanStateTransitionCommandFromApiJsonDeserializer loanStateTransitionCommandFromApiJsonDeserializer; + private final LoanRepository loanRepository; + private final NoteRepository noteRepository; + private final LoanScheduleCalculationPlatformService calculationPlatformService; + private final LoanTransactionRepository loanTransactionRepository; + private final LoanAssembler loanAssembler; + private final ClientRepository clientRepository; + private final LoanProductRepository loanProductRepository; private final ChargeRepository chargeRepository; private final LoanChargeRepository loanChargeRepository; - private final LoanChargeAssembler loanChargeAssembler; + private final LoanChargeAssembler loanChargeAssembler; + private final AprCalculator aprCalculator; + private final ApplicationCurrencyRepository applicationCurrencyRepository; - @Autowired - public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, - final LoanAssembler loanAssembler, final LoanChargeAssembler loanChargeAssembler, - final LoanRepository loanRepository, final LoanTransactionRepository loanTransactionRepository, - final NoteRepository noteRepository, final CalculationPlatformService calculationPlatformService, - final ClientRepository clientRepository, final LoanProductRepository loanProductRepository, - final ChargeRepository chargeRepository, final LoanChargeRepository loanChargeRepository) { - this.context = context; - this.loanAssembler = loanAssembler; - this.loanChargeAssembler = loanChargeAssembler; - this.loanRepository = loanRepository; - this.loanTransactionRepository = loanTransactionRepository; - this.noteRepository = noteRepository; - this.calculationPlatformService = calculationPlatformService; - this.clientRepository = clientRepository; - this.loanProductRepository = loanProductRepository; + @Autowired + public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper, + final LoanApplicationCommandFromApiJsonDeserializer fromApiJsonDeserializer, + final LoanStateTransitionCommandFromApiJsonDeserializer loanStateTransitionCommandFromApiJsonDeserializer, + final CalculateLoanScheduleQueryFromApiJsonDeserializer calculateLoanScheduleQueryFromApiJsonDeserializer, + final AprCalculator aprCalculator, final LoanAssembler loanAssembler, final LoanChargeAssembler loanChargeAssembler, + final LoanRepository loanRepository, final LoanTransactionRepository loanTransactionRepository, + final NoteRepository noteRepository, final LoanScheduleCalculationPlatformService calculationPlatformService, + final ClientRepository clientRepository, final LoanProductRepository loanProductRepository, + final ChargeRepository chargeRepository, final LoanChargeRepository loanChargeRepository, + final ApplicationCurrencyRepository applicationCurrencyRepository) { + this.context = context; + this.fromJsonHelper = fromJsonHelper; + this.fromApiJsonDeserializer = fromApiJsonDeserializer; + this.loanStateTransitionCommandFromApiJsonDeserializer = loanStateTransitionCommandFromApiJsonDeserializer; + this.calculateLoanScheduleQueryFromApiJsonDeserializer = calculateLoanScheduleQueryFromApiJsonDeserializer; + this.aprCalculator = aprCalculator; + this.loanAssembler = loanAssembler; + this.loanChargeAssembler = loanChargeAssembler; + this.loanRepository = loanRepository; + this.loanTransactionRepository = loanTransactionRepository; + this.noteRepository = noteRepository; + this.calculationPlatformService = calculationPlatformService; + this.clientRepository = clientRepository; + this.loanProductRepository = loanProductRepository; this.chargeRepository = chargeRepository; this.loanChargeRepository = loanChargeRepository; - } - - private boolean isBeforeToday(final LocalDate date) { - return date.isBefore(new LocalDate()); - } - - private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { - List allowedLoanStatuses = Arrays.asList(LoanStatus.values()); - return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses); - } - - @Transactional - @Override - public EntityIdentifier submitLoanApplication(final LoanApplicationCommand command) { - - AppUser currentUser = context.authenticatedUser(); - - LoanApplicationCommandValidator validator = new LoanApplicationCommandValidator(command); - validator.validate(); - - LocalDate submittedOn = command.getSubmittedOnDate(); - if (this.isBeforeToday(submittedOn) && currentUser.hasNotPermissionForAnyOf("ALL_FUNCTIONS", "CREATEHISTORIC_LOAN", "PORTFOLIO_MANAGEMENT_SUPER_USER")) { - throw new NoAuthorizationException("Cannot add backdated loan."); - } - - Loan loan = loanAssembler.assembleFrom(command); - - this.loanRepository.save(loan); - - if (StringUtils.isNotBlank(command.getSubmittedOnNote())) { - Note note = Note.loanNote(loan, command.getSubmittedOnNote()); - this.noteRepository.save(note); - } - - return new EntityIdentifier(loan.getId()); - } - - @Transactional - @Override - public EntityIdentifier modifyLoanApplication(final LoanApplicationCommand command) { - AppUser currentUser = context.authenticatedUser(); - - LoanApplicationCommandValidator validator = new LoanApplicationCommandValidator(command); - validator.validate(); - - final LocalDate submittedOn = command.getSubmittedOnDate(); - if (this.isBeforeToday(submittedOn) && currentUser.hasNotPermissionForAnyOf("ALL_FUNCTIONS", "UPDATEHISTORIC_LOAN", "PORTFOLIO_MANAGEMENT_SUPER_USER")) { - throw new NoAuthorizationException("Cannot modify backdated loan."); - } - - final Loan loan = retrieveLoanBy(command.getLoanId()); - - final LoanProduct loanProduct = this.loanProductRepository.findOne(command.getProductId()); - if (loanProduct == null) { - throw new LoanProductNotFoundException(command.getProductId()); - } - - final Client client = this.clientRepository.findOne(command.getClientId()); - if (client == null || client.isDeleted()) { - throw new ClientNotFoundException(command.getClientId()); - } - - final Fund fund = this.loanAssembler.findFundByIdIfProvided(command.getFundId()); - final LoanTransactionProcessingStrategy strategy = this.loanAssembler.findStrategyByIdIfProvided(command.getTransactionProcessingStrategyId()); - final Set charges = this.loanChargeAssembler.assembleFrom(command.getCharges(), loanProduct.getCharges(), loan.getPrincpal().getAmount()); - - final LoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanSchedule(command.toCalculateLoanScheduleCommand()); - - // FIXME - kw - restrict ability to modify loan to modifying the 'loan application' stage - once it has been disbursed, should be allowed to use this feature - // - other facilities are in place such as 'undo disbursal' - loan.modifyLoanApplication(command, client, loanProduct, fund, strategy, loanSchedule, charges, defaultLoanLifecycleStateMachine()); - - this.loanRepository.save(loan); - - if (StringUtils.isNotBlank(command.getSubmittedOnNote())) { - Note note = Note.loanNote(loan, command.getSubmittedOnNote()); - this.noteRepository.save(note); - } - - return new EntityIdentifier(loan.getId()); - } - - @Transactional - @Override - public EntityIdentifier deleteLoan(final Long loanId) { - - context.authenticatedUser(); - - final Loan loan = retrieveLoanBy(loanId); - - if (loan.isNotSubmittedAndPendingApproval()) { - throw new LoanNotInSubmittedAndPendingApprovalStateCannotBeDeleted(loanId); - } - - List relatedNotes = this.noteRepository.findByLoanId(loan.getId()); - this.noteRepository.deleteInBatch(relatedNotes); - - this.loanRepository.delete(loanId); - - return new EntityIdentifier(loanId); - } - - @Transactional - @Override - public EntityIdentifier approveLoanApplication(final LoanStateTransitionCommand command) { - - AppUser currentUser = context.authenticatedUser(); - - LoanStateTransitionCommandValidator validator = new LoanStateTransitionCommandValidator(command); - validator.validate(); - - final Loan loan = retrieveLoanBy(command.getLoanId()); - - LocalDate eventDate = command.getEventDate(); - if (this.isBeforeToday(eventDate) && currentUser.canNotApproveLoanInPast()) { - throw new NoAuthorizationException("User has no authority to approve loan with a date in the past."); - } - - loan.approve(eventDate, defaultLoanLifecycleStateMachine()); - this.loanRepository.save(loan); - - String noteText = command.getNote(); - if (StringUtils.isNotBlank(noteText)) { - Note note = Note.loanNote(loan, noteText); - this.noteRepository.save(note); - } - - return new EntityIdentifier(loan.getId()); - } - - @Transactional - @Override - public EntityIdentifier undoLoanApproval(final UndoStateTransitionCommand command) { - - context.authenticatedUser(); - - final Loan loan = retrieveLoanBy(command.getLoanId()); - - loan.undoApproval(defaultLoanLifecycleStateMachine()); - this.loanRepository.save(loan); - - String noteText = command.getNote(); - if (StringUtils.isNotBlank(noteText)) { - Note note = Note.loanNote(loan, noteText); - this.noteRepository.save(note); - } - - return new EntityIdentifier(loan.getId()); - } - - @Transactional - @Override - public EntityIdentifier rejectLoan(final LoanStateTransitionCommand command) { - - AppUser currentUser = (AppUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); - - LoanStateTransitionCommandValidator validator = new LoanStateTransitionCommandValidator(command); - validator.validate(); - - final Loan loan = retrieveLoanBy(command.getLoanId()); - - LocalDate eventDate = command.getEventDate(); - if (this.isBeforeToday(eventDate) && currentUser.canNotRejectLoanInPast()) { - throw new NoAuthorizationException("User has no authority to reject loan with a date in the past."); - } - - loan.reject(eventDate, defaultLoanLifecycleStateMachine()); - this.loanRepository.save(loan); - - String noteText = command.getNote(); - if (StringUtils.isNotBlank(noteText)) { - Note note = Note.loanNote(loan, noteText); - this.noteRepository.save(note); - } - - return new EntityIdentifier(loan.getId()); - } - - @Transactional - @Override - public EntityIdentifier withdrawLoan(final LoanStateTransitionCommand command) { - - AppUser currentUser = context.authenticatedUser(); - - LoanStateTransitionCommandValidator validator = new LoanStateTransitionCommandValidator(command); - validator.validate(); - - final Loan loan = retrieveLoanBy(command.getLoanId()); - - LocalDate eventDate = command.getEventDate(); - if (this.isBeforeToday(eventDate) && currentUser.canNotWithdrawByClientLoanInPast()) { - throw new NoAuthorizationException("User has no authority to mark loan as withdrawn by client with a date in the past."); - } - - loan.withdraw(eventDate, defaultLoanLifecycleStateMachine()); - this.loanRepository.save(loan); - - String noteText = command.getNote(); - if (StringUtils.isNotBlank(noteText)) { - Note note = Note.loanNote(loan, noteText); - this.noteRepository.save(note); - } - - return new EntityIdentifier(loan.getId()); - } - - @Transactional - @Override - public EntityIdentifier disburseLoan(final LoanStateTransitionCommand command) { - - AppUser currentUser = context.authenticatedUser(); - - LoanStateTransitionCommandValidator validator = new LoanStateTransitionCommandValidator(command); - validator.validate(); - - final Loan loan = retrieveLoanBy(command.getLoanId()); - - String noteText = command.getNote(); - LocalDate actualDisbursementDate = command.getEventDate(); - if (this.isBeforeToday(actualDisbursementDate) && currentUser.canNotDisburseLoanInPast()) { - throw new NoAuthorizationException("User has no authority to disburse loan with a date in the past."); - } - - if (loan.isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate)) { - - LocalDate repaymentsStartingFromDate = loan.getExpectedFirstRepaymentOnDate(); - LocalDate interestCalculatedFromDate = loan.getInterestChargedFromDate(); - - BigDecimal principalAsDecimal = loan.repaymentScheduleDetail().getPrincipal().getAmount(); - BigDecimal interestRatePerYear = loan.repaymentScheduleDetail().getAnnualNominalInterestRate(); - Integer numberOfInstallments = loan.repaymentScheduleDetail().getNumberOfRepayments(); - - Integer repaidEvery = loan.repaymentScheduleDetail().getRepayEvery(); - Integer selectedRepaymentFrequency = loan.repaymentScheduleDetail().getRepaymentPeriodFrequencyType().getValue(); - Integer selectedAmortizationMethod = loan.repaymentScheduleDetail().getAmortizationMethod().getValue(); - - // FIXME - KW - use values from loan table here instead of inferring. - Integer loanTermFrequency = repaidEvery * numberOfInstallments; - Integer loanTermFrequencyType = selectedRepaymentFrequency; - - // use annual percentage rate to re-calculate loan schedule for late disbursement - BigDecimal interestRatePerPeriod = interestRatePerYear; - Integer interestRateFrequencyMethod = PeriodFrequencyType.YEARS.getValue(); - - Integer interestMethod = loan.repaymentScheduleDetail().getInterestMethod().getValue(); - Integer interestCalculationInPeriod = loan.repaymentScheduleDetail().getInterestCalculationPeriodMethod().getValue(); - - Set loanCharges = loan.getCharges(); - List commands = new ArrayList(); - for (LoanCharge loanCharge : loanCharges) { - commands.add(loanCharge.toCommand()); - } - - LoanChargeCommand[] loanChargeCommands = commands.toArray(new LoanChargeCommand[commands.size()]); - - CalculateLoanScheduleCommand calculateCommand = new CalculateLoanScheduleCommand( - loan.loanProduct().getId(), - principalAsDecimal, - interestRatePerPeriod, interestRateFrequencyMethod, interestMethod, interestCalculationInPeriod, - repaidEvery, selectedRepaymentFrequency, numberOfInstallments, selectedAmortizationMethod, - loanTermFrequency, loanTermFrequencyType, - actualDisbursementDate, repaymentsStartingFromDate, interestCalculatedFromDate, loanChargeCommands); - - LoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanSchedule(calculateCommand); - - List modifiedLoanRepaymentSchedule = new ArrayList(); - - for (LoanSchedulePeriodData scheduledLoanPeriod : loanSchedule.getPeriods()) { - if (scheduledLoanPeriod.isRepaymentPeriod()) { - LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment( - loan, - scheduledLoanPeriod.periodNumber(), - scheduledLoanPeriod.periodFromDate(), - scheduledLoanPeriod.periodDueDate(), - scheduledLoanPeriod.principalDue(), - scheduledLoanPeriod.interestDue(), - scheduledLoanPeriod.feeChargesDue(), - scheduledLoanPeriod.penaltyChargesDue()); - - modifiedLoanRepaymentSchedule.add(installment); - } - } - loan.disburseWithModifiedRepaymentSchedule(actualDisbursementDate, modifiedLoanRepaymentSchedule, defaultLoanLifecycleStateMachine()); - } else { - loan.disburse(actualDisbursementDate, defaultLoanLifecycleStateMachine(), true); - } - - this.loanRepository.save(loan); - - if (StringUtils.isNotBlank(noteText)) { - Note note = Note.loanNote(loan, noteText); - this.noteRepository.save(note); - } - - return new EntityIdentifier(loan.getId()); - } - - @Transactional - @Override - public EntityIdentifier undoLoanDisbursal(final UndoStateTransitionCommand command) { - - context.authenticatedUser(); - - Loan loan = this.loanRepository.findOne(command.getLoanId()); - if (loan == null) { - throw new LoanNotFoundException(command.getLoanId()); - } - - loan.undoDisbursal(defaultLoanLifecycleStateMachine()); - - this.loanRepository.save(loan); - - String noteText = command.getNote(); - if (StringUtils.isNotBlank(noteText)) { - Note note = Note.loanNote(loan, noteText); - this.noteRepository.save(note); - } - - return new EntityIdentifier(loan.getId()); - } - - @Transactional - @Override - public EntityIdentifier makeLoanRepayment(final LoanTransactionCommand command) { - - AppUser currentUser = context.authenticatedUser(); - - LoanTransactionCommandValidator validator = new LoanTransactionCommandValidator(command); - validator.validate(); - - Loan loan = this.loanRepository.findOne(command.getLoanId()); - if (loan == null) { - throw new LoanNotFoundException(command.getLoanId()); - } - - LocalDate transactionDate = command.getTransactionDate(); - if (this.isBeforeToday(transactionDate) && currentUser.canNotMakeRepaymentOnLoanInPast()) { - throw new NoAuthorizationException("error.msg.no.permission.to.make.repayment.on.loan.in.past"); - } - - Money repayment = Money.of(loan.repaymentScheduleDetail() - .getPrincipal().getCurrency(), - command.getTransactionAmount()); - - LoanTransaction loanRepayment = LoanTransaction.repayment(repayment, transactionDate); - loan.makeRepayment(loanRepayment, defaultLoanLifecycleStateMachine()); - this.loanTransactionRepository.save(loanRepayment); - this.loanRepository.save(loan); - - String noteText = command.getNote(); - if (StringUtils.isNotBlank(noteText)) { - Note note = Note.loanTransactionNote(loan, loanRepayment, noteText); - this.noteRepository.save(note); - } - - return new EntityIdentifier(loan.getId()); - } - - @Transactional - @Override - public EntityIdentifier adjustLoanTransaction(final AdjustLoanTransactionCommand command) { - - context.authenticatedUser(); - - AdjustLoanTransactionCommandValidator validator = new AdjustLoanTransactionCommandValidator(command); - validator.validate(); - - Loan loan = this.loanRepository.findOne(command.getLoanId()); - if (loan == null) { - throw new LoanNotFoundException(command.getLoanId()); - } - - LoanTransaction transactionToAdjust = this.loanTransactionRepository.findOne(command.getTransactionId()); - if (transactionToAdjust == null) { - throw new LoanTransactionNotFoundException(command.getTransactionId()); - } - - final MonetaryCurrency currency = loan.repaymentScheduleDetail().getPrincipal().getCurrency(); - final Money transactionAmount = Money.of(currency, command.getTransactionAmount()); - - // adjustment is only supported for repayments and waivers at present - LocalDate transactionDate = command.getTransactionDate(); - LoanTransaction newTransactionDetail = LoanTransaction.repayment(transactionAmount, transactionDate); - if (transactionToAdjust.isInterestWaiver()) { - newTransactionDetail = LoanTransaction.waiver(loan, transactionAmount, transactionDate); - } - - loan.adjustExistingTransaction(transactionToAdjust, newTransactionDetail, defaultLoanLifecycleStateMachine()); - - if (newTransactionDetail.isGreaterThanZero(currency)) { - this.loanTransactionRepository.save(newTransactionDetail); - } - - this.loanRepository.save(loan); - - String noteText = command.getNote(); - if (StringUtils.isNotBlank(noteText)) { - Note note = Note.loanTransactionNote(loan, newTransactionDetail, noteText); - this.noteRepository.save(note); - } - - return new EntityIdentifier(loan.getId()); - } - - @Transactional - @Override - public EntityIdentifier waiveInterestOnLoan(final LoanTransactionCommand command) { - - context.authenticatedUser(); - - final LoanTransactionCommandValidator validator = new LoanTransactionCommandValidator(command); - validator.validate(); - - final Loan loan = this.loanRepository.findOne(command.getLoanId()); - if (loan == null) { - throw new LoanNotFoundException(command.getLoanId()); - } - - final LoanTransaction waiveTransaction = loan.waiveInterest(command.getTransactionAmount(), command.getTransactionDate(), defaultLoanLifecycleStateMachine()); - - 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 EntityIdentifier(loan.getId()); - } - - @Transactional - @Override - public EntityIdentifier writeOff(final LoanTransactionCommand command) { - context.authenticatedUser(); - - final LoanTransactionCommandValidator validator = new LoanTransactionCommandValidator(command); - validator.validateNonMonetaryTransaction(); - - final Loan loan = this.loanRepository.findOne(command.getLoanId()); - if (loan == null) { - throw new LoanNotFoundException(command.getLoanId()); - } - - final LoanTransaction writeoff = loan.closeAsWrittenOff(command.getTransactionDate(), defaultLoanLifecycleStateMachine()); - - this.loanTransactionRepository.save(writeoff); - this.loanRepository.save(loan); - - final String noteText = command.getNote(); - if (StringUtils.isNotBlank(noteText)) { - final Note note = Note.loanTransactionNote(loan, writeoff, noteText); - this.noteRepository.save(note); - } - - return new EntityIdentifier(loan.getId()); - } - - @Transactional - @Override - public EntityIdentifier closeLoan(final LoanTransactionCommand command) { - - context.authenticatedUser(); - - final LoanTransactionCommandValidator validator = new LoanTransactionCommandValidator(command); - validator.validateNonMonetaryTransaction(); - - final Loan loan = this.loanRepository.findOne(command.getLoanId()); - if (loan == null) { - throw new LoanNotFoundException(command.getLoanId()); - } - - final LoanTransaction possibleClosingTransaction = loan.close(command.getTransactionDate(), defaultLoanLifecycleStateMachine()); - if (possibleClosingTransaction != null) { - this.loanTransactionRepository.save(possibleClosingTransaction); - } - this.loanRepository.save(loan); - - final String noteText = command.getNote(); - if (StringUtils.isNotBlank(noteText)) { - final Note note = Note.loanNote(loan, noteText); - this.noteRepository.save(note); - } - - return new EntityIdentifier(loan.getId()); - } - - @Transactional - @Override - public EntityIdentifier closeAsRescheduled(final LoanTransactionCommand command) { - context.authenticatedUser(); - - final LoanTransactionCommandValidator validator = new LoanTransactionCommandValidator(command); - validator.validateNonMonetaryTransaction(); - - final Loan loan = this.loanRepository.findOne(command.getLoanId()); - if (loan == null) { - throw new LoanNotFoundException(command.getLoanId()); - } - - loan.closeAsMarkedForReschedule(command.getTransactionDate(), defaultLoanLifecycleStateMachine()); - - this.loanRepository.save(loan); - - final String noteText = command.getNote(); - if (StringUtils.isNotBlank(noteText)) { - final Note note = Note.loanNote(loan, noteText); - this.noteRepository.save(note); - } - - return new EntityIdentifier(loan.getId()); - } - - @Transactional + this.applicationCurrencyRepository = applicationCurrencyRepository; + } + + private boolean isBeforeToday(final LocalDate date) { + return date.isBefore(new LocalDate()); + } + + private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { + List allowedLoanStatuses = Arrays.asList(LoanStatus.values()); + return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses); + } + + @Transactional + @Override + public EntityIdentifier submitLoanApplication(final JsonCommand command) { + + context.authenticatedUser(); + + LoanApplicationCommand loanApplicationCommand = this.fromApiJsonDeserializer.commandFromApiJson(command.json()); + loanApplicationCommand.validate(); + + CalculateLoanScheduleQuery calculateLoanScheduleQuery = this.calculateLoanScheduleQueryFromApiJsonDeserializer + .commandFromApiJson(command.json()); + calculateLoanScheduleQuery.validate(); + + final Loan newLoanApplication = loanAssembler.assembleFrom(command); + + this.loanRepository.save(newLoanApplication); + + final String submittedOnNote = command.stringValueOfParameterNamed("submittedOnNote"); + if (StringUtils.isNotBlank(submittedOnNote)) { + Note note = Note.loanNote(newLoanApplication, submittedOnNote); + this.noteRepository.save(note); + } + + return EntityIdentifier.resourceResult(newLoanApplication.getId(), command.commandId()); + } + + @Transactional + @Override + public EntityIdentifier modifyLoanApplication(final Long loanId, final JsonCommand command) { + + context.authenticatedUser(); + + LoanApplicationCommand loanApplicationCommand = this.fromApiJsonDeserializer.commandFromApiJson(command.json()); + loanApplicationCommand.validate(); + + CalculateLoanScheduleQuery calculateLoanScheduleQuery = this.calculateLoanScheduleQueryFromApiJsonDeserializer + .commandFromApiJson(command.json()); + calculateLoanScheduleQuery.validate(); + + final Loan existingLoanApplication = retrieveLoanBy(loanId); + + final Map changes = existingLoanApplication.modifyLoanApplication(command, loanApplicationCommand.getCharges(), + this.aprCalculator); + + final String clientIdParamName = "clientId"; + if (changes.containsKey(clientIdParamName)) { + final Long clientId = command.longValueOfParameterNamed(clientIdParamName); + final Client client = this.clientRepository.findOne(clientId); + if (client == null || client.isDeleted()) { throw new ClientNotFoundException(clientId); } + + existingLoanApplication.updateClient(client); + } + + final String productIdParamName = "productId"; + if (changes.containsKey(productIdParamName)) { + final Long productId = command.longValueOfParameterNamed(productIdParamName); + final LoanProduct loanProduct = this.loanProductRepository.findOne(productId); + if (loanProduct == null) { throw new LoanProductNotFoundException(productId); } + + existingLoanApplication.updateLoanProduct(loanProduct); + } + + final String fundIdParamName = "fundId"; + if (changes.containsKey(fundIdParamName)) { + final Long fundId = command.longValueOfParameterNamed(fundIdParamName); + final Fund fund = this.loanAssembler.findFundByIdIfProvided(fundId); + + existingLoanApplication.updateFund(fund); + } + + final String strategyIdParamName = "transactionProcessingStrategyId"; + if (changes.containsKey(strategyIdParamName)) { + final Long strategyId = command.longValueOfParameterNamed(strategyIdParamName); + final LoanTransactionProcessingStrategy strategy = this.loanAssembler.findStrategyByIdIfProvided(strategyId); + + existingLoanApplication.updateTransactionProcessingStrategy(strategy); + } + + final String chargesParamName = "charges"; + if (changes.containsKey(chargesParamName)) { + final Set loanCharges = this.loanChargeAssembler.fromParsedJson(command.parsedJson()); + existingLoanApplication.updateLoanCharges(loanCharges); + } + + if (changes.containsKey("recalculateLoanSchedule")) { + changes.remove("recalculateLoanSchedule"); + + final JsonElement parsedQuery = this.fromJsonHelper.parse(command.json()); + final JsonQuery query = JsonQuery.from(command.json(), parsedQuery, this.fromJsonHelper); + + final LoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanSchedule(query); + existingLoanApplication.updateLoanSchedule(loanSchedule); + existingLoanApplication.updateLoanScheduleDependentDerivedFields(); + } + + this.loanRepository.save(existingLoanApplication); + + final String submittedOnNote = command.stringValueOfParameterNamed("submittedOnNote"); + if (StringUtils.isNotBlank(submittedOnNote)) { + Note note = Note.loanNote(existingLoanApplication, submittedOnNote); + this.noteRepository.save(note); + } + + return EntityIdentifier.resourceResult(loanId, command.commandId(), changes); + } + + @Transactional + @Override + public EntityIdentifier deleteLoan(final Long loanId) { + + context.authenticatedUser(); + + final Loan loan = retrieveLoanBy(loanId); + + if (loan.isNotSubmittedAndPendingApproval()) { throw new LoanNotInSubmittedAndPendingApprovalStateCannotBeDeleted(loanId); } + + List relatedNotes = this.noteRepository.findByLoanId(loan.getId()); + this.noteRepository.deleteInBatch(relatedNotes); + + this.loanRepository.delete(loanId); + + return new EntityIdentifier(loanId); + } + + @Transactional + @Override + public EntityIdentifier approveLoanApplication(final Long loanId, final JsonCommand command) { + + AppUser currentUser = context.authenticatedUser(); + + final LoanStateTransitionCommand approveLoanApplication = this.loanStateTransitionCommandFromApiJsonDeserializer + .commandFromApiJson(command.json()); + approveLoanApplication.validate(); + + final Loan loan = retrieveLoanBy(loanId); + + final LocalDate approvedOnLocalDate = approveLoanApplication.getApprovedOnDate(); + if (this.isBeforeToday(approvedOnLocalDate) && currentUser.canNotApproveLoanInPast()) { throw new NoAuthorizationException( + "User has no authority to approve loan with a date in the past."); } + + final Map changes = loan.loanApplicationApproval(command, defaultLoanLifecycleStateMachine()); + this.loanRepository.save(loan); + + final String noteText = command.stringValueOfParameterNamed("note"); + if (StringUtils.isNotBlank(noteText)) { + Note note = Note.loanNote(loan, noteText); + changes.put("note", noteText); + this.noteRepository.save(note); + } + + return EntityIdentifier.resourceResult(loanId, command.commandId(), changes); + } + + @Transactional + @Override + public EntityIdentifier undoLoanApplicationApproval(final Long loanId, final JsonCommand command) { + + context.authenticatedUser(); + + final Loan loan = retrieveLoanBy(loanId); + + final Map changes = loan.undoApproval(defaultLoanLifecycleStateMachine()); + this.loanRepository.save(loan); + + String noteText = command.stringValueOfParameterNamed("note"); + if (StringUtils.isNotBlank(noteText)) { + Note note = Note.loanNote(loan, noteText); + this.noteRepository.save(note); + } + + return EntityIdentifier.resourceResult(loanId, command.commandId(), changes); + } + + @Transactional + @Override + public EntityIdentifier rejectLoanApplication(final Long loanId, final JsonCommand command) { + + final AppUser currentUser = context.authenticatedUser(); + + final LoanStateTransitionCommand rejectLoanApplication = this.loanStateTransitionCommandFromApiJsonDeserializer + .commandFromApiJson(command.json()); + rejectLoanApplication.validate(); + + final Loan loan = retrieveLoanBy(loanId); + + final LocalDate eventDate = rejectLoanApplication.getRejectedOnDate(); + if (this.isBeforeToday(eventDate) && currentUser.canNotRejectLoanInPast()) { throw new NoAuthorizationException( + "User has no authority to reject loan with a date in the past."); } + + final Map changes = loan.loanApplicationRejection(command, defaultLoanLifecycleStateMachine()); + this.loanRepository.save(loan); + + String noteText = command.stringValueOfParameterNamed("note"); + if (StringUtils.isNotBlank(noteText)) { + Note note = Note.loanNote(loan, noteText); + this.noteRepository.save(note); + } + + return EntityIdentifier.resourceResult(loanId, command.commandId(), changes); + } + + @Transactional + @Override + public EntityIdentifier applicantWithdrawsFromLoanApplication(final Long loanId, final JsonCommand command) { + + final AppUser currentUser = context.authenticatedUser(); + + final LoanStateTransitionCommand applicantWithdrawsFromLoanApplication = this.loanStateTransitionCommandFromApiJsonDeserializer + .commandFromApiJson(command.json()); + applicantWithdrawsFromLoanApplication.validate(); + + final Loan loan = retrieveLoanBy(loanId); + + final LocalDate eventDate = applicantWithdrawsFromLoanApplication.getWithdrawnOnDate(); + if (this.isBeforeToday(eventDate) && currentUser.canNotWithdrawByClientLoanInPast()) { throw new NoAuthorizationException( + "User has no authority to mark loan as withdrawn by applicant with a date in the past."); } + + final Map changes = loan.loanApplicationWithdrawnByApplicant(command, defaultLoanLifecycleStateMachine()); + this.loanRepository.save(loan); + + String noteText = command.stringValueOfParameterNamed("note"); + if (StringUtils.isNotBlank(noteText)) { + Note note = Note.loanNote(loan, noteText); + this.noteRepository.save(note); + } + + return EntityIdentifier.resourceResult(loanId, command.commandId(), changes); + } + + @Transactional + @Override + public EntityIdentifier disburseLoan(final Long loanId, final JsonCommand command) { + + final AppUser currentUser = context.authenticatedUser(); + + final LoanStateTransitionCommand disburseLoan = this.loanStateTransitionCommandFromApiJsonDeserializer.commandFromApiJson(command + .json()); + disburseLoan.validate(); + + final Loan loan = retrieveLoanBy(loanId); + + final String noteText = command.stringValueOfParameterNamed("note"); + final LocalDate actualDisbursementDate = disburseLoan.getDisbursedOnDate(); + if (this.isBeforeToday(actualDisbursementDate) && currentUser.canNotDisburseLoanInPast()) { throw new NoAuthorizationException( + "User has no authority to disburse loan with a date in the past."); } + + final ApplicationCurrency currency = this.applicationCurrencyRepository.findOneByCode(loan.getPrincpal().getCurrencyCode()); + + final Map changes = loan.disburse(command, defaultLoanLifecycleStateMachine(), currency); + this.loanRepository.save(loan); + + if (StringUtils.isNotBlank(noteText)) { + Note note = Note.loanNote(loan, noteText); + this.noteRepository.save(note); + } + + return EntityIdentifier.resourceResult(loanId, command.commandId(), changes); + } + + @Transactional + @Override + public EntityIdentifier undoLoanDisbursal(final Long loanId, final JsonCommand command) { + + context.authenticatedUser(); + + final Loan loan = retrieveLoanBy(loanId); + + final Map changes = loan.undoDisbursal(defaultLoanLifecycleStateMachine()); + this.loanRepository.save(loan); + + final String noteText = command.stringValueOfParameterNamed("note"); + if (StringUtils.isNotBlank(noteText)) { + Note note = Note.loanNote(loan, noteText); + this.noteRepository.save(note); + } + + return EntityIdentifier.resourceResult(loanId, command.commandId(), changes); + } + + @Transactional + @Override + public EntityIdentifier makeLoanRepayment(final LoanTransactionCommand command) { + + AppUser currentUser = context.authenticatedUser(); + + LoanTransactionCommandValidator validator = new LoanTransactionCommandValidator(command); + validator.validate(); + + Loan loan = this.loanRepository.findOne(command.getLoanId()); + if (loan == null) { throw new LoanNotFoundException(command.getLoanId()); } + + LocalDate transactionDate = command.getTransactionDate(); + if (this.isBeforeToday(transactionDate) && currentUser.canNotMakeRepaymentOnLoanInPast()) { throw new NoAuthorizationException( + "error.msg.no.permission.to.make.repayment.on.loan.in.past"); } + + Money repayment = Money.of(loan.repaymentScheduleDetail().getPrincipal().getCurrency(), command.getTransactionAmount()); + + LoanTransaction loanRepayment = LoanTransaction.repayment(repayment, transactionDate); + loan.makeRepayment(loanRepayment, defaultLoanLifecycleStateMachine()); + this.loanTransactionRepository.save(loanRepayment); + this.loanRepository.save(loan); + + String noteText = command.getNote(); + if (StringUtils.isNotBlank(noteText)) { + Note note = Note.loanTransactionNote(loan, loanRepayment, noteText); + this.noteRepository.save(note); + } + + return new EntityIdentifier(loan.getId()); + } + + @Transactional + @Override + public EntityIdentifier adjustLoanTransaction(final AdjustLoanTransactionCommand command) { + + context.authenticatedUser(); + + AdjustLoanTransactionCommandValidator validator = new AdjustLoanTransactionCommandValidator(command); + validator.validate(); + + Loan loan = this.loanRepository.findOne(command.getLoanId()); + if (loan == null) { throw new LoanNotFoundException(command.getLoanId()); } + + LoanTransaction transactionToAdjust = this.loanTransactionRepository.findOne(command.getTransactionId()); + if (transactionToAdjust == null) { throw new LoanTransactionNotFoundException(command.getTransactionId()); } + + final MonetaryCurrency currency = loan.repaymentScheduleDetail().getPrincipal().getCurrency(); + final Money transactionAmount = Money.of(currency, command.getTransactionAmount()); + + // adjustment is only supported for repayments and waivers at present + LocalDate transactionDate = command.getTransactionDate(); + LoanTransaction newTransactionDetail = LoanTransaction.repayment(transactionAmount, transactionDate); + if (transactionToAdjust.isInterestWaiver()) { + newTransactionDetail = LoanTransaction.waiver(loan, transactionAmount, transactionDate); + } + + loan.adjustExistingTransaction(transactionToAdjust, newTransactionDetail, defaultLoanLifecycleStateMachine()); + + if (newTransactionDetail.isGreaterThanZero(currency)) { + this.loanTransactionRepository.save(newTransactionDetail); + } + + this.loanRepository.save(loan); + + String noteText = command.getNote(); + if (StringUtils.isNotBlank(noteText)) { + Note note = Note.loanTransactionNote(loan, newTransactionDetail, noteText); + this.noteRepository.save(note); + } + + return new EntityIdentifier(loan.getId()); + } + + @Transactional + @Override + public EntityIdentifier waiveInterestOnLoan(final LoanTransactionCommand command) { + + context.authenticatedUser(); + + final LoanTransactionCommandValidator validator = new LoanTransactionCommandValidator(command); + validator.validate(); + + final Loan loan = this.loanRepository.findOne(command.getLoanId()); + if (loan == null) { throw new LoanNotFoundException(command.getLoanId()); } + + final LoanTransaction waiveTransaction = loan.waiveInterest(command.getTransactionAmount(), command.getTransactionDate(), + defaultLoanLifecycleStateMachine()); + + 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 EntityIdentifier(loan.getId()); + } + + @Transactional + @Override + public EntityIdentifier writeOff(final LoanTransactionCommand command) { + context.authenticatedUser(); + + final LoanTransactionCommandValidator validator = new LoanTransactionCommandValidator(command); + validator.validateNonMonetaryTransaction(); + + final Loan loan = this.loanRepository.findOne(command.getLoanId()); + if (loan == null) { throw new LoanNotFoundException(command.getLoanId()); } + + final LoanTransaction writeoff = loan.closeAsWrittenOff(command.getTransactionDate(), defaultLoanLifecycleStateMachine()); + + this.loanTransactionRepository.save(writeoff); + this.loanRepository.save(loan); + + final String noteText = command.getNote(); + if (StringUtils.isNotBlank(noteText)) { + final Note note = Note.loanTransactionNote(loan, writeoff, noteText); + this.noteRepository.save(note); + } + + return new EntityIdentifier(loan.getId()); + } + + @Transactional + @Override + public EntityIdentifier closeLoan(final LoanTransactionCommand command) { + + context.authenticatedUser(); + + final LoanTransactionCommandValidator validator = new LoanTransactionCommandValidator(command); + validator.validateNonMonetaryTransaction(); + + final Loan loan = this.loanRepository.findOne(command.getLoanId()); + if (loan == null) { throw new LoanNotFoundException(command.getLoanId()); } + + final LoanTransaction possibleClosingTransaction = loan.close(command.getTransactionDate(), defaultLoanLifecycleStateMachine()); + if (possibleClosingTransaction != null) { + this.loanTransactionRepository.save(possibleClosingTransaction); + } + this.loanRepository.save(loan); + + final String noteText = command.getNote(); + if (StringUtils.isNotBlank(noteText)) { + final Note note = Note.loanNote(loan, noteText); + this.noteRepository.save(note); + } + + return new EntityIdentifier(loan.getId()); + } + + @Transactional + @Override + public EntityIdentifier closeAsRescheduled(final LoanTransactionCommand command) { + context.authenticatedUser(); + + final LoanTransactionCommandValidator validator = new LoanTransactionCommandValidator(command); + validator.validateNonMonetaryTransaction(); + + final Loan loan = this.loanRepository.findOne(command.getLoanId()); + if (loan == null) { throw new LoanNotFoundException(command.getLoanId()); } + + loan.closeAsMarkedForReschedule(command.getTransactionDate(), defaultLoanLifecycleStateMachine()); + + this.loanRepository.save(loan); + + final String noteText = command.getNote(); + if (StringUtils.isNotBlank(noteText)) { + final Note note = Note.loanNote(loan, noteText); + this.noteRepository.save(note); + } + + return new EntityIdentifier(loan.getId()); + } + + @Transactional @Override public EntityIdentifier addLoanCharge(final LoanChargeCommand command) { this.context.authenticatedUser(); @@ -624,22 +596,16 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf validator.validateForCreate(); final Loan loan = this.loanRepository.findOne(command.getLoanId()); - if (loan == null) { - throw new LoanNotFoundException(command.getLoanId()); - } + if (loan == null) { throw new LoanNotFoundException(command.getLoanId()); } final Charge chargeDefinition = this.chargeRepository.findOne(command.getChargeId()); - if (chargeDefinition == null || chargeDefinition.isDeleted()) { - throw new ChargeNotFoundException(command.getChargeId()); - } + if (chargeDefinition == null || chargeDefinition.isDeleted()) { throw new ChargeNotFoundException(command.getChargeId()); } + + if (!chargeDefinition.isActive()) { throw new ChargeIsNotActiveException(chargeDefinition.getId(), chargeDefinition.getName()); } - if (!chargeDefinition.isActive()) { - throw new ChargeIsNotActiveException(chargeDefinition.getId(), chargeDefinition.getName()); - } - final LoanCharge loanCharge = LoanCharge.createNew(loan, chargeDefinition, command); - if (!loan.hasCurrencyCodeOf(chargeDefinition.getCurrencyCode())){ + if (!loan.hasCurrencyCodeOf(chargeDefinition.getCurrencyCode())) { String errorMessage = "Charge and Loan must have the same currency."; throw new InvalidCurrencyException("charge", "attach.to.loan", errorMessage); } @@ -650,7 +616,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf return new EntityIdentifier(loanCharge.getId()); } - @Transactional + @Transactional @Override public EntityIdentifier updateLoanCharge(final LoanChargeCommand command) { @@ -661,81 +627,76 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final Long loanId = command.getLoanId(); final Loan loan = retrieveLoanBy(loanId); - + final Long loanChargeId = command.getId(); - final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId); - + final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId); + loan.updateLoanCharge(loanCharge, command); - + this.loanRepository.save(loan); return new EntityIdentifier(loanCharge.getId()); } - - @Transactional - @Override - public EntityIdentifier waiveLoanCharge(final LoanChargeCommand command) { - - this.context.authenticatedUser(); -// LoanChargeCommandValidator validator = new LoanChargeCommandValidator(command); -// validator.validateForUpdate(); + @Transactional + @Override + public EntityIdentifier waiveLoanCharge(final LoanChargeCommand command) { + + this.context.authenticatedUser(); + + // LoanChargeCommandValidator validator = new + // LoanChargeCommandValidator(command); + // validator.validateForUpdate(); final Long loanId = command.getLoanId(); final Loan loan = retrieveLoanBy(loanId); - - final Long loanChargeId = command.getId(); - final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId); - - final LoanTransaction waiveTransaction = loan.waiveLoanCharge(loanCharge, defaultLoanLifecycleStateMachine()); - - 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 EntityIdentifier(loanCharge.getId()); - } - @Transactional + final Long loanChargeId = command.getId(); + final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId); + + final LoanTransaction waiveTransaction = loan.waiveLoanCharge(loanCharge, defaultLoanLifecycleStateMachine()); + + 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 EntityIdentifier(loanCharge.getId()); + } + + @Transactional @Override public EntityIdentifier deleteLoanCharge(final Long loanId, final Long loanChargeId) { this.context.authenticatedUser(); final Loan loan = retrieveLoanBy(loanId); - + final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId); - + loan.removeLoanCharge(loanCharge); this.loanRepository.save(loan); return new EntityIdentifier(loanCharge.getId()); } - - private Loan retrieveLoanBy(final Long loanId) { - final Loan loan = this.loanRepository.findOne(loanId); - if (loan == null) { - throw new LoanNotFoundException(loanId); - } - return loan; - } - private LoanCharge retrieveLoanChargeBy(final Long loanId, final Long loanChargeId) { - final LoanCharge loanCharge = this.loanChargeRepository.findOne(loanChargeId); - if (loanCharge == null) { - throw new LoanChargeNotFoundException(loanChargeId); - } - - if (loanCharge.hasNotLoanIdentifiedBy(loanId)) { - throw new LoanChargeNotFoundException(loanChargeId, loanId); - } - return loanCharge; - } + private Loan retrieveLoanBy(final Long loanId) { + final Loan loan = this.loanRepository.findOne(loanId); + if (loan == null) { throw new LoanNotFoundException(loanId); } + return loan; + } + + private LoanCharge retrieveLoanChargeBy(final Long loanId, final Long loanChargeId) { + final LoanCharge loanCharge = this.loanChargeRepository.findOne(loanChargeId); + if (loanCharge == null) { throw new LoanChargeNotFoundException(loanChargeId); } + + if (loanCharge.hasNotLoanIdentifiedBy(loanId)) { throw new LoanChargeNotFoundException(loanChargeId, loanId); } + return loanCharge; + } @Transactional @Override @@ -751,9 +712,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final Loan loan = retrieveLoanBy(command.getLoanId()); - if (!loan.hasLoanOfficer(fromLoanOfficer)){ - throw new LoanOfficerAssignmentException(loan.getId(), fromLoanOfficer.getId()); - } + if (!loan.hasLoanOfficer(fromLoanOfficer)) { throw new LoanOfficerAssignmentException(loan.getId(), fromLoanOfficer.getId()); } loan.reassignLoanOfficer(toLoanOfficer, command.getAssignmentDate()); @@ -774,14 +733,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf Staff fromLoanOfficer = loanAssembler.findLoanOfficerByIdIfProvided(command.getFromLoanOfficerId()); Staff toLoanOfficer = loanAssembler.findLoanOfficerByIdIfProvided(command.getToLoanOfficerId()); - for (String loanIdString : command.getLoans()){ + for (String loanIdString : command.getLoans()) { final Long loanId = Long.valueOf(loanIdString); final Loan loan = retrieveLoanBy(loanId); - - if (!loan.hasLoanOfficer(fromLoanOfficer)){ - throw new LoanOfficerAssignmentException(loan.getId(), fromLoanOfficer.getId()); - } + + if (!loan.hasLoanOfficer(fromLoanOfficer)) { throw new LoanOfficerAssignmentException(loan.getId(), fromLoanOfficer.getId()); } loan.reassignLoanOfficer(toLoanOfficer, command.getAssignmentDate()); this.loanRepository.save(loan); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java index 7811debc7..7e411f004 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java @@ -138,9 +138,9 @@ public class LoanProduct extends AbstractAuditableCustom { this.charges = charges; } - public Map update(final JsonCommand command) { + public Map update(final JsonCommand command, final AprCalculator aprCalculator) { - final Map actualChanges = this.loanProductRelatedDetail.update(command); + final Map actualChanges = this.loanProductRelatedDetail.update(command, aprCalculator); final String nameParamName = "name"; if (command.isChangeInStringParameterNamed(nameParamName, this.name)) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductRelatedDetail.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductRelatedDetail.java index 3c30ae8f7..7edc826ac 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductRelatedDetail.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductRelatedDetail.java @@ -14,6 +14,7 @@ import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.portfolio.loanaccount.domain.Loan; +import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator; /** * LoanRepaymentScheduleDetail encapsulates all the details of a @@ -31,6 +32,8 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche @Column(name = "nominal_interest_rate_per_period", scale = 6, precision = 19, nullable = false) private BigDecimal nominalInterestRatePerPeriod; + // FIXME - move away form JPA ordinal use for enums using just integer - + // requires sql patch for existing users of software. @Enumerated(EnumType.ORDINAL) @Column(name = "interest_period_frequency_enum", nullable = false) private PeriodFrequencyType interestPeriodFrequencyType; @@ -38,10 +41,14 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche @Column(name = "annual_nominal_interest_rate", scale = 6, precision = 19, nullable = false) private BigDecimal annualNominalInterestRate; + // FIXME - move away form JPA ordinal use for enums using just integer - + // requires sql patch for existing users of software. @Enumerated(EnumType.ORDINAL) @Column(name = "interest_method_enum", nullable = false) private InterestMethod interestMethod; + // FIXME - move away form JPA ordinal use for enums using just integer - + // requires sql patch for existing users of software. @Enumerated(EnumType.ORDINAL) @Column(name = "interest_calculated_in_period_enum", nullable = false) private InterestCalculationPeriodMethod interestCalculationPeriodMethod; @@ -49,6 +56,8 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche @Column(name = "repay_every", nullable = false) private Integer repayEvery; + // FIXME - move away form JPA ordinal use for enums using just integer - + // requires sql patch for existing users of software. @Enumerated(EnumType.ORDINAL) @Column(name = "repayment_period_frequency_enum", nullable = false) private PeriodFrequencyType repaymentPeriodFrequencyType; @@ -56,6 +65,8 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche @Column(name = "number_of_repayments", nullable = false) private Integer numberOfRepayments; + // FIXME - move away form JPA ordinal use for enums using just integer - + // requires sql patch for existing users of software. @Enumerated(EnumType.ORDINAL) @Column(name = "amortization_method_enum", nullable = false) private AmortizationMethod amortizationMethod; @@ -63,6 +74,18 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche @Column(name = "arrearstolerance_amount", scale = 6, precision = 19, nullable = true) private BigDecimal inArrearsTolerance; + public static LoanProductRelatedDetail createFrom(final MonetaryCurrency currency, final BigDecimal principal, + final BigDecimal nominalInterestRatePerPeriod, final PeriodFrequencyType interestRatePeriodFrequencyType, + final BigDecimal nominalAnnualInterestRate, final InterestMethod interestMethod, + final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final Integer repaymentEvery, + final PeriodFrequencyType repaymentPeriodFrequencyType, final Integer numberOfRepayments, + final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance) { + + return new LoanProductRelatedDetail(currency, principal, nominalInterestRatePerPeriod, interestRatePeriodFrequencyType, + nominalAnnualInterestRate, interestMethod, interestCalculationPeriodMethod, repaymentEvery, repaymentPeriodFrequencyType, + numberOfRepayments, amortizationMethod, inArrearsTolerance); + } + protected LoanProductRelatedDetail() { // } @@ -142,7 +165,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche return amortizationMethod; } - public Map update(final JsonCommand command) { + public Map update(final JsonCommand command, final AprCalculator aprCalculator) { final Map actualChanges = new LinkedHashMap(20); @@ -168,6 +191,18 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche this.currency = new MonetaryCurrency(currencyCode, digitsAfterDecimal); } + final Map loanApplicationAttributeChanges = updateLoanApplicationAttributes(command, aprCalculator); + + actualChanges.putAll(loanApplicationAttributeChanges); + + return actualChanges; + } + + public Map updateLoanApplicationAttributes(final JsonCommand command, final AprCalculator aprCalculator) { + final Map actualChanges = new LinkedHashMap(20); + + final String localeAsInput = command.locale(); + final String principalParamName = "principal"; if (command.isChangeInBigDecimalParameterNamed(principalParamName, this.principal)) { final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(principalParamName); @@ -222,6 +257,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche actualChanges.put(interestRatePerPeriodParamName, newValue); actualChanges.put("locale", localeAsInput); this.nominalInterestRatePerPeriod = newValue; + updateInterestRateDerivedFields(aprCalculator); } final String interestRateFrequencyTypeParamName = "interestRateFrequencyType"; @@ -230,6 +266,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche actualChanges.put(interestRateFrequencyTypeParamName, newValue); actualChanges.put("locale", localeAsInput); this.interestPeriodFrequencyType = PeriodFrequencyType.fromInt(newValue); + updateInterestRateDerivedFields(aprCalculator); } final String interestTypeParamName = "interestType"; @@ -251,4 +288,8 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche return actualChanges; } + + private void updateInterestRateDerivedFields(final AprCalculator aprCalculator) { + this.annualNominalInterestRate = aprCalculator.calculateFrom(this.interestPeriodFrequencyType, this.nominalInterestRatePerPeriod); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java index 016281789..e9e6dc216 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java @@ -108,7 +108,7 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro final LoanProduct product = this.loanProductRepository.findOne(loanProductId); if (product == null) { throw new LoanProductNotFoundException(loanProductId); } - final Map changes = product.update(command); + final Map changes = product.update(command, this.aprCalculator); // associating fund with loan product at creation is optional for now. if (changes.containsKey("fundId")) {