MIFOSX-97: further support for maker-checker and audit around loans.

This commit is contained in:
Keith Woodlock
2012-12-19 13:34:42 +00:00
parent 281931e19b
commit e694355f8c
27 changed files with 1317 additions and 1082 deletions
@@ -191,4 +191,28 @@ public class CommandWrapper {
public boolean isUndoDisbursementOfLoan() { public boolean isUndoDisbursementOfLoan() {
return this.actionName.equalsIgnoreCase("DISBURSALUNDO") && this.entityName.equalsIgnoreCase("LOAN"); return this.actionName.equalsIgnoreCase("DISBURSALUNDO") && this.entityName.equalsIgnoreCase("LOAN");
} }
public boolean isLoanRepayment() {
return this.actionName.equalsIgnoreCase("REPAYMENT") && this.entityName.equalsIgnoreCase("LOAN");
}
public boolean isLoanRepaymentAdjustment() {
return this.actionName.equalsIgnoreCase("ADJUST") && this.entityName.equalsIgnoreCase("LOAN");
}
public boolean isWaiveInterestPortionOnLoan() {
return this.actionName.equalsIgnoreCase("WAIVEINTERESTPORTION") && this.entityName.equalsIgnoreCase("LOAN");
}
public boolean isLoanWriteOff() {
return this.actionName.equalsIgnoreCase("WRITEOFF") && this.entityName.equalsIgnoreCase("LOAN");
}
public boolean isCloseLoanAsObligationsMet() {
return this.actionName.equalsIgnoreCase("CLOSE") && this.entityName.equalsIgnoreCase("LOAN");
}
public boolean isCloseLoanAsRescheduled() {
return this.actionName.equalsIgnoreCase("CLOSEASRESCHEDULED") && this.entityName.equalsIgnoreCase("LOAN");
}
} }
@@ -224,6 +224,18 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
handler = applicationContext.getBean("disburseLoanCommandHandler", NewCommandSourceHandler.class); handler = applicationContext.getBean("disburseLoanCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isUndoDisbursementOfLoan()) { } else if (wrapper.isUndoDisbursementOfLoan()) {
handler = applicationContext.getBean("undoDisbursalLoanCommandHandler", NewCommandSourceHandler.class); handler = applicationContext.getBean("undoDisbursalLoanCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isLoanRepayment()) {
handler = applicationContext.getBean("loanRepaymentCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isLoanRepaymentAdjustment()) {
handler = applicationContext.getBean("loanRepaymentAdjustmentCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isWaiveInterestPortionOnLoan()) {
handler = applicationContext.getBean("waiveInterestPortionOnLoanCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isLoanWriteOff()) {
handler = applicationContext.getBean("writeOffLoanCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isCloseLoanAsObligationsMet()) {
handler = applicationContext.getBean("closeLoanCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isCloseLoanAsRescheduled()) {
handler = applicationContext.getBean("closeLoanAsRescheduledCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isCreate()) { } else if (wrapper.isCreate()) {
handler = applicationContext.getBean("createLoanApplicationCommandHandler", NewCommandSourceHandler.class); handler = applicationContext.getBean("createLoanApplicationCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isUpdate()) { } else if (wrapper.isUpdate()) {
@@ -16,7 +16,6 @@ import org.mifosplatform.portfolio.client.data.ClientAccountSummaryCollectionDat
import org.mifosplatform.portfolio.group.data.GroupAccountSummaryCollectionData; import org.mifosplatform.portfolio.group.data.GroupAccountSummaryCollectionData;
import org.mifosplatform.portfolio.group.data.GroupData; 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.gaurantor.data.GuarantorData; import org.mifosplatform.portfolio.loanaccount.gaurantor.data.GuarantorData;
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;
@@ -78,9 +77,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_TRANSACTION_NEW_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("id", "type", "date",
"currency", "amount"));
private static final Set<String> LOAN_REASSIGNMENT_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("officeId", "fromLoanOfficerId", private static final Set<String> LOAN_REASSIGNMENT_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("officeId", "fromLoanOfficerId",
"assignmentDate", "officeOptions", "loanOfficerOptions", "accountSummaryCollection")); "assignmentDate", "officeOptions", "loanOfficerOptions", "accountSummaryCollection"));
@@ -206,14 +202,6 @@ public class GoogleGsonPortfolioApiJsonSerializerService implements PortfolioApi
return helper.serializedJsonFrom(gsonDeserializer, group); return helper.serializedJsonFrom(gsonDeserializer, group);
} }
@Override
public String serializeLoanTransactionDataToJson(final boolean prettyPrint, final Set<String> responseParameters,
final LoanTransactionData transaction) {
final Gson gsonDeserializer = helper.createGsonBuilderWithParameterExclusionSerializationStrategy(
LOAN_TRANSACTION_NEW_DATA_PARAMETERS, prettyPrint, responseParameters);
return helper.serializedJsonFrom(gsonDeserializer, transaction);
}
@Override @Override
public String serializeLoanReassignmentDataToJson(boolean prettyPrint, Set<String> responseParameters, public String serializeLoanReassignmentDataToJson(boolean prettyPrint, Set<String> responseParameters,
BulkTransferLoanOfficerData loanReassignmentData) { BulkTransferLoanOfficerData loanReassignmentData) {
@@ -4,9 +4,7 @@ import org.mifosplatform.organisation.staff.command.BulkTransferLoanOfficerComma
import org.mifosplatform.portfolio.client.command.NoteCommand; 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.LoanChargeCommand; import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand;
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;
import org.mifosplatform.portfolio.savingsaccountproduct.command.SavingProductCommand; import org.mifosplatform.portfolio.savingsaccountproduct.command.SavingProductCommand;
@@ -29,10 +27,6 @@ public interface PortfolioApiDataConversionService {
LoanChargeCommand convertJsonToLoanChargeCommand(Long loanChargeId, Long loanId, String json); LoanChargeCommand convertJsonToLoanChargeCommand(Long loanChargeId, Long loanId, String json);
LoanTransactionCommand convertJsonToLoanTransactionCommand(Long resourceIdentifier, String json);
AdjustLoanTransactionCommand convertJsonToAdjustLoanTransactionCommand(Long loanId, Long transactionId, String json);
NoteCommand convertJsonToNoteCommand(Long resourceIdentifier, Long clientId, String json); NoteCommand convertJsonToNoteCommand(Long resourceIdentifier, Long clientId, String json);
DepositAccountCommand convertJsonToDepositAccountCommand(Long resourceIdentifier, String json); DepositAccountCommand convertJsonToDepositAccountCommand(Long resourceIdentifier, String json);
@@ -27,9 +27,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.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.LoanChargeCommand; import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand;
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;
import org.mifosplatform.portfolio.savingsaccountproduct.command.SavingProductCommand; import org.mifosplatform.portfolio.savingsaccountproduct.command.SavingProductCommand;
@@ -203,50 +201,6 @@ public class PortfolioApiDataConversionServiceImpl implements PortfolioApiDataCo
submittedOnDate); submittedOnDate);
} }
@Override
public LoanTransactionCommand convertJsonToLoanTransactionCommand(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("transactionDate", "transactionAmount", "note", "dateFormat",
"locale"));
checkForUnsupportedParameters(requestMap, supportedParams);
Set<String> modifiedParameters = new HashSet<String>();
LocalDate transactionDate = extractLocalDateParameter("transactionDate", requestMap, modifiedParameters);
BigDecimal transactionAmount = extractBigDecimalParameter("transactionAmount", requestMap, modifiedParameters);
String note = extractStringParameter("note", requestMap, modifiedParameters);
return new LoanTransactionCommand(resourceIdentifier, transactionDate, transactionAmount, note);
}
@Override
public AdjustLoanTransactionCommand convertJsonToAdjustLoanTransactionCommand(final Long loanId, final Long transactionId,
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("transactionDate", "transactionAmount", "note", "dateFormat",
"locale"));
checkForUnsupportedParameters(requestMap, supportedParams);
Set<String> modifiedParameters = new HashSet<String>();
LocalDate transactionDate = extractLocalDateParameter("transactionDate", requestMap, modifiedParameters);
BigDecimal transactionAmount = extractBigDecimalParameter("transactionAmount", requestMap, modifiedParameters);
String note = extractStringParameter("note", requestMap, modifiedParameters);
return new AdjustLoanTransactionCommand(loanId, transactionId, transactionDate, note, transactionAmount);
}
@Override @Override
public NoteCommand convertJsonToNoteCommand(final Long noteId, final Long clientId, final String json) { public NoteCommand convertJsonToNoteCommand(final Long noteId, final Long clientId, final String json) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
@@ -767,9 +721,9 @@ public class PortfolioApiDataConversionServiceImpl implements PortfolioApiDataCo
Integer lockinPeriodType = extractIntegerParameter("lockinPeriodType", requestMap, modifiedParameters); Integer lockinPeriodType = extractIntegerParameter("lockinPeriodType", requestMap, modifiedParameters);
return new SavingProductCommand(modifiedParameters, resourceIdentifier, name, description, currencyCode, digitsAfterDecimalValue, return new SavingProductCommand(modifiedParameters, resourceIdentifier, name, description, currencyCode, digitsAfterDecimalValue,
interestRate, minInterestRate, maxInterestRate, savingsDepositAmount, depositEvery, savingProductType, tenureType, tenure, frequency, interestRate, minInterestRate, maxInterestRate, savingsDepositAmount, depositEvery, savingProductType, tenureType, tenure,
interestType, interestCalculationMethod, minimumBalanceForWithdrawal, isPartialDepositAllowed, isLockinPeriodAllowed, frequency, interestType, interestCalculationMethod, minimumBalanceForWithdrawal, isPartialDepositAllowed,
lockinPeriod, lockinPeriodType); isLockinPeriodAllowed, lockinPeriod, lockinPeriodType);
} }
@Override @Override
@@ -13,7 +13,6 @@ import org.mifosplatform.portfolio.client.data.ClientAccountSummaryCollectionDat
import org.mifosplatform.portfolio.group.data.GroupAccountSummaryCollectionData; import org.mifosplatform.portfolio.group.data.GroupAccountSummaryCollectionData;
import org.mifosplatform.portfolio.group.data.GroupData; 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.gaurantor.data.GuarantorData; import org.mifosplatform.portfolio.loanaccount.gaurantor.data.GuarantorData;
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;
@@ -51,8 +50,6 @@ public interface PortfolioApiJsonSerializerService {
String serializeGroupDataToJson(boolean prettyPrint, Set<String> responseParameters, GroupData group); String serializeGroupDataToJson(boolean prettyPrint, Set<String> responseParameters, GroupData group);
String serializeLoanTransactionDataToJson(boolean prettyPrint, Set<String> responseParameters, LoanTransactionData newTransactionData);
String serializeLoanReassignmentDataToJson(boolean prettyPrint, Set<String> responseParameters, String serializeLoanReassignmentDataToJson(boolean prettyPrint, Set<String> responseParameters,
BulkTransferLoanOfficerData loanReassignmentData); BulkTransferLoanOfficerData loanReassignmentData);
@@ -0,0 +1,170 @@
package org.mifosplatform.portfolio.loanaccount.api;
import java.util.Collection;
import java.util.Set;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import org.apache.commons.lang.StringUtils;
import org.mifosplatform.infrastructure.core.api.ApiParameterHelper;
import org.mifosplatform.infrastructure.core.api.PortfolioApiDataConversionService;
import org.mifosplatform.infrastructure.core.api.PortfolioApiJsonSerializerService;
import org.mifosplatform.infrastructure.core.data.EntityIdentifier;
import org.mifosplatform.infrastructure.core.exception.UnrecognizedQueryParamException;
import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.portfolio.charge.data.ChargeData;
import org.mifosplatform.portfolio.charge.service.ChargeReadPlatformService;
import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand;
import org.mifosplatform.portfolio.loanaccount.data.LoanAccountData;
import org.mifosplatform.portfolio.loanaccount.data.LoanChargeData;
import org.mifosplatform.portfolio.loanaccount.service.LoanWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Path("/loans/{loanId}/charges")
@Component
@Scope("singleton")
public class LoanChargesApiResource {
private final PlatformSecurityContext context;
private final LoanWritePlatformService loanWritePlatformService;
private final ChargeReadPlatformService chargeReadPlatformService;
private final PortfolioApiJsonSerializerService apiJsonSerializerService;
private final PortfolioApiDataConversionService apiDataConversionService;
private final DefaultToApiJsonSerializer<LoanAccountData> toApiJsonSerializer;
// private final ApiRequestParameterHelper apiRequestParameterHelper;
// private final FromJsonHelper fromJsonHelper;
// private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
@Autowired
public LoanChargesApiResource(final PlatformSecurityContext context, final LoanWritePlatformService loanWritePlatformService,
final ChargeReadPlatformService chargeReadPlatformService,
final DefaultToApiJsonSerializer<LoanAccountData> toApiJsonSerializer,
// final ApiRequestParameterHelper apiRequestParameterHelper,
final PortfolioApiJsonSerializerService apiJsonSerializerService,
final PortfolioApiDataConversionService apiDataConversionService
// , final FromJsonHelper fromJsonHelper,
// final PortfolioCommandSourceWritePlatformService
// commandsSourceWritePlatformService
) {
this.context = context;
this.loanWritePlatformService = loanWritePlatformService;
this.chargeReadPlatformService = chargeReadPlatformService;
this.toApiJsonSerializer = toApiJsonSerializer;
// this.apiRequestParameterHelper = apiRequestParameterHelper;
this.apiJsonSerializerService = apiJsonSerializerService;
this.apiDataConversionService = apiDataConversionService;
// this.fromJsonHelper = fromJsonHelper;
// this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
}
private boolean is(final String commandParam, final String commandValue) {
return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
}
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public Response addLoanCharge(@PathParam("loanId") final Long loanId, final String jsonRequestBody) {
final LoanChargeCommand command = this.apiDataConversionService.convertJsonToLoanChargeCommand(null, loanId, jsonRequestBody);
final EntityIdentifier identifier = this.loanWritePlatformService.addLoanCharge(command);
return Response.ok().entity(identifier).build();
}
@GET
@Path("template")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveNewLoanChargeDetails(@Context final UriInfo uriInfo) {
context.authenticatedUser().validateHasReadPermission("LOAN");
final Set<String> responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters());
final boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
final boolean feeChargesOnly = false;
final Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveLoanApplicableCharges(feeChargesOnly);
final LoanChargeData loanChargeTemplate = LoanChargeData.template(chargeOptions);
return this.apiJsonSerializerService.serializeLoanChargeDataToJson(prettyPrint, responseParameters, loanChargeTemplate);
}
@GET
@Path("{chargeId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveLoanCharge(@PathParam("loanId") final Long loanId, @PathParam("chargeId") final Long loanChargeId,
@Context final UriInfo uriInfo) {
context.authenticatedUser().validateHasReadPermission("LOAN");
final Set<String> responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters());
final boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
final LoanChargeData loanCharge = this.chargeReadPlatformService.retrieveLoanChargeDetails(loanChargeId, loanId);
return this.apiJsonSerializerService.serializeLoanChargeDataToJson(prettyPrint, responseParameters, loanCharge);
}
@PUT
@Path("{chargeId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String updateLoanCharge(@PathParam("loanId") final Long loanId, @PathParam("chargeId") final Long loanChargeId,
final String jsonRequestBody) {
final LoanChargeCommand command = this.apiDataConversionService.convertJsonToLoanChargeCommand(loanChargeId, loanId,
jsonRequestBody);
final EntityIdentifier identifier = this.loanWritePlatformService.updateLoanCharge(command);
return this.toApiJsonSerializer.serialize(identifier);
}
@POST
@Path("{chargeId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String waiveLoanCharge(@PathParam("loanId") final Long loanId, @PathParam("chargeId") final Long loanChargeId,
@QueryParam("command") final String commandParam) {
final LoanChargeCommand command = LoanChargeCommand.forWaiver(loanChargeId, loanId);
String json = "";
if (is(commandParam, "waive")) {
final EntityIdentifier identifier = this.loanWritePlatformService.waiveLoanCharge(command);
json = this.toApiJsonSerializer.serialize(identifier);
} else {
throw new UnrecognizedQueryParamException("command", commandParam);
}
return json;
}
@DELETE
@Path("{chargeId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String deleteLoanCharge(@PathParam("loanId") final Long loanId, @PathParam("chargeId") final Long loanChargeId) {
final EntityIdentifier identifier = this.loanWritePlatformService.deleteLoanCharge(loanId, loanChargeId);
return this.toApiJsonSerializer.serialize(identifier);
}
}
@@ -0,0 +1,150 @@
package org.mifosplatform.portfolio.loanaccount.api;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import org.apache.commons.lang.StringUtils;
import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService;
import org.mifosplatform.infrastructure.core.api.ApiRequestParameterHelper;
import org.mifosplatform.infrastructure.core.data.EntityIdentifier;
import org.mifosplatform.infrastructure.core.exception.UnrecognizedQueryParamException;
import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionData;
import org.mifosplatform.portfolio.loanaccount.service.LoanReadPlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
// FIXME: - undo disbursal fails when transactions have notes against them
// FIXME: - repayments before disbursement date allowed
@Path("/loans/{loanId}/transactions")
@Component
@Scope("singleton")
public class LoanTransactionsApiResource {
private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("id", "type", "date", "currency", "amount"));
private final String resourceNameForPermissions = "LOAN";
private final PlatformSecurityContext context;
private final LoanReadPlatformService loanReadPlatformService;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final DefaultToApiJsonSerializer<LoanTransactionData> toApiJsonSerializer;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
@Autowired
public LoanTransactionsApiResource(final PlatformSecurityContext context, final LoanReadPlatformService loanReadPlatformService,
final ApiRequestParameterHelper apiRequestParameterHelper,
final DefaultToApiJsonSerializer<LoanTransactionData> toApiJsonSerializer,
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
this.context = context;
this.loanReadPlatformService = loanReadPlatformService;
this.apiRequestParameterHelper = apiRequestParameterHelper;
this.toApiJsonSerializer = toApiJsonSerializer;
this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
}
private boolean is(final String commandParam, final String commandValue) {
return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
}
@GET
@Path("template")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveTransactionTemplate(@PathParam("loanId") final Long loanId, @QueryParam("command") final String commandParam,
@Context final UriInfo uriInfo) {
context.authenticatedUser().validateHasReadPermission(resourceNameForPermissions);
LoanTransactionData transactionData = null;
if (is(commandParam, "repayment")) {
transactionData = this.loanReadPlatformService.retrieveLoanTransactionTemplate(loanId);
} else if (is(commandParam, "waiveinterest")) {
transactionData = this.loanReadPlatformService.retrieveWaiveInterestDetails(loanId);
} else if (is(commandParam, "writeoff")) {
transactionData = this.loanReadPlatformService.retrieveNewClosureDetails();
} else if (is(commandParam, "close-rescheduled")) {
transactionData = this.loanReadPlatformService.retrieveNewClosureDetails();
} else if (is(commandParam, "close")) {
transactionData = this.loanReadPlatformService.retrieveNewClosureDetails();
} else {
throw new UnrecognizedQueryParamException("command", commandParam);
}
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, transactionData, RESPONSE_DATA_PARAMETERS);
}
@GET
@Path("{transactionId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveTransaction(@PathParam("loanId") final Long loanId, @PathParam("transactionId") final Long transactionId,
@Context final UriInfo uriInfo) {
context.authenticatedUser().validateHasReadPermission(resourceNameForPermissions);
final LoanTransactionData transactionData = this.loanReadPlatformService.retrieveLoanTransaction(loanId, transactionId);
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, transactionData, RESPONSE_DATA_PARAMETERS);
}
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String executeLoanTransaction(@PathParam("loanId") final Long loanId, @QueryParam("command") final String commandParam,
final String apiRequestBodyAsJson) {
EntityIdentifier result = null;
if (is(commandParam, "repayment")) {
result = this.commandsSourceWritePlatformService.logCommandSource("REPAYMENT", "LOAN", "N/A", "loans", loanId, "transactions",
null, apiRequestBodyAsJson);
} else if (is(commandParam, "waiveinterest")) {
result = this.commandsSourceWritePlatformService.logCommandSource("WAIVEINTERESTPORTION", "LOAN", "N/A", "loans", loanId,
"transactions", null, apiRequestBodyAsJson);
} else if (is(commandParam, "writeoff")) {
result = this.commandsSourceWritePlatformService.logCommandSource("WRITEOFF", "LOAN", "N/A", "loans", loanId, "transactions",
null, apiRequestBodyAsJson);
} else if (is(commandParam, "close-rescheduled")) {
result = this.commandsSourceWritePlatformService.logCommandSource("CLOSEASRESCHEDULED", "LOAN", "N/A", "loans", loanId,
"transactions", null, apiRequestBodyAsJson);
} else if (is(commandParam, "close")) {
result = this.commandsSourceWritePlatformService
.logCommandSource("CLOSE", "LOAN", "N/A", "loans", loanId, apiRequestBodyAsJson);
}
if (result == null) { throw new UnrecognizedQueryParamException("command", commandParam); }
return this.toApiJsonSerializer.serialize(result);
}
@POST
@Path("{transactionId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String adjustLoanTransaction(@PathParam("loanId") final Long loanId, @PathParam("transactionId") final Long transactionId,
final String apiRequestBodyAsJson) {
final EntityIdentifier result = this.commandsSourceWritePlatformService.logCommandSource("ADJUST", "LOAN", "N/A", "loans", loanId,
"transactions", transactionId, apiRequestBodyAsJson);
return this.toApiJsonSerializer.serialize(result);
}
}
@@ -47,9 +47,6 @@ import org.mifosplatform.portfolio.charge.data.ChargeData;
import org.mifosplatform.portfolio.charge.service.ChargeReadPlatformService; 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.LoanChargeCommand;
import org.mifosplatform.portfolio.loanaccount.command.LoanTransactionCommand;
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;
@@ -412,188 +409,6 @@ public class LoansApiResource {
return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue); return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
} }
@POST
@Path("{loanId}/transactions")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public Response executeLoanTransaction(@PathParam("loanId") final Long loanId, @QueryParam("command") final String commandParam,
final String jsonRequestBody) {
final LoanTransactionCommand command = this.apiDataConversionService.convertJsonToLoanTransactionCommand(loanId, jsonRequestBody);
EntityIdentifier identifier = null;
if (is(commandParam, "repayment")) {
identifier = this.loanWritePlatformService.makeLoanRepayment(command);
} else if (is(commandParam, "waiveinterest")) {
identifier = this.loanWritePlatformService.waiveInterestOnLoan(command);
} else if (is(commandParam, "writeoff")) {
identifier = this.loanWritePlatformService.writeOff(command);
} else if (is(commandParam, "close-rescheduled")) {
identifier = this.loanWritePlatformService.closeAsRescheduled(command);
} else if (is(commandParam, "close")) {
identifier = this.loanWritePlatformService.closeLoan(command);
}
if (identifier == null) { throw new UnrecognizedQueryParamException("command", commandParam); }
return Response.ok().entity(identifier).build();
}
@GET
@Path("{loanId}/transactions/template")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveNewRepaymentDetails(@PathParam("loanId") final Long loanId, @QueryParam("command") final String commandParam,
@Context final UriInfo uriInfo) {
context.authenticatedUser().validateHasReadPermission("LOAN");
final Set<String> responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters());
final boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
LoanTransactionData transactionData = null;
if (is(commandParam, "repayment")) {
transactionData = this.loanReadPlatformService.retrieveNewLoanRepaymentDetails(loanId);
} else if (is(commandParam, "waiveinterest")) {
transactionData = this.loanReadPlatformService.retrieveNewLoanWaiveInterestDetails(loanId);
} else if (is(commandParam, "writeoff")) {
transactionData = this.loanReadPlatformService.retrieveNewClosureDetails();
} else if (is(commandParam, "close-rescheduled")) {
transactionData = this.loanReadPlatformService.retrieveNewClosureDetails();
} else if (is(commandParam, "close")) {
transactionData = this.loanReadPlatformService.retrieveNewClosureDetails();
} else {
throw new UnrecognizedQueryParamException("command", commandParam);
}
return this.apiJsonSerializerService.serializeLoanTransactionDataToJson(prettyPrint, responseParameters, transactionData);
}
@GET
@Path("{loanId}/transactions/{transactionId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveTransaction(@PathParam("loanId") final Long loanId, @PathParam("transactionId") final Long transactionId,
@Context final UriInfo uriInfo) {
context.authenticatedUser().validateHasReadPermission("LOAN");
final Set<String> responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters());
final boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
final LoanTransactionData transactionData = this.loanReadPlatformService.retrieveLoanTransactionDetails(loanId, transactionId);
return this.apiJsonSerializerService.serializeLoanTransactionDataToJson(prettyPrint, responseParameters, transactionData);
}
@POST
@Path("{loanId}/transactions/{transactionId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public Response adjustLoanTransaction(@PathParam("loanId") final Long loanId, @PathParam("transactionId") final Long transactionId,
final String jsonRequestBody) {
final AdjustLoanTransactionCommand command = this.apiDataConversionService.convertJsonToAdjustLoanTransactionCommand(loanId,
transactionId, jsonRequestBody);
EntityIdentifier identifier = this.loanWritePlatformService.adjustLoanTransaction(command);
return Response.ok().entity(identifier).build();
}
@POST
@Path("{loanId}/charges")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public Response addLoanCharge(@PathParam("loanId") final Long loanId, final String jsonRequestBody) {
final LoanChargeCommand command = this.apiDataConversionService.convertJsonToLoanChargeCommand(null, loanId, jsonRequestBody);
final EntityIdentifier identifier = this.loanWritePlatformService.addLoanCharge(command);
return Response.ok().entity(identifier).build();
}
@GET
@Path("{loanId}/charges/template")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveNewLoanChargeDetails(@Context final UriInfo uriInfo) {
context.authenticatedUser().validateHasReadPermission("LOAN");
final Set<String> responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters());
final boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
final boolean feeChargesOnly = false;
final Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveLoanApplicableCharges(feeChargesOnly);
final LoanChargeData loanChargeTemplate = LoanChargeData.template(chargeOptions);
return this.apiJsonSerializerService.serializeLoanChargeDataToJson(prettyPrint, responseParameters, loanChargeTemplate);
}
@GET
@Path("{loanId}/charges/{chargeId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveLoanCharge(@PathParam("loanId") final Long loanId, @PathParam("chargeId") final Long loanChargeId,
@Context final UriInfo uriInfo) {
context.authenticatedUser().validateHasReadPermission("LOAN");
final Set<String> responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters());
final boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
final LoanChargeData loanCharge = this.chargeReadPlatformService.retrieveLoanChargeDetails(loanChargeId, loanId);
return this.apiJsonSerializerService.serializeLoanChargeDataToJson(prettyPrint, responseParameters, loanCharge);
}
@PUT
@Path("{loanId}/charges/{chargeId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String updateLoanCharge(@PathParam("loanId") final Long loanId, @PathParam("chargeId") final Long loanChargeId,
final String jsonRequestBody) {
final LoanChargeCommand command = this.apiDataConversionService.convertJsonToLoanChargeCommand(loanChargeId, loanId,
jsonRequestBody);
final EntityIdentifier identifier = this.loanWritePlatformService.updateLoanCharge(command);
return this.toApiJsonSerializer.serialize(identifier);
}
@POST
@Path("{loanId}/charges/{chargeId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String waiveLoanCharge(@PathParam("loanId") final Long loanId, @PathParam("chargeId") final Long loanChargeId,
@QueryParam("command") final String commandParam) {
final LoanChargeCommand command = LoanChargeCommand.forWaiver(loanChargeId, loanId);
String json = "";
if (is(commandParam, "waive")) {
final EntityIdentifier identifier = this.loanWritePlatformService.waiveLoanCharge(command);
json = this.toApiJsonSerializer.serialize(identifier);
} else {
throw new UnrecognizedQueryParamException("command", commandParam);
}
return json;
}
@DELETE
@Path("{loanId}/charges/{chargeId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String deleteLoanCharge(@PathParam("loanId") final Long loanId, @PathParam("chargeId") final Long loanChargeId) {
final EntityIdentifier identifier = this.loanWritePlatformService.deleteLoanCharge(loanId, loanChargeId);
return this.toApiJsonSerializer.serialize(identifier);
}
@POST @POST
@Path("{loanId}/assign") @Path("{loanId}/assign")
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@@ -1,47 +0,0 @@
package org.mifosplatform.portfolio.loanaccount.command;
import java.math.BigDecimal;
import org.joda.time.LocalDate;
/**
* Immutable command for adjusting loan transactions.
*/
public class AdjustLoanTransactionCommand {
private final Long loanId;
private final Long transactionId;
private final LocalDate transactionDate;
private final BigDecimal transactionAmount;
private final String note;
public AdjustLoanTransactionCommand(final Long loanId, final Long transactionId,
final LocalDate transactionLocalDate, final String note,
final BigDecimal transactionAmountValue) {
this.loanId = loanId;
this.transactionId = transactionId;
this.transactionDate = transactionLocalDate;
this.transactionAmount = transactionAmountValue;
this.note = note;
}
public Long getLoanId() {
return loanId;
}
public Long getTransactionId() {
return transactionId;
}
public LocalDate getTransactionDate() {
return transactionDate;
}
public BigDecimal getTransactionAmount() {
return transactionAmount;
}
public String getNote() {
return note;
}
}
@@ -1,34 +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 AdjustLoanTransactionCommandValidator {
private final AdjustLoanTransactionCommand command;
public AdjustLoanTransactionCommandValidator(AdjustLoanTransactionCommand command) {
this.command = command;
}
public void validate() {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
baseDataValidator.reset().parameter("loanId").value(command.getLoanId()).notNull().integerGreaterThanZero();
baseDataValidator.reset().parameter("transactionId").value(command.getTransactionId()).notNull().integerGreaterThanZero();
baseDataValidator.reset().parameter("transactionDate").value(command.getTransactionDate()).notNull();
baseDataValidator.reset().parameter("transactionAmount").value(command.getTransactionAmount()).notNull().zeroOrPositiveAmount();
baseDataValidator.reset().parameter("note").value(command.getNote()).notExceedingLengthOf(1000);
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors);
}
}
}
@@ -9,7 +9,7 @@ import org.joda.time.LocalDate;
/** /**
* Immutable command for creating and updating loan charges. * Immutable command for creating and updating loan charges.
*/ */
public class LoanChargeCommand { public class LoanChargeCommand implements Comparable<LoanChargeCommand> {
private final Long id; private final Long id;
private final Long chargeId; private final Long chargeId;
@@ -44,6 +44,15 @@ public class LoanChargeCommand {
this.specifiedDueDate = specifiedDueDate; this.specifiedDueDate = specifiedDueDate;
} }
@Override
public int compareTo(final LoanChargeCommand o) {
int comparison = this.chargeId.compareTo(o.chargeId);
if (comparison == 0) {
comparison = this.amount.compareTo(o.amount);
}
return comparison;
}
public Long getId() { public Long getId() {
return id; return id;
} }
@@ -1,39 +1,52 @@
package org.mifosplatform.portfolio.loanaccount.command; package org.mifosplatform.portfolio.loanaccount.command;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
/** /**
* Immutable command for loan transactions. * Immutable command for loan transactions.
*/ */
public class LoanTransactionCommand { public class LoanTransactionCommand {
private final Long loanId;
private final LocalDate transactionDate; private final LocalDate transactionDate;
private final BigDecimal transactionAmount; private final BigDecimal transactionAmount;
private final String note; private final String note;
public LoanTransactionCommand(final Long loanId, final LocalDate paymentDate, final BigDecimal paymentAmount,final String note) { public LoanTransactionCommand(final LocalDate paymentDate, final BigDecimal paymentAmount, final String note) {
this.loanId = loanId;
this.transactionDate = paymentDate; this.transactionDate = paymentDate;
this.transactionAmount = paymentAmount; this.transactionAmount = paymentAmount;
this.note = note; this.note = note;
} }
public Long getLoanId() { public void validate() {
return loanId;
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
baseDataValidator.reset().parameter("transactionDate").value(this.transactionDate).notNull();
baseDataValidator.reset().parameter("transactionAmount").value(this.transactionAmount).notNull().positiveAmount();
baseDataValidator.reset().parameter("note").value(this.note).notExceedingLengthOf(1000);
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
} }
public LocalDate getTransactionDate() { public void validateNonMonetaryTransaction() {
return transactionDate; List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
}
public BigDecimal getTransactionAmount() { DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
return transactionAmount;
}
public String getNote() { baseDataValidator.reset().parameter("transactionDate").value(this.transactionDate).notNull();
return note; 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,47 +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 LoanTransactionCommandValidator {
private final LoanTransactionCommand command;
public LoanTransactionCommandValidator(final LoanTransactionCommand command) {
this.command = command;
}
public void validate() {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
baseDataValidator.reset().parameter("loanId").value(command.getLoanId()).notNull().integerGreaterThanZero();
baseDataValidator.reset().parameter("transactionDate").value(command.getTransactionDate()).notNull();
baseDataValidator.reset().parameter("transactionAmount").value(command.getTransactionAmount()).notNull().positiveAmount();
baseDataValidator.reset().parameter("note").value(command.getNote()).notExceedingLengthOf(1000);
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors);
}
}
public void validateNonMonetaryTransaction() {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
baseDataValidator.reset().parameter("loanId").value(command.getLoanId()).notNull().integerGreaterThanZero();
baseDataValidator.reset().parameter("transactionDate").value(command.getTransactionDate()).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);
}
}
}
@@ -850,13 +850,10 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
approvedOn = command.localDateValueOfParameterNamed("eventDate"); approvedOn = command.localDateValueOfParameterNamed("eventDate");
} }
final Locale locale = new Locale(command.locale());
final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
this.approvedOnDate = approvedOn.toDate(); this.approvedOnDate = approvedOn.toDate();
actualChanges.put("locale", command.locale()); actualChanges.put("locale", command.locale());
actualChanges.put("dateFormat", command.dateFormat()); actualChanges.put("dateFormat", command.dateFormat());
actualChanges.put("approvedOnDate", approvedOn.toString(fmt)); actualChanges.put("approvedOnDate", command.stringValueOfParameterNamed("approvedOnDate"));
final LocalDate submittalDate = new LocalDate(this.submittedOnDate); final LocalDate submittalDate = new LocalDate(this.submittedOnDate);
if (approvedOn.isBefore(submittalDate)) { if (approvedOn.isBefore(submittalDate)) {
@@ -1059,21 +1056,12 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, this.charges); wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, this.charges);
} }
/** public LoanTransaction waiveInterest(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine) {
*
* @param amount
* The amount of interest to be waived.
* @param transactionDate
* The date on which the interest waiver is to be processed
* against.
* @param defaultLoanLifecycleStateMachine
*
* @return {@link LoanTransaction} details of executed transaction
*/
public LoanTransaction waiveInterest(final BigDecimal amount, final LocalDate transactionDate,
final LoanLifecycleStateMachine loanLifecycleStateMachine) {
final Money amountToWaive = Money.of(loanCurrency(), amount); final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
final Money amountToWaive = Money.of(loanCurrency(), transactionAmount);
final LoanTransaction waiveInterestTransaction = LoanTransaction.waiver(this, amountToWaive, transactionDate); final LoanTransaction waiveInterestTransaction = LoanTransaction.waiver(this, amountToWaive, transactionDate);
handleRepaymentOrWaiverTransaction(waiveInterestTransaction, loanLifecycleStateMachine, null); handleRepaymentOrWaiverTransaction(waiveInterestTransaction, loanLifecycleStateMachine, null);
@@ -1081,8 +1069,15 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
return waiveInterestTransaction; return waiveInterestTransaction;
} }
public void makeRepayment(final LoanTransaction loanTransaction, final LoanLifecycleStateMachine loanLifecycleStateMachine) { public LoanTransaction makeRepayment(final LocalDate transactionDate, final BigDecimal transactionAmount,
final LoanLifecycleStateMachine loanLifecycleStateMachine) {
final Money repayment = Money.of(loanCurrency(), transactionAmount);
final LoanTransaction loanTransaction = LoanTransaction.repayment(repayment, transactionDate);
handleRepaymentOrWaiverTransaction(loanTransaction, loanLifecycleStateMachine, null); handleRepaymentOrWaiverTransaction(loanTransaction, loanLifecycleStateMachine, null);
return loanTransaction;
} }
private void handleRepaymentOrWaiverTransaction(final LoanTransaction loanTransaction, private void handleRepaymentOrWaiverTransaction(final LoanTransaction loanTransaction,
@@ -1286,8 +1281,14 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
return LoanTransaction.waiver(this, possibleInterestToWaive, transactionDate); return LoanTransaction.waiver(this, possibleInterestToWaive, transactionDate);
} }
public void adjustExistingTransaction(final LoanTransaction transactionForAdjustment, final LoanTransaction newTransactionDetail, public LoanTransaction adjustExistingTransaction(final LocalDate transactionDate, final BigDecimal transactionAmountValue,
final LoanLifecycleStateMachine loanLifecycleStateMachine) { final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction transactionForAdjustment) {
final Money transactionAmount = Money.of(loanCurrency(), transactionAmountValue);
LoanTransaction newTransactionDetail = LoanTransaction.repayment(transactionAmount, transactionDate);
if (transactionForAdjustment.isInterestWaiver()) {
newTransactionDetail = LoanTransaction.waiver(this, transactionAmount, transactionDate);
}
if (transactionForAdjustment.isNotRepayment() && transactionForAdjustment.isNotWaiver()) { if (transactionForAdjustment.isNotRepayment() && transactionForAdjustment.isNotWaiver()) {
final String errorMessage = "Only transactions of type repayment or waiver can be adjusted."; final String errorMessage = "Only transactions of type repayment or waiver can be adjusted.";
@@ -1304,6 +1305,8 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
if (newTransactionDetail.isRepayment() || newTransactionDetail.isInterestWaiver()) { if (newTransactionDetail.isRepayment() || newTransactionDetail.isInterestWaiver()) {
handleRepaymentOrWaiverTransaction(newTransactionDetail, loanLifecycleStateMachine, transactionForAdjustment); handleRepaymentOrWaiverTransaction(newTransactionDetail, loanLifecycleStateMachine, transactionForAdjustment);
} }
return newTransactionDetail;
} }
private boolean isRepaidInFull() { private boolean isRepaidInFull() {
@@ -1365,15 +1368,20 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
return this.loanRepaymentScheduleDetail.getCurrency(); return this.loanRepaymentScheduleDetail.getCurrency();
} }
public LoanTransaction closeAsWrittenOff(final LocalDate writtenOffOnLocalDate, public LoanTransaction closeAsWrittenOff(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine, final Map<String, Object> changes) {
final LoanLifecycleStateMachine loanLifecycleStateMachine) {
final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING, final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING, LoanStatus.fromInt(this.loanStatus));
LoanStatus.fromInt(this.loanStatus)); if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) {
this.loanStatus = statusEnum.getValue(); this.loanStatus = statusEnum.getValue();
changes.put("status", LoanEnumerations.status(this.loanStatus));
}
final LocalDate writtenOffOnLocalDate = command.localDateValueOfParameterNamed("transactionDate");
this.closedOnDate = writtenOffOnLocalDate.toDate(); this.closedOnDate = writtenOffOnLocalDate.toDate();
this.writtenOffOnDate = writtenOffOnLocalDate.toDate(); this.writtenOffOnDate = writtenOffOnLocalDate.toDate();
changes.put("closedOnDate", command.stringValueOfParameterNamed("transactionDate"));
changes.put("writtenOffOnDate", command.stringValueOfParameterNamed("transactionDate"));
if (writtenOffOnLocalDate.isBefore(this.getDisbursementDate())) { if (writtenOffOnLocalDate.isBefore(this.getDisbursementDate())) {
final String errorMessage = "The date on which a loan is written off cannot be before the loan disbursement date: " final String errorMessage = "The date on which a loan is written off cannot be before the loan disbursement date: "
@@ -1404,7 +1412,12 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
return loanTransaction; return loanTransaction;
} }
public LoanTransaction close(final LocalDate closureDate, final LoanLifecycleStateMachine loanLifecycleStateMachine) { public LoanTransaction close(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine, final Map<String, Object> changes) {
final LocalDate closureDate = command.localDateValueOfParameterNamed("transactionDate");
this.closedOnDate = closureDate.toDate();
changes.put("closedOnDate", command.stringValueOfParameterNamed("transactionDate"));
if (closureDate.isBefore(this.getDisbursementDate())) { if (closureDate.isBefore(this.getDisbursementDate())) {
final String errorMessage = "The date on which a loan is closed cannot be before the loan disbursement date: " final String errorMessage = "The date on which a loan is closed cannot be before the loan disbursement date: "
@@ -1424,7 +1437,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
final Money outstanding = getTotalOutstanding(); final Money outstanding = getTotalOutstanding();
if (outstanding.isGreaterThanZero() && getInArrearsTolerance().isGreaterThanOrEqualTo(outstanding)) { if (outstanding.isGreaterThanZero() && getInArrearsTolerance().isGreaterThanOrEqualTo(outstanding)) {
updateLoanForClosure(closureDate, loanLifecycleStateMachine); updateLoanForClosure(closureDate, loanLifecycleStateMachine, changes);
loanTransaction = LoanTransaction.writeoff(this, closureDate); loanTransaction = LoanTransaction.writeoff(this, closureDate);
boolean isLastTransaction = isChronologicallyLatestTransaction(loanTransaction, loanTransactions); boolean isLastTransaction = isChronologicallyLatestTransaction(loanTransaction, loanTransactions);
@@ -1454,7 +1467,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
if (totalLoanOverpayment.isGreaterThanZero() && getInArrearsTolerance().isGreaterThanOrEqualTo(totalLoanOverpayment)) { if (totalLoanOverpayment.isGreaterThanZero() && getInArrearsTolerance().isGreaterThanOrEqualTo(totalLoanOverpayment)) {
// TODO - technically should set somewhere that this loan has // TODO - technically should set somewhere that this loan has
// 'overpaid' amount // 'overpaid' amount
updateLoanForClosure(closureDate, loanLifecycleStateMachine); updateLoanForClosure(closureDate, loanLifecycleStateMachine, changes);
} else if (totalLoanOverpayment.isGreaterThanZero()) { } else if (totalLoanOverpayment.isGreaterThanZero()) {
final String errorMessage = "The loan is marked as 'Overpaid' and cannot be moved to 'Closed (obligations met)."; final String errorMessage = "The loan is marked as 'Overpaid' and cannot be moved to 'Closed (obligations met).";
throw new InvalidLoanStateTransitionException("close", "loan.is.overpaid", errorMessage, totalLoanOverpayment.toString()); throw new InvalidLoanStateTransitionException("close", "loan.is.overpaid", errorMessage, totalLoanOverpayment.toString());
@@ -1474,23 +1487,35 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
return totalOutstanding; return totalOutstanding;
} }
private void updateLoanForClosure(final LocalDate closureDate, final LoanLifecycleStateMachine loanLifecycleStateMachine) { private void updateLoanForClosure(final LocalDate closureDate, final LoanLifecycleStateMachine loanLifecycleStateMachine, final Map<String, Object> changes) {
final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.REPAID_IN_FULL, LoanStatus.fromInt(this.loanStatus)); final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.REPAID_IN_FULL, LoanStatus.fromInt(this.loanStatus));
if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) {
this.loanStatus = statusEnum.getValue(); this.loanStatus = statusEnum.getValue();
changes.put("status", LoanEnumerations.status(this.loanStatus));
}
this.closedOnDate = closureDate.toDate(); this.closedOnDate = closureDate.toDate();
} }
/** /**
* Behaviour added to comply with capability of previous mifos product to * Behaviour added to comply with capability of previous mifos product to
* support easier transition to mifosx platform. * support easier transition to mifosx platform.
* @param changes
*/ */
public void closeAsMarkedForReschedule(final LocalDate rescheduledOn, final LoanLifecycleStateMachine loanLifecycleStateMachine) { public void closeAsMarkedForReschedule(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine, final Map<String, Object> changes) {
final LocalDate rescheduledOn = command.localDateValueOfParameterNamed("transactionDate");
final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_RESCHEDULE, LoanStatus.fromInt(this.loanStatus)); final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_RESCHEDULE, LoanStatus.fromInt(this.loanStatus));
if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) {
this.loanStatus = statusEnum.getValue(); this.loanStatus = statusEnum.getValue();
changes.put("status", LoanEnumerations.status(this.loanStatus));
}
this.closedOnDate = rescheduledOn.toDate(); this.closedOnDate = rescheduledOn.toDate();
this.rescheduledOnDate = rescheduledOn.toDate(); this.rescheduledOnDate = rescheduledOn.toDate();
changes.put("closedOnDate", command.stringValueOfParameterNamed("transactionDate"));
changes.put("rescheduledOnDate", command.stringValueOfParameterNamed("transactionDate"));
LocalDate rescheduledOnLocalDate = new LocalDate(rescheduledOnDate); LocalDate rescheduledOnLocalDate = new LocalDate(rescheduledOnDate);
if (rescheduledOnLocalDate.isBefore(this.getDisbursementDate())) { if (rescheduledOnLocalDate.isBefore(this.getDisbursementDate())) {
@@ -8,12 +8,13 @@ import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
public interface LoanRepaymentScheduleTransactionProcessor { public interface LoanRepaymentScheduleTransactionProcessor {
void handleTransaction(LoanTransaction loanTransaction, MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges); void handleTransaction(LoanTransaction loanTransaction, MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments,
Set<LoanCharge> charges);
void handleTransaction(LocalDate disbursementDate, List<LoanTransaction> repaymentsOrWaivers, MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, Set<LoanCharge> charges); void handleTransaction(LocalDate disbursementDate, List<LoanTransaction> repaymentsOrWaivers, MonetaryCurrency currency,
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, Set<LoanCharge> charges);
void handleWriteOff(LoanTransaction loanTransaction, void handleWriteOff(LoanTransaction loanTransaction, MonetaryCurrency loanCurrency,
MonetaryCurrency loanCurrency,
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments); List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments);
} }
@@ -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 CloseLoanAsRescheduledCommandHandler implements NewCommandSourceHandler {
private final LoanWritePlatformService writePlatformService;
@Autowired
public CloseLoanAsRescheduledCommandHandler(final LoanWritePlatformService writePlatformService) {
this.writePlatformService = writePlatformService;
}
@Transactional
@Override
public EntityIdentifier processCommand(final JsonCommand command) {
return this.writePlatformService.closeAsRescheduled(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 CloseLoanCommandHandler implements NewCommandSourceHandler {
private final LoanWritePlatformService writePlatformService;
@Autowired
public CloseLoanCommandHandler(final LoanWritePlatformService writePlatformService) {
this.writePlatformService = writePlatformService;
}
@Transactional
@Override
public EntityIdentifier processCommand(final JsonCommand command) {
return this.writePlatformService.closeLoan(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 LoanRepaymentAdjustmentCommandHandler implements NewCommandSourceHandler {
private final LoanWritePlatformService writePlatformService;
@Autowired
public LoanRepaymentAdjustmentCommandHandler(final LoanWritePlatformService writePlatformService) {
this.writePlatformService = writePlatformService;
}
@Transactional
@Override
public EntityIdentifier processCommand(final JsonCommand command) {
return this.writePlatformService.adjustLoanTransaction(command.resourceId(), command.subResourceId(), 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 LoanRepaymentCommandHandler implements NewCommandSourceHandler {
private final LoanWritePlatformService writePlatformService;
@Autowired
public LoanRepaymentCommandHandler(final LoanWritePlatformService writePlatformService) {
this.writePlatformService = writePlatformService;
}
@Transactional
@Override
public EntityIdentifier processCommand(final JsonCommand command) {
return this.writePlatformService.makeLoanRepayment(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 WaiveInterestPortionOnLoanCommandHandler implements NewCommandSourceHandler {
private final LoanWritePlatformService writePlatformService;
@Autowired
public WaiveInterestPortionOnLoanCommandHandler(final LoanWritePlatformService writePlatformService) {
this.writePlatformService = writePlatformService;
}
@Transactional
@Override
public EntityIdentifier processCommand(final JsonCommand command) {
return this.writePlatformService.waiveInterestOnLoan(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 WriteOffLoanCommandHandler implements NewCommandSourceHandler {
private final LoanWritePlatformService writePlatformService;
@Autowired
public WriteOffLoanCommandHandler(final LoanWritePlatformService writePlatformService) {
this.writePlatformService = writePlatformService;
}
@Transactional
@Override
public EntityIdentifier processCommand(final JsonCommand command) {
return this.writePlatformService.writeOff(command.resourceId(), command);
}
}
@@ -0,0 +1,58 @@
package org.mifosplatform.portfolio.loanaccount.serialization;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
import org.mifosplatform.infrastructure.core.serialization.AbstractFromApiJsonDeserializer;
import org.mifosplatform.infrastructure.core.serialization.FromApiJsonDeserializer;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.portfolio.loanaccount.command.LoanTransactionCommand;
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 LoanTransactionCommand}'s.
*/
@Component
public final class LoanTransactionCommandFromApiJsonDeserializer extends AbstractFromApiJsonDeserializer<LoanTransactionCommand> {
/**
* The parameters supported for this command.
*/
final Set<String> supportedParameters = new HashSet<String>(Arrays.asList("transactionDate", "transactionAmount", "note", "dateFormat",
"locale" ,"status", "closedOnDate", "writtenOffOnDate", "rescheduledOnDate"));
private final FromJsonHelper fromApiJsonHelper;
@Autowired
public LoanTransactionCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
this.fromApiJsonHelper = fromApiJsonHelper;
}
@Override
public LoanTransactionCommand 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 transactionDate = fromApiJsonHelper.extractLocalDateNamed("transactionDate", element);
final BigDecimal transactionAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("transactionAmount", element);
final String note = fromApiJsonHelper.extractStringNamed("note", element);
return new LoanTransactionCommand(transactionDate, transactionAmount, note);
}
}
@@ -14,23 +14,23 @@ public interface LoanReadPlatformService {
LoanBasicDetailsData retrieveLoanAccountDetails(Long loanId); LoanBasicDetailsData retrieveLoanAccountDetails(Long loanId);
LoanScheduleData retrieveRepaymentSchedule(Long loanId, CurrencyData currency, DisbursementData disbursement, BigDecimal totalChargesAtDisbursement, BigDecimal inArrearsTolerance); LoanScheduleData retrieveRepaymentSchedule(Long loanId, CurrencyData currency, DisbursementData disbursement,
BigDecimal totalChargesAtDisbursement, BigDecimal inArrearsTolerance);
Collection<LoanTransactionData> retrieveLoanTransactions(Long loanId); Collection<LoanTransactionData> retrieveLoanTransactions(Long loanId);
LoanPermissionData retrieveLoanPermissions( LoanPermissionData retrieveLoanPermissions(LoanBasicDetailsData loanBasicDetails, boolean isWaiverAllowed, int repaymentAndWaiveCount,
LoanBasicDetailsData loanBasicDetails, boolean isWaiverAllowed, boolean existsGuarantor);
int repaymentAndWaiveCount, boolean existsGuarantor);
LoanBasicDetailsData retrieveClientAndProductDetails(Long clientId, Long productId); LoanBasicDetailsData retrieveClientAndProductDetails(Long clientId, Long productId);
LoanBasicDetailsData retrieveGroupAndProductDetails(Long groupId, Long productId); LoanBasicDetailsData retrieveGroupAndProductDetails(Long groupId, Long productId);
LoanTransactionData retrieveNewLoanRepaymentDetails(Long loanId); LoanTransactionData retrieveLoanTransactionTemplate(Long loanId);
LoanTransactionData retrieveNewLoanWaiveInterestDetails(Long loanId); LoanTransactionData retrieveWaiveInterestDetails(Long loanId);
LoanTransactionData retrieveLoanTransactionDetails(Long loanId, Long transactionId); LoanTransactionData retrieveLoanTransaction(Long loanId, Long transactionId);
LoanTransactionData retrieveNewClosureDetails(); LoanTransactionData retrieveNewClosureDetails();
} }
@@ -57,15 +57,10 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
private final LoanTransactionRepository loanTransactionRepository; private final LoanTransactionRepository loanTransactionRepository;
@Autowired @Autowired
public LoanReadPlatformServiceImpl( public LoanReadPlatformServiceImpl(final PlatformSecurityContext context, final LoanRepository loanRepository,
final PlatformSecurityContext context, final LoanTransactionRepository loanTransactionRepository, final ApplicationCurrencyRepository applicationCurrencyRepository,
final LoanRepository loanRepository, final LoanProductReadPlatformService loanProductReadPlatformService, final ClientReadPlatformService clientReadPlatformService,
final LoanTransactionRepository loanTransactionRepository, final GroupReadPlatformService groupReadPlatformService, final TenantAwareRoutingDataSource dataSource) {
final ApplicationCurrencyRepository applicationCurrencyRepository,
final LoanProductReadPlatformService loanProductReadPlatformService,
final ClientReadPlatformService clientReadPlatformService,
final GroupReadPlatformService groupReadPlatformService,
final TenantAwareRoutingDataSource dataSource) {
this.context = context; this.context = context;
this.loanRepository = loanRepository; this.loanRepository = loanRepository;
this.loanTransactionRepository = loanTransactionRepository; this.loanTransactionRepository = loanTransactionRepository;
@@ -95,11 +90,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
} }
@Override @Override
public LoanScheduleData retrieveRepaymentSchedule(final Long loanId, public LoanScheduleData retrieveRepaymentSchedule(final Long loanId, final CurrencyData currency, final DisbursementData disbursement,
final CurrencyData currency, final BigDecimal totalFeeChargesAtDisbursement, final BigDecimal inArrearsTolerance) {
final DisbursementData disbursement,
final BigDecimal totalFeeChargesAtDisbursement,
final BigDecimal inArrearsTolerance) {
try { try {
context.authenticatedUser(); context.authenticatedUser();
@@ -107,7 +99,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final LoanScheduleMapper rm = new LoanScheduleMapper(disbursement); final LoanScheduleMapper rm = new LoanScheduleMapper(disbursement);
final String sql = "select " + rm.loanScheduleSchema() + " where l.id = ? order by ls.loan_id, ls.installment"; final String sql = "select " + rm.loanScheduleSchema() + " where l.id = ? order by ls.loan_id, ls.installment";
final LoanSchedulePeriodData disbursementPeriod = LoanSchedulePeriodData.disbursementOnlyPeriod(disbursement.disbursementDate(), disbursement.amount(), totalFeeChargesAtDisbursement, disbursement.isDisbursed()); final LoanSchedulePeriodData disbursementPeriod = LoanSchedulePeriodData.disbursementOnlyPeriod(
disbursement.disbursementDate(), disbursement.amount(), totalFeeChargesAtDisbursement, disbursement.isDisbursed());
final Collection<LoanSchedulePeriodData> repaymentSchedulePeriods = this.jdbcTemplate.query(sql, rm, new Object[] { loanId }); final Collection<LoanSchedulePeriodData> repaymentSchedulePeriods = this.jdbcTemplate.query(sql, rm, new Object[] { loanId });
@@ -143,33 +136,41 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final BigDecimal cumulativePenaltyChargesWrittenOff = wrapper.deriveCumulativePenaltyChargesWrittenOff(); final BigDecimal cumulativePenaltyChargesWrittenOff = wrapper.deriveCumulativePenaltyChargesWrittenOff();
final BigDecimal cumulativePenaltyChargesOutstanding = wrapper.deriveCumulativePenaltyChargesOutstanding(); final BigDecimal cumulativePenaltyChargesOutstanding = wrapper.deriveCumulativePenaltyChargesOutstanding();
final BigDecimal totalExpectedCostOfLoan = cumulativeInterestExpected.add(cumulativeFeeChargesExpected).add(cumulativePenaltyChargesExpected); final BigDecimal totalExpectedCostOfLoan = cumulativeInterestExpected.add(cumulativeFeeChargesExpected).add(
cumulativePenaltyChargesExpected);
final BigDecimal totalExpectedRepayment = cumulativePrincipalDisbursed.add(totalExpectedCostOfLoan); final BigDecimal totalExpectedRepayment = cumulativePrincipalDisbursed.add(totalExpectedCostOfLoan);
final BigDecimal totalPaidToDate = cumulativePrincipalPaid.add(cumulativeInterestPaid).add(cumulativeFeeChargesPaid).add(cumulativePenaltyChargesPaid); final BigDecimal totalPaidToDate = cumulativePrincipalPaid.add(cumulativeInterestPaid).add(cumulativeFeeChargesPaid)
final BigDecimal totalWaivedToDate = cumulativeInterestWaived.add(cumulativeFeeChargesWaived).add(cumulativePenaltyChargesWaived); .add(cumulativePenaltyChargesPaid);
final BigDecimal totalWrittenOffToDate = cumulativePrincipalWrittenOff.add(cumulativeInterestWrittenOff).add(cumulativeFeeChargesWrittenOff).add(cumulativePenaltyChargesWrittenOff); final BigDecimal totalWaivedToDate = cumulativeInterestWaived.add(cumulativeFeeChargesWaived).add(
cumulativePenaltyChargesWaived);
final BigDecimal totalWrittenOffToDate = cumulativePrincipalWrittenOff.add(cumulativeInterestWrittenOff)
.add(cumulativeFeeChargesWrittenOff).add(cumulativePenaltyChargesWrittenOff);
final BigDecimal totalOutstanding = cumulativePrincipalOutstanding.add(cumulativeInterestOutstanding).add(cumulativeFeeChargesOutstanding).add(cumulativePenaltyChargesOutstanding); final BigDecimal totalOutstanding = cumulativePrincipalOutstanding.add(cumulativeInterestOutstanding)
.add(cumulativeFeeChargesOutstanding).add(cumulativePenaltyChargesOutstanding);
final BigDecimal totalOverdue = wrapper.deriveCumulativeTotalOverdue(); final BigDecimal totalOverdue = wrapper.deriveCumulativeTotalOverdue();
final MonetaryCurrency monetaryCurrency = new MonetaryCurrency(currency.code(), currency.decimalPlaces()); final MonetaryCurrency monetaryCurrency = new MonetaryCurrency(currency.code(), currency.decimalPlaces());
final Money tolerance = Money.of(monetaryCurrency, inArrearsTolerance); final Money tolerance = Money.of(monetaryCurrency, inArrearsTolerance);
final Money totalOverdueMoney = Money.of(monetaryCurrency, totalOverdue); final Money totalOverdueMoney = Money.of(monetaryCurrency, totalOverdue);
boolean isWaiveAllowed = totalOverdueMoney.isGreaterThanZero() && (tolerance.isGreaterThan(totalOverdueMoney) || tolerance.isEqualTo(totalOverdueMoney)); boolean isWaiveAllowed = totalOverdueMoney.isGreaterThanZero()
&& (tolerance.isGreaterThan(totalOverdueMoney) || tolerance.isEqualTo(totalOverdueMoney));
BigDecimal totalInArrears = null; BigDecimal totalInArrears = null;
if (!isWaiveAllowed) { if (!isWaiveAllowed) {
totalInArrears = totalOverdueMoney.getAmount(); totalInArrears = totalOverdueMoney.getAmount();
} }
return new LoanScheduleData(currency, periods, loanTermInDays, return new LoanScheduleData(currency, periods, loanTermInDays, cumulativePrincipalDisbursed, cumulativePrincipalDue,
cumulativePrincipalDisbursed, cumulativePrincipalDue, cumulativePrincipalPaid, cumulativePrincipalWrittenOff, cumulativePrincipalOutstanding, cumulativePrincipalPaid, cumulativePrincipalWrittenOff, cumulativePrincipalOutstanding, cumulativeInterestExpected,
cumulativeInterestExpected, cumulativeInterestPaid, cumulativeInterestWaived, cumulativeInterestWrittenOff, cumulativeInterestOutstanding, cumulativeInterestPaid, cumulativeInterestWaived, cumulativeInterestWrittenOff, cumulativeInterestOutstanding,
cumulativeFeeChargesExpected, cumulativeFeeChargesPaid, cumulativeFeeChargesWaived, cumulativeFeeChargesWrittenOff, cumulativeFeeChargesOutstanding, cumulativeFeeChargesExpected, cumulativeFeeChargesPaid, cumulativeFeeChargesWaived, cumulativeFeeChargesWrittenOff,
cumulativePenaltyChargesExpected, cumulativePenaltyChargesPaid, cumulativePenaltyChargesWaived, cumulativePenaltyChargesWrittenOff, cumulativePenaltyChargesOutstanding, cumulativeFeeChargesOutstanding, cumulativePenaltyChargesExpected, cumulativePenaltyChargesPaid,
totalExpectedCostOfLoan, totalExpectedRepayment, totalPaidToDate, totalWaivedToDate, totalWrittenOffToDate, totalOutstanding, totalInArrears); cumulativePenaltyChargesWaived, cumulativePenaltyChargesWrittenOff, cumulativePenaltyChargesOutstanding,
totalExpectedCostOfLoan, totalExpectedRepayment, totalPaidToDate, totalWaivedToDate, totalWrittenOffToDate,
totalOutstanding, totalInArrears);
} catch (EmptyResultDataAccessException e) { } catch (EmptyResultDataAccessException e) {
throw new LoanNotFoundException(loanId); throw new LoanNotFoundException(loanId);
} }
@@ -182,7 +183,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
LoanTransactionsMapper rm = new LoanTransactionsMapper(); LoanTransactionsMapper rm = new LoanTransactionsMapper();
// retrieve all loan transactions that are not invalid and have not been 'contra'ed by another transaction // retrieve all loan transactions that are not invalid and have not
// been 'contra'ed by another transaction
// repayments at time of disbursement (e.g. charges) // repayments at time of disbursement (e.g. charges)
String sql = "select " String sql = "select "
+ rm.LoanPaymentsSchema() + rm.LoanPaymentsSchema()
@@ -194,11 +196,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
} }
@Override @Override
public LoanPermissionData retrieveLoanPermissions( public LoanPermissionData retrieveLoanPermissions(final LoanBasicDetailsData loanBasicDetails, final boolean isWaiverAllowed,
final LoanBasicDetailsData loanBasicDetails, final int repaymentAndWaiveCount, final boolean existsGuarantor) {
final boolean isWaiverAllowed,
final int repaymentAndWaiveCount,
final boolean existsGuarantor) {
final boolean pendingApproval = (loanBasicDetails.getStatus().getId().equals(100L)); final boolean pendingApproval = (loanBasicDetails.getStatus().getId().equals(100L));
final boolean waitingForDisbursal = (loanBasicDetails.getStatus().getId().equals(200L)); final boolean waitingForDisbursal = (loanBasicDetails.getStatus().getId().equals(200L));
@@ -231,22 +230,17 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final boolean closeLoanAsRescheduledAllowed = !closed && !isOverpaid && isActive; final boolean closeLoanAsRescheduledAllowed = !closed && !isOverpaid && isActive;
final boolean rejectAllowed = pendingApproval; final boolean rejectAllowed = pendingApproval;
final boolean withdrawnByApplicantAllowed = waitingForDisbursal final boolean withdrawnByApplicantAllowed = waitingForDisbursal || pendingApproval;
|| pendingApproval;
final boolean undoApprovalAllowed = waitingForDisbursal; final boolean undoApprovalAllowed = waitingForDisbursal;
final boolean undoDisbursalAllowed = isActive final boolean undoDisbursalAllowed = isActive && (repaymentAndWaiveCount == 0);
&& (repaymentAndWaiveCount == 0);
final boolean disbursalAllowed = waitingForDisbursal; final boolean disbursalAllowed = waitingForDisbursal;
return new LoanPermissionData(closeLoanAllowed, closeLoanAsRescheduledAllowed, return new LoanPermissionData(closeLoanAllowed, closeLoanAsRescheduledAllowed, addLoanChargeAllowed, waiveAllowed,
addLoanChargeAllowed, waiveAllowed, makeRepaymentAllowed, makeRepaymentAllowed, rejectAllowed, withdrawnByApplicantAllowed, undoApprovalAllowed, undoDisbursalAllowed,
rejectAllowed, withdrawnByApplicantAllowed, disbursalAllowed, setGuarantorAllowed, editGuarantorAllowed, pendingApproval, waitingForDisbursal, closedObligationsMet);
undoApprovalAllowed, undoDisbursalAllowed, disbursalAllowed,
setGuarantorAllowed,editGuarantorAllowed,
pendingApproval, waitingForDisbursal, closedObligationsMet);
} }
@Override @Override
@@ -257,8 +251,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final LocalDate expectedDisbursementDate = new LocalDate(); final LocalDate expectedDisbursementDate = new LocalDate();
final ClientData clientAccount = this.clientReadPlatformService.retrieveIndividualClient(clientId); final ClientData clientAccount = this.clientReadPlatformService.retrieveIndividualClient(clientId);
LoanBasicDetailsData loanDetails = LoanBasicDetailsData.populateForNewIndividualClientLoanCreation(clientAccount.id(), clientAccount.displayName(), expectedDisbursementDate, LoanBasicDetailsData loanDetails = LoanBasicDetailsData.populateForNewIndividualClientLoanCreation(clientAccount.id(),
clientAccount.officeId()); clientAccount.displayName(), expectedDisbursementDate, clientAccount.officeId());
if (productId != null) { if (productId != null) {
LoanProductData selectedProduct = this.loanProductReadPlatformService.retrieveLoanProduct(productId); LoanProductData selectedProduct = this.loanProductReadPlatformService.retrieveLoanProduct(productId);
@@ -276,8 +270,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final LocalDate expectedDisbursementDate = new LocalDate(); final LocalDate expectedDisbursementDate = new LocalDate();
final GroupData groupAccount = this.groupReadPlatformService.retrieveGroup(groupId); final GroupData groupAccount = this.groupReadPlatformService.retrieveGroup(groupId);
LoanBasicDetailsData loanDetails = LoanBasicDetailsData.populateForNewGroupLoanCreation(groupAccount.getId(), groupAccount.getName(), expectedDisbursementDate, LoanBasicDetailsData loanDetails = LoanBasicDetailsData.populateForNewGroupLoanCreation(groupAccount.getId(),
groupAccount.getOfficeId()); groupAccount.getName(), expectedDisbursementDate, groupAccount.getOfficeId());
if (productId != null) { if (productId != null) {
LoanProductData selectedProduct = this.loanProductReadPlatformService.retrieveLoanProduct(productId); LoanProductData selectedProduct = this.loanProductReadPlatformService.retrieveLoanProduct(productId);
@@ -288,60 +282,54 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
} }
@Override @Override
public LoanTransactionData retrieveNewLoanRepaymentDetails(final Long loanId) { public LoanTransactionData retrieveLoanTransactionTemplate(final Long loanId) {
context.authenticatedUser(); context.authenticatedUser();
// TODO - OPTIMIZE - write simple sql query to fetch back date of // TODO - OPTIMIZE - write simple sql query to fetch back date of
// possible next transaction date. // possible next transaction date.
Loan loan = this.loanRepository.findOne(loanId); Loan loan = this.loanRepository.findOne(loanId);
if (loan == null) { if (loan == null) { throw new LoanNotFoundException(loanId); }
throw new LoanNotFoundException(loanId);
}
final String currencyCode = loan.repaymentScheduleDetail().getPrincipal().getCurrencyCode(); final String currencyCode = loan.repaymentScheduleDetail().getPrincipal().getCurrencyCode();
ApplicationCurrency currency = this.applicationCurrencyRepository.findOneByCode(currencyCode); ApplicationCurrency currency = this.applicationCurrencyRepository.findOneByCode(currencyCode);
if (currency == null) { if (currency == null) { throw new CurrencyNotFoundException(currencyCode); }
throw new CurrencyNotFoundException(currencyCode);
}
final CurrencyData currencyData = new CurrencyData(currency.getCode(), final CurrencyData currencyData = new CurrencyData(currency.getCode(), currency.getName(), currency.getDecimalPlaces(),
currency.getName(), currency.getDecimalPlaces(),
currency.getDisplaySymbol(), currency.getNameCode()); currency.getDisplaySymbol(), currency.getNameCode());
final LocalDate earliestUnpaidInstallmentDate = loan.possibleNextRepaymentDate(); final LocalDate earliestUnpaidInstallmentDate = loan.possibleNextRepaymentDate();
final Money possibleNextRepaymentAmount = loan.possibleNextRepaymentAmount(); final Money possibleNextRepaymentAmount = loan.possibleNextRepaymentAmount();
final EnumOptionData transactionType = LoanEnumerations.transactionType(LoanTransactionType.REPAYMENT); final EnumOptionData transactionType = LoanEnumerations.transactionType(LoanTransactionType.REPAYMENT);
return new LoanTransactionData(null, transactionType, currencyData, earliestUnpaidInstallmentDate, possibleNextRepaymentAmount.getAmount(), null, null, null, null); return new LoanTransactionData(null, transactionType, currencyData, earliestUnpaidInstallmentDate,
possibleNextRepaymentAmount.getAmount(), null, null, null, null);
} }
@Override @Override
public LoanTransactionData retrieveNewLoanWaiveInterestDetails(final Long loanId) { public LoanTransactionData retrieveWaiveInterestDetails(final Long loanId) {
context.authenticatedUser(); context.authenticatedUser();
// TODO - OPTIMIZE - write simple sql query to fetch back overdue interest that can be waived along with the date of repayment period interest is overdue. // TODO - OPTIMIZE - write simple sql query to fetch back overdue
// interest that can be waived along with the date of repayment period
// interest is overdue.
final Loan loan = this.loanRepository.findOne(loanId); final Loan loan = this.loanRepository.findOne(loanId);
if (loan == null) { if (loan == null) { throw new LoanNotFoundException(loanId); }
throw new LoanNotFoundException(loanId);
}
final String currencyCode = loan.repaymentScheduleDetail().getPrincipal().getCurrencyCode(); final String currencyCode = loan.repaymentScheduleDetail().getPrincipal().getCurrencyCode();
final ApplicationCurrency currency = this.applicationCurrencyRepository.findOneByCode(currencyCode); final ApplicationCurrency currency = this.applicationCurrencyRepository.findOneByCode(currencyCode);
if (currency == null) { if (currency == null) { throw new CurrencyNotFoundException(currencyCode); }
throw new CurrencyNotFoundException(currencyCode);
}
final CurrencyData currencyData = new CurrencyData(currency.getCode(), final CurrencyData currencyData = new CurrencyData(currency.getCode(), currency.getName(), currency.getDecimalPlaces(),
currency.getName(), currency.getDecimalPlaces(),
currency.getDisplaySymbol(), currency.getNameCode()); currency.getDisplaySymbol(), currency.getNameCode());
final LoanTransaction waiveOfInterest = loan.deriveDefaultInterestWaiverTransaction(); final LoanTransaction waiveOfInterest = loan.deriveDefaultInterestWaiverTransaction();
final EnumOptionData transactionType = LoanEnumerations.transactionType(LoanTransactionType.WAIVE_INTEREST); final EnumOptionData transactionType = LoanEnumerations.transactionType(LoanTransactionType.WAIVE_INTEREST);
return new LoanTransactionData(null, transactionType, currencyData, waiveOfInterest.getTransactionDate(), waiveOfInterest.getAmount(), null, null, null, null); return new LoanTransactionData(null, transactionType, currencyData, waiveOfInterest.getTransactionDate(),
waiveOfInterest.getAmount(), null, null, null, null);
} }
@Override @Override
@@ -355,40 +343,29 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
} }
@Override @Override
public LoanTransactionData retrieveLoanTransactionDetails( public LoanTransactionData retrieveLoanTransaction(final Long loanId, final Long transactionId) {
final Long loanId, final Long transactionId) {
context.authenticatedUser(); context.authenticatedUser();
final Loan loan = this.loanRepository.findOne(loanId); final Loan loan = this.loanRepository.findOne(loanId);
if (loan == null) { if (loan == null) { throw new LoanNotFoundException(loanId); }
throw new LoanNotFoundException(loanId);
}
final String currencyCode = loan.repaymentScheduleDetail().getPrincipal().getCurrencyCode(); final String currencyCode = loan.repaymentScheduleDetail().getPrincipal().getCurrencyCode();
final ApplicationCurrency currency = this.applicationCurrencyRepository.findOneByCode(currencyCode); final ApplicationCurrency currency = this.applicationCurrencyRepository.findOneByCode(currencyCode);
if (currency == null) { if (currency == null) { throw new CurrencyNotFoundException(currencyCode); }
throw new CurrencyNotFoundException(currencyCode);
}
final LoanTransaction transaction = this.loanTransactionRepository.findOne(transactionId); final LoanTransaction transaction = this.loanTransactionRepository.findOne(transactionId);
if (transaction == null) { if (transaction == null) { throw new LoanTransactionNotFoundException(transactionId); }
throw new LoanTransactionNotFoundException(transactionId);
}
if (transaction.isNotBelongingToLoanOf(loan)) { if (transaction.isNotBelongingToLoanOf(loan)) { throw new LoanTransactionNotFoundException(transactionId, loanId); }
throw new LoanTransactionNotFoundException(transactionId, loanId);
}
final CurrencyData currencyData = new CurrencyData(currency.getCode(), final CurrencyData currencyData = new CurrencyData(currency.getCode(), currency.getName(), currency.getDecimalPlaces(),
currency.getName(), currency.getDecimalPlaces(),
currency.getDisplaySymbol(), currency.getNameCode()); currency.getDisplaySymbol(), currency.getNameCode());
return transaction.toData(currencyData); return transaction.toData(currencyData);
} }
private static final class LoanMapper implements private static final class LoanMapper implements RowMapper<LoanBasicDetailsData> {
RowMapper<LoanBasicDetailsData> {
public String loanSchema() { public String loanSchema() {
return "l.id as id, l.external_id as externalId, l.fund_id as fundId, f.name as fundName, " return "l.id as id, l.external_id as externalId, l.fund_id as fundId, f.name as fundName, "
@@ -405,38 +382,34 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
+ " l.amortization_method_enum as amortizationType, l.interest_method_enum as interestType, l.interest_calculated_in_period_enum as interestCalculationPeriodType," + " l.amortization_method_enum as amortizationType, l.interest_method_enum as interestType, l.interest_calculated_in_period_enum as interestCalculationPeriodType,"
+ " l.loan_status_id as lifeCycleStatusId, l.loan_transaction_strategy_id as transactionStrategyId, " + " l.loan_status_id as lifeCycleStatusId, l.loan_transaction_strategy_id as transactionStrategyId, "
+ " l.currency_code as currencyCode, l.currency_digits as currencyDigits, rc.`name` as currencyName, rc.display_symbol as currencyDisplaySymbol, rc.internationalized_name_code as currencyNameCode, " + " l.currency_code as currencyCode, l.currency_digits as currencyDigits, rc.`name` as currencyName, rc.display_symbol as currencyDisplaySymbol, rc.internationalized_name_code as currencyNameCode, "
+ " l.loan_officer_id as loanOfficerId, s.display_name as loanOfficerName" + " l.loan_officer_id as loanOfficerId, s.display_name as loanOfficerName" + " from m_loan l"
+ " from m_loan l" + " left join m_client c on c.id = l.client_id" + " left join m_group g on g.id = l.group_id"
+ " left join m_client c on c.id = l.client_id" + " join m_product_loan lp on lp.id = l.product_id" + " join m_currency rc on rc.`code` = l.currency_code"
+ " left join m_group g on g.id = l.group_id" + " left join m_fund f on f.id = l.fund_id" + " left join m_staff s on s.id = l.loan_officer_id";
+ " join m_product_loan lp on lp.id = l.product_id"
+ " join m_currency rc on rc.`code` = l.currency_code"
+ " left join m_fund f on f.id = l.fund_id"
+ " left join m_staff s on s.id = l.loan_officer_id";
} }
@Override @Override
public LoanBasicDetailsData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) public LoanBasicDetailsData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
throws SQLException {
String currencyCode = rs.getString("currencyCode"); String currencyCode = rs.getString("currencyCode");
String currencyName = rs.getString("currencyName"); String currencyName = rs.getString("currencyName");
String currencyNameCode = rs.getString("currencyNameCode"); String currencyNameCode = rs.getString("currencyNameCode");
String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
CurrencyData currencyData = new CurrencyData(currencyCode, CurrencyData currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, currencyDisplaySymbol,
currencyName, currencyDigits, currencyDisplaySymbol,
currencyNameCode); currencyNameCode);
Long id = rs.getLong("id"); Long id = rs.getLong("id");
String externalId = rs.getString("externalId"); String externalId = rs.getString("externalId");
Long clientId = JdbcSupport.getLong(rs, "clientId"); Long clientId = JdbcSupport.getLong(rs, "clientId");
Long clientOfficeId = JdbcSupport.getLong(rs, "clientOfficeId");; Long clientOfficeId = JdbcSupport.getLong(rs, "clientOfficeId");
;
String clientName = rs.getString("clientName"); String clientName = rs.getString("clientName");
Long groupId = JdbcSupport.getLong(rs, "groupId"); Long groupId = JdbcSupport.getLong(rs, "groupId");
Long groupOfficeId = JdbcSupport.getLong(rs, "groupOfficeId");; Long groupOfficeId = JdbcSupport.getLong(rs, "groupOfficeId");
;
String groupName = rs.getString("groupName"); String groupName = rs.getString("groupName");
Long fundId = JdbcSupport.getLong(rs, "fundId"); Long fundId = JdbcSupport.getLong(rs, "fundId");
@@ -447,22 +420,14 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
String loanProductName = rs.getString("loanProductName"); String loanProductName = rs.getString("loanProductName");
String loanProductDescription = rs.getString("loanProductDescription"); String loanProductDescription = rs.getString("loanProductDescription");
LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate");
"submittedOnDate"); LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, "approvedOnDate");
LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, LocalDate expectedDisbursementDate = JdbcSupport.getLocalDate(rs, "expectedDisbursementDate");
"approvedOnDate"); LocalDate actualDisbursementDate = JdbcSupport.getLocalDate(rs, "actualDisbursementDate");
LocalDate expectedDisbursementDate = JdbcSupport.getLocalDate(rs, LocalDate expectedFirstRepaymentOnDate = JdbcSupport.getLocalDate(rs, "expectedFirstRepaymentOnDate");
"expectedDisbursementDate"); LocalDate interestChargedFromDate = JdbcSupport.getLocalDate(rs, "interestChargedFromDate");
LocalDate actualDisbursementDate = JdbcSupport.getLocalDate(rs, LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedOnDate");
"actualDisbursementDate"); LocalDate expectedMaturityDate = JdbcSupport.getLocalDate(rs, "expectedMaturityDate");
LocalDate expectedFirstRepaymentOnDate = JdbcSupport.getLocalDate(
rs, "expectedFirstRepaymentOnDate");
LocalDate interestChargedFromDate = JdbcSupport.getLocalDate(rs,
"interestChargedFromDate");
LocalDate closedOnDate = JdbcSupport.getLocalDate(rs,
"closedOnDate");
LocalDate expectedMaturityDate = JdbcSupport.getLocalDate(rs,
"expectedMaturityDate");
BigDecimal principal = rs.getBigDecimal("principal"); BigDecimal principal = rs.getBigDecimal("principal");
BigDecimal inArrearsTolerance = rs.getBigDecimal("inArrearsTolerance"); BigDecimal inArrearsTolerance = rs.getBigDecimal("inArrearsTolerance");
@@ -508,24 +473,13 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
} }
Collection<LoanChargeData> charges = null; Collection<LoanChargeData> charges = null;
return new LoanBasicDetailsData(id, externalId, return new LoanBasicDetailsData(id, externalId, clientId, clientName, clientOfficeId, groupId, groupName, groupOfficeId,
clientId, clientName, clientOfficeId, loanProductId, loanProductName, loanProductDescription, fundId, fundName, closedOnDate, submittedOnDate,
groupId, groupName, groupOfficeId, approvedOnDate, expectedDisbursementDate, actualDisbursementDate, expectedMaturityDate, expectedFirstRepaymentOnDate,
loanProductId, loanProductName, loanProductDescription, interestChargedFromDate, currencyData, principal, inArrearsTolerance, numberOfRepayments, repaymentEvery,
fundId, fundName, interestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType, amortizationType,
closedOnDate, interestType, interestCalculationPeriodType, status, lifeCycleStatusDate, termFrequency, termPeriodFrequencyType,
submittedOnDate, approvedOnDate, expectedDisbursementDate, transactionStrategyId, charges, loanOfficerId, loanOfficerName, totalDisbursementCharges);
actualDisbursementDate, expectedMaturityDate,
expectedFirstRepaymentOnDate, interestChargedFromDate,
currencyData,
principal, inArrearsTolerance, numberOfRepayments,
repaymentEvery, interestRatePerPeriod, annualInterestRate,
repaymentFrequencyType, interestRateFrequencyType,
amortizationType, interestType,
interestCalculationPeriodType,
status,
lifeCycleStatusDate, termFrequency, termPeriodFrequencyType, transactionStrategyId, charges,
loanOfficerId, loanOfficerName, totalDisbursementCharges);
} }
} }
@@ -546,8 +500,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
+ " ls.interest_amount as interestDue, ls.interest_completed_derived as interestPaid, ls.interest_waived_derived as interestWaived, ls.interest_writtenoff_derived as interestWrittenOff, " + " ls.interest_amount as interestDue, ls.interest_completed_derived as interestPaid, ls.interest_waived_derived as interestWaived, ls.interest_writtenoff_derived as interestWrittenOff, "
+ " ls.fee_charges_amount as feeChargesDue, ls.fee_charges_completed_derived as feeChargesPaid, ls.fee_charges_waived_derived as feeChargesWaived, ls.fee_charges_writtenoff_derived as feeChargesWrittenOff, " + " ls.fee_charges_amount as feeChargesDue, ls.fee_charges_completed_derived as feeChargesPaid, ls.fee_charges_waived_derived as feeChargesWaived, ls.fee_charges_writtenoff_derived as feeChargesWrittenOff, "
+ " ls.penalty_charges_amount as penaltyChargesDue, ls.penalty_charges_completed_derived as penaltyChargesPaid, ls.penalty_charges_waived_derived as penaltyChargesWaived, ls.penalty_charges_writtenoff_derived as penaltyChargesWrittenOff " + " ls.penalty_charges_amount as penaltyChargesDue, ls.penalty_charges_completed_derived as penaltyChargesPaid, ls.penalty_charges_waived_derived as penaltyChargesWaived, ls.penalty_charges_writtenoff_derived as penaltyChargesWrittenOff "
+ " from m_loan l " + " from m_loan l " + " join m_loan_repayment_schedule ls on ls.loan_id = l.id ";
+ " join m_loan_repayment_schedule ls on ls.loan_id = l.id ";
} }
@Override @Override
@@ -561,7 +514,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final BigDecimal principalPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalPaid"); final BigDecimal principalPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalPaid");
final BigDecimal principalWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalWrittenOff"); final BigDecimal principalWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalWrittenOff");
// TODO - KW - rather than calculate this here should we put calculation on derived column on table like other columns? // TODO - KW - rather than calculate this here should we put
// calculation on derived column on table like other columns?
final BigDecimal principalOutstanding = principalDue.subtract(principalPaid).subtract(principalWrittenOff); final BigDecimal principalOutstanding = principalDue.subtract(principalPaid).subtract(principalWrittenOff);
final BigDecimal interestExpectedDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestDue"); final BigDecimal interestExpectedDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestDue");
@@ -569,7 +523,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final BigDecimal interestWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestWaived"); final BigDecimal interestWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestWaived");
final BigDecimal interestWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestWrittenOff"); final BigDecimal interestWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestWrittenOff");
// TODO - KW - rather than calculate this here should we put calculation on derived column on table like other columns? // TODO - KW - rather than calculate this here should we put
// calculation on derived column on table like other columns?
final BigDecimal interestActualDue = interestExpectedDue.subtract(interestWaived).subtract(interestWrittenOff); final BigDecimal interestActualDue = interestExpectedDue.subtract(interestWaived).subtract(interestWrittenOff);
final BigDecimal interestOutstanding = interestActualDue.subtract(interestPaid); final BigDecimal interestOutstanding = interestActualDue.subtract(interestPaid);
@@ -586,16 +541,20 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final BigDecimal penaltyChargesWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyChargesWaived"); final BigDecimal penaltyChargesWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyChargesWaived");
final BigDecimal penaltyChargesWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyChargesWrittenOff"); final BigDecimal penaltyChargesWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyChargesWrittenOff");
final BigDecimal penaltyChargesActualDue = penaltyChargesExpectedDue.subtract(penaltyChargesWaived).subtract(penaltyChargesWrittenOff); final BigDecimal penaltyChargesActualDue = penaltyChargesExpectedDue.subtract(penaltyChargesWaived).subtract(
penaltyChargesWrittenOff);
final BigDecimal penaltyChargesOutstanding = penaltyChargesActualDue.subtract(penaltyChargesPaid); final BigDecimal penaltyChargesOutstanding = penaltyChargesActualDue.subtract(penaltyChargesPaid);
final BigDecimal totalExpectedCostOfLoanForPeriod = interestExpectedDue.add(feeChargesExpectedDue).add(penaltyChargesExpectedDue); final BigDecimal totalExpectedCostOfLoanForPeriod = interestExpectedDue.add(feeChargesExpectedDue).add(
penaltyChargesExpectedDue);
final BigDecimal totalDueForPeriod = principalDue.add(totalExpectedCostOfLoanForPeriod); final BigDecimal totalDueForPeriod = principalDue.add(totalExpectedCostOfLoanForPeriod);
final BigDecimal totalPaidForPeriod = principalPaid.add(interestPaid).add(feeChargesPaid).add(penaltyChargesPaid); final BigDecimal totalPaidForPeriod = principalPaid.add(interestPaid).add(feeChargesPaid).add(penaltyChargesPaid);
final BigDecimal totalWaivedForPeriod = interestWaived.add(feeChargesWaived).add(penaltyChargesWaived); final BigDecimal totalWaivedForPeriod = interestWaived.add(feeChargesWaived).add(penaltyChargesWaived);
final BigDecimal totalWrittenOffForPeriod = principalWrittenOff.add(interestWrittenOff).add(feeChargesWrittenOff).add(penaltyChargesWrittenOff); final BigDecimal totalWrittenOffForPeriod = principalWrittenOff.add(interestWrittenOff).add(feeChargesWrittenOff)
final BigDecimal totalOutstandingForPeriod = principalOutstanding.add(interestOutstanding).add(feeChargesOutstanding).add(penaltyChargesOutstanding); .add(penaltyChargesWrittenOff);
final BigDecimal totalOutstandingForPeriod = principalOutstanding.add(interestOutstanding).add(feeChargesOutstanding)
.add(penaltyChargesOutstanding);
final BigDecimal totalActualCostOfLoanForPeriod = interestActualDue.add(feeChargesActualDue).add(penaltyChargesActualDue); final BigDecimal totalActualCostOfLoanForPeriod = interestActualDue.add(feeChargesActualDue).add(penaltyChargesActualDue);
@@ -608,12 +567,12 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
this.lastDueDate = dueDate; this.lastDueDate = dueDate;
outstandingLoanPrincipalBalance = outstandingLoanPrincipalBalance.subtract(principalDue); outstandingLoanPrincipalBalance = outstandingLoanPrincipalBalance.subtract(principalDue);
return LoanSchedulePeriodData.repaymentPeriodWithPayments(loanId, period, fromDate, dueDate, return LoanSchedulePeriodData.repaymentPeriodWithPayments(loanId, period, fromDate, dueDate, principalDue, principalPaid,
principalDue, principalPaid, principalWrittenOff, principalOutstanding, outstandingPrincipleBalanceOfLoan, principalWrittenOff, principalOutstanding, outstandingPrincipleBalanceOfLoan, interestExpectedDue, interestPaid,
interestExpectedDue, interestPaid, interestWaived, interestWrittenOff, interestOutstanding, interestWaived, interestWrittenOff, interestOutstanding, feeChargesExpectedDue, feeChargesPaid, feeChargesWaived,
feeChargesExpectedDue, feeChargesPaid, feeChargesWaived, feeChargesWrittenOff, feeChargesOutstanding, feeChargesWrittenOff, feeChargesOutstanding, penaltyChargesExpectedDue, penaltyChargesPaid, penaltyChargesWaived,
penaltyChargesExpectedDue, penaltyChargesPaid, penaltyChargesWaived, penaltyChargesWrittenOff, penaltyChargesOutstanding, penaltyChargesWrittenOff, penaltyChargesOutstanding, totalDueForPeriod, totalPaidForPeriod, totalWaivedForPeriod,
totalDueForPeriod, totalPaidForPeriod, totalWaivedForPeriod, totalWrittenOffForPeriod, totalOutstandingForPeriod, totalActualCostOfLoanForPeriod); totalWrittenOffForPeriod, totalOutstandingForPeriod, totalActualCostOfLoanForPeriod);
} }
} }
@@ -622,28 +581,23 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
public String LoanPaymentsSchema() { public String LoanPaymentsSchema() {
return " tr.id as id, tr.transaction_type_enum as transactionType, tr.transaction_date as `date`, tr.amount as total, " return " tr.id as id, tr.transaction_type_enum as transactionType, tr.transaction_date as `date`, tr.amount as total, "
+ "tr.principal_portion_derived as principal, " + "tr.principal_portion_derived as principal, " + "tr.interest_portion_derived as interest, "
+ "tr.interest_portion_derived as interest, " + "tr.fee_charges_portion_derived as fees, " + "tr.penalty_charges_portion_derived as penalties, "
+ "tr.fee_charges_portion_derived as fees, "
+ "tr.penalty_charges_portion_derived as penalties, "
+ " l.currency_code as currencyCode, l.currency_digits as currencyDigits, rc.`name` as currencyName, " + " l.currency_code as currencyCode, l.currency_digits as currencyDigits, rc.`name` as currencyName, "
+ " rc.display_symbol as currencyDisplaySymbol, rc.internationalized_name_code as currencyNameCode " + " rc.display_symbol as currencyDisplaySymbol, rc.internationalized_name_code as currencyNameCode "
+ " from m_loan l " + " from m_loan l " + " join m_loan_transaction tr on tr.loan_id = l.id"
+ " join m_loan_transaction tr on tr.loan_id = l.id"
+ " join m_currency rc on rc.`code` = l.currency_code "; + " join m_currency rc on rc.`code` = l.currency_code ";
} }
@Override @Override
public LoanTransactionData mapRow(final ResultSet rs, public LoanTransactionData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
@SuppressWarnings("unused") final int rowNum) throws SQLException {
final String currencyCode = rs.getString("currencyCode"); final String currencyCode = rs.getString("currencyCode");
final String currencyName = rs.getString("currencyName"); final String currencyName = rs.getString("currencyName");
final String currencyNameCode = rs.getString("currencyNameCode"); final String currencyNameCode = rs.getString("currencyNameCode");
final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
final CurrencyData currencyData = new CurrencyData(currencyCode, final CurrencyData currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, currencyDisplaySymbol,
currencyName, currencyDigits, currencyDisplaySymbol,
currencyNameCode); currencyNameCode);
final Long id = rs.getLong("id"); final Long id = rs.getLong("id");
@@ -656,7 +610,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final BigDecimal feeChargesPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "fees"); final BigDecimal feeChargesPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "fees");
final BigDecimal penaltyChargesPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penalties"); final BigDecimal penaltyChargesPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penalties");
return new LoanTransactionData(id, transactionType, currencyData, date, totalAmount, principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion); return new LoanTransactionData(id, transactionType, currencyData, date, totalAmount, principalPortion, interestPortion,
feeChargesPortion, penaltyChargesPortion);
} }
} }
} }
@@ -3,9 +3,7 @@ package org.mifosplatform.portfolio.loanaccount.service;
import org.mifosplatform.infrastructure.core.api.JsonCommand; 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.LoanChargeCommand; import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand;
import org.mifosplatform.portfolio.loanaccount.command.LoanTransactionCommand;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
public interface LoanWritePlatformService { public interface LoanWritePlatformService {
@@ -26,25 +24,19 @@ public interface LoanWritePlatformService {
EntityIdentifier disburseLoan(Long loanId, JsonCommand command); EntityIdentifier disburseLoan(Long loanId, JsonCommand command);
public EntityIdentifier undoLoanDisbursal(Long loanId, JsonCommand command); EntityIdentifier undoLoanDisbursal(Long loanId, JsonCommand command);
@PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'REPAYMENT_LOAN', 'REPAYMENTINPAST_LOAN')") EntityIdentifier makeLoanRepayment(Long loanId, JsonCommand command);
public EntityIdentifier makeLoanRepayment(LoanTransactionCommand command);
@PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'ADJUST_LOAN')") EntityIdentifier adjustLoanTransaction(Long loanId, Long transactionId, JsonCommand command);
EntityIdentifier adjustLoanTransaction(AdjustLoanTransactionCommand command);
@PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'WAIVEINTERESTPORTION_LOAN')") EntityIdentifier waiveInterestOnLoan(Long loanId, JsonCommand command);
EntityIdentifier waiveInterestOnLoan(LoanTransactionCommand command);
@PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'WRITEOFF_LOAN')") EntityIdentifier writeOff(Long loanId, JsonCommand command);
EntityIdentifier writeOff(LoanTransactionCommand command);
@PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'CLOSE_LOAN')") EntityIdentifier closeLoan(Long loanId, JsonCommand command);
EntityIdentifier closeLoan(LoanTransactionCommand command);
@PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'CLOSEASRESCHEDULED_LOAN')") EntityIdentifier closeAsRescheduled(Long loanId, JsonCommand command);
EntityIdentifier closeAsRescheduled(LoanTransactionCommand command);
@PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'CREATE_LOANCHARGE')") @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'CREATE_LOANCHARGE')")
EntityIdentifier addLoanCharge(LoanChargeCommand command); EntityIdentifier addLoanCharge(LoanChargeCommand command);
@@ -1,6 +1,8 @@
package org.mifosplatform.portfolio.loanaccount.service; package org.mifosplatform.portfolio.loanaccount.service;
import java.math.BigDecimal;
import java.util.Arrays; import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@@ -15,8 +17,6 @@ import org.mifosplatform.infrastructure.security.exception.NoAuthorizationExcept
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepository; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepository;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.organisation.staff.command.BulkTransferLoanOfficerCommand; import org.mifosplatform.organisation.staff.command.BulkTransferLoanOfficerCommand;
import org.mifosplatform.organisation.staff.command.BulkTransferLoanOfficerCommandValidator; import org.mifosplatform.organisation.staff.command.BulkTransferLoanOfficerCommandValidator;
import org.mifosplatform.organisation.staff.domain.Staff; import org.mifosplatform.organisation.staff.domain.Staff;
@@ -31,14 +31,11 @@ import org.mifosplatform.portfolio.client.domain.Note;
import org.mifosplatform.portfolio.client.domain.NoteRepository; import org.mifosplatform.portfolio.client.domain.NoteRepository;
import org.mifosplatform.portfolio.client.exception.ClientNotFoundException; import org.mifosplatform.portfolio.client.exception.ClientNotFoundException;
import org.mifosplatform.portfolio.fund.domain.Fund; import org.mifosplatform.portfolio.fund.domain.Fund;
import org.mifosplatform.portfolio.loanaccount.command.AdjustLoanTransactionCommand;
import org.mifosplatform.portfolio.loanaccount.command.AdjustLoanTransactionCommandValidator;
import org.mifosplatform.portfolio.loanaccount.command.LoanApplicationCommand; import org.mifosplatform.portfolio.loanaccount.command.LoanApplicationCommand;
import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand; import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand;
import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommandValidator; import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommandValidator;
import org.mifosplatform.portfolio.loanaccount.command.LoanStateTransitionCommand; 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.LoanTransactionCommandValidator;
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;
@@ -59,6 +56,7 @@ import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanSchedule
import org.mifosplatform.portfolio.loanaccount.serialization.CalculateLoanScheduleQueryFromApiJsonDeserializer; import org.mifosplatform.portfolio.loanaccount.serialization.CalculateLoanScheduleQueryFromApiJsonDeserializer;
import org.mifosplatform.portfolio.loanaccount.serialization.LoanApplicationCommandFromApiJsonDeserializer; import org.mifosplatform.portfolio.loanaccount.serialization.LoanApplicationCommandFromApiJsonDeserializer;
import org.mifosplatform.portfolio.loanaccount.serialization.LoanStateTransitionCommandFromApiJsonDeserializer; import org.mifosplatform.portfolio.loanaccount.serialization.LoanStateTransitionCommandFromApiJsonDeserializer;
import org.mifosplatform.portfolio.loanaccount.serialization.LoanTransactionCommandFromApiJsonDeserializer;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct; import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct;
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;
@@ -79,6 +77,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final LoanApplicationCommandFromApiJsonDeserializer fromApiJsonDeserializer; private final LoanApplicationCommandFromApiJsonDeserializer fromApiJsonDeserializer;
private final CalculateLoanScheduleQueryFromApiJsonDeserializer calculateLoanScheduleQueryFromApiJsonDeserializer; private final CalculateLoanScheduleQueryFromApiJsonDeserializer calculateLoanScheduleQueryFromApiJsonDeserializer;
private final LoanStateTransitionCommandFromApiJsonDeserializer loanStateTransitionCommandFromApiJsonDeserializer; private final LoanStateTransitionCommandFromApiJsonDeserializer loanStateTransitionCommandFromApiJsonDeserializer;
private final LoanTransactionCommandFromApiJsonDeserializer loanTransactionCommandFromApiJsonDeserializer;
private final LoanRepository loanRepository; private final LoanRepository loanRepository;
private final NoteRepository noteRepository; private final NoteRepository noteRepository;
private final LoanScheduleCalculationPlatformService calculationPlatformService; private final LoanScheduleCalculationPlatformService calculationPlatformService;
@@ -96,6 +95,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper, public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper,
final LoanApplicationCommandFromApiJsonDeserializer fromApiJsonDeserializer, final LoanApplicationCommandFromApiJsonDeserializer fromApiJsonDeserializer,
final LoanStateTransitionCommandFromApiJsonDeserializer loanStateTransitionCommandFromApiJsonDeserializer, final LoanStateTransitionCommandFromApiJsonDeserializer loanStateTransitionCommandFromApiJsonDeserializer,
final LoanTransactionCommandFromApiJsonDeserializer loanTransactionCommandFromApiJsonDeserializer,
final CalculateLoanScheduleQueryFromApiJsonDeserializer calculateLoanScheduleQueryFromApiJsonDeserializer, final CalculateLoanScheduleQueryFromApiJsonDeserializer calculateLoanScheduleQueryFromApiJsonDeserializer,
final AprCalculator aprCalculator, final LoanAssembler loanAssembler, final LoanChargeAssembler loanChargeAssembler, final AprCalculator aprCalculator, final LoanAssembler loanAssembler, final LoanChargeAssembler loanChargeAssembler,
final LoanRepository loanRepository, final LoanTransactionRepository loanTransactionRepository, final LoanRepository loanRepository, final LoanTransactionRepository loanTransactionRepository,
@@ -107,6 +107,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.fromJsonHelper = fromJsonHelper; this.fromJsonHelper = fromJsonHelper;
this.fromApiJsonDeserializer = fromApiJsonDeserializer; this.fromApiJsonDeserializer = fromApiJsonDeserializer;
this.loanStateTransitionCommandFromApiJsonDeserializer = loanStateTransitionCommandFromApiJsonDeserializer; this.loanStateTransitionCommandFromApiJsonDeserializer = loanStateTransitionCommandFromApiJsonDeserializer;
this.loanTransactionCommandFromApiJsonDeserializer = loanTransactionCommandFromApiJsonDeserializer;
this.calculateLoanScheduleQueryFromApiJsonDeserializer = calculateLoanScheduleQueryFromApiJsonDeserializer; this.calculateLoanScheduleQueryFromApiJsonDeserializer = calculateLoanScheduleQueryFromApiJsonDeserializer;
this.aprCalculator = aprCalculator; this.aprCalculator = aprCalculator;
this.loanAssembler = loanAssembler; this.loanAssembler = loanAssembler;
@@ -412,179 +413,222 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
@Transactional @Transactional
@Override @Override
public EntityIdentifier makeLoanRepayment(final LoanTransactionCommand command) { public EntityIdentifier makeLoanRepayment(final Long loanId, final JsonCommand command) {
AppUser currentUser = context.authenticatedUser(); final AppUser currentUser = context.authenticatedUser();
LoanTransactionCommandValidator validator = new LoanTransactionCommandValidator(command); final LoanTransactionCommand loanTransactionCommand = this.loanTransactionCommandFromApiJsonDeserializer.commandFromApiJson(command
validator.validate(); .json());
loanTransactionCommand.validate();
Loan loan = this.loanRepository.findOne(command.getLoanId()); final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
if (loan == null) { throw new LoanNotFoundException(command.getLoanId()); } final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
final Map<String, Object> changes = new LinkedHashMap<String, Object>();
changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
changes.put("transactionAmount", command.stringValueOfParameterNamed("transactionAmount"));
final Loan loan = retrieveLoanBy(loanId);
LocalDate transactionDate = command.getTransactionDate();
if (this.isBeforeToday(transactionDate) && currentUser.canNotMakeRepaymentOnLoanInPast()) { throw new NoAuthorizationException( if (this.isBeforeToday(transactionDate) && currentUser.canNotMakeRepaymentOnLoanInPast()) { throw new NoAuthorizationException(
"error.msg.no.permission.to.make.repayment.on.loan.in.past"); } "error.msg.no.permission.to.make.repayment.on.loan.in.past"); }
Money repayment = Money.of(loan.repaymentScheduleDetail().getPrincipal().getCurrency(), command.getTransactionAmount()); final LoanTransaction loanRepayment = loan.makeRepayment(transactionDate, transactionAmount, defaultLoanLifecycleStateMachine());
LoanTransaction loanRepayment = LoanTransaction.repayment(repayment, transactionDate);
loan.makeRepayment(loanRepayment, defaultLoanLifecycleStateMachine());
this.loanTransactionRepository.save(loanRepayment); this.loanTransactionRepository.save(loanRepayment);
this.loanRepository.save(loan); this.loanRepository.save(loan);
String noteText = command.getNote(); final String noteText = command.stringValueOfParameterNamed("note");
if (StringUtils.isNotBlank(noteText)) { if (StringUtils.isNotBlank(noteText)) {
changes.put("note", noteText);
Note note = Note.loanTransactionNote(loan, loanRepayment, noteText); Note note = Note.loanTransactionNote(loan, loanRepayment, noteText);
this.noteRepository.save(note); this.noteRepository.save(note);
} }
return new EntityIdentifier(loan.getId()); changes.put("locale", command.locale());
changes.put("dateFormat", command.dateFormat());
return EntityIdentifier.subResourceResult(loanId, loanRepayment.getId(), command.commandId(), changes);
} }
@Transactional @Transactional
@Override @Override
public EntityIdentifier adjustLoanTransaction(final AdjustLoanTransactionCommand command) { public EntityIdentifier adjustLoanTransaction(final Long loanId, final Long transactionId, final JsonCommand command) {
context.authenticatedUser(); context.authenticatedUser();
AdjustLoanTransactionCommandValidator validator = new AdjustLoanTransactionCommandValidator(command); final LoanTransactionCommand loanTransactionCommand = this.loanTransactionCommandFromApiJsonDeserializer.commandFromApiJson(command
validator.validate(); .json());
loanTransactionCommand.validate();
Loan loan = this.loanRepository.findOne(command.getLoanId()); final Loan loan = retrieveLoanBy(loanId);
if (loan == null) { throw new LoanNotFoundException(command.getLoanId()); }
LoanTransaction transactionToAdjust = this.loanTransactionRepository.findOne(command.getTransactionId()); final LoanTransaction transactionToAdjust = this.loanTransactionRepository.findOne(transactionId);
if (transactionToAdjust == null) { throw new LoanTransactionNotFoundException(command.getTransactionId()); } if (transactionToAdjust == null) { throw new LoanTransactionNotFoundException(transactionId); }
final MonetaryCurrency currency = loan.repaymentScheduleDetail().getPrincipal().getCurrency(); final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
final Money transactionAmount = Money.of(currency, command.getTransactionAmount()); final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
// adjustment is only supported for repayments and waivers at present final Map<String, Object> changes = new LinkedHashMap<String, Object>();
LocalDate transactionDate = command.getTransactionDate(); changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
LoanTransaction newTransactionDetail = LoanTransaction.repayment(transactionAmount, transactionDate); changes.put("transactionAmount", command.stringValueOfParameterNamed("transactionAmount"));
if (transactionToAdjust.isInterestWaiver()) {
newTransactionDetail = LoanTransaction.waiver(loan, transactionAmount, transactionDate);
}
loan.adjustExistingTransaction(transactionToAdjust, newTransactionDetail, defaultLoanLifecycleStateMachine()); final LoanTransaction newTransactionDetail = loan.adjustExistingTransaction(transactionDate, transactionAmount, defaultLoanLifecycleStateMachine(),
transactionToAdjust);
if (newTransactionDetail.isGreaterThanZero(currency)) { if (newTransactionDetail.isGreaterThanZero(loan.getPrincpal().getCurrency())) {
this.loanTransactionRepository.save(newTransactionDetail); this.loanTransactionRepository.save(newTransactionDetail);
} }
this.loanRepository.save(loan); this.loanRepository.save(loan);
String noteText = command.getNote(); final String noteText = command.stringValueOfParameterNamed("note");
if (StringUtils.isNotBlank(noteText)) { if (StringUtils.isNotBlank(noteText)) {
changes.put("note", noteText);
Note note = Note.loanTransactionNote(loan, newTransactionDetail, noteText); Note note = Note.loanTransactionNote(loan, newTransactionDetail, noteText);
this.noteRepository.save(note); this.noteRepository.save(note);
} }
return new EntityIdentifier(loan.getId()); changes.put("locale", command.locale());
changes.put("dateFormat", command.dateFormat());
return EntityIdentifier.subResourceResult(loanId, transactionId, command.commandId(), changes);
} }
@Transactional @Transactional
@Override @Override
public EntityIdentifier waiveInterestOnLoan(final LoanTransactionCommand command) { public EntityIdentifier waiveInterestOnLoan(final Long loanId, final JsonCommand command) {
context.authenticatedUser(); context.authenticatedUser();
final LoanTransactionCommandValidator validator = new LoanTransactionCommandValidator(command); final LoanTransactionCommand loanTransactionCommand = this.loanTransactionCommandFromApiJsonDeserializer.commandFromApiJson(command
validator.validate(); .json());
loanTransactionCommand.validate();
final Loan loan = this.loanRepository.findOne(command.getLoanId()); final Map<String, Object> changes = new LinkedHashMap<String, Object>();
if (loan == null) { throw new LoanNotFoundException(command.getLoanId()); } changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
changes.put("transactionAmount", command.stringValueOfParameterNamed("transactionAmount"));
final LoanTransaction waiveTransaction = loan.waiveInterest(command.getTransactionAmount(), command.getTransactionDate(), final Loan loan = retrieveLoanBy(loanId);
defaultLoanLifecycleStateMachine());
final LoanTransaction waiveTransaction = loan.waiveInterest(command, defaultLoanLifecycleStateMachine());
this.loanTransactionRepository.save(waiveTransaction); this.loanTransactionRepository.save(waiveTransaction);
this.loanRepository.save(loan); this.loanRepository.save(loan);
final String noteText = command.getNote(); final String noteText = command.stringValueOfParameterNamed("note");
if (StringUtils.isNotBlank(noteText)) { if (StringUtils.isNotBlank(noteText)) {
changes.put("note", noteText);
final Note note = Note.loanTransactionNote(loan, waiveTransaction, noteText); final Note note = Note.loanTransactionNote(loan, waiveTransaction, noteText);
this.noteRepository.save(note); this.noteRepository.save(note);
} }
return new EntityIdentifier(loan.getId()); changes.put("locale", command.locale());
changes.put("dateFormat", command.dateFormat());
return EntityIdentifier.subResourceResult(loanId, waiveTransaction.getId(), command.commandId(), changes);
} }
@Transactional @Transactional
@Override @Override
public EntityIdentifier writeOff(final LoanTransactionCommand command) { public EntityIdentifier writeOff(final Long loanId, final JsonCommand command) {
context.authenticatedUser(); context.authenticatedUser();
final LoanTransactionCommandValidator validator = new LoanTransactionCommandValidator(command); final LoanTransactionCommand loanTransactionCommand = this.loanTransactionCommandFromApiJsonDeserializer.commandFromApiJson(command
validator.validateNonMonetaryTransaction(); .json());
loanTransactionCommand.validateNonMonetaryTransaction();
final Loan loan = this.loanRepository.findOne(command.getLoanId()); final Map<String, Object> changes = new LinkedHashMap<String, Object>();
if (loan == null) { throw new LoanNotFoundException(command.getLoanId()); } changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
final LoanTransaction writeoff = loan.closeAsWrittenOff(command.getTransactionDate(), defaultLoanLifecycleStateMachine()); final Loan loan = retrieveLoanBy(loanId);
final LoanTransaction writeoff = loan.closeAsWrittenOff(command, defaultLoanLifecycleStateMachine(), changes);
this.loanTransactionRepository.save(writeoff); this.loanTransactionRepository.save(writeoff);
this.loanRepository.save(loan); this.loanRepository.save(loan);
final String noteText = command.getNote(); final String noteText = command.stringValueOfParameterNamed("note");
if (StringUtils.isNotBlank(noteText)) { if (StringUtils.isNotBlank(noteText)) {
changes.put("note", noteText);
final Note note = Note.loanTransactionNote(loan, writeoff, noteText); final Note note = Note.loanTransactionNote(loan, writeoff, noteText);
this.noteRepository.save(note); this.noteRepository.save(note);
} }
return new EntityIdentifier(loan.getId()); changes.put("locale", command.locale());
changes.put("dateFormat", command.dateFormat());
return EntityIdentifier.subResourceResult(loanId, writeoff.getId(), command.commandId(), changes);
} }
@Transactional @Transactional
@Override @Override
public EntityIdentifier closeLoan(final LoanTransactionCommand command) { public EntityIdentifier closeLoan(final Long loanId, final JsonCommand command) {
context.authenticatedUser(); context.authenticatedUser();
final LoanTransactionCommandValidator validator = new LoanTransactionCommandValidator(command); final LoanTransactionCommand loanTransactionCommand = this.loanTransactionCommandFromApiJsonDeserializer.commandFromApiJson(command
validator.validateNonMonetaryTransaction(); .json());
loanTransactionCommand.validateNonMonetaryTransaction();
final Loan loan = this.loanRepository.findOne(command.getLoanId()); final Loan loan = retrieveLoanBy(loanId);
if (loan == null) { throw new LoanNotFoundException(command.getLoanId()); }
final LoanTransaction possibleClosingTransaction = loan.close(command.getTransactionDate(), defaultLoanLifecycleStateMachine()); final Map<String, Object> changes = new LinkedHashMap<String, Object>();
changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
final LoanTransaction possibleClosingTransaction = loan.close(command, defaultLoanLifecycleStateMachine(), changes);
if (possibleClosingTransaction != null) { if (possibleClosingTransaction != null) {
this.loanTransactionRepository.save(possibleClosingTransaction); this.loanTransactionRepository.save(possibleClosingTransaction);
} }
this.loanRepository.save(loan); this.loanRepository.save(loan);
final String noteText = command.getNote(); final String noteText = command.stringValueOfParameterNamed("note");
if (StringUtils.isNotBlank(noteText)) { if (StringUtils.isNotBlank(noteText)) {
changes.put("note", noteText);
final Note note = Note.loanNote(loan, noteText); final Note note = Note.loanNote(loan, noteText);
this.noteRepository.save(note); this.noteRepository.save(note);
} }
return new EntityIdentifier(loan.getId()); changes.put("locale", command.locale());
changes.put("dateFormat", command.dateFormat());
EntityIdentifier result = null;
if (possibleClosingTransaction != null) {
result = EntityIdentifier.subResourceResult(loanId, possibleClosingTransaction.getId(), command.commandId(), changes);
} else {
result = EntityIdentifier.resourceResult(loanId, command.commandId(), changes);
}
return result;
} }
@Transactional @Transactional
@Override @Override
public EntityIdentifier closeAsRescheduled(final LoanTransactionCommand command) { public EntityIdentifier closeAsRescheduled(final Long loanId, final JsonCommand command) {
context.authenticatedUser(); context.authenticatedUser();
final LoanTransactionCommandValidator validator = new LoanTransactionCommandValidator(command); final LoanTransactionCommand loanTransactionCommand = this.loanTransactionCommandFromApiJsonDeserializer.commandFromApiJson(command
validator.validateNonMonetaryTransaction(); .json());
loanTransactionCommand.validateNonMonetaryTransaction();
final Loan loan = this.loanRepository.findOne(command.getLoanId()); final Loan loan = retrieveLoanBy(loanId);
if (loan == null) { throw new LoanNotFoundException(command.getLoanId()); }
loan.closeAsMarkedForReschedule(command.getTransactionDate(), defaultLoanLifecycleStateMachine()); final Map<String, Object> changes = new LinkedHashMap<String, Object>();
changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
loan.closeAsMarkedForReschedule(command, defaultLoanLifecycleStateMachine(), changes);
this.loanRepository.save(loan); this.loanRepository.save(loan);
final String noteText = command.getNote(); final String noteText = command.stringValueOfParameterNamed("note");
if (StringUtils.isNotBlank(noteText)) { if (StringUtils.isNotBlank(noteText)) {
changes.put("note", noteText);
final Note note = Note.loanNote(loan, noteText); final Note note = Note.loanNote(loan, noteText);
this.noteRepository.save(note); this.noteRepository.save(note);
} }
return new EntityIdentifier(loan.getId()); changes.put("locale", command.locale());
changes.put("dateFormat", command.dateFormat());
return EntityIdentifier.resourceResult(loanId, command.commandId(), changes);
} }
@Transactional @Transactional