Collection sheet UI and bulk update

This commit is contained in:
ashok-conflux
2013-03-27 16:30:45 +05:30
parent 69d228a502
commit c77f3e15f1
20 changed files with 719 additions and 50 deletions
@@ -463,4 +463,9 @@ public class CommandWrapper {
public boolean isGroupResource() {
return this.entityName.equalsIgnoreCase("GROUP");
}
public boolean isCollectionSheetResource(){
return this.entityName.equals("COLLECTIONSHEET");
}
}
@@ -826,5 +826,13 @@ public class CommandWrapperBuilder {
this.loanId = loanId;
this.href = "/loans/" + loanId + "/collaterals/" + collateralId;
return this;
}
public CommandWrapperBuilder updateCollectionSheet(final Long groupId) {
this.actionName = "UPDATE";
this.entityName = "COLLECTIONSHEET";
this.entityId = groupId;
this.href = "/groups/" + groupId + "/collectionsheet";
return this;
}
}
@@ -393,6 +393,14 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
} else if (wrapper.isCollectionSheetResource()) {
if (wrapper.isUpdate()) {
handler = applicationContext.getBean("updateCollectionSheetCommandHandler", NewCommandSourceHandler.class);
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
} else {
throw new UnsupportedCommandException(wrapper.commandName());
@@ -6,6 +6,7 @@ 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;
@@ -15,7 +16,11 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import org.joda.time.LocalDate;
import org.mifosplatform.commands.domain.CommandWrapper;
import org.mifosplatform.commands.service.CommandWrapperBuilder;
import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService;
import org.mifosplatform.infrastructure.core.api.ApiRequestParameterHelper;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.mifosplatform.infrastructure.core.serialization.ToApiJsonSerializer;
import org.mifosplatform.infrastructure.core.service.DateUtils;
@@ -37,7 +42,7 @@ import org.springframework.stereotype.Component;
@Scope("singleton")
public class CollectionSheetApiResource {
private final Set<String> COLLECTIONSHEET_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("dueDate", "groups"));
private final Set<String> COLLECTIONSHEET_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("dueDate", "loanProducts", "groups"));
/*
* dueDate format is ISO 8601 Calendar Date (YYYYMMDD)
*/
@@ -46,17 +51,18 @@ public class CollectionSheetApiResource {
private final CollectionSheetReadPlatformService collectionSheetReadPlatformService;
private final ToApiJsonSerializer<JLGCollectionSheetData> toApiJsonSerializer;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
@Autowired
public CollectionSheetApiResource(final PlatformSecurityContext context,
final CollectionSheetReadPlatformService collectionSheetReadPlatformService,
final ToApiJsonSerializer<JLGCollectionSheetData> toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper) {
final ToApiJsonSerializer<JLGCollectionSheetData> toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper, final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
this.context = context;
this.collectionSheetReadPlatformService = collectionSheetReadPlatformService;
this.toApiJsonSerializer = toApiJsonSerializer;
this.apiRequestParameterHelper = apiRequestParameterHelper;
this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
}
@GET
@@ -72,4 +78,16 @@ public class CollectionSheetApiResource {
return this.toApiJsonSerializer.serialize(settings, collectionSheet, this.COLLECTIONSHEET_DATA_PARAMETERS);
}
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String updateCalendar(@PathParam("groupId") final Long groupId, final String jsonRequestBody) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateCollectionSheet(groupId).withJson(jsonRequestBody).build();
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
return this.toApiJsonSerializer.serialize(result);
}
}
@@ -0,0 +1,38 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.collectionsheet.command;
import org.joda.time.LocalDate;
/**
* Immutable command for loan bulk disburse.
*/
public class CollectionSheetBulkDisbursalCommand {
private final String note;
private final LocalDate transactionDate;
private final SingleDisbursalCommand[] disburseTransactions;
public CollectionSheetBulkDisbursalCommand(String note, final LocalDate transactionDate, SingleDisbursalCommand[] disburseTransactions) {
this.note = note;
this.transactionDate = transactionDate;
this.disburseTransactions = disburseTransactions;
}
public String getNote() {
return this.note;
}
public SingleDisbursalCommand[] getDisburseTransactions() {
return this.disburseTransactions;
}
public LocalDate getTransactionDate() {
return this.transactionDate;
}
}
@@ -0,0 +1,39 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.collectionsheet.command;
import org.joda.time.LocalDate;
/**
* Immutable command for loan bulk repayment.
*/
public class CollectionSheetBulkRepaymentCommand {
private final String note;
private final LocalDate transactionDate;
private final SingleRepaymentCommand[] repaymentTransactions;
public CollectionSheetBulkRepaymentCommand(String note, final LocalDate transactionDate, SingleRepaymentCommand[] repaymentTransactions) {
this.note = note;
this.transactionDate = transactionDate;
this.repaymentTransactions = repaymentTransactions;
}
public String getNote() {
return this.note;
}
public SingleRepaymentCommand[] getLoanTransactions() {
return this.repaymentTransactions;
}
public LocalDate getTransactionDate() {
return this.transactionDate;
}
}
@@ -0,0 +1,39 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.collectionsheet.command;
import java.math.BigDecimal;
import org.joda.time.LocalDate;
/**
* Immutable command for Single loan repayment.
*/
public class SingleDisbursalCommand {
private final Long loanId;
private final BigDecimal transactionAmount;
private final LocalDate transactionDate;
public SingleDisbursalCommand(Long loanId, BigDecimal transactionAmount, LocalDate transactionDate) {
this.loanId = loanId;
this.transactionAmount = transactionAmount;
this.transactionDate = transactionDate;
}
public Long getLoanId() {
return this.loanId;
}
public BigDecimal getTransactionAmount() {
return this.transactionAmount;
}
public LocalDate getTransactionDate() {
return this.transactionDate;
}
}
@@ -0,0 +1,39 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.collectionsheet.command;
import java.math.BigDecimal;
import org.joda.time.LocalDate;
/**
* Immutable command for Single loan repayment.
*/
public class SingleRepaymentCommand {
private final Long loanId;
private final BigDecimal transactionAmount;
private final LocalDate transactionDate;
public SingleRepaymentCommand(Long loanId, BigDecimal transactionAmount, LocalDate transactionDate) {
this.loanId = loanId;
this.transactionAmount = transactionAmount;
this.transactionDate = transactionDate;
}
public Long getLoanId() {
return this.loanId;
}
public BigDecimal getTransactionAmount() {
return this.transactionAmount;
}
public LocalDate getTransactionDate() {
return this.transactionDate;
}
}
@@ -8,6 +8,7 @@ package org.mifosplatform.portfolio.collectionsheet.data;
import java.util.Collection;
import org.joda.time.LocalDate;
import org.mifosplatform.portfolio.loanproduct.data.LoanProductData;
/**
* Immutable data object for join liability group's collection sheet.
@@ -15,10 +16,12 @@ import org.joda.time.LocalDate;
public class JLGCollectionSheetData {
private final LocalDate dueDate;
private final Collection<LoanProductData> loanProducts;
private final Collection<JLGClientsData> groups;
public JLGCollectionSheetData(final LocalDate date, final Collection<JLGClientsData> groups) {
public JLGCollectionSheetData(final LocalDate date, final Collection<LoanProductData> loanProducts, final Collection<JLGClientsData> groups) {
this.dueDate = date;
this.loanProducts = loanProducts;
this.groups = groups;
}
@@ -30,4 +33,7 @@ public class JLGCollectionSheetData {
return this.groups;
}
public Collection<LoanProductData> getLoanProducts() {
return this.loanProducts;
}
}
@@ -7,6 +7,8 @@ package org.mifosplatform.portfolio.collectionsheet.data;
import java.math.BigDecimal;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
/**
* Immutable data object for extracting flat data for joint liability group's collection sheet.
*/
@@ -25,8 +27,7 @@ public class JLGCollectionSheetFlatData {
private final Integer accountStatusId;
private final String productShortName;
private final Long productId;
private final String currencyCode;
private final Integer currencyDigits;
private final CurrencyData currency;
private BigDecimal disbursementAmount = BigDecimal.ZERO;
private BigDecimal principalDue = BigDecimal.ZERO;
private BigDecimal principalPaid = BigDecimal.ZERO;
@@ -37,7 +38,7 @@ public class JLGCollectionSheetFlatData {
public JLGCollectionSheetFlatData(final String groupName, final Long groupId, final Long staffId, final String staffName,
final Long levelId, final String levelName, final String clientName, final Long clientId, final Long loanId,
final String accountId, final Integer accountStatusId, final String productShortName, final Long productId,
final String currencyCode, final Integer currencyDigits, final BigDecimal disbursementAmount, final BigDecimal principalDue,
final CurrencyData currency, final BigDecimal disbursementAmount, final BigDecimal principalDue,
final BigDecimal principalPaid, final BigDecimal interestDue, final BigDecimal interestPaid, final BigDecimal chargesDue) {
this.groupName = groupName;
this.groupId = groupId;
@@ -52,8 +53,7 @@ public class JLGCollectionSheetFlatData {
this.accountStatusId = accountStatusId;
this.productShortName = productShortName;
this.productId = productId;
this.currencyCode = currencyCode;
this.currencyDigits = currencyDigits;
this.currency = currency;
this.disbursementAmount = disbursementAmount;
this.principalDue = principalDue;
this.principalPaid = principalPaid;
@@ -113,13 +113,9 @@ public class JLGCollectionSheetFlatData {
public Long getProductId() {
return this.productId;
}
public String getCurrencyCode() {
return this.currencyCode;
}
public Integer getCurrencyDigits() {
return this.currencyDigits;
public CurrencyData getCurrency() {
return this.currency;
}
public BigDecimal getDisbursementAmount() {
@@ -147,9 +143,8 @@ public class JLGCollectionSheetFlatData {
}
public LoanDueData getLoanDueData() {
return new LoanDueData(this.loanId, this.accountId, this.accountStatusId, this.productShortName, this.productId, this.currencyCode,
this.currencyDigits, this.disbursementAmount, this.principalDue, this.principalPaid, this.interestDue, this.interestPaid,
this.chargesDue);
return new LoanDueData(this.loanId, this.accountId, this.accountStatusId, this.productShortName, this.productId, this.currency,
this.disbursementAmount, this.principalDue, this.principalPaid, this.interestDue, this.interestPaid, this.chargesDue);
}
public ClientLoansData getClientLoansData() {
@@ -7,6 +7,8 @@ package org.mifosplatform.portfolio.collectionsheet.data;
import java.math.BigDecimal;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
/**
* Immutable data object for representing loan with dues (example: loan is due for disbursement, repayments).
*/
@@ -17,17 +19,17 @@ public class LoanDueData {
private final Integer accountStatusId;
private final String productShortName;
private final Long productId;
private final String currencyCode;
private final Integer currencyDigits;
private final CurrencyData currency;
private BigDecimal disbursementAmount = BigDecimal.ZERO;
private BigDecimal principalDue = BigDecimal.ZERO;
private BigDecimal principalPaid = BigDecimal.ZERO;
private BigDecimal interestDue = BigDecimal.ZERO;
private BigDecimal interestPaid = BigDecimal.ZERO;
private BigDecimal chargesDue = BigDecimal.ZERO;
private BigDecimal totalDue = BigDecimal.ZERO;
public LoanDueData(final Long loanId, final String accountId, final Integer accountStatusId, final String productShortName,
final Long productId, final String currencyCode, final Integer currencyDigits, final BigDecimal disbursementAmount,
final Long productId, final CurrencyData currency, final BigDecimal disbursementAmount,
final BigDecimal principalDue, final BigDecimal principalPaid, final BigDecimal interestDue, final BigDecimal interestPaid,
final BigDecimal chargesDue) {
this.loanId = loanId;
@@ -35,14 +37,14 @@ public class LoanDueData {
this.accountStatusId = accountStatusId;
this.productShortName = productShortName;
this.productId = productId;
this.currencyCode = currencyCode;
this.currencyDigits = currencyDigits;
this.currency = currency;
this.disbursementAmount = disbursementAmount;
this.principalDue = principalDue;
this.principalPaid = principalPaid;
this.interestDue = interestDue;
this.interestPaid = interestPaid;
this.chargesDue = chargesDue;
this.totalDue = this.totalDue.add(principalDue).add(interestDue);
}
public Long getLoanId() {
@@ -64,13 +66,9 @@ public class LoanDueData {
public Long getProductId() {
return this.productId;
}
public String getCurrencyCode() {
return this.currencyCode;
}
public Integer getCurrencyDigits() {
return this.currencyDigits;
public CurrencyData getCurrency() {
return this.currency;
}
public BigDecimal getDisbursementAmount() {
@@ -0,0 +1,28 @@
package org.mifosplatform.portfolio.collectionsheet.handler;
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.portfolio.collectionsheet.service.CollectionSheetWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UpdateCollectionSheetCommandHandler implements NewCommandSourceHandler {
private final CollectionSheetWritePlatformService collectionSheetWritePlatformService;
@Autowired
public UpdateCollectionSheetCommandHandler(final CollectionSheetWritePlatformService collectionSheetWritePlatformService) {
this.collectionSheetWritePlatformService = collectionSheetWritePlatformService;
}
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.collectionSheetWritePlatformService.updateCollectionSheet(command);
}
}
@@ -0,0 +1,118 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.collectionsheet.serialization;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
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.collectionsheet.command.CollectionSheetBulkDisbursalCommand;
import org.mifosplatform.portfolio.collectionsheet.command.SingleDisbursalCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
/**
* Implementation of {@link FromApiJsonDeserializer} for
* {@link CollectionSheetBulkDisbursalCommand}'s.
*/
@Component
public final class CollectionSheetBulkDisbursalCommandFromApiJsonDeserializer extends AbstractFromApiJsonDeserializer<CollectionSheetBulkDisbursalCommand> {
private final FromJsonHelper fromApiJsonHelper;
@Autowired
public CollectionSheetBulkDisbursalCommandFromApiJsonDeserializer(FromJsonHelper fromApiJsonHelper) {
this.fromApiJsonHelper = fromApiJsonHelper;
}
@Override
public CollectionSheetBulkDisbursalCommand commandFromApiJson(final String json) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
//TODO: AA need to refactor transactionDate and actualDisbursementDate
//actualDisbursementDate used in Loan.disburse()
final Set<String> supportedParameters = new HashSet<String>(Arrays.asList("transactionDate", "actualDisbursementDate", "bulkRepaymentTransactions", "bulkDisbursementTransactions", "note",
"locale", "dateFormat"));
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters);
final JsonElement element = this.fromApiJsonHelper.parse(json);
final LocalDate transactionDate = fromApiJsonHelper.extractLocalDateNamed("transactionDate", element);
final String note = fromApiJsonHelper.extractStringNamed("note", element);
final JsonObject topLevelJsonElement = element.getAsJsonObject();
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
SingleDisbursalCommand[] loanDisbursementTransactions = null;
if (element.isJsonObject()) {
if (topLevelJsonElement.has("bulkDisbursementTransactions")
&& topLevelJsonElement.get("bulkDisbursementTransactions").isJsonArray()) {
final JsonArray array = topLevelJsonElement.get("bulkDisbursementTransactions").getAsJsonArray();
loanDisbursementTransactions = new SingleDisbursalCommand[array.size()];
for (int i = 0; i < array.size(); i++) {
final JsonObject loanTransactionElement = array.get(i).getAsJsonObject();
final Long loanId = this.fromApiJsonHelper.extractLongNamed("loanId", loanTransactionElement);
final BigDecimal disbursementAmount = this.fromApiJsonHelper.extractBigDecimalNamed("transactionAmount", loanTransactionElement, locale);
loanDisbursementTransactions[i] = new SingleDisbursalCommand(loanId, disbursementAmount, transactionDate);
}
}
}
return new CollectionSheetBulkDisbursalCommand(note, transactionDate, loanDisbursementTransactions);
}
public void validateBulkDisbursalTransaction(final CollectionSheetBulkDisbursalCommand bulkDisburseCommand) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.bulk.disbursement.transaction");
baseDataValidator.reset().parameter("transactionDate").value(bulkDisburseCommand.getTransactionDate()).notNull();
if (StringUtils.isNotBlank(bulkDisburseCommand.getNote())) {
baseDataValidator.reset().parameter("note").value(bulkDisburseCommand.getNote()).notExceedingLengthOf(1000);
}
final SingleDisbursalCommand[] loanDisbursements = bulkDisburseCommand.getDisburseTransactions();
if (loanDisbursements != null) {
for (int i = 0; i < loanDisbursements.length; i++) {
SingleDisbursalCommand singleLoanDisburseCommand = loanDisbursements[i];
baseDataValidator.reset().parameter("bulktransaction" + "[" + i + "].loan.id").value(singleLoanDisburseCommand.getLoanId())
.notNull().integerGreaterThanZero();
baseDataValidator.reset().parameter("bulktransaction" + "[" + i + "].disbursement.amount")
.value(singleLoanDisburseCommand.getTransactionAmount()).notNull().zeroOrPositiveAmount();
}
}
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
}
}
@@ -0,0 +1,116 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.collectionsheet.serialization;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
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.collectionsheet.command.CollectionSheetBulkRepaymentCommand;
import org.mifosplatform.portfolio.collectionsheet.command.SingleRepaymentCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
/**
* Implementation of {@link FromApiJsonDeserializer} for
* {@link CollectionSheetBulkRepaymentCommand}'s.
*/
@Component
public final class CollectionSheetBulkRepaymentCommandFromApiJsonDeserializer extends AbstractFromApiJsonDeserializer<CollectionSheetBulkRepaymentCommand> {
private final FromJsonHelper fromApiJsonHelper;
@Autowired
public CollectionSheetBulkRepaymentCommandFromApiJsonDeserializer(FromJsonHelper fromApiJsonHelper) {
this.fromApiJsonHelper = fromApiJsonHelper;
}
@Override
public CollectionSheetBulkRepaymentCommand commandFromApiJson(final String json) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
final Set<String> supportedParameters = new HashSet<String>(Arrays.asList("transactionDate", "actualDisbursementDate", "bulkRepaymentTransactions", "bulkDisbursementTransactions", "note",
"locale", "dateFormat"));
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters);
final JsonElement element = this.fromApiJsonHelper.parse(json);
final LocalDate transactionDate = fromApiJsonHelper.extractLocalDateNamed("transactionDate", element);
final String note = fromApiJsonHelper.extractStringNamed("note", element);
final JsonObject topLevelJsonElement = element.getAsJsonObject();
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
SingleRepaymentCommand[] loanRepaymentTransactions = null;
if (element.isJsonObject()) {
if (topLevelJsonElement.has("bulkRepaymentTransactions")
&& topLevelJsonElement.get("bulkRepaymentTransactions").isJsonArray()) {
final JsonArray array = topLevelJsonElement.get("bulkRepaymentTransactions").getAsJsonArray();
loanRepaymentTransactions = new SingleRepaymentCommand[array.size()];
for (int i = 0; i < array.size(); i++) {
final JsonObject loanTransactionElement = array.get(i).getAsJsonObject();
final Long loanId = this.fromApiJsonHelper.extractLongNamed("loanId", loanTransactionElement);
final BigDecimal transactionAmount = this.fromApiJsonHelper.extractBigDecimalNamed("transactionAmount", loanTransactionElement, locale);
loanRepaymentTransactions[i] = new SingleRepaymentCommand(loanId, transactionAmount, transactionDate);
}
}
}
return new CollectionSheetBulkRepaymentCommand(note, transactionDate, loanRepaymentTransactions);
}
public void validateBulkRepaymentTransaction(final CollectionSheetBulkRepaymentCommand bulkRepaymentCommand) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.bulk.repayment.transaction");
baseDataValidator.reset().parameter("transactionDate").value(bulkRepaymentCommand.getTransactionDate()).notNull();
if (StringUtils.isNotBlank(bulkRepaymentCommand.getNote())) {
baseDataValidator.reset().parameter("note").value(bulkRepaymentCommand.getNote()).notExceedingLengthOf(1000);
}
final SingleRepaymentCommand[] loanRepayments = bulkRepaymentCommand.getLoanTransactions();
if (loanRepayments != null) {
for (int i = 0; i < loanRepayments.length; i++) {
SingleRepaymentCommand singleLoanRepaymentCommand = loanRepayments[i];
baseDataValidator.reset().parameter("bulktransaction" + "[" + i + "].loan.id")
.value(singleLoanRepaymentCommand.getLoanId()).notNull().integerGreaterThanZero();
baseDataValidator.reset().parameter("bulktransaction" + "[" + i + "].transaction.amount")
.value(singleLoanRepaymentCommand.getTransactionAmount()).notNull().zeroOrPositiveAmount();
}
}
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
}
}
@@ -5,12 +5,15 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
import org.mifosplatform.infrastructure.core.service.TenantAwareRoutingDataSource;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.portfolio.collectionsheet.data.ClientLoansData;
import org.mifosplatform.portfolio.collectionsheet.data.JLGClientsData;
import org.mifosplatform.portfolio.collectionsheet.data.JLGCollectionSheetData;
@@ -19,6 +22,7 @@ import org.mifosplatform.portfolio.collectionsheet.data.LoanDueData;
import org.mifosplatform.portfolio.group.data.GroupData;
import org.mifosplatform.portfolio.group.exception.GroupNotFoundException;
import org.mifosplatform.portfolio.group.service.GroupReadPlatformServiceImpl;
import org.mifosplatform.portfolio.loanproduct.data.LoanProductData;
import org.mifosplatform.useradministration.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.RowMapper;
@@ -88,11 +92,12 @@ public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetRe
JLGCollectionSheetData jlgCollectionSheetData = null;
JLGCollectionSheetFlatData prevCollectioSheetFlatData = null;
JLGCollectionSheetFlatData corrCollectioSheetFlatData = null;
Set<LoanProductData> loanProducts = new HashSet<LoanProductData>();
if (jlgCollectionSheetFlatData != null) {
for (final JLGCollectionSheetFlatData collectionSheetFlatData : jlgCollectionSheetFlatData) {
loanProducts.add(LoanProductData.lookupWithCurrency(collectionSheetFlatData.getProductId(), collectionSheetFlatData.getProductShortName(), collectionSheetFlatData.getCurrency()));
corrCollectioSheetFlatData = collectionSheetFlatData;
if (firstTime || collectionSheetFlatData.getGroupId().equals(prevGroupId)) {
@@ -149,7 +154,7 @@ public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetRe
jlgClientsDatas.add(jlgClientsData);
jlgCollectionSheetData = new JLGCollectionSheetData(dueDate, jlgClientsDatas);
jlgCollectionSheetData = new JLGCollectionSheetData(dueDate, loanProducts, jlgClientsDatas);
}
return jlgCollectionSheetData;
@@ -184,12 +189,11 @@ public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetRe
+ "ln.loan_status_id As accountStatusId, "
+ "pl.name As productShortName, "
+ "ln.product_id As productId, "
+ "ln.currency_code As currencyCode, "
+ "ln.currency_digits As currencyDigits, "
+ "ln.currency_code as currencyCode, ln.currency_digits as currencyDigits, rc.`name` as currencyName, rc.display_symbol as currencyDisplaySymbol, rc.internationalized_name_code as currencyNameCode, "
+ "if(ln.loan_status_id = 200 , ln.principal_amount , null) As disbursementAmount, "
+ "sum(ls.principal_amount - ls.principal_completed_derived) As principalDue, "
+ "sum(ifnull(ls.principal_amount, 0.0) - ifnull(ls.principal_completed_derived, 0.0)) As principalDue, "
+ "ln.principal_repaid_derived As principalPaid, "
+ "sum(ls.interest_amount - ls.interest_completed_derived) As interestDue, "
+ "sum(ifnull(ls.interest_amount, 0.0) - ifnull(ls.interest_completed_derived, 0.0)) As interestDue, "
+ "ln.interest_repaid_derived As interestPaid, "
+ "sum(lc.amount_outstanding_derived) as chargesDue "
+ "FROM m_group gp "
@@ -200,6 +204,7 @@ public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetRe
+ "JOIN m_client cl ON cl.id = gc.client_id "
+ "LEFT JOIN m_loan ln ON cl.id = ln.client_id AND ln.group_id is not null AND ( ln.loan_status_id = 300 OR ( ln.loan_status_id =200 AND ln.expected_disbursedon_date <= :dueDate )) "
+ "LEFT JOIN m_product_loan pl ON pl.id = ln.product_id "
+ "JOIN m_currency rc on rc.`code` = ln.currency_code "
+ "LEFT JOIN m_loan_repayment_schedule ls ON ls.loan_id = ln.id AND ls.completed_derived = 0 AND ls.duedate <= :dueDate "
+ "LEFT JOIN m_loan_charge lc ON lc.loan_id = ln.id AND lc.is_paid_derived = 0 AND ( lc.due_for_collection_as_of_date <= :dueDate OR lc.charge_time_enum = 1) "
+ "WHERE gp.hierarchy like :hierarchy " + "GROUP BY gp.id ,cl.id , ln.id " + "ORDER BY gp.id , cl.id , ln.id ";
@@ -221,8 +226,15 @@ public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetRe
final Integer accountStatusId = JdbcSupport.getInteger(rs, "accountStatusId");
final String productShortName = rs.getString("productShortName");
final Long productId = JdbcSupport.getLong(rs, "productId");
final String currencyCode = rs.getString("currencyCode");
final String currencyName = rs.getString("currencyName");
final String currencyNameCode = rs.getString("currencyNameCode");
final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
final CurrencyData currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, currencyDisplaySymbol,
currencyNameCode);
final BigDecimal disbursementAmount = rs.getBigDecimal("disbursementAmount");
final BigDecimal principalDue = rs.getBigDecimal("principalDue");
final BigDecimal principalPaid = rs.getBigDecimal("principalPaid");
@@ -231,7 +243,7 @@ public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetRe
final BigDecimal chargesDue = rs.getBigDecimal("chargesDue");
return new JLGCollectionSheetFlatData(groupName, groupId, staffId, staffName, levelId, levelName, clientName, clientId, loanId,
accountId, accountStatusId, productShortName, productId, currencyCode, currencyDigits, disbursementAmount,
accountId, accountStatusId, productShortName, productId, currencyData, disbursementAmount,
principalDue, principalPaid, interestDue, interestPaid, chargesDue);
}
@@ -0,0 +1,9 @@
package org.mifosplatform.portfolio.collectionsheet.service;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
public interface CollectionSheetWritePlatformService {
CommandProcessingResult updateCollectionSheet(JsonCommand command);
}
@@ -0,0 +1,79 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.collectionsheet.service;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkDisbursalCommand;
import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkRepaymentCommand;
import org.mifosplatform.portfolio.collectionsheet.serialization.CollectionSheetBulkDisbursalCommandFromApiJsonDeserializer;
import org.mifosplatform.portfolio.collectionsheet.serialization.CollectionSheetBulkRepaymentCommandFromApiJsonDeserializer;
import org.mifosplatform.portfolio.loanaccount.service.LoanWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class CollectionSheetWritePlatformServiceJpaRepositoryImpl implements CollectionSheetWritePlatformService {
private final LoanWritePlatformService loanWritePlatformService;
private final CollectionSheetBulkRepaymentCommandFromApiJsonDeserializer bulkRepaymentCommandFromApiJsonDeserializer;
private final CollectionSheetBulkDisbursalCommandFromApiJsonDeserializer bulkDisbursalCommandFromApiJsonDeserializer;
@Autowired
public CollectionSheetWritePlatformServiceJpaRepositoryImpl(LoanWritePlatformService loanWritePlatformService,
CollectionSheetBulkRepaymentCommandFromApiJsonDeserializer bulkRepaymentCommandFromApiJsonDeserializer,
CollectionSheetBulkDisbursalCommandFromApiJsonDeserializer bulkDisbursalCommandFromApiJsonDeserializer) {
this.loanWritePlatformService = loanWritePlatformService;
this.bulkRepaymentCommandFromApiJsonDeserializer = bulkRepaymentCommandFromApiJsonDeserializer;
this.bulkDisbursalCommandFromApiJsonDeserializer = bulkDisbursalCommandFromApiJsonDeserializer;
}
@Override
public CommandProcessingResult updateCollectionSheet(JsonCommand command) {
Map<String, Object> changes = new HashMap<String, Object>();
changes.put("locale", command.locale());
changes.put("dateFormat", command.dateFormat());
final String noteText = command.stringValueOfParameterNamed("note");
if (StringUtils.isNotBlank(noteText)) {
changes.put("note", noteText);
}
changes.putAll(updateBulkReapayments(command));
changes.putAll(updateBulkDisbursals(command));
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(command.entityId()) //
.withGroupId(command.entityId()) //
.with(changes)
.with(changes).build();
}
private Map<String, Object> updateBulkReapayments(JsonCommand command) {
Map<String, Object> changes = new HashMap<String, Object>();
final CollectionSheetBulkRepaymentCommand bulkRepaymentCommand = this.bulkRepaymentCommandFromApiJsonDeserializer.commandFromApiJson(command.json());
this.bulkRepaymentCommandFromApiJsonDeserializer.validateBulkRepaymentTransaction(bulkRepaymentCommand);
changes.putAll(this.loanWritePlatformService.makeLoanBulkRepayment(bulkRepaymentCommand));
return changes;
}
private Map<String, Object> updateBulkDisbursals(JsonCommand command) {
Map<String, Object> changes = new HashMap<String, Object>();
final CollectionSheetBulkDisbursalCommand bulkDisbursalCommand = this.bulkDisbursalCommandFromApiJsonDeserializer.commandFromApiJson(command.json());
this.bulkDisbursalCommandFromApiJsonDeserializer.validateBulkDisbursalTransaction(bulkDisbursalCommand);
changes.putAll(this.loanWritePlatformService.bulkLoanDisbursal(command, bulkDisbursalCommand));
return changes;
}
}
@@ -5,16 +5,24 @@
*/
package org.mifosplatform.portfolio.loanaccount.service;
import java.util.Map;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkDisbursalCommand;
import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkRepaymentCommand;
public interface LoanWritePlatformService {
CommandProcessingResult disburseLoan(Long loanId, JsonCommand command);
Map<String, Object> bulkLoanDisbursal(JsonCommand command, CollectionSheetBulkDisbursalCommand bulkDisbursalCommand);
CommandProcessingResult undoLoanDisbursal(Long loanId, JsonCommand command);
CommandProcessingResult makeLoanRepayment(Long loanId, JsonCommand command);
Map<String, Object> makeLoanBulkRepayment(CollectionSheetBulkRepaymentCommand bulkRepaymentCommand);
CommandProcessingResult adjustLoanTransaction(Long loanId, Long transactionId, JsonCommand command);
@@ -32,6 +32,10 @@ import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeUpdatedExc
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeWaivedException;
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeWaivedException.LOAN_CHARGE_CANNOT_BE_WAIVED_REASON;
import org.mifosplatform.portfolio.charge.exception.LoanChargeNotFoundException;
import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkDisbursalCommand;
import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkRepaymentCommand;
import org.mifosplatform.portfolio.collectionsheet.command.SingleDisbursalCommand;
import org.mifosplatform.portfolio.collectionsheet.command.SingleRepaymentCommand;
import org.mifosplatform.portfolio.loanaccount.command.LoanUpdateCommand;
import org.mifosplatform.portfolio.loanaccount.domain.ChangedTransactionDetail;
import org.mifosplatform.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine;
@@ -143,6 +147,44 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
.with(changes) //
.build();
}
@Transactional
@Override
public Map<String, Object> bulkLoanDisbursal(JsonCommand command, CollectionSheetBulkDisbursalCommand bulkDisbursalCommand) {
final AppUser currentUser = context.authenticatedUser();
SingleDisbursalCommand[] disbursalCommand = bulkDisbursalCommand.getDisburseTransactions();
Map<String, Object> changes = new LinkedHashMap<String, Object>();
if(disbursalCommand == null){
return changes;
}
for (int i = 0; i < disbursalCommand.length; i++) {
SingleDisbursalCommand singleLoanDisbursalCommand = disbursalCommand[i];
final Loan loan = retrieveLoanBy(singleLoanDisbursalCommand.getLoanId());
final ApplicationCurrency currency = this.applicationCurrencyRepository.findOneByCode(loan.getCurrencyCode());
final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
changes.putAll(loan.disburse(currentUser, command, currency, existingTransactionIds,
existingReversedTransactionIds));
if (!changes.isEmpty()) {
this.loanRepository.save(loan);
final String noteText = command.stringValueOfParameterNamed("note");
if (StringUtils.isNotBlank(noteText)) {
Note note = Note.loanNote(loan, noteText);
this.noteRepository.save(note);
}
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
}
}
return changes;
}
@Transactional
@Override
@@ -204,6 +246,22 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
changes.put("locale", command.locale());
changes.put("dateFormat", command.dateFormat());
final String noteText = command.stringValueOfParameterNamed("note");
if (StringUtils.isNotBlank(noteText)) {
changes.put("note", noteText);
}
CommandProcessingResultBuilder commandProcessingResultBuilder = saveLoanRepayment(loanId, transactionAmount, transactionDate, noteText);
return commandProcessingResultBuilder
.withCommandId(command.commandId()) //
.withLoanId(loanId) //
.with(changes) //
.build();
}
private CommandProcessingResultBuilder saveLoanRepayment(final Long loanId, final BigDecimal transactionAmount,
final LocalDate transactionDate, final String noteText) {
final Loan loan = retrieveLoanBy(loanId);
final List<Long> existingTransactionIds = new ArrayList<Long>();
@@ -230,9 +288,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
this.loanRepository.save(loan);
final String noteText = command.stringValueOfParameterNamed("note");
if (StringUtils.isNotBlank(noteText)) {
changes.put("note", noteText);
Note note = Note.loanTransactionNote(loan, newRepaymentTransaction, noteText);
this.noteRepository.save(note);
}
@@ -240,16 +297,29 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(newRepaymentTransaction.getId()) //
.withOfficeId(loan.getOfficeId()) //
.withClientId(loan.getClientId()) //
.withGroupId(loan.getGroupId()) //
.withLoanId(loanId) //
.with(changes) //
.build();
.withEntityId(newRepaymentTransaction.getId()) //
.withOfficeId(loan.getOfficeId()) //
.withClientId(loan.getClientId()) //
.withGroupId(loan.getGroupId()); //
}
@Transactional
@Override
public Map<String, Object> makeLoanBulkRepayment(final CollectionSheetBulkRepaymentCommand bulkRepaymentCommand) {
context.authenticatedUser();
final SingleRepaymentCommand[] repaymentCommand = bulkRepaymentCommand.getLoanTransactions();
final Map<String, Object> changes = new LinkedHashMap<String, Object>();
if(repaymentCommand == null) return changes;
for (SingleRepaymentCommand singleLoanRepaymentCommand : repaymentCommand) {
saveLoanRepayment(singleLoanRepaymentCommand.getLoanId(), singleLoanRepaymentCommand.getTransactionAmount(), bulkRepaymentCommand.getTransactionDate(), bulkRepaymentCommand.getNote());
changes.put("bulkTransations", singleLoanRepaymentCommand);
}
return changes;
}
@Transactional
@Override
public CommandProcessingResult adjustLoanTransaction(final Long loanId, final Long transactionId, final JsonCommand command) {
@@ -102,6 +102,32 @@ public class LoanProductData {
interestType, interestCalculationPeriodType, fundId, fundName, transactionProcessingStrategyId,
transactionProcessingStrategyName, charges, accountingType);
}
public static LoanProductData lookupWithCurrency(final Long id, final String name, final CurrencyData currency) {
final String description = null;
final BigDecimal principal = null;
final BigDecimal tolerance = null;
final Integer numberOfRepayments = null;
final Integer repaymentEvery = null;
final BigDecimal interestRatePerPeriod = null;
final BigDecimal annualInterestRate = null;
final EnumOptionData repaymentFrequencyType = null;
final EnumOptionData interestRateFrequencyType = null;
final EnumOptionData amortizationType = null;
final EnumOptionData interestType = null;
final EnumOptionData interestCalculationPeriodType = null;
final Long fundId = null;
final String fundName = null;
final Long transactionProcessingStrategyId = null;
final String transactionProcessingStrategyName = null;
final Collection<ChargeData> charges = null;
final EnumOptionData accountingType = null;
return new LoanProductData(id, name, description, currency, principal, tolerance, numberOfRepayments, repaymentEvery,
interestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType, amortizationType,
interestType, interestCalculationPeriodType, fundId, fundName, transactionProcessingStrategyId,
transactionProcessingStrategyName, charges, accountingType);
}
public static LoanProductData sensibleDefaultsForNewLoanProductCreation() {
final Long id = null;
@@ -378,4 +404,14 @@ public class LoanProductData {
public Collection<ChargeData> getChargeOptions() {
return chargeOptions;
}
@Override
public boolean equals(Object obj) {
LoanProductData loanProductData = (LoanProductData) obj;
return loanProductData.id.equals(this.id);
}
@Override
public int hashCode() {
return this.id.hashCode();
}
}