MIFOSX-97: support audit for loan application creation, loan application modification, approval, undo approval, disbursal and undo of disbursal.

This commit is contained in:
Keith Woodlock
2012-12-18 20:41:12 +00:00
parent 43b1cdd3ef
commit 013276d9e4
53 changed files with 3469 additions and 2356 deletions
@@ -163,4 +163,32 @@ public class CommandWrapper {
public boolean isIdentifiersSubResource() { public boolean isIdentifiersSubResource() {
return "IDENTIFIERS".equalsIgnoreCase(this.subResource); 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");
}
} }
@@ -210,6 +210,29 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
} else { } else {
throw new UnsupportedCommandException(wrapper.commandName()); 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 { } else {
throw new UnsupportedCommandException(wrapper.commandName()); throw new UnsupportedCommandException(wrapper.commandName());
} }
@@ -18,7 +18,6 @@ import org.mifosplatform.portfolio.group.data.GroupData;
import org.mifosplatform.portfolio.loanaccount.data.LoanChargeData; import org.mifosplatform.portfolio.loanaccount.data.LoanChargeData;
import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionData; import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionData;
import org.mifosplatform.portfolio.loanaccount.gaurantor.data.GuarantorData; 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.SavingAccountData;
import org.mifosplatform.portfolio.savingsaccount.data.SavingScheduleData; import org.mifosplatform.portfolio.savingsaccount.data.SavingScheduleData;
import org.mifosplatform.portfolio.savingsaccountproduct.data.SavingProductData; import org.mifosplatform.portfolio.savingsaccountproduct.data.SavingProductData;
@@ -59,8 +58,8 @@ public class GoogleGsonPortfolioApiJsonSerializerService implements PortfolioApi
"interestCompoundedEveryPeriodType", "renewalAllowed", "preClosureAllowed", "preClosureInterestRate", "withdrawnonDate", "interestCompoundedEveryPeriodType", "renewalAllowed", "preClosureAllowed", "preClosureInterestRate", "withdrawnonDate",
"rejectedonDate", "closedonDate", "transactions", "permissions", "isInterestWithdrawable", "interestPaid", "rejectedonDate", "closedonDate", "transactions", "permissions", "isInterestWithdrawable", "interestPaid",
"interestCompoundingAllowed", "availableInterestForWithdrawal", "availableWithdrawalAmount", "todaysDate", "interestCompoundingAllowed", "availableInterestForWithdrawal", "availableWithdrawalAmount", "todaysDate",
"isLockinPeriodAllowed", "lockinPeriod", "lockinPeriodType", "printFDdetailsLocation","availableInterest", "interestPostedAmount", "isLockinPeriodAllowed", "lockinPeriod", "lockinPeriodType", "printFDdetailsLocation", "availableInterest",
"lastInterestPostedDate", "nextInterestPostedDate","fatherName","address","imageKey")); "interestPostedAmount", "lastInterestPostedDate", "nextInterestPostedDate", "fatherName", "address", "imageKey"));
private static final Set<String> DOCUMENT_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("id", "parentEntityType", private static final Set<String> DOCUMENT_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("id", "parentEntityType",
"parentEntityId", "name", "fileName", "type", "size", "description")); "parentEntityId", "name", "fileName", "type", "size", "description"));
@@ -79,9 +78,6 @@ public class GoogleGsonPortfolioApiJsonSerializerService implements PortfolioApi
private static final Set<String> GROUP_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("id", "officeId", "name", "externalId", private static final Set<String> GROUP_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("id", "officeId", "name", "externalId",
"clientMembers", "allowedClients", "allowedOffices")); "clientMembers", "allowedClients", "allowedOffices"));
private static final Set<String> LOAN_SCHEDULE_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("periods",
"cumulativePrincipalDisbursed"));
private static final Set<String> LOAN_TRANSACTION_NEW_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("id", "type", "date", private static final Set<String> LOAN_TRANSACTION_NEW_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("id", "type", "date",
"currency", "amount")); "currency", "amount"));
@@ -103,7 +99,7 @@ public class GoogleGsonPortfolioApiJsonSerializerService implements PortfolioApi
private static final Set<String> GUARANTOR_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("externalGuarantor", "existingClientId", private static final Set<String> GUARANTOR_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("externalGuarantor", "existingClientId",
"firstname", "lastname", "addressLine1", "addressLine2", "city", "state", "zip", "country", "mobileNumber", "housePhoneNumber", "firstname", "lastname", "addressLine1", "addressLine2", "city", "state", "zip", "country", "mobileNumber", "housePhoneNumber",
"comment", "dob")); "comment", "dob"));
private static final Set<String> SAVING_SCHEDULE_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("periods", "cumulativeDepositDue")); private static final Set<String> SAVING_SCHEDULE_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("periods", "cumulativeDepositDue"));
private final GoogleGsonSerializerHelper helper; private final GoogleGsonSerializerHelper helper;
@@ -210,14 +206,6 @@ public class GoogleGsonPortfolioApiJsonSerializerService implements PortfolioApi
return helper.serializedJsonFrom(gsonDeserializer, group); return helper.serializedJsonFrom(gsonDeserializer, group);
} }
@Override
public String serializeLoanScheduleDataToJson(final boolean prettyPrint, final Set<String> responseParameters,
final LoanScheduleData loanSchedule) {
final Gson gsonDeserializer = helper.createGsonBuilderWithParameterExclusionSerializationStrategy(LOAN_SCHEDULE_DATA_PARAMETERS,
prettyPrint, responseParameters);
return helper.serializedJsonFrom(gsonDeserializer, loanSchedule);
}
@Override @Override
public String serializeLoanTransactionDataToJson(final boolean prettyPrint, final Set<String> responseParameters, public String serializeLoanTransactionDataToJson(final boolean prettyPrint, final Set<String> responseParameters,
final LoanTransactionData transaction) { final LoanTransactionData transaction) {
@@ -55,6 +55,10 @@ public final class JsonCommand {
return this.jsonCommand; return this.jsonCommand;
} }
public JsonElement parsedJson() {
return this.parsedCommand;
}
public Long commandId() { public Long commandId() {
return this.commandId; return this.commandId;
} }
@@ -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<String, Boolean> mapValueOfParameterNamed(final String parameterName) {
final Type typeOfMap = new TypeToken<Map<String, Boolean>>() {}.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);
}
}
@@ -5,9 +5,7 @@ import org.mifosplatform.portfolio.client.command.NoteCommand;
import org.mifosplatform.portfolio.client.data.ClientData; import org.mifosplatform.portfolio.client.data.ClientData;
import org.mifosplatform.portfolio.group.command.GroupCommand; import org.mifosplatform.portfolio.group.command.GroupCommand;
import org.mifosplatform.portfolio.loanaccount.command.AdjustLoanTransactionCommand; 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.LoanChargeCommand;
import org.mifosplatform.portfolio.loanaccount.command.LoanStateTransitionCommand;
import org.mifosplatform.portfolio.loanaccount.command.LoanTransactionCommand; import org.mifosplatform.portfolio.loanaccount.command.LoanTransactionCommand;
import org.mifosplatform.portfolio.loanaccount.gaurantor.command.GuarantorCommand; import org.mifosplatform.portfolio.loanaccount.gaurantor.command.GuarantorCommand;
import org.mifosplatform.portfolio.savingsaccount.command.SavingAccountCommand; import org.mifosplatform.portfolio.savingsaccount.command.SavingAccountCommand;
@@ -29,12 +27,8 @@ public interface PortfolioApiDataConversionService {
GroupCommand convertJsonToGroupCommand(Long resourceIdentifier, String json); GroupCommand convertJsonToGroupCommand(Long resourceIdentifier, String json);
LoanApplicationCommand convertApiRequestJsonToLoanApplicationCommand(Long resourceIdentifier, String json);
LoanChargeCommand convertJsonToLoanChargeCommand(Long loanChargeId, Long loanId, String json); LoanChargeCommand convertJsonToLoanChargeCommand(Long loanChargeId, Long loanId, String json);
LoanStateTransitionCommand convertJsonToLoanStateTransitionCommand(Long resourceIdentifier, String json);
LoanTransactionCommand convertJsonToLoanTransactionCommand(Long resourceIdentifier, String json); LoanTransactionCommand convertJsonToLoanTransactionCommand(Long resourceIdentifier, String json);
AdjustLoanTransactionCommand convertJsonToAdjustLoanTransactionCommand(Long loanId, Long transactionId, String json); AdjustLoanTransactionCommand convertJsonToAdjustLoanTransactionCommand(Long loanId, Long transactionId, String json);
@@ -21,7 +21,6 @@ import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.exception.UnsupportedParameterException; import org.mifosplatform.infrastructure.core.exception.UnsupportedParameterException;
import org.mifosplatform.infrastructure.core.serialization.JsonParserHelper;
import org.mifosplatform.organisation.staff.command.BulkTransferLoanOfficerCommand; import org.mifosplatform.organisation.staff.command.BulkTransferLoanOfficerCommand;
import org.mifosplatform.portfolio.client.command.ClientCommand; import org.mifosplatform.portfolio.client.command.ClientCommand;
import org.mifosplatform.portfolio.client.command.NoteCommand; 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.client.serialization.ClientCommandFromApiJsonDeserializer;
import org.mifosplatform.portfolio.group.command.GroupCommand; import org.mifosplatform.portfolio.group.command.GroupCommand;
import org.mifosplatform.portfolio.loanaccount.command.AdjustLoanTransactionCommand; 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.LoanChargeCommand;
import org.mifosplatform.portfolio.loanaccount.command.LoanStateTransitionCommand;
import org.mifosplatform.portfolio.loanaccount.command.LoanTransactionCommand; import org.mifosplatform.portfolio.loanaccount.command.LoanTransactionCommand;
import org.mifosplatform.portfolio.loanaccount.gaurantor.command.GuarantorCommand; import org.mifosplatform.portfolio.loanaccount.gaurantor.command.GuarantorCommand;
import org.mifosplatform.portfolio.savingsaccount.command.SavingAccountCommand; 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); return new GroupCommand(modifiedParameters, resourceIdentifier, externalId, name, officeId, clientMembers);
} }
@Override /**
public LoanApplicationCommand convertApiRequestJsonToLoanApplicationCommand(final Long resourceIdentifier, final String json) { * @deprecated - to be removed soon
*/
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } @Deprecated
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
final Map<String, Object> requestMap = gsonConverter.fromJson(json, typeOfMap);
final Set<String> supportedParams = new HashSet<String>(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<String> parametersPassedInCommand = new HashSet<String>();
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<String> parametersPassedInForChargesCommand = new HashSet<String>();
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);
}
@Override @Override
public LoanChargeCommand convertJsonToLoanChargeCommand(final Long loanChargeId, final Long loanId, final String json) { public LoanChargeCommand convertJsonToLoanChargeCommand(final Long loanChargeId, final Long loanId, final String json) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
@@ -302,25 +203,6 @@ public class PortfolioApiDataConversionServiceImpl implements PortfolioApiDataCo
submittedOnDate); submittedOnDate);
} }
@Override
public LoanStateTransitionCommand convertJsonToLoanStateTransitionCommand(final Long resourceIdentifier, final String json) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
Map<String, Object> requestMap = gsonConverter.fromJson(json, typeOfMap);
Set<String> supportedParams = new HashSet<String>(Arrays.asList("eventDate", "note", "locale", "dateFormat"));
checkForUnsupportedParameters(requestMap, supportedParams);
Set<String> modifiedParameters = new HashSet<String>();
LocalDate eventDate = extractLocalDateParameter("eventDate", requestMap, modifiedParameters);
String note = extractStringParameter("note", requestMap, modifiedParameters);
return new LoanStateTransitionCommand(resourceIdentifier, eventDate, note);
}
@Override @Override
public LoanTransactionCommand convertJsonToLoanTransactionCommand(final Long resourceIdentifier, final String json) { public LoanTransactionCommand convertJsonToLoanTransactionCommand(final Long resourceIdentifier, final String json) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
@@ -15,7 +15,6 @@ import org.mifosplatform.portfolio.group.data.GroupData;
import org.mifosplatform.portfolio.loanaccount.data.LoanChargeData; import org.mifosplatform.portfolio.loanaccount.data.LoanChargeData;
import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionData; import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionData;
import org.mifosplatform.portfolio.loanaccount.gaurantor.data.GuarantorData; 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.SavingAccountData;
import org.mifosplatform.portfolio.savingsaccount.data.SavingScheduleData; import org.mifosplatform.portfolio.savingsaccount.data.SavingScheduleData;
import org.mifosplatform.portfolio.savingsaccountproduct.data.SavingProductData; import org.mifosplatform.portfolio.savingsaccountproduct.data.SavingProductData;
@@ -52,8 +51,6 @@ public interface PortfolioApiJsonSerializerService {
String serializeGroupDataToJson(boolean prettyPrint, Set<String> responseParameters, GroupData group); String serializeGroupDataToJson(boolean prettyPrint, Set<String> responseParameters, GroupData group);
String serializeLoanScheduleDataToJson(boolean prettyPrint, Set<String> responseParameters, LoanScheduleData loanSchedule);
String serializeLoanTransactionDataToJson(boolean prettyPrint, Set<String> responseParameters, LoanTransactionData newTransactionData); String serializeLoanTransactionDataToJson(boolean prettyPrint, Set<String> responseParameters, LoanTransactionData newTransactionData);
String serializeLoanReassignmentDataToJson(boolean prettyPrint, Set<String> responseParameters, String serializeLoanReassignmentDataToJson(boolean prettyPrint, Set<String> responseParameters,
@@ -77,5 +74,4 @@ public interface PortfolioApiJsonSerializerService {
String serializeSavingScheduleDataToJson(boolean prettyPrint, Set<String> responseParameters, SavingScheduleData savingScheduleData); String serializeSavingScheduleDataToJson(boolean prettyPrint, Set<String> responseParameters, SavingScheduleData savingScheduleData);
} }
@@ -17,6 +17,7 @@ import org.springframework.stereotype.Component;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
@Component @Component
@@ -98,6 +99,11 @@ public class FromJsonHelper {
public LocalDate extractLocalDateNamed(final String parameterName, JsonElement element) { public LocalDate extractLocalDateNamed(final String parameterName, JsonElement element) {
return helperDelegator.extractLocalDateNamed(parameterName, element, new HashSet<String>()); return helperDelegator.extractLocalDateNamed(parameterName, element, new HashSet<String>());
} }
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<String>());
}
public LocalDate extractLocalDateNamed(final String parameterName, final JsonElement element, public LocalDate extractLocalDateNamed(final String parameterName, final JsonElement element,
final Set<String> parametersPassedInRequest) { final Set<String> parametersPassedInRequest) {
@@ -118,11 +124,15 @@ public class FromJsonHelper {
return helperDelegator.extractBigDecimalWithLocaleNamed(parameterName, element, parametersPassedInRequest); 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<String>());
}
public BigDecimal extractBigDecimalNamed(final String parameterName, final JsonElement element, public BigDecimal extractBigDecimalNamed(final String parameterName, final JsonElement element,
final Set<String> parametersPassedInRequest) { final Set<String> parametersPassedInRequest) {
return helperDelegator.extractBigDecimalNamed(parameterName, element.getAsJsonObject(), Locale.US, parametersPassedInRequest); return helperDelegator.extractBigDecimalNamed(parameterName, element.getAsJsonObject(), Locale.US, parametersPassedInRequest);
} }
public Integer extractIntegerWithLocaleNamed(final String parameterName, final JsonElement element) { public Integer extractIntegerWithLocaleNamed(final String parameterName, final JsonElement element) {
return helperDelegator.extractIntegerWithLocaleNamed(parameterName, element.getAsJsonObject(), new HashSet<String>()); return helperDelegator.extractIntegerWithLocaleNamed(parameterName, element.getAsJsonObject(), new HashSet<String>());
} }
@@ -131,9 +141,21 @@ public class FromJsonHelper {
final Set<String> parametersPassedInRequest) { final Set<String> parametersPassedInRequest) {
return helperDelegator.extractIntegerWithLocaleNamed(parameterName, element.getAsJsonObject(), 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<String>());
}
public Integer extractIntegerNamed(final String parameterName, final JsonElement element, public Integer extractIntegerNamed(final String parameterName, final JsonElement element,
final Set<String> parametersPassedInRequest) { final Set<String> parametersPassedInRequest) {
return helperDelegator.extractIntegerNamed(parameterName, element.getAsJsonObject(), Locale.US, 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);
}
} }
@@ -21,8 +21,10 @@ import javax.ws.rs.core.UriInfo;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService;
import org.mifosplatform.infrastructure.core.api.ApiParameterHelper; import org.mifosplatform.infrastructure.core.api.ApiParameterHelper;
import org.mifosplatform.infrastructure.core.api.ApiRequestParameterHelper; 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.PortfolioApiDataConversionService;
import org.mifosplatform.infrastructure.core.api.PortfolioApiJsonSerializerService; import org.mifosplatform.infrastructure.core.api.PortfolioApiJsonSerializerService;
import org.mifosplatform.infrastructure.core.data.EntityIdentifier; 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.exception.UnrecognizedQueryParamException;
import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer; import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money; 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.data.FundData;
import org.mifosplatform.portfolio.fund.service.FundReadPlatformService; import org.mifosplatform.portfolio.fund.service.FundReadPlatformService;
import org.mifosplatform.portfolio.loanaccount.command.AdjustLoanTransactionCommand; 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.LoanChargeCommand;
import org.mifosplatform.portfolio.loanaccount.command.LoanStateTransitionCommand;
import org.mifosplatform.portfolio.loanaccount.command.LoanTransactionCommand; 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.DisbursementData;
import org.mifosplatform.portfolio.loanaccount.data.LoanAccountData; import org.mifosplatform.portfolio.loanaccount.data.LoanAccountData;
import org.mifosplatform.portfolio.loanaccount.data.LoanBasicDetailsData; 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.data.LoanTransactionData;
import org.mifosplatform.portfolio.loanaccount.gaurantor.data.GuarantorData; import org.mifosplatform.portfolio.loanaccount.gaurantor.data.GuarantorData;
import org.mifosplatform.portfolio.loanaccount.gaurantor.service.GuarantorReadPlatformService; 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.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.LoanReadPlatformService;
import org.mifosplatform.portfolio.loanaccount.service.LoanWritePlatformService; import org.mifosplatform.portfolio.loanaccount.service.LoanWritePlatformService;
import org.mifosplatform.portfolio.loanproduct.data.LoanProductData; 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.stereotype.Component;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import com.google.gson.JsonElement;
@Path("/loans") @Path("/loans")
@Component @Component
@Scope("singleton") @Scope("singleton")
@@ -88,7 +89,7 @@ public class LoansApiResource {
"interestRateFrequencyTypeOptions", "fundOptions", "repaymentStrategyOptions", "chargeOptions", "loanOfficerId", "interestRateFrequencyTypeOptions", "fundOptions", "repaymentStrategyOptions", "chargeOptions", "loanOfficerId",
"loanOfficerName", "loanOfficerOptions", "chargeTemplate")); "loanOfficerName", "loanOfficerOptions", "chargeTemplate"));
// private final String resourceNameForPermissions = "OFFICE"; private final String resourceNameForPermissions = "LOAN";
private final PlatformSecurityContext context; private final PlatformSecurityContext context;
private final LoanReadPlatformService loanReadPlatformService; private final LoanReadPlatformService loanReadPlatformService;
@@ -97,25 +98,31 @@ public class LoansApiResource {
private final LoanDropdownReadPlatformService dropdownReadPlatformService; private final LoanDropdownReadPlatformService dropdownReadPlatformService;
private final FundReadPlatformService fundReadPlatformService; private final FundReadPlatformService fundReadPlatformService;
private final ChargeReadPlatformService chargeReadPlatformService; private final ChargeReadPlatformService chargeReadPlatformService;
private final CalculationPlatformService calculationPlatformService; private final LoanScheduleCalculationPlatformService calculationPlatformService;
private final StaffReadPlatformService staffReadPlatformService; private final StaffReadPlatformService staffReadPlatformService;
private final OfficeReadPlatformService officeReadPlatformService; private final OfficeReadPlatformService officeReadPlatformService;
private final GuarantorReadPlatformService guarantorReadPlatformService; private final GuarantorReadPlatformService guarantorReadPlatformService;
private final PortfolioApiJsonSerializerService apiJsonSerializerService; private final PortfolioApiJsonSerializerService apiJsonSerializerService;
private final PortfolioApiDataConversionService apiDataConversionService; private final PortfolioApiDataConversionService apiDataConversionService;
private final DefaultToApiJsonSerializer<LoanAccountData> toApiJsonSerializer; private final DefaultToApiJsonSerializer<LoanAccountData> toApiJsonSerializer;
private final DefaultToApiJsonSerializer<LoanScheduleData> loanScheduleToApiJsonSerializer;
private final ApiRequestParameterHelper apiRequestParameterHelper; private final ApiRequestParameterHelper apiRequestParameterHelper;
private final FromJsonHelper fromJsonHelper;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
@Autowired @Autowired
public LoansApiResource(final PlatformSecurityContext context, final LoanReadPlatformService loanReadPlatformService, public LoansApiResource(final PlatformSecurityContext context, final LoanReadPlatformService loanReadPlatformService,
final LoanWritePlatformService loanWritePlatformService, final LoanProductReadPlatformService loanProductReadPlatformService, final LoanWritePlatformService loanWritePlatformService, final LoanProductReadPlatformService loanProductReadPlatformService,
final LoanDropdownReadPlatformService dropdownReadPlatformService, final FundReadPlatformService fundReadPlatformService, 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 StaffReadPlatformService staffReadPlatformService, final OfficeReadPlatformService officeReadPlatformService,
final GuarantorReadPlatformService guarantorReadPlatformService, final GuarantorReadPlatformService guarantorReadPlatformService,
final DefaultToApiJsonSerializer<LoanAccountData> toApiJsonSerializer, final DefaultToApiJsonSerializer<LoanAccountData> toApiJsonSerializer,
final DefaultToApiJsonSerializer<LoanScheduleData> loanScheduleToApiJsonSerializer,
final ApiRequestParameterHelper apiRequestParameterHelper, final PortfolioApiJsonSerializerService apiJsonSerializerService, final ApiRequestParameterHelper apiRequestParameterHelper, final PortfolioApiJsonSerializerService apiJsonSerializerService,
final PortfolioApiDataConversionService apiDataConversionService) { final PortfolioApiDataConversionService apiDataConversionService, final FromJsonHelper fromJsonHelper,
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
this.context = context; this.context = context;
this.loanReadPlatformService = loanReadPlatformService; this.loanReadPlatformService = loanReadPlatformService;
this.loanWritePlatformService = loanWritePlatformService; this.loanWritePlatformService = loanWritePlatformService;
@@ -128,9 +135,12 @@ public class LoansApiResource {
this.officeReadPlatformService = officeReadPlatformService; this.officeReadPlatformService = officeReadPlatformService;
this.guarantorReadPlatformService = guarantorReadPlatformService; this.guarantorReadPlatformService = guarantorReadPlatformService;
this.toApiJsonSerializer = toApiJsonSerializer; this.toApiJsonSerializer = toApiJsonSerializer;
this.loanScheduleToApiJsonSerializer = loanScheduleToApiJsonSerializer;
this.apiRequestParameterHelper = apiRequestParameterHelper; this.apiRequestParameterHelper = apiRequestParameterHelper;
this.apiJsonSerializerService = apiJsonSerializerService; this.apiJsonSerializerService = apiJsonSerializerService;
this.apiDataConversionService = apiDataConversionService; this.apiDataConversionService = apiDataConversionService;
this.fromJsonHelper = fromJsonHelper;
this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
} }
@GET @GET
@@ -140,7 +150,7 @@ public class LoansApiResource {
public String retrieveDetailsForNewLoanApplicationStepOne(@QueryParam("clientId") final Long clientId, public String retrieveDetailsForNewLoanApplicationStepOne(@QueryParam("clientId") final Long clientId,
@QueryParam("groupId") final Long groupId, @QueryParam("productId") final Long productId, @Context final UriInfo uriInfo) { @QueryParam("groupId") final Long groupId, @QueryParam("productId") final Long productId, @Context final UriInfo uriInfo) {
context.authenticatedUser().validateHasReadPermission("LOAN"); context.authenticatedUser().validateHasReadPermission(resourceNameForPermissions);
// tempate related // tempate related
Collection<LoanProductData> productOptions = this.loanProductReadPlatformService.retrieveAllLoanProductsForLookup(); Collection<LoanProductData> productOptions = this.loanProductReadPlatformService.retrieveAllLoanProductsForLookup();
@@ -193,7 +203,7 @@ public class LoansApiResource {
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
public String retrieveLoanAccountDetails(@PathParam("loanId") final Long loanId, @Context final UriInfo uriInfo) { 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); final LoanBasicDetailsData loanBasicDetails = this.loanReadPlatformService.retrieveLoanAccountDetails(loanId);
@@ -319,94 +329,83 @@ public class LoansApiResource {
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
public String calculateLoanScheduleOrSubmitLoanApplication(@QueryParam("command") final String commandParam, public String calculateLoanScheduleOrSubmitLoanApplication(@QueryParam("command") final String commandParam,
@Context final UriInfo uriInfo, final String jsonRequestBody) { @Context final UriInfo uriInfo, final String apiRequestBodyAsJson) {
final LoanApplicationCommand command = this.apiDataConversionService.convertApiRequestJsonToLoanApplicationCommand(null,
jsonRequestBody);
if (is(commandParam, "calculateLoanSchedule")) { 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<String>());
} }
final EntityIdentifier identifier = this.loanWritePlatformService.submitLoanApplication(command); final EntityIdentifier result = this.commandsSourceWritePlatformService.logCommandSource("CREATE", "LOAN", "CREATE", "loans", null,
apiRequestBodyAsJson);
return this.toApiJsonSerializer.serialize(identifier); return this.toApiJsonSerializer.serialize(result);
}
private String calculateLoanSchedule(final UriInfo uriInfo, final CalculateLoanScheduleCommand command) {
final LoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanSchedule(command);
final Set<String> responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters());
boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
return this.apiJsonSerializerService.serializeLoanScheduleDataToJson(prettyPrint, responseParameters, loanSchedule);
} }
@PUT @PUT
@Path("{loanId}") @Path("{loanId}")
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@Produces({ 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, final EntityIdentifier result = this.commandsSourceWritePlatformService.logCommandSource("UPDATE", "LOAN", "UPDATE", "loans",
jsonRequestBody); loanId, apiRequestBodyAsJson);
final EntityIdentifier identifier = this.loanWritePlatformService.modifyLoanApplication(command); return this.toApiJsonSerializer.serialize(result);
return this.toApiJsonSerializer.serialize(identifier);
} }
@DELETE @DELETE
@Path("{loanId}") @Path("{loanId}")
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@Produces({ 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 @POST
@Path("{loanId}") @Path("{loanId}")
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
public Response stateTransitions(@PathParam("loanId") final Long loanId, @QueryParam("command") final String commandParam, public String stateTransitions(@PathParam("loanId") final Long loanId, @QueryParam("command") final String commandParam,
final String jsonRequestBody) { final String apiRequestBodyAsJson) {
LoanStateTransitionCommand command = this.apiDataConversionService.convertJsonToLoanStateTransitionCommand(loanId, jsonRequestBody); EntityIdentifier result = null;
Response response = null;
if (is(commandParam, "reject")) { if (is(commandParam, "reject")) {
EntityIdentifier identifier = this.loanWritePlatformService.rejectLoan(command); result = this.commandsSourceWritePlatformService.logCommandSource("REJECT", "LOAN", "N/A", "loans", loanId,
response = Response.ok().entity(identifier).build(); apiRequestBodyAsJson);
} else if (is(commandParam, "withdrewbyclient")) { } else if (is(commandParam, "withdrewbyclient")) {
EntityIdentifier identifier = this.loanWritePlatformService.withdrawLoan(command); result = this.commandsSourceWritePlatformService.logCommandSource("WITHDRAW", "LOAN", "N/A", "loans", loanId,
response = Response.ok().entity(identifier).build(); apiRequestBodyAsJson);
} else if (is(commandParam, "approve")) { } else if (is(commandParam, "approve")) {
EntityIdentifier identifier = this.loanWritePlatformService.approveLoanApplication(command); result = this.commandsSourceWritePlatformService.logCommandSource("APPROVE", "LOAN", "N/A", "loans", loanId,
response = Response.ok().entity(identifier).build(); apiRequestBodyAsJson);
} else if (is(commandParam, "disburse")) { } else if (is(commandParam, "disburse")) {
EntityIdentifier identifier = this.loanWritePlatformService.disburseLoan(command); result = this.commandsSourceWritePlatformService.logCommandSource("DISBURSE", "LOAN", "N/A", "loans", loanId,
response = Response.ok().entity(identifier).build(); apiRequestBodyAsJson);
} }
UndoStateTransitionCommand undoCommand = new UndoStateTransitionCommand(loanId, command.getNote());
if (is(commandParam, "undoapproval")) { if (is(commandParam, "undoapproval")) {
EntityIdentifier identifier = this.loanWritePlatformService.undoLoanApproval(undoCommand); result = this.commandsSourceWritePlatformService.logCommandSource("APPROVALUNDO", "LOAN", "N/A", "loans", loanId,
response = Response.ok().entity(identifier).build(); apiRequestBodyAsJson);
} else if (is(commandParam, "undodisbursal")) { } else if (is(commandParam, "undodisbursal")) {
EntityIdentifier identifier = this.loanWritePlatformService.undoLoanDisbursal(undoCommand); result = this.commandsSourceWritePlatformService.logCommandSource("DISBURSALUNDO", "LOAN", "N/A", "loans", loanId,
response = Response.ok().entity(identifier).build(); 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) { private boolean is(final String commandParam, final String commandValue) {
@@ -1,11 +1,15 @@
package org.mifosplatform.portfolio.loanaccount.command; package org.mifosplatform.portfolio.loanaccount.command;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Set; import java.util.Set;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosplatform.portfolio.loanaccount.loanschedule.command.CalculateLoanScheduleCommand; import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.portfolio.loanproduct.command.LoanProductCommand; 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. * Immutable command for submitting new loan application.
@@ -90,8 +94,8 @@ public class LoanApplicationCommand {
this.charges = charges; this.charges = charges;
} }
public CalculateLoanScheduleCommand toCalculateLoanScheduleCommand() { public CalculateLoanScheduleQuery toCalculateLoanScheduleCommand() {
return new CalculateLoanScheduleCommand(this.productId, this.principal, this.interestRatePerPeriod, this.interestRateFrequencyType, return new CalculateLoanScheduleQuery(this.productId, this.principal, this.interestRatePerPeriod, this.interestRateFrequencyType,
this.interestType, this.interestCalculationPeriodType, this.repaymentEvery, this.repaymentFrequencyType, this.interestType, this.interestCalculationPeriodType, this.repaymentEvery, this.repaymentFrequencyType,
this.numberOfRepayments, this.amortizationType, this.loanTermFrequency, this.loanTermFrequencyType, this.numberOfRepayments, this.amortizationType, this.loanTermFrequency, this.loanTermFrequencyType,
this.expectedDisbursementDate, this.repaymentsStartingFromDate, this.interestChargedFromDate, this.charges); this.expectedDisbursementDate, this.repaymentsStartingFromDate, this.interestChargedFromDate, this.charges);
@@ -197,16 +201,6 @@ public class LoanApplicationCommand {
return charges; 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() { public boolean isClientChanged() {
return this.modifiedParameters.contains("clientId"); return this.modifiedParameters.contains("clientId");
} }
@@ -258,4 +252,52 @@ public class LoanApplicationCommand {
public Long getLoanOfficerId() { public Long getLoanOfficerId() {
return loanOfficerId; return loanOfficerId;
} }
public void validate() {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
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); }
}
} }
@@ -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<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
// 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);
}
}
}
@@ -21,25 +21,19 @@ public class LoanChargeCommand {
private final Integer chargeCalculationType; private final Integer chargeCalculationType;
/** /**
* Used to capture what parameters were passed in the json api request. * Used to capture what parameters were passed in the json api request. It
* It does not indicate that these values are modified from their original values when tyring to update. * does not indicate that these values are modified from their original
* values when tyring to update.
*/ */
private final Set<String> requestParameters; private final Set<String> requestParameters;
public static LoanChargeCommand forWaiver(final Long id, final Long loanId) {
final Set<String> parametersPassedInCommand = new HashSet<String>();
return new LoanChargeCommand(parametersPassedInCommand, id, loanId, null, null, null, null, null);
}
public LoanChargeCommand( public static LoanChargeCommand forWaiver(final Long id, final Long loanId) {
final Set<String> parametersPassedInCommand, final Set<String> parametersPassedInCommand = new HashSet<String>();
final Long id, return new LoanChargeCommand(parametersPassedInCommand, id, loanId, null, null, null, null, null);
final Long loanId, }
final Long chargeId,
final BigDecimal amount, public LoanChargeCommand(final Set<String> parametersPassedInCommand, final Long id, final Long loanId, final Long chargeId,
final Integer chargeTimeType, final BigDecimal amount, final Integer chargeTimeType, final Integer chargeCalculationType, final LocalDate specifiedDueDate) {
final Integer chargeCalculationType,
final LocalDate specifiedDueDate) {
this.requestParameters = parametersPassedInCommand; this.requestParameters = parametersPassedInCommand;
this.id = id; this.id = id;
this.chargeId = chargeId; this.chargeId = chargeId;
@@ -47,7 +41,7 @@ public class LoanChargeCommand {
this.amount = amount; this.amount = amount;
this.chargeTimeType = chargeTimeType; this.chargeTimeType = chargeTimeType;
this.chargeCalculationType = chargeCalculationType; this.chargeCalculationType = chargeCalculationType;
this.specifiedDueDate = specifiedDueDate; this.specifiedDueDate = specifiedDueDate;
} }
public Long getId() { public Long getId() {
@@ -69,28 +63,28 @@ public class LoanChargeCommand {
public Integer getChargeTimeType() { public Integer getChargeTimeType() {
return chargeTimeType; return chargeTimeType;
} }
public LocalDate getSpecifiedDueDate() {
return specifiedDueDate;
}
public Integer getChargeCalculationType() { public LocalDate getSpecifiedDueDate() {
return specifiedDueDate;
}
public Integer getChargeCalculationType() {
return chargeCalculationType; return chargeCalculationType;
} }
public boolean isAmountChanged(){ public boolean isAmountChanged() {
return this.requestParameters.contains("amount"); return this.requestParameters.contains("amount");
} }
public boolean isChargeTimeTypeChanged(){ public boolean isChargeTimeTypeChanged() {
return this.requestParameters.contains("chargeTimeType"); return this.requestParameters.contains("chargeTimeType");
} }
public boolean isSpecifiedDueDateChanged(){ public boolean isSpecifiedDueDateChanged() {
return this.requestParameters.contains("specifiedDueDate"); return this.requestParameters.contains("specifiedDueDate");
} }
public boolean isChargeCalculationTypeChanged(){ public boolean isChargeCalculationTypeChanged() {
return this.requestParameters.contains("chargeCalculationType"); return this.requestParameters.contains("chargeCalculationType");
} }
} }
@@ -1,31 +1,77 @@
package org.mifosplatform.portfolio.loanaccount.command; package org.mifosplatform.portfolio.loanaccount.command;
import java.util.ArrayList;
import java.util.List;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.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 { public class LoanStateTransitionCommand {
private final Long loanId; private final LocalDate eventDate;
private final LocalDate eventDate; private final String note;
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) { public LoanStateTransitionCommand(final LocalDate eventDate, final String note, final LocalDate approvedOnDate,
this.loanId = loanId; final LocalDate rejectedOnDate, final LocalDate withdrawnOnDate, final LocalDate disbursedOnDate) {
this.eventDate = eventDate; this.eventDate = eventDate;
this.note = note; this.note = note;
} this.approvedOnDate = approvedOnDate;
this.rejectedOnDate = rejectedOnDate;
this.withdrawnOnDate = withdrawnOnDate;
this.disbursedOnDate = disbursedOnDate;
}
public Long getLoanId() { public LocalDate getApprovedOnDate() {
return loanId; LocalDate dateValue = this.approvedOnDate;
} if (this.approvedOnDate == null) {
dateValue = eventDate;
}
return dateValue;
}
public LocalDate getEventDate() { public LocalDate getRejectedOnDate() {
return eventDate; LocalDate dateValue = this.rejectedOnDate;
} if (this.rejectedOnDate == null) {
dateValue = eventDate;
}
return dateValue;
}
public String getNote() { public LocalDate getWithdrawnOnDate() {
return note; 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<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
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); }
}
} }
@@ -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<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
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);
}
}
}
@@ -2,9 +2,13 @@ package org.mifosplatform.portfolio.loanaccount.domain;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set; import java.util.Set;
import javax.persistence.CascadeType; import javax.persistence.CascadeType;
@@ -26,7 +30,11 @@ import org.apache.commons.lang.StringUtils;
import org.hibernate.annotations.LazyCollection; import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption; import org.hibernate.annotations.LazyCollectionOption;
import org.joda.time.LocalDate; 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.infrastructure.core.domain.AbstractAuditableCustom;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.organisation.staff.domain.Staff; 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.client.domain.Client;
import org.mifosplatform.portfolio.fund.domain.Fund; import org.mifosplatform.portfolio.fund.domain.Fund;
import org.mifosplatform.portfolio.group.domain.Group; 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.command.LoanChargeCommand;
import org.mifosplatform.portfolio.loanaccount.exception.InvalidLoanStateTransitionException; import org.mifosplatform.portfolio.loanaccount.exception.InvalidLoanStateTransitionException;
import org.mifosplatform.portfolio.loanaccount.exception.InvalidLoanTransactionTypeException; import org.mifosplatform.portfolio.loanaccount.exception.InvalidLoanTransactionTypeException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentException; import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData; import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData; 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.LoanProduct;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy; import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy;
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations;
import org.mifosplatform.useradministration.domain.AppUser; import org.mifosplatform.useradministration.domain.AppUser;
@Entity @Entity
@@ -68,7 +83,6 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
@JoinColumn(name = "product_id") @JoinColumn(name = "product_id")
private LoanProduct loanProduct; private LoanProduct loanProduct;
@SuppressWarnings("unused")
@ManyToOne @ManyToOne
@JoinColumn(name = "fund_id", nullable = true) @JoinColumn(name = "fund_id", nullable = true)
private Fund fund; private Fund fund;
@@ -87,11 +101,9 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
@Embedded @Embedded
private LoanProductRelatedDetail loanRepaymentScheduleDetail; private LoanProductRelatedDetail loanRepaymentScheduleDetail;
@SuppressWarnings("unused")
@Column(name = "term_frequency", nullable = false) @Column(name = "term_frequency", nullable = false)
private Integer termFrequency; private Integer termFrequency;
@SuppressWarnings("unused")
@Column(name = "term_period_frequency_enum", nullable = false) @Column(name = "term_period_frequency_enum", nullable = false)
private Integer termPeriodFrequencyType; private Integer termPeriodFrequencyType;
@@ -178,6 +190,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
@Transient @Transient
private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessor = new LoanRepaymentScheduleTransactionProcessorFactory(); private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessor = new LoanRepaymentScheduleTransactionProcessorFactory();
@Deprecated
public static Loan createNew(final Fund fund, final Staff loanOfficer, public static Loan createNew(final Fund fund, final Staff loanOfficer,
final LoanTransactionProcessingStrategy transactionProcessingStrategy, final LoanProduct loanProduct, final Client client, final LoanTransactionProcessingStrategy transactionProcessingStrategy, final LoanProduct loanProduct, final Client client,
final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set<LoanCharge> loanCharges) { final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set<LoanCharge> loanCharges) {
@@ -185,6 +198,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
loanCharges); loanCharges);
} }
@Deprecated
public static Loan createNew(final Fund fund, final Staff loanOfficer, public static Loan createNew(final Fund fund, final Staff loanOfficer,
final LoanTransactionProcessingStrategy transactionProcessingStrategy, final LoanProduct loanProduct, final Group group, final LoanTransactionProcessingStrategy transactionProcessingStrategy, final LoanProduct loanProduct, final Group group,
final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set<LoanCharge> loanCharges) { final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set<LoanCharge> loanCharges) {
@@ -192,13 +206,24 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
loanCharges); 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<LoanCharge> 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<LoanCharge> loanCharges) {
final LoanStatus status = null;
LoanProductRelatedDetail loanRepaymentScheduleDetail = loanSchedule.loanProductRelatedDetail();
return new Loan(group, fund, officer, transactionProcessingStrategy, loanProduct, loanRepaymentScheduleDetail, status, loanCharges);
}
protected Loan() { 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, private Loan(final Client client, Fund fund, Staff loanOfficer, final LoanTransactionProcessingStrategy transactionProcessingStrategy,
@@ -472,79 +497,31 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
return this.loanRepaymentScheduleDetail; return this.loanRepaymentScheduleDetail;
} }
public void modifyLoanApplication(final LoanApplicationCommand command, final Client client, final LoanProduct loanProduct, public void updateClient(final Client client) {
final Fund fund, final LoanTransactionProcessingStrategy strategy, final LoanScheduleData modifiedLoanSchedule, this.client = client;
final Set<LoanCharge> charges, final LoanLifecycleStateMachine loanLifecycleStateMachine) { }
// FIXME - KW - whilst the methods are named isXXXChanged, this really public void updateLoanProduct(final LoanProduct loanProduct) {
// only is asking has the parameter for that field been passed in this.loanProduct = loanProduct;
// 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;
}
if (command.isTermFrequencyChanged()) { public void updateFund(final Fund fund) {
this.termFrequency = command.getLoanTermFrequency(); this.fund = fund;
} }
if (command.isTermFrequencyTypeChanged()) { public void updateTransactionProcessingStrategy(final LoanTransactionProcessingStrategy strategy) {
this.termPeriodFrequencyType = PeriodFrequencyType.fromInt(command.getLoanTermFrequencyType()).getValue(); this.transactionProcessingStrategy = strategy;
} }
if (command.isSubmittedOnDateChanged()) { public void updateLoanCharges(final Set<LoanCharge> loanCharges) {
this.submittedOnDate = command.getSubmittedOnDate().toDate(); this.charges.clear();
} this.charges.addAll(associateChargesWithThisLoan(loanCharges));
this.totalChargesDueAtDisbursement = deriveSumTotalOfChargesDueAtDisbursement();
}
if (command.isExpectedDisbursementDatePassed()) { public void updateLoanSchedule(final LoanScheduleData modifiedLoanSchedule) {
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.
this.repaymentScheduleInstallments.clear(); this.repaymentScheduleInstallments.clear();
for (LoanSchedulePeriodData scheduledLoanInstallment : modifiedLoanSchedule.getPeriods()) { for (LoanSchedulePeriodData scheduledLoanInstallment : modifiedLoanSchedule.getPeriods()) {
if (scheduledLoanInstallment.isRepaymentPeriod()) { if (scheduledLoanInstallment.isRepaymentPeriod()) {
@@ -567,17 +544,147 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
this.repaymentScheduleInstallments, this.charges); 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<String, Object> modifyLoanApplication(final JsonCommand command, final LoanChargeCommand[] loanChargeCommands,
final AprCalculator aprCalculator) {
if (originalLocalDate != null) { final Map<String, Object> actualChanges = this.loanRepaymentScheduleDetail.updateLoanApplicationAttributes(command, aprCalculator);
dateHasChanged = !originalLocalDate.equals(providedLocalDate); if (!actualChanges.isEmpty()) {
} else { boolean recalculateLoanSchedule = !(actualChanges.size() == 1 && actualChanges.containsKey("inArrearsTolerance"));
dateHasChanged = (providedLocalDate != null); 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<LoanChargeCommand> loanChargesList = new ArrayList<LoanChargeCommand>();
for (LoanCharge loanCharge : this.charges) {
loanChargesList.add(loanCharge.toCommand());
}
existingLoanCharges = loanChargesList.toArray(new LoanChargeCommand[loanChargesList.size()]);
}
return existingLoanCharges;
} }
private void removeFirstDisbursementTransaction() { private void removeFirstDisbursementTransaction() {
@@ -589,9 +696,11 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
} }
} }
public void submitApplication(final Integer loanTermFrequency, final PeriodFrequencyType loanTermFrequencyType, public void loanApplicationSubmittal(final LoanSchedule loanSchedule, final LoanLifecycleStateMachine lifecycleStateMachine,
final LocalDate submittedOn, final LocalDate expectedDisbursementDate, final LocalDate repaymentsStartingFromDate, final LocalDate submittedOn, final String externalId) {
final LocalDate interestChargedFromDate, final LoanLifecycleStateMachine lifecycleStateMachine) {
final LoanScheduleData loanScheduleData = loanSchedule.generate();
updateLoanSchedule(loanScheduleData);
LoanStatus from = null; LoanStatus from = null;
if (this.loanStatus != null) { if (this.loanStatus != null) {
@@ -601,22 +710,22 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
LoanStatus statusEnum = lifecycleStateMachine.transition(LoanEvent.LOAN_CREATED, from); LoanStatus statusEnum = lifecycleStateMachine.transition(LoanEvent.LOAN_CREATED, from);
this.loanStatus = statusEnum.getValue(); this.loanStatus = statusEnum.getValue();
this.termFrequency = loanTermFrequency; this.termFrequency = loanSchedule.getLoanTermFrequency();
this.termPeriodFrequencyType = loanTermFrequencyType.getValue(); this.termPeriodFrequencyType = loanSchedule.getLoanTermPeriodFrequencyType().getValue();
this.submittedOnDate = submittedOn.toDate(); this.submittedOnDate = submittedOn.toDate();
this.expectedMaturityDate = determineExpectedMaturityDate().toDate(); this.expectedMaturityDate = determineExpectedMaturityDate().toDate();
if (expectedDisbursementDate != null) {
// can be null during bulk upload of loans if (loanSchedule.getDisbursementDate() != null) {
this.expectedDisbursedOnDate = expectedDisbursementDate.toDate(); this.expectedDisbursedOnDate = loanSchedule.getDisbursementDate().toDate();
} }
if (repaymentsStartingFromDate != null) { if (loanSchedule.getRepaymentStartFromDate() != null) {
this.expectedFirstRepaymentOnDate = repaymentsStartingFromDate.toDate(); this.expectedFirstRepaymentOnDate = loanSchedule.getRepaymentStartFromDate().toDate();
} }
if (interestChargedFromDate != null) { if (loanSchedule.getInterestChargedFromDate() != null) {
this.interestChargedFromDate = interestChargedFromDate.toDate(); this.interestChargedFromDate = loanSchedule.getInterestChargedFromDate().toDate();
} }
if (submittedOn.isAfter(new LocalDate())) { if (submittedOn.isAfter(new LocalDate())) {
@@ -637,6 +746,8 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate()); validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate());
} }
} }
this.externalId = externalId;
} }
private LocalDate determineExpectedMaturityDate() { private LocalDate determineExpectedMaturityDate() {
@@ -644,13 +755,30 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
return this.repaymentScheduleInstallments.get(numberOfInstallments - 1).getDueDate(); return this.repaymentScheduleInstallments.get(numberOfInstallments - 1).getDueDate();
} }
public void reject(final LocalDate rejectedOn, final LoanLifecycleStateMachine loanLifecycleStateMachine) { public Map<String, Object> loanApplicationRejection(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine) {
LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_REJECTED, LoanStatus.fromInt(this.loanStatus)); final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>();
this.loanStatus = statusEnum.getValue();
this.rejectedOnDate = rejectedOn.toDateTimeAtCurrentTime().toDate(); final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_REJECTED, LoanStatus.fromInt(this.loanStatus));
this.closedOnDate = rejectedOn.toDateTimeAtCurrentTime().toDate(); 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())) { if (rejectedOn.isBefore(getSubmittedOnDate())) {
final String errorMessage = "The date on which a loan is rejected cannot be before its submittal date: " 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<AppUser, Long> {
final String errorMessage = "The date on which a loan is rejected cannot be in the future."; 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); throw new InvalidLoanStateTransitionException("reject", "cannot.be.a.future.date", errorMessage, rejectedOn);
} }
return actualChanges;
} }
public void withdraw(final LocalDate withdrawnOn, final LoanLifecycleStateMachine loanLifecycleStateMachine) { public Map<String, Object> loanApplicationWithdrawnByApplicant(final JsonCommand command,
final LoanLifecycleStateMachine loanLifecycleStateMachine) {
LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_WITHDRAWN, LoanStatus.fromInt(this.loanStatus)); final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>();
this.loanStatus = statusEnum.getValue();
this.withdrawnOnDate = withdrawnOn.toDateTimeAtCurrentTime().toDate(); final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_WITHDRAWN, LoanStatus.fromInt(this.loanStatus));
this.closedOnDate = withdrawnOn.toDateTimeAtCurrentTime().toDate(); 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())) { if (withdrawnOn.isBefore(getSubmittedOnDate())) {
final String errorMessage = "The date on which a loan is withdrawn cannot be before its submittal date: " final String errorMessage = "The date on which a loan is withdrawn cannot be before its submittal date: "
+ getSubmittedOnDate().toString(); + 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()); getSubmittedOnDate());
} }
if (withdrawnOn.isAfter(new LocalDate())) { if (withdrawnOn.isAfter(new LocalDate())) {
final String errorMessage = "The date on which a loan is withdrawn cannot be in the future."; 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<String, Object> loanApplicationApproval(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine) {
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>();
final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_APPROVED, LoanStatus.fromInt(this.loanStatus)); 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); final LocalDate submittalDate = new LocalDate(this.submittedOnDate);
if (approvedOn.isBefore(submittalDate)) { if (approvedOn.isBefore(submittalDate)) {
@@ -708,47 +875,112 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
this.loanofficer, approvedOn); this.loanofficer, approvedOn);
this.loanOfficerHistory.add(loanOfficerAssignmentHistory); this.loanOfficerHistory.add(loanOfficerAssignmentHistory);
} }
return actualChanges;
} }
public void undoApproval(LoanLifecycleStateMachine loanLifecycleStateMachine) { public Map<String, Object> undoApproval(final LoanLifecycleStateMachine loanLifecycleStateMachine) {
final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_APPROVAL_UNDO, final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>();
LoanStatus.fromInt(this.loanStatus));
this.loanStatus = statusEnum.getValue(); 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; this.approvedOnDate = null;
actualChanges.put("approvedOnDate", "");
this.loanOfficerHistory.clear(); this.loanOfficerHistory.clear();
return actualChanges;
} }
public void disburseWithModifiedRepaymentSchedule(final LocalDate disbursedOn, private void disburse(final LocalDate expectedDisbursedOnLocalDate) {
final List<LoanRepaymentScheduleInstallment> modifiedLoanRepaymentSchedule, LoanLifecycleStateMachine loanLifecycleStateMachine) { this.disbursedOnDate = expectedDisbursedOnLocalDate.toDate();
this.repaymentScheduleInstallments.clear(); this.expectedMaturityDate = determineExpectedMaturityDate().toDate();
for (LoanRepaymentScheduleInstallment modifiedInstallment : modifiedLoanRepaymentSchedule) {
modifiedInstallment.updateLoan(this); handleDisbursementTransaction(expectedDisbursedOnLocalDate);
this.repaymentScheduleInstallments.add(modifiedInstallment);
}
disburse(disbursedOn, loanLifecycleStateMachine, true);
} }
public void disburse(final LocalDate disbursedOn, final LoanLifecycleStateMachine loanLifecycleStateMachine, public Map<String, Object> disburse(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine,
final boolean statusTransition) { final ApplicationCurrency currency) {
if (statusTransition) { final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>();
// ensure loan is in pre-disbursal state
updateLoanToPreDisbursalState();
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(); this.loanStatus = statusEnum.getValue();
actualChanges.put("status", LoanEnumerations.status(this.loanStatus));
} }
this.disbursedOnDate = disbursedOn.toDateTimeAtCurrentTime().toDate(); LocalDate disbursedOn = command.localDateValueOfParameterNamed("disbursedOnDate");
this.expectedMaturityDate = this.repaymentScheduleInstallments.get(this.repaymentScheduleInstallments.size() - 1).getDueDate() if (disbursedOn == null) {
.toDateMidnight().toDate(); disbursedOn = command.localDateValueOfParameterNamed("eventDate");
}
// add disbursement transaction to track outgoing money from mfi to final Locale locale = new Locale(command.locale());
// client final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
LoanTransaction loanTransaction = LoanTransaction.disbursement(this.loanRepaymentScheduleDetail.getPrincipal(), disbursedOn);
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); loanTransaction.updateLoan(this);
this.loanTransactions.add(loanTransaction); this.loanTransactions.add(loanTransaction);
@@ -769,7 +1001,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
} }
} }
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: " final String errorMessage = "The date on which a loan is disbursed cannot be before its approval date: "
+ getApprovedOnDate().toString(); + getApprovedOnDate().toString();
throw new InvalidLoanStateTransitionException("disbursal", "cannot.be.before.approval.date", errorMessage, disbursedOn, throw new InvalidLoanStateTransitionException("disbursal", "cannot.be.before.approval.date", errorMessage, disbursedOn,
@@ -790,13 +1022,23 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
} }
} }
public void undoDisbursal(final LoanLifecycleStateMachine loanLifecycleStateMachine) { public Map<String, Object> undoDisbursal(final LoanLifecycleStateMachine loanLifecycleStateMachine) {
final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSAL_UNDO, final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>();
LoanStatus.fromInt(this.loanStatus));
this.loanStatus = statusEnum.getValue(); 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(); updateLoanToPreDisbursalState();
return actualChanges;
} }
private void updateLoanToPreDisbursalState() { private void updateLoanToPreDisbursalState() {
@@ -1489,14 +1731,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
} }
public boolean isRepaymentScheduleRegenerationRequiredForDisbursement(final LocalDate actualDisbursementDate) { public boolean isRepaymentScheduleRegenerationRequiredForDisbursement(final LocalDate actualDisbursementDate) {
return isActualDisbursedOnDateEarlierOrLaterThanExpected(actualDisbursementDate);
boolean regenerationRequired = false;
if (isActualDisbursedOnDateEarlierOrLaterThanExpected(actualDisbursementDate)) {
regenerationRequired = true;
}
return regenerationRequired;
} }
private Money getTotalPaidInRepayments() { private Money getTotalPaidInRepayments() {
@@ -1655,5 +1890,4 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
public Client getClient() { public Client getClient() {
return client; return client;
} }
} }
@@ -86,6 +86,14 @@ public class LoanCharge extends AbstractPersistable<Long> {
return new LoanCharge(chargeDefinition, command, loanPrincipal); 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) { public static LoanCharge createNew(final Charge chargeDefinition) {
return new LoanCharge(null, chargeDefinition); return new LoanCharge(null, chargeDefinition);
} }
@@ -94,6 +102,45 @@ public class LoanCharge extends AbstractPersistable<Long> {
// //
} }
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) { public LoanCharge(final Charge chargeDefinition, final LoanChargeCommand command, final BigDecimal loanPrincipal) {
this.loan = null; this.loan = null;
this.charge = chargeDefinition; this.charge = chargeDefinition;
@@ -133,6 +180,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
this.paid = determineIfFullyPaid(); this.paid = determineIfFullyPaid();
} }
@Deprecated
private LoanCharge(final Loan loan, final Charge chargeDefinition, final LoanChargeCommand command) { private LoanCharge(final Loan loan, final Charge chargeDefinition, final LoanChargeCommand command) {
this.loan = loan; this.loan = loan;
this.charge = chargeDefinition; this.charge = chargeDefinition;
@@ -270,6 +318,40 @@ public class LoanCharge extends AbstractPersistable<Long> {
public void update(final Loan loan) { public void update(final Loan loan) {
this.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) { public void update(final LoanChargeCommand command, final BigDecimal loanPrincipal) {
@@ -4,106 +4,106 @@ package org.mifosplatform.portfolio.loanaccount.domain;
* Enum representation of loan status states. * Enum representation of loan status states.
*/ */
public enum LoanStatus { public enum LoanStatus {
INVALID(0, "loanStatusType.invalid"), // INVALID(0, "loanStatusType.invalid"), //
SUBMITED_AND_PENDING_APPROVAL(100, "loanStatusType.submitted.and.pending.approval"), // SUBMITED_AND_PENDING_APPROVAL(100, "loanStatusType.submitted.and.pending.approval"), //
APPROVED(200, "loanStatusType.approved"), // APPROVED(200, "loanStatusType.approved"), //
ACTIVE(300, "loanStatusType.active"), // ACTIVE(300, "loanStatusType.active"), //
WITHDRAWN_BY_CLIENT(400, "loanStatusType.withdrawn.by.client"), // WITHDRAWN_BY_CLIENT(400, "loanStatusType.withdrawn.by.client"), //
REJECTED(500, "loanStatusType.rejected"), // REJECTED(500, "loanStatusType.rejected"), //
CLOSED_OBLIGATIONS_MET(600, "loanStatusType.closed.obligations.met"), // CLOSED_OBLIGATIONS_MET(600, "loanStatusType.closed.obligations.met"), //
CLOSED_WRITTEN_OFF(601, "loanStatusType.closed.written.off"), // CLOSED_WRITTEN_OFF(601, "loanStatusType.closed.written.off"), //
CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT(602, "loanStatusType.closed.reschedule.outstanding.amount"), // CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT(602, "loanStatusType.closed.reschedule.outstanding.amount"), //
OVERPAID(700, "loanStatusType.overpaid"); OVERPAID(700, "loanStatusType.overpaid");
private final Integer value; private final Integer value;
private final String code; private final String code;
public static LoanStatus fromInt(final Integer statusValue) {
LoanStatus enumeration = LoanStatus.INVALID; public static LoanStatus fromInt(final Integer statusValue) {
switch (statusValue) {
case 100: LoanStatus enumeration = LoanStatus.INVALID;
enumeration = LoanStatus.SUBMITED_AND_PENDING_APPROVAL; switch (statusValue) {
break; case 100:
case 200: enumeration = LoanStatus.SUBMITED_AND_PENDING_APPROVAL;
enumeration = LoanStatus.APPROVED; break;
break; case 200:
case 300: enumeration = LoanStatus.APPROVED;
enumeration = LoanStatus.ACTIVE; break;
break; case 300:
case 400: enumeration = LoanStatus.ACTIVE;
enumeration = LoanStatus.WITHDRAWN_BY_CLIENT; break;
break; case 400:
case 500: enumeration = LoanStatus.WITHDRAWN_BY_CLIENT;
enumeration = LoanStatus.REJECTED; break;
break; case 500:
case 600: enumeration = LoanStatus.REJECTED;
enumeration = LoanStatus.CLOSED_OBLIGATIONS_MET; break;
break; case 600:
case 601: enumeration = LoanStatus.CLOSED_OBLIGATIONS_MET;
enumeration = LoanStatus.CLOSED_WRITTEN_OFF; break;
break; case 601:
case 602: enumeration = LoanStatus.CLOSED_WRITTEN_OFF;
enumeration = LoanStatus.CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT; break;
break; case 602:
case 700: enumeration = LoanStatus.CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT;
enumeration = LoanStatus.OVERPAID; break;
break; case 700:
} enumeration = LoanStatus.OVERPAID;
return enumeration; break;
}
return enumeration;
} }
private LoanStatus(final Integer value, final String code) { private LoanStatus(final Integer value, final String code) {
this.value = value; this.value = value;
this.code = code; this.code = code;
} }
public boolean hasStateOf(final LoanStatus state) { public boolean hasStateOf(final LoanStatus state) {
return this.value.equals(state.getValue()); return this.value.equals(state.getValue());
} }
public Integer getValue() { public Integer getValue() {
return this.value; return this.value;
} }
public String getCode() { public String getCode() {
return code; return code;
} }
public boolean isSubmittedAndPendingApproval() { public boolean isSubmittedAndPendingApproval() {
return this.value.equals(LoanStatus.SUBMITED_AND_PENDING_APPROVAL.getValue()); 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 isClosed() { public boolean isApproved() {
return isClosedObligationsMet() || isClosedWrittenOff() || isClosedWithOutsandingAmountMarkedForReschedule(); return this.value.equals(LoanStatus.APPROVED.getValue());
} }
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() { public boolean isActive() {
return this.value.equals(LoanStatus.CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT.getValue()); return this.value.equals(LoanStatus.ACTIVE.getValue());
} }
public boolean isWithdrawnByClient() {
return this.value.equals(LoanStatus.WITHDRAWN_BY_CLIENT.getValue());
}
public boolean isRejected() { public boolean isClosed() {
return this.value.equals(LoanStatus.REJECTED.getValue()); 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());
}
} }
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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());
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
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);
}
}
}
@@ -5,301 +5,284 @@ import java.util.Collection;
import org.mifosplatform.organisation.monetary.data.CurrencyData; import org.mifosplatform.organisation.monetary.data.CurrencyData;
/** /**
* Immutable data object to represent aspects of a loan schedule such as: * Immutable data object to represent aspects of a loan schedule such as:
* *
* <ul> * <ul>
* <li>summary information - the totals for each part of schedule monitored</li> * <li>summary information - the totals for each part of schedule monitored</li>
* <li>repayment schedule - the principal due, outstanding balance and cost of loan items such as interest and charges</li> * <li>repayment schedule - the principal due, outstanding balance and cost of
* loan items such as interest and charges</li>
* </ul> * </ul>
*/ */
public class LoanScheduleData { public class LoanScheduleData {
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final Integer loanTermInDays; 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 <code>principalDue</code> 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:
* <ul>
* <li>Interest</li>
* <li>Charges (fees and penalties)</li>
* </ul>
*/
@SuppressWarnings("unused")
private final BigDecimal totalCostOfLoan;
/**
* The sum of <code>principalDisbursed</code> and <code>totalCostOfLoan</code>. (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;
/**
* <code>periods</code> is collection of data objects containing specific
* information to each period of the loan schedule including disbursement and
* repayment information.
*/
private final Collection<LoanSchedulePeriodData> periods;
public LoanScheduleData() { /**
this.cumulativePrincipalDisbursed = null; * The currency associated with all monetary values in loan schedule.
this.cumulativePrincipalPaid = null; */
this.cumulativePrincipalWrittenOff = null; @SuppressWarnings("unused")
this.cumulativePrincipalDue = null; private final CurrencyData currency;
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, * The cumulative total of all money (principal) disbursed to the loan
final Collection<LoanSchedulePeriodData> periods, * applicant.
final Integer loanTermInDays, */
final BigDecimal cumulativePrincipalDisbursed, @SuppressWarnings("unused")
final BigDecimal cumulativePrincipalDue, private final BigDecimal cumulativePrincipalDisbursed;
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 * The cumulative total of all <code>principalDue</code> for each period of
*/ * loan schedule. (originalPrincipal)
public LoanScheduleData(final CurrencyData currency, final Collection<LoanSchedulePeriodData> periods, */
final Integer loanTermInDays, @SuppressWarnings("unused")
final BigDecimal cumulativePrincipalDisbursed, private final BigDecimal cumulativePrincipalDue;
final BigDecimal cumulativePrincipalDue, @SuppressWarnings("unused")
final BigDecimal cumulativePrincipalPaid, private final BigDecimal cumulativePrincipalPaid;
final BigDecimal cumulativePrincipalWrittenOff, @SuppressWarnings("unused")
final BigDecimal cumulativePrincipalOutstanding, private final BigDecimal cumulativePrincipalWrittenOff;
final BigDecimal cumulativeInterestExpected, @SuppressWarnings("unused")
final BigDecimal cumulativeInterestPaid, private final BigDecimal cumulativePrincipalOutstanding;
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<LoanSchedulePeriodData> 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:
* <ul>
* <li>Interest</li>
* <li>Charges (fees and penalties)</li>
* </ul>
*/
@SuppressWarnings("unused")
private final BigDecimal totalCostOfLoan;
/**
* The sum of <code>principalDisbursed</code> and
* <code>totalCostOfLoan</code>. (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;
/**
* <code>periods</code> is collection of data objects containing specific
* information to each period of the loan schedule including disbursement
* and repayment information.
*/
private final Collection<LoanSchedulePeriodData> 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<LoanSchedulePeriodData> 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<LoanSchedulePeriodData> 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<LoanSchedulePeriodData> getPeriods() {
return periods;
}
public BigDecimal totalOutstanding() {
return this.totalOutstanding;
}
} }
@@ -8,25 +8,25 @@ import org.springframework.stereotype.Component;
@Component @Component
public class AprCalculator { public class AprCalculator {
public BigDecimal calculateFrom(PeriodFrequencyType interestPeriodFrequencyType, BigDecimal interestRatePerPeriod) { public BigDecimal calculateFrom(PeriodFrequencyType interestPeriodFrequencyType, BigDecimal interestRatePerPeriod) {
BigDecimal defaultAnnualNominalInterestRate = BigDecimal.ZERO; BigDecimal defaultAnnualNominalInterestRate = BigDecimal.ZERO;
switch (interestPeriodFrequencyType) { switch (interestPeriodFrequencyType) {
case DAYS: case DAYS:
break; break;
case WEEKS: case WEEKS:
defaultAnnualNominalInterestRate = interestRatePerPeriod.multiply(BigDecimal.valueOf(52)); defaultAnnualNominalInterestRate = interestRatePerPeriod.multiply(BigDecimal.valueOf(52));
break; break;
case MONTHS: case MONTHS:
defaultAnnualNominalInterestRate = interestRatePerPeriod.multiply(BigDecimal.valueOf(12)); defaultAnnualNominalInterestRate = interestRatePerPeriod.multiply(BigDecimal.valueOf(12));
break; break;
case YEARS: case YEARS:
defaultAnnualNominalInterestRate = interestRatePerPeriod.multiply(BigDecimal.valueOf(1)); defaultAnnualNominalInterestRate = interestRatePerPeriod.multiply(BigDecimal.valueOf(1));
break; break;
case INVALID: case INVALID:
break; break;
} }
return defaultAnnualNominalInterestRate; return defaultAnnualNominalInterestRate;
} }
} }
@@ -14,55 +14,56 @@ import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
/** /**
* <p> * <p>
* Declining balance can amortized (see {@link AmortizationMethod}) in two ways at present: * Declining balance can be amortized (see {@link AmortizationMethod}) in two ways
* <ol><li>Equal principal payments</li><li>Equal installment payments</li></ol> * at present:
* <ol>
* <li>Equal principal payments</li>
* <li>Equal installment payments</li>
* </ol>
* </p> * </p>
* *
* <p> * <p>
* When amortized using <i>equal principal payments</i>, the <b>principal component</b> of each installment is fixed and <b>interest due</b> * When amortized using <i>equal principal payments</i>, the <b>principal
* is calculated from the <b>outstanding principal balance</b> resulting in a different <b>total payment due</b> for each installment. * component</b> of each installment is fixed and <b>interest due</b> is
* calculated from the <b>outstanding principal balance</b> resulting in a
* different <b>total payment due</b> for each installment.
* </p> * </p>
* *
* <p> * <p>
* When amortized using <i>equal installments</i>, the <b>total payment due</b> for each installment is fixed and * When amortized using <i>equal installments</i>, the <b>total payment due</b>
* is calculated using the excel like <code>pmt</code> function. The <b>interest due</b> is calculated from the <b>outstanding principal balance</b> * for each installment is fixed and is calculated using the excel like
* which results in a <b>principal component</b> that is <b>total payment due</b> minus <b>interest due</b>. * <code>pmt</code> function. The <b>interest due</b> is calculated from the
* </p> * <b>outstanding principal balance</b> which results in a <b>principal
* component</b> that is <b>total payment due</b> minus <b>interest due</b>.
* </p>
*/ */
public class DecliningBalanceMethodLoanScheduleGenerator implements LoanScheduleGenerator { public class DecliningBalanceMethodLoanScheduleGenerator implements LoanScheduleGenerator {
private final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator(); private final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
private final PeriodicInterestRateCalculator periodicInterestRateCalculator = new PeriodicInterestRateCalculator(); private final PeriodicInterestRateCalculator periodicInterestRateCalculator = new PeriodicInterestRateCalculator();
private final AmortizationLoanScheduleGeneratorFactory amortizationLoanScheduleGeneratorFactory = new AmortizationLoanScheduleGeneratorFactory(); private final AmortizationLoanScheduleGeneratorFactory amortizationLoanScheduleGeneratorFactory = new AmortizationLoanScheduleGeneratorFactory();
@Override @Override
public LoanScheduleData generate( public LoanScheduleData generate(final ApplicationCurrency applicationCurrency, final LoanProductRelatedDetail loanScheduleInfo,
final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency, final PeriodFrequencyType loanTermFrequencyType, final LocalDate disbursementDate,
final LoanProductRelatedDetail loanScheduleInfo, final LocalDate firstRepaymentDate, final LocalDate interestCalculatedFrom, final Set<LoanCharge> loanCharges) {
final Integer loanTermFrequency,
final PeriodFrequencyType loanTermFrequencyType, // 1. generate valid set of 'due dates' based on some of the 'loan
final LocalDate disbursementDate, // attributes'
final LocalDate firstRepaymentDate, final List<LocalDate> scheduledDates = this.scheduledDateGenerator.generate(loanScheduleInfo, disbursementDate, firstRepaymentDate);
final LocalDate interestCalculatedFrom,
final Set<LoanCharge> loanCharges) { final LocalDate idealDisbursementDateBasedOnFirstRepaymentDate = this.scheduledDateGenerator
.idealDisbursementDateBasedOnFirstRepaymentDate(loanScheduleInfo, scheduledDates);
// 1. generate valid set of 'due dates' based on some of the 'loan attributes'
final List<LocalDate> scheduledDates = this.scheduledDateGenerator.generate(loanScheduleInfo, disbursementDate, firstRepaymentDate); // 2. determine the 'periodic' interest rate based on the 'repayment
// periods' so we can use
final LocalDate idealDisbursementDateBasedOnFirstRepaymentDate = this.scheduledDateGenerator.idealDisbursementDateBasedOnFirstRepaymentDate(loanScheduleInfo, scheduledDates); final BigDecimal periodInterestRateForRepaymentPeriod = this.periodicInterestRateCalculator.calculateFrom(loanScheduleInfo);
// 2. determine the 'periodic' interest rate based on the 'repayment periods' so we can use // Determine with 'amortisation' approach to use
final BigDecimal periodInterestRateForRepaymentPeriod = this.periodicInterestRateCalculator.calculateFrom(loanScheduleInfo); final AmortizationLoanScheduleGenerator generator = this.amortizationLoanScheduleGeneratorFactory.createGenerator(loanScheduleInfo
.getAmortizationMethod());
// 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);
return generator.generate(applicationCurrency, }
loanScheduleInfo,
disbursementDate,
interestCalculatedFrom,
periodInterestRateForRepaymentPeriod,
idealDisbursementDateBasedOnFirstRepaymentDate,
scheduledDates, loanCharges);
}
} }
@@ -1,26 +1,27 @@
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain; package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
import org.springframework.stereotype.Component;
public class DefaultLoanScheduleGeneratorFactory implements @Component
LoanScheduleGeneratorFactory { public class DefaultLoanScheduleGeneratorFactory implements LoanScheduleGeneratorFactory {
@Override @Override
public LoanScheduleGenerator create(final InterestMethod interestMethod) { public LoanScheduleGenerator create(final InterestMethod interestMethod) {
LoanScheduleGenerator loanScheduleGenerator = null; LoanScheduleGenerator loanScheduleGenerator = null;
switch (interestMethod) { switch (interestMethod) {
case FLAT: case FLAT:
loanScheduleGenerator = new FlatMethodLoanScheduleGenerator(); loanScheduleGenerator = new FlatMethodLoanScheduleGenerator();
break; break;
case DECLINING_BALANCE: case DECLINING_BALANCE:
loanScheduleGenerator = new DecliningBalanceMethodLoanScheduleGenerator(); loanScheduleGenerator = new DecliningBalanceMethodLoanScheduleGenerator();
break; break;
case INVALID: case INVALID:
break; break;
} }
return loanScheduleGenerator; return loanScheduleGenerator;
} }
} }
@@ -22,130 +22,117 @@ import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
public class FlatMethodLoanScheduleGenerator implements LoanScheduleGenerator { public class FlatMethodLoanScheduleGenerator implements LoanScheduleGenerator {
private final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator(); private final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator = new DefaultPaymentPeriodsInOneYearCalculator(); 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<LoanCharge> loanCharges) {
final Collection<LoanSchedulePeriodData> periods = new ArrayList<LoanSchedulePeriodData>();
final List<LocalDate> scheduledDates = this.scheduledDateGenerator.generate(loanScheduleInfo, disbursementDate, firstRepaymentDate);
MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN); @Override
public LoanScheduleData generate(final ApplicationCurrency currency, final LoanProductRelatedDetail loanScheduleInfo,
BigDecimal loanTermPeriodsInYear = BigDecimal.valueOf(this.paymentPeriodsInOneYearCalculator.calculate(loanTermFrequencyType)); final Integer loanTermFrequency, final PeriodFrequencyType loanTermFrequencyType, final LocalDate disbursementDate,
BigDecimal interestRateForLoanTerm = loanScheduleInfo final LocalDate firstRepaymentDate, final LocalDate interestCalculatedFrom, final Set<LoanCharge> loanCharges) {
.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);
Money interestPerInstallment = totalInterestForLoanTerm.dividedBy(Long.valueOf(loanScheduleInfo.getNumberOfRepayments()), RoundingMode.HALF_EVEN); final Collection<LoanSchedulePeriodData> periods = new ArrayList<LoanSchedulePeriodData>();
Money principalPerInstallment = loanScheduleInfo.getPrincipal()
.dividedBy(loanScheduleInfo.getNumberOfRepayments(),
RoundingMode.HALF_EVEN);
Money outstandingBalance = loanScheduleInfo.getPrincipal(); final List<LocalDate> scheduledDates = this.scheduledDateGenerator.generate(loanScheduleInfo, disbursementDate, firstRepaymentDate);
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()) { MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN);
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); 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); final MonetaryCurrency monetaryCurrency = loanScheduleInfo.getPrincipal().getCurrency();
Money penaltyChargesForInstallment = Money.zero(monetaryCurrency); Money totalInterestForLoanTerm = loanScheduleInfo.getPrincipal().multiplyRetainScale(interestRateForLoanTerm,
RoundingMode.HALF_EVEN);
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); Money interestPerInstallment = totalInterestForLoanTerm.dividedBy(Long.valueOf(loanScheduleInfo.getNumberOfRepayments()),
RoundingMode.HALF_EVEN);
// handle cumulative fields
loanTermInDays += daysInPeriod;
cumulativePrincipalDue = cumulativePrincipalDue.add(principalPerInstallment.getAmount());
cumulativeInterestExpected = cumulativeInterestExpected.add(interestPerInstallment.getAmount());
totalExpectedRepayment = totalExpectedRepayment.add(totalInstallmentDue.getAmount());
startDate = scheduledDueDate;
periodNumber++; Money principalPerInstallment = loanScheduleInfo.getPrincipal().dividedBy(loanScheduleInfo.getNumberOfRepayments(),
} RoundingMode.HALF_EVEN);
final BigDecimal cumulativePrincipalOutstanding = cumulativePrincipalDisbursed.subtract(cumulativePrincipalDue); Money outstandingBalance = loanScheduleInfo.getPrincipal();
Money principalDisbursed = loanScheduleInfo.getPrincipal();
CurrencyData currencyData = new CurrencyData( Money totalPrincipal = Money.zero(outstandingBalance.getCurrency());
currency.getCode(), Money totalInterest = Money.zero(outstandingBalance.getCurrency());
currency.getName(),
monetaryCurrency.getDigitsAfterDecimal(), BigDecimal chargesDueAtTimeOfDisbursement = BigDecimal.ZERO;
currency.getDisplaySymbol(), for (LoanCharge loanCharge : loanCharges) {
currency.getNameCode()); if (loanCharge.isDueAtDisbursement()) {
chargesDueAtTimeOfDisbursement = chargesDueAtTimeOfDisbursement.add(loanCharge.amount());
return new LoanScheduleData(currencyData, periods, loanTermInDays, cumulativePrincipalDisbursed, cumulativePrincipalDue, }
cumulativePrincipalOutstanding, cumulativeInterestExpected, cumulativeChargesToDate, totalExpectedRepayment); }
}
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);
}
} }
@@ -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<LoanCharge> 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<LoanCharge> 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;
}
}
@@ -11,10 +11,8 @@ import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
public interface LoanScheduleGenerator { public interface LoanScheduleGenerator {
LoanScheduleData generate(ApplicationCurrency applicationCurrency, LoanScheduleData generate(ApplicationCurrency applicationCurrency, LoanProductRelatedDetail loanScheduleInfo,
LoanProductRelatedDetail loanScheduleInfo, Integer loanTermFrequency, PeriodFrequencyType loanTermFrequencyType, LocalDate disbursementDate, LocalDate firstRepaymentDate,
Integer loanTermFrequency, LocalDate interestCalculatedFrom, Set<LoanCharge> loanCharges);
PeriodFrequencyType loanTermFrequencyType,
LocalDate disbursementDate, LocalDate firstRepaymentDate, LocalDate interestCalculatedFrom, Set<LoanCharge> loanCharges);
} }
@@ -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<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
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); }
}
}
@@ -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);
}
@@ -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<LoanCharge> loanCharges = this.loanChargeAssembler.assembleFrom(command.getCharges(), loanScheduleRelatedDetails.getPrincipal().getAmount());
return loanScheduleGenerator.generate(applicationCurrency,
loanScheduleRelatedDetails, loanTermFrequency,
loanTermFrequencyType, command.getExpectedDisbursementDate(),
command.getRepaymentsStartingFromDate(),
command.getInterestChargedFromDate(), loanCharges);
}
}
@@ -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<LoanCharge> 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);
}
}
@@ -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);
}
@@ -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();
}
}
@@ -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<CalculateLoanScheduleQuery> {
/**
* The parameters supported for this command.
*/
final Set<String> supportedParameters = new HashSet<String>(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<Map<String, Object>>() {}.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<String> parametersPassedInForChargesCommand = new HashSet<String>();
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);
}
}
@@ -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<LoanApplicationCommand> {
/**
* The parameters supported for this command.
*/
final Set<String> supportedParameters = new HashSet<String>(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<Map<String, Object>>() {}.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<String> parametersPassedInForChargesCommand = new HashSet<String>();
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<String>(), 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);
}
}
@@ -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<LoanStateTransitionCommand> {
/**
* The parameters supported for this command.
*/
final Set<String> supportedParameters = new HashSet<String>(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<Map<String, Object>>() {}.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);
}
}
@@ -5,7 +5,9 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Set; 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.Staff;
import org.mifosplatform.organisation.staff.domain.StaffRepository; import org.mifosplatform.organisation.staff.domain.StaffRepository;
import org.mifosplatform.organisation.staff.exception.StaffNotFoundException; 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.Group;
import org.mifosplatform.portfolio.group.domain.GroupRepository; import org.mifosplatform.portfolio.group.domain.GroupRepository;
import org.mifosplatform.portfolio.group.exception.GroupNotFoundException; 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.DefaultLoanLifecycleStateMachine;
import org.mifosplatform.portfolio.loanaccount.domain.Loan; import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanLifecycleStateMachine; 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.LoanStatus;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionProcessingStrategyRepository; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionProcessingStrategyRepository;
import org.mifosplatform.portfolio.loanaccount.exception.LoanTransactionProcessingStrategyNotFoundException; import org.mifosplatform.portfolio.loanaccount.exception.LoanTransactionProcessingStrategyNotFoundException;
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanSchedule;
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData; import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler;
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.loanproduct.domain.LoanProduct; 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.LoanProductRepository;
import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy; import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy;
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.loanproduct.exception.LoanProductNotFoundException; import org.mifosplatform.portfolio.loanproduct.exception.LoanProductNotFoundException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.google.gson.JsonElement;
@Service @Service
public class LoanAssembler { public class LoanAssembler {
private final LoanProductRepository loanProductRepository; private final LoanProductRepository loanProductRepository;
private final ClientRepository clientRepository; private final ClientRepository clientRepository;
private final GroupRepository groupRepository; private final GroupRepository groupRepository;
private final AprCalculator aprCalculator = new AprCalculator(); private final FundRepository fundRepository;
private final FundRepository fundRepository; private final LoanTransactionProcessingStrategyRepository loanTransactionProcessingStrategyRepository;
private final LoanTransactionProcessingStrategyRepository loanTransactionProcessingStrategyRepository; private final StaffRepository staffRepository;
private final StaffRepository staffRepository; private final LoanChargeAssembler loanChargeAssembler;
private final CalculationPlatformService calculationPlatformService; private final LoanScheduleAssembler loanScheduleAssembler;
private final LoanChargeAssembler loanChargeAssembler; private final FromJsonHelper fromApiJsonHelper;
@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());
}
Client client = null; @Autowired
if (command.getClientId() != null) { public LoanAssembler(final FromJsonHelper fromApiJsonHelper, final LoanProductRepository loanProductRepository,
client = this.clientRepository.findOne(command.getClientId()); final ClientRepository clientRepository, final GroupRepository groupRepository, final FundRepository fundRepository,
if (client == null || client.isDeleted()) { final LoanTransactionProcessingStrategyRepository loanTransactionProcessingStrategyRepository,
throw new ClientNotFoundException(command.getClientId()); final StaffRepository staffRepository, final LoanScheduleAssembler loanScheduleAssembler,
} final LoanChargeAssembler loanChargeAssembler) {
} this.fromApiJsonHelper = fromApiJsonHelper;
this.loanProductRepository = loanProductRepository;
Group group = null; this.clientRepository = clientRepository;
if (command.getGroupId() != null) { this.groupRepository = groupRepository;
group = this.groupRepository.findOne(command.getGroupId()); this.fundRepository = fundRepository;
if (group == null || group.isDeleted()) { this.loanTransactionProcessingStrategyRepository = loanTransactionProcessingStrategyRepository;
throw new GroupNotFoundException(command.getGroupId()); this.staffRepository = staffRepository;
} this.loanScheduleAssembler = loanScheduleAssembler;
} this.loanChargeAssembler = loanChargeAssembler;
}
final MonetaryCurrency currency = loanProduct.getCurrency();
final Integer loanTermFrequency = command.getLoanTermFrequency();
final PeriodFrequencyType loanTermFrequencyType = PeriodFrequencyType.fromInt(command.getLoanTermFrequencyType());
final LoanProductRelatedDetail loanRepaymentScheduleDetail = assembleLoanProductRelatedDetailFrom(command, currency);
// associating fund with loan product at creation is optional for now. public Loan assembleFrom(final JsonCommand command) {
final Fund fund = findFundByIdIfProvided(command.getFundId()); final JsonElement element = command.parsedJson();
final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy = findStrategyByIdIfProvided(command.getTransactionProcessingStrategyId());
//optionally associate a loan officer to the loan final Long clientId = fromApiJsonHelper.extractLongNamed("clientId", element);
final Staff loanOfficer= findLoanOfficerByIdIfProvided(command.getLoanOfficerId()); final Long groupId = fromApiJsonHelper.extractLongNamed("groupId", element);
// optionally, see if charges are associated with loan on creation (through loan product or by being directly added)
final Set<LoanCharge> loanCharges = this.loanChargeAssembler.assembleFrom(command.getCharges(), loanProduct.getCharges(), command.getPrincipal());
Loan loan = null; return assembleApplication(element, clientId, groupId);
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();
}
final LoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanSchedule(command.toCalculateLoanScheduleCommand()); private Loan assembleApplication(final JsonElement element, final Long clientId, final Long groupId) {
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);
}
}
loan.submitApplication(loanTermFrequency, loanTermFrequencyType, final Long productId = fromApiJsonHelper.extractLongNamed("productId", element);
command.getSubmittedOnDate(), command.getExpectedDisbursementDate(), final Long fundId = fromApiJsonHelper.extractLongNamed("fundId", element);
command.getRepaymentsStartingFromDate(), command.getInterestChargedFromDate(), final Long loanOfficerId = fromApiJsonHelper.extractLongNamed("loanOfficerId", element);
defaultLoanLifecycleStateMachine()); final Long transactionProcessingStrategyId = fromApiJsonHelper.extractLongNamed("transactionProcessingStrategyId", element);
return loan;
}
public LoanProductRelatedDetail assembleLoanProductRelatedDetailFrom(final LoanApplicationCommand command, MonetaryCurrency currency) { final LoanProduct loanProduct = this.loanProductRepository.findOne(productId);
final BigDecimal defaultNominalInterestRatePerPeriod = command.getInterestRatePerPeriod(); if (loanProduct == null) { throw new LoanProductNotFoundException(productId); }
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;
}
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { final Fund fund = findFundByIdIfProvided(fundId);
List<LoanStatus> allowedLoanStatuses = Arrays.asList(LoanStatus.values()); final Staff loanOfficer = findLoanOfficerByIdIfProvided(loanOfficerId);
return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses); final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy = findStrategyByIdIfProvided(transactionProcessingStrategyId);
}
final Set<LoanCharge> loanCharges = this.loanChargeAssembler.fromParsedJson(element);
public Fund findFundByIdIfProvided(final Long fundId) {
Fund fund = null; final BigDecimal inArrearsTolerance = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("inArrearsTolerance", element);
if (fundId != null) { LoanSchedule loanSchedule = this.loanScheduleAssembler.fromJson(element, inArrearsTolerance);
fund = this.fundRepository.findOne(fundId);
if (fund == null) { Loan loanApplication = null;
throw new FundNotFoundException(fundId); if (clientId != null) {
} final Client client = this.clientRepository.findOne(clientId);
} if (client == null || client.isDeleted()) { throw new ClientNotFoundException(clientId); }
return fund;
} loanApplication = Loan.newIndividualLoanApplication(client, loanProduct, fund, loanOfficer, loanTransactionProcessingStrategy,
loanSchedule, loanCharges);
public Staff findLoanOfficerByIdIfProvided(final Long loanOfficerId) { }
Staff staff = null;
if (loanOfficerId != null) { if (groupId != null) {
staff = this.staffRepository.findOne(loanOfficerId); final Group group = this.groupRepository.findOne(groupId);
if (staff == null) { if (group == null || group.isDeleted()) { throw new GroupNotFoundException(groupId); }
throw new StaffNotFoundException(loanOfficerId);
} else if (staff.isNotLoanOfficer()) { loanApplication = Loan.newGroupLoanApplication(group, loanProduct, fund, loanOfficer, loanTransactionProcessingStrategy,
throw new StaffRoleException(loanOfficerId, loanSchedule, loanCharges);
StaffRoleException.STAFF_ROLE.LOAN_OFFICER); }
}
} final String externalId = fromApiJsonHelper.extractStringNamed("externalId", element);
return staff; 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)."); }
public LoanTransactionProcessingStrategy findStrategyByIdIfProvided(final Long transactionProcessingStrategyId) {
LoanTransactionProcessingStrategy strategy = null; loanApplication.loanApplicationSubmittal(loanSchedule, defaultLoanLifecycleStateMachine(), submittedOnDate, externalId);
if (transactionProcessingStrategyId != null) {
strategy = this.loanTransactionProcessingStrategyRepository.findOne(transactionProcessingStrategyId); return loanApplication;
if (strategy == null) { }
throw new LoanTransactionProcessingStrategyNotFoundException(transactionProcessingStrategyId);
} private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
} List<LoanStatus> allowedLoanStatuses = Arrays.asList(LoanStatus.values());
return strategy; 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;
}
} }
@@ -2,81 +2,99 @@ package org.mifosplatform.portfolio.loanaccount.service;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.HashSet; import java.util.HashSet;
import java.util.Locale;
import java.util.Set; 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.Charge;
import org.mifosplatform.portfolio.charge.domain.ChargeCalculationType;
import org.mifosplatform.portfolio.charge.domain.ChargeRepository; 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.ChargeIsNotActiveException;
import org.mifosplatform.portfolio.charge.exception.ChargeNotFoundException; import org.mifosplatform.portfolio.charge.exception.ChargeNotFoundException;
import org.mifosplatform.portfolio.charge.exception.LoanChargeNotFoundException; import org.mifosplatform.portfolio.charge.exception.LoanChargeNotFoundException;
import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanChargeRepository; import org.mifosplatform.portfolio.loanaccount.domain.LoanChargeRepository;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
@Service @Service
public class LoanChargeAssembler { public class LoanChargeAssembler {
private final ChargeRepository chargeRepository; private final FromJsonHelper fromApiJsonHelper;
private final LoanChargeRepository loanChargeRepository; private final ChargeRepository chargeRepository;
private final LoanChargeRepository loanChargeRepository;
@Autowired @Autowired
public LoanChargeAssembler(final ChargeRepository chargeRepository, final LoanChargeRepository loanChargeRepository) { public LoanChargeAssembler(final FromJsonHelper fromApiJsonHelper, final ChargeRepository chargeRepository,
this.chargeRepository = chargeRepository; final LoanChargeRepository loanChargeRepository) {
this.loanChargeRepository = loanChargeRepository; this.fromApiJsonHelper = fromApiJsonHelper;
} this.chargeRepository = chargeRepository;
this.loanChargeRepository = loanChargeRepository;
public Set<LoanCharge> assembleFrom(final LoanChargeCommand[] charges, final BigDecimal loanPrincipal) { }
final Set<LoanCharge> loanCharges = new HashSet<LoanCharge>();
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;
}
public Set<LoanCharge> assembleFrom(final LoanChargeCommand[] charges, final Set<Charge> chargesInheritedFromProduct, final BigDecimal loanPrincipal) { public Set<LoanCharge> fromParsedJson(final JsonElement element) {
Set<LoanCharge> loanCharges = new HashSet<LoanCharge>(); final Set<LoanCharge> loanCharges = new HashSet<LoanCharge>();
if (charges != null) { final BigDecimal principal = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("principal", element);
loanCharges = assembleFrom(charges, loanPrincipal);
} if (element.isJsonObject()) {
// TODO - kw - dont inherit charges from product by default, user may remove charges (that were default) during creation - expect final JsonObject topLevelJsonElement = element.getAsJsonObject();
// charges to be passed along in request at time of creation final String dateFormat = fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement);
// else { final Locale locale = fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
// for (Charge productCharge : chargesInheritedFromProduct) { if (topLevelJsonElement.has("charges") && topLevelJsonElement.get("charges").isJsonArray()) {
// loanCharges.add(LoanCharge.createNew(productCharge)); final JsonArray array = topLevelJsonElement.get("charges").getAsJsonArray();
// } for (int i = 0; i < array.size(); i++) {
// }
final JsonObject loanChargeElement = array.get(i).getAsJsonObject();
return loanCharges;
} 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;
}
} }
@@ -3,8 +3,8 @@ package org.mifosplatform.portfolio.loanaccount.service;
import java.math.BigDecimal; import java.math.BigDecimal;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; 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.domain.AprCalculator;
import org.mifosplatform.portfolio.loanaccount.loanschedule.query.CalculateLoanScheduleQuery;
import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod; import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
@@ -18,48 +18,45 @@ import org.springframework.stereotype.Service;
@Service @Service
public class LoanProductRelatedDetailAssembler { public class LoanProductRelatedDetailAssembler {
private final LoanProductRepository loanProductRepository;
private final AprCalculator aprCalculator;
@Autowired private final LoanProductRepository loanProductRepository;
public LoanProductRelatedDetailAssembler( private final AprCalculator aprCalculator;
final LoanProductRepository loanProductRepository,
final AprCalculator aprCalculator) {
this.loanProductRepository = loanProductRepository;
this.aprCalculator = aprCalculator;
}
public LoanProductRelatedDetail assembleFrom(final CalculateLoanScheduleCommand command) { @Autowired
public LoanProductRelatedDetailAssembler(final LoanProductRepository loanProductRepository, final AprCalculator aprCalculator) {
final BigDecimal principalAmount = command.getPrincipal(); this.loanProductRepository = loanProductRepository;
final BigDecimal defaultNominalInterestRatePerPeriod = command.getInterestRatePerPeriod(); this.aprCalculator = aprCalculator;
final PeriodFrequencyType interestPeriodFrequencyType = PeriodFrequencyType.fromInt(command.getInterestRateFrequencyType()); }
final InterestMethod interestMethod = InterestMethod.fromInt(command.getInterestType());
final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod.fromInt(command.getInterestCalculationPeriodType()); public LoanProductRelatedDetail assembleFrom(final CalculateLoanScheduleQuery command) {
final Integer defaultNumberOfInstallments = command.getNumberOfRepayments(); final BigDecimal principalAmount = command.getPrincipal();
final Integer repayEvery = command.getRepaymentEvery(); final BigDecimal defaultNominalInterestRatePerPeriod = command.getInterestRatePerPeriod();
final PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.fromInt(command.getRepaymentFrequencyType()); final PeriodFrequencyType interestPeriodFrequencyType = PeriodFrequencyType.fromInt(command.getInterestRateFrequencyType());
final InterestMethod interestMethod = InterestMethod.fromInt(command.getInterestType());
final AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(command.getAmortizationType()); final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod.fromInt(command
.getInterestCalculationPeriodType());
final BigDecimal defaultAnnualNominalInterestRate = this.aprCalculator.calculateFrom(interestPeriodFrequencyType, defaultNominalInterestRatePerPeriod);
final Integer defaultNumberOfInstallments = command.getNumberOfRepayments();
final LoanProduct loanProduct = this.loanProductRepository.findOne(command.getProductId()); final Integer repayEvery = command.getRepaymentEvery();
if (loanProduct == null) { final PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.fromInt(command.getRepaymentFrequencyType());
throw new LoanProductNotFoundException(command.getProductId());
} final AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(command.getAmortizationType());
final MonetaryCurrency currency = loanProduct.getCurrency(); final BigDecimal defaultAnnualNominalInterestRate = this.aprCalculator.calculateFrom(interestPeriodFrequencyType,
defaultNominalInterestRatePerPeriod);
// in arrerars tolerance isnt relevant when auto-calculating a loan schedule
final BigDecimal inArrearsTolerance = BigDecimal.ZERO; final LoanProduct loanProduct = this.loanProductRepository.findOne(command.getProductId());
if (loanProduct == null) { throw new LoanProductNotFoundException(command.getProductId()); }
return new LoanProductRelatedDetail(currency, principalAmount,
defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType, defaultAnnualNominalInterestRate, final MonetaryCurrency currency = loanProduct.getCurrency();
interestMethod, interestCalculationPeriodMethod,
repayEvery, repaymentFrequencyType, defaultNumberOfInstallments, amortizationMethod, // in arrerars tolerance isnt relevant when auto-calculating a loan
inArrearsTolerance); // schedule
} final BigDecimal inArrearsTolerance = BigDecimal.ZERO;
return new LoanProductRelatedDetail(currency, principalAmount, defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType,
defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery, repaymentFrequencyType,
defaultNumberOfInstallments, amortizationMethod, inArrearsTolerance);
}
} }
@@ -1,73 +1,62 @@
package org.mifosplatform.portfolio.loanaccount.service; package org.mifosplatform.portfolio.loanaccount.service;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.EntityIdentifier; import org.mifosplatform.infrastructure.core.data.EntityIdentifier;
import org.mifosplatform.organisation.staff.command.BulkTransferLoanOfficerCommand; import org.mifosplatform.organisation.staff.command.BulkTransferLoanOfficerCommand;
import org.mifosplatform.portfolio.loanaccount.command.AdjustLoanTransactionCommand; 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.LoanChargeCommand;
import org.mifosplatform.portfolio.loanaccount.command.LoanStateTransitionCommand;
import org.mifosplatform.portfolio.loanaccount.command.LoanTransactionCommand; import org.mifosplatform.portfolio.loanaccount.command.LoanTransactionCommand;
import org.mifosplatform.portfolio.loanaccount.command.UndoStateTransitionCommand;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
public interface LoanWritePlatformService { 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 submitLoanApplication(JsonCommand command);
EntityIdentifier undoLoanApproval(UndoStateTransitionCommand command);
@PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'REJECT_LOAN', 'REJECTINPAST_LOAN')") EntityIdentifier modifyLoanApplication(Long loanId, JsonCommand command);
EntityIdentifier rejectLoan(LoanStateTransitionCommand command);
@PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'WITHDRAW_LOAN', 'WITHDRAWINPAST_LOAN')") EntityIdentifier deleteLoan(Long loanId);
EntityIdentifier withdrawLoan(LoanStateTransitionCommand command);
@PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'DISBURSE_LOAN', 'DISBURSEINPAST_LOAN')")
EntityIdentifier disburseLoan(LoanStateTransitionCommand command);
@PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'DISBURSALUNDO_LOAN')") EntityIdentifier approveLoanApplication(Long loanId, JsonCommand command);
public EntityIdentifier undoLoanDisbursal(UndoStateTransitionCommand 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 undoLoanApplicationApproval(Long loanId, JsonCommand command);
EntityIdentifier adjustLoanTransaction(AdjustLoanTransactionCommand command);
@PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'WAIVEINTERESTPORTION_LOAN')") EntityIdentifier rejectLoanApplication(Long loanId, JsonCommand command);
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 applicantWithdrawsFromLoanApplication(Long loanId, JsonCommand command);
EntityIdentifier closeAsRescheduled(LoanTransactionCommand command);
EntityIdentifier disburseLoan(Long loanId, JsonCommand command);
@PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'CREATE_LOANCHARGE')")
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); 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); EntityIdentifier updateLoanCharge(LoanChargeCommand command);
@PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'DELETE_LOANCHARGE')") @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'DELETE_LOANCHARGE')")
EntityIdentifier deleteLoanCharge(final Long loanId, final Long loanChargeId); EntityIdentifier deleteLoanCharge(final Long loanId, final Long loanChargeId);
@PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'WAIVE_LOANCHARGE')") @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')") @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'BULKREASSIGN_LOAN')")
EntityIdentifier bulkLoanReassignment(final BulkTransferLoanOfficerCommand command); EntityIdentifier bulkLoanReassignment(final BulkTransferLoanOfficerCommand command);
@@ -138,9 +138,9 @@ public class LoanProduct extends AbstractAuditableCustom<AppUser, Long> {
this.charges = charges; this.charges = charges;
} }
public Map<String, Object> update(final JsonCommand command) { public Map<String, Object> update(final JsonCommand command, final AprCalculator aprCalculator) {
final Map<String, Object> actualChanges = this.loanProductRelatedDetail.update(command); final Map<String, Object> actualChanges = this.loanProductRelatedDetail.update(command, aprCalculator);
final String nameParamName = "name"; final String nameParamName = "name";
if (command.isChangeInStringParameterNamed(nameParamName, this.name)) { if (command.isChangeInStringParameterNamed(nameParamName, this.name)) {
@@ -14,6 +14,7 @@ import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.portfolio.loanaccount.domain.Loan; import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator;
/** /**
* LoanRepaymentScheduleDetail encapsulates all the details of a * 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) @Column(name = "nominal_interest_rate_per_period", scale = 6, precision = 19, nullable = false)
private BigDecimal nominalInterestRatePerPeriod; 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) @Enumerated(EnumType.ORDINAL)
@Column(name = "interest_period_frequency_enum", nullable = false) @Column(name = "interest_period_frequency_enum", nullable = false)
private PeriodFrequencyType interestPeriodFrequencyType; private PeriodFrequencyType interestPeriodFrequencyType;
@@ -38,10 +41,14 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
@Column(name = "annual_nominal_interest_rate", scale = 6, precision = 19, nullable = false) @Column(name = "annual_nominal_interest_rate", scale = 6, precision = 19, nullable = false)
private BigDecimal annualNominalInterestRate; 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) @Enumerated(EnumType.ORDINAL)
@Column(name = "interest_method_enum", nullable = false) @Column(name = "interest_method_enum", nullable = false)
private InterestMethod interestMethod; 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) @Enumerated(EnumType.ORDINAL)
@Column(name = "interest_calculated_in_period_enum", nullable = false) @Column(name = "interest_calculated_in_period_enum", nullable = false)
private InterestCalculationPeriodMethod interestCalculationPeriodMethod; private InterestCalculationPeriodMethod interestCalculationPeriodMethod;
@@ -49,6 +56,8 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
@Column(name = "repay_every", nullable = false) @Column(name = "repay_every", nullable = false)
private Integer repayEvery; 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) @Enumerated(EnumType.ORDINAL)
@Column(name = "repayment_period_frequency_enum", nullable = false) @Column(name = "repayment_period_frequency_enum", nullable = false)
private PeriodFrequencyType repaymentPeriodFrequencyType; private PeriodFrequencyType repaymentPeriodFrequencyType;
@@ -56,6 +65,8 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
@Column(name = "number_of_repayments", nullable = false) @Column(name = "number_of_repayments", nullable = false)
private Integer numberOfRepayments; 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) @Enumerated(EnumType.ORDINAL)
@Column(name = "amortization_method_enum", nullable = false) @Column(name = "amortization_method_enum", nullable = false)
private AmortizationMethod amortizationMethod; private AmortizationMethod amortizationMethod;
@@ -63,6 +74,18 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
@Column(name = "arrearstolerance_amount", scale = 6, precision = 19, nullable = true) @Column(name = "arrearstolerance_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal inArrearsTolerance; 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() { protected LoanProductRelatedDetail() {
// //
} }
@@ -142,7 +165,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
return amortizationMethod; return amortizationMethod;
} }
public Map<String, Object> update(final JsonCommand command) { public Map<String, Object> update(final JsonCommand command, final AprCalculator aprCalculator) {
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>(20); final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>(20);
@@ -168,6 +191,18 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
this.currency = new MonetaryCurrency(currencyCode, digitsAfterDecimal); this.currency = new MonetaryCurrency(currencyCode, digitsAfterDecimal);
} }
final Map<String, Object> loanApplicationAttributeChanges = updateLoanApplicationAttributes(command, aprCalculator);
actualChanges.putAll(loanApplicationAttributeChanges);
return actualChanges;
}
public Map<String, Object> updateLoanApplicationAttributes(final JsonCommand command, final AprCalculator aprCalculator) {
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>(20);
final String localeAsInput = command.locale();
final String principalParamName = "principal"; final String principalParamName = "principal";
if (command.isChangeInBigDecimalParameterNamed(principalParamName, this.principal)) { if (command.isChangeInBigDecimalParameterNamed(principalParamName, this.principal)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(principalParamName); final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(principalParamName);
@@ -222,6 +257,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
actualChanges.put(interestRatePerPeriodParamName, newValue); actualChanges.put(interestRatePerPeriodParamName, newValue);
actualChanges.put("locale", localeAsInput); actualChanges.put("locale", localeAsInput);
this.nominalInterestRatePerPeriod = newValue; this.nominalInterestRatePerPeriod = newValue;
updateInterestRateDerivedFields(aprCalculator);
} }
final String interestRateFrequencyTypeParamName = "interestRateFrequencyType"; final String interestRateFrequencyTypeParamName = "interestRateFrequencyType";
@@ -230,6 +266,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
actualChanges.put(interestRateFrequencyTypeParamName, newValue); actualChanges.put(interestRateFrequencyTypeParamName, newValue);
actualChanges.put("locale", localeAsInput); actualChanges.put("locale", localeAsInput);
this.interestPeriodFrequencyType = PeriodFrequencyType.fromInt(newValue); this.interestPeriodFrequencyType = PeriodFrequencyType.fromInt(newValue);
updateInterestRateDerivedFields(aprCalculator);
} }
final String interestTypeParamName = "interestType"; final String interestTypeParamName = "interestType";
@@ -251,4 +288,8 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
return actualChanges; return actualChanges;
} }
private void updateInterestRateDerivedFields(final AprCalculator aprCalculator) {
this.annualNominalInterestRate = aprCalculator.calculateFrom(this.interestPeriodFrequencyType, this.nominalInterestRatePerPeriod);
}
} }
@@ -108,7 +108,7 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
final LoanProduct product = this.loanProductRepository.findOne(loanProductId); final LoanProduct product = this.loanProductRepository.findOne(loanProductId);
if (product == null) { throw new LoanProductNotFoundException(loanProductId); } if (product == null) { throw new LoanProductNotFoundException(loanProductId); }
final Map<String, Object> changes = product.update(command); final Map<String, Object> changes = product.update(command, this.aprCalculator);
// associating fund with loan product at creation is optional for now. // associating fund with loan product at creation is optional for now.
if (changes.containsKey("fundId")) { if (changes.containsKey("fundId")) {