From f7c0a833cbfdc01bbaaa6b07e0f0603e4514de81 Mon Sep 17 00:00:00 2001 From: Vishwas Babu A J Date: Fri, 21 Aug 2015 04:39:49 +0530 Subject: [PATCH] MIFOSX-583 wip --- .../service/CommandWrapperBuilder.java | 9 ++ .../client/api/ClientApiConstants.java | 20 ++- .../client/api/ClientChargesApiResource.java | 48 +++++- .../api/ClientTransactionsApiResource.java | 33 +++++ .../client/data/ClientChargeData.java | 35 ++++- .../portfolio/client/domain/ClientCharge.java | 61 +++++--- .../client/domain/ClientChargePaidBy.java | 16 ++ .../client/domain/ClientEnumerations.java | 1 - .../client/domain/ClientTransaction.java | 46 +++++- .../ClientTransactionRepositoryWrapper.java | 47 ++++++ ...entTransactionCannotBeUndoneException.java | 19 +++ .../ClientTransactionNotFoundException.java | 18 +++ .../DeleteClientChargeCommandHandler.java | 2 +- .../UndoClientTransactionCommandHandler.java | 34 +++++ .../ClientChargeReadPlatformService.java | 5 +- .../ClientChargeReadPlatformServiceImpl.java | 39 +++-- .../ClientChargeWritePlatformService.java | 2 +- ...WritePlatformServiceJpaRepositoryImpl.java | 138 +++++++++++++----- .../ClientTransactionReadPlatformService.java | 5 +- ...entTransactionReadPlatformServiceImpl.java | 32 +++- ...ClientTransactionWritePlatformService.java | 16 ++ ...WritePlatformServiceJpaRepositoryImpl.java | 76 ++++++++++ .../V267__client_transaction_permissions.sql | 3 + 23 files changed, 599 insertions(+), 106 deletions(-) create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientTransactionRepositoryWrapper.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/exception/ClientTransactionCannotBeUndoneException.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/exception/ClientTransactionNotFoundException.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/handler/UndoClientTransactionCommandHandler.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientTransactionWritePlatformService.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientTransactionWritePlatformServiceJpaRepositoryImpl.java create mode 100644 mifosng-provider/src/main/resources/sql/migrations/core_db/V267__client_transaction_permissions.sql diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java index a4f62e5e5..0b0e7d50c 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java @@ -2448,4 +2448,13 @@ public class CommandWrapperBuilder { return this; } + public CommandWrapperBuilder undoClientTransaction(final Long clientId, final Long transactionId) { + this.actionName = ClientApiConstants.CLIENT_TRANSACTION_ACTION_UNDO; + this.entityName = ClientApiConstants.CLIENT_RESOURCE_NAME; + this.entityId = transactionId; + this.clientId = clientId; + this.href = "/clients/" + clientId + "/transactions/" + transactionId + "?command=undo"; + return this; + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientApiConstants.java index 6ff9fb5cc..6541a2fae 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientApiConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientApiConstants.java @@ -5,16 +5,11 @@ */ package org.mifosplatform.portfolio.client.api; -import java.math.BigDecimal; import java.util.Arrays; import java.util.HashSet; import java.util.Set; -import org.joda.time.LocalDate; -import org.mifosplatform.infrastructure.core.data.EnumOptionData; -import org.mifosplatform.organisation.monetary.data.CurrencyData; import org.mifosplatform.portfolio.client.data.ClientData; -import org.mifosplatform.portfolio.paymentdetail.data.PaymentDetailData; public class ClientApiConstants { @@ -28,10 +23,22 @@ public class ClientApiConstants { public static final String CLIENT_CHARGE_ACTION_PAY = "PAY"; public static final String CLIENT_CHARGE_ACTION_INACTIVATE = "INACTIVATE"; + // Client charge associations and query parameters + public static final String CLIENT_CHARGE_QUERY_PARAM_STATUS = "chargeStatus"; + public static final String CLIENT_CHARGE_QUERY_PARAM_STATUS_VALUE_ALL = "all"; + public static final String CLIENT_CHARGE_QUERY_PARAM_STATUS_VALUE_ACTIVE = "active"; + public static final String CLIENT_CHARGE_QUERY_PARAM_STATUS_VALUE_INACTIVE = "inactive"; + public static final String CLIENT_CHARGE_ASSOCIATIONS_TRANSACTIONS = "transactions"; + + // Client transaction action names + public static final String CLIENT_TRANSACTION_ACTION_READ = "READTRANSACTION"; + public static final String CLIENT_TRANSACTION_ACTION_UNDO = "UNDOTRANSACTION"; + // Commands public static final String CLIENT_CHARGE_COMMAND_WAIVE_CHARGE = "waive"; public static final String CLIENT_CHARGE_COMMAND_PAY_CHARGE = "paycharge"; public static final String CLIENT_CHARGE_COMMAND_INACTIVATE_CHARGE = "inactivate"; + public static final String CLIENT_TRANSACTION_COMMAND_UNDO = "undo"; public static final String CLIENT_CLOSURE_REASON = "ClientClosureReason"; public static final String CLIENT_ACTION_REASON = "ClientActionReason"; @@ -119,6 +126,7 @@ public class ClientApiConstants { public static final String amountParamName = "amount"; public static final String amountPaidParamName = "amountPaid"; public static final String chargeOptionsParamName = "chargeOptions"; + public static final String transactionsParamName = "transactions"; // client transactions response parameters public static final String transactionAmountParamName = "transactionAmount"; @@ -176,7 +184,7 @@ public class ClientApiConstants { public static final Set CLIENT_CHARGES_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(chargeIdParamName, clientIdParamName, chargeNameParamName, penaltyParamName, chargeTimeTypeParamName, dueAsOfDateParamName, chargeCalculationTypeParamName, currencyParamName, amountWaivedParamName, amountWrittenOffParamName, amountOutstandingParamName, - amountOrPercentageParamName, amountParamName, amountPaidParamName, chargeOptionsParamName)); + amountOrPercentageParamName, amountParamName, amountPaidParamName, chargeOptionsParamName, transactionsParamName)); public static final Set CLIENT_CHARGES_ADD_REQUEST_DATA_PARAMETERS = new HashSet<>( Arrays.asList(chargeIdParamName, amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName)); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientChargesApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientChargesApiResource.java index 9070326c5..c30e362fc 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientChargesApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientChargesApiResource.java @@ -5,7 +5,9 @@ */ package org.mifosplatform.portfolio.client.api; +import java.util.Arrays; import java.util.Collection; +import java.util.Set; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; @@ -24,6 +26,7 @@ import org.apache.commons.lang.StringUtils; import org.mifosplatform.commands.domain.CommandWrapper; import org.mifosplatform.commands.service.CommandWrapperBuilder; import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService; +import org.mifosplatform.infrastructure.core.api.ApiParameterHelper; import org.mifosplatform.infrastructure.core.api.ApiRequestParameterHelper; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.exception.UnrecognizedQueryParamException; @@ -33,9 +36,12 @@ 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.client.data.ClientChargeData; +import org.mifosplatform.portfolio.client.data.ClientTransactionData; import org.mifosplatform.portfolio.client.service.ClientChargeReadPlatformService; +import org.mifosplatform.portfolio.client.service.ClientTransactionReadPlatformService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; @Path("/clients/{clientId}/charges") @Component @@ -44,6 +50,7 @@ public class ClientChargesApiResource { private final PlatformSecurityContext context; private final ChargeReadPlatformService chargeReadPlatformService; private final ClientChargeReadPlatformService clientChargeReadPlatformService; + private final ClientTransactionReadPlatformService clientTransactionReadPlatformService; private final DefaultToApiJsonSerializer toApiJsonSerializer; private final ApiRequestParameterHelper apiRequestParameterHelper; private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; @@ -53,28 +60,36 @@ public class ClientChargesApiResource { final ClientChargeReadPlatformService clientChargeReadPlatformService, final DefaultToApiJsonSerializer toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper, - final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) { + final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService, + final ClientTransactionReadPlatformService clientTransactionReadPlatformService) { this.context = context; this.chargeReadPlatformService = chargeReadPlatformService; this.clientChargeReadPlatformService = clientChargeReadPlatformService; this.toApiJsonSerializer = toApiJsonSerializer; this.apiRequestParameterHelper = apiRequestParameterHelper; this.commandsSourceWritePlatformService = commandsSourceWritePlatformService; + this.clientTransactionReadPlatformService = clientTransactionReadPlatformService; } @GET @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) public String retrieveAllClientCharges(@PathParam("clientId") final Long clientId, - @DefaultValue("all") @QueryParam("chargeStatus") final String chargeStatus, @Context final UriInfo uriInfo) { + @DefaultValue(ClientApiConstants.CLIENT_CHARGE_QUERY_PARAM_STATUS_VALUE_ALL) @QueryParam(ClientApiConstants.CLIENT_CHARGE_QUERY_PARAM_STATUS) final String chargeStatus, + @QueryParam("paid") final Boolean paid, @Context final UriInfo uriInfo) { this.context.authenticatedUser().validateHasReadPermission(ClientApiConstants.CLIENT_CHARGES_RESOURCE_NAME); - if (!(is(chargeStatus, "all") || is(chargeStatus, "active") - || is(chargeStatus, "inactive"))) { throw new UnrecognizedQueryParamException("status", chargeStatus, - new Object[] { "all", "active", "inactive" }); } + if (!(is(chargeStatus, ClientApiConstants.CLIENT_CHARGE_QUERY_PARAM_STATUS_VALUE_ALL) + || is(chargeStatus, ClientApiConstants.CLIENT_CHARGE_QUERY_PARAM_STATUS_VALUE_ACTIVE) + || is(chargeStatus, + ClientApiConstants.CLIENT_CHARGE_QUERY_PARAM_STATUS_VALUE_INACTIVE))) { throw new UnrecognizedQueryParamException( + ClientApiConstants.CLIENT_CHARGE_QUERY_PARAM_STATUS, chargeStatus, + new Object[] { ClientApiConstants.CLIENT_CHARGE_QUERY_PARAM_STATUS_VALUE_ALL, + ClientApiConstants.CLIENT_CHARGE_QUERY_PARAM_STATUS_VALUE_ACTIVE, + ClientApiConstants.CLIENT_CHARGE_QUERY_PARAM_STATUS_VALUE_INACTIVE }); } final Collection clientCharges = this.clientChargeReadPlatformService.retrieveClientCharges(clientId, - chargeStatus); + chargeStatus, paid); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); return this.toApiJsonSerializer.serialize(settings, clientCharges, ClientApiConstants.CLIENT_CHARGES_RESPONSE_DATA_PARAMETERS); @@ -109,7 +124,24 @@ public class ClientChargesApiResource { this.context.authenticatedUser().validateHasReadPermission(ClientApiConstants.CLIENT_CHARGES_RESOURCE_NAME); - final ClientChargeData clientCharge = this.clientChargeReadPlatformService.retrieveClientCharge(clientId, chargeId); + ClientChargeData clientCharge = this.clientChargeReadPlatformService.retrieveClientCharge(clientId, chargeId); + + // extract associations + final Set associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters()); + if (!associationParameters.isEmpty()) { + if (associationParameters.contains("all")) { + associationParameters.addAll(Arrays.asList(ClientApiConstants.CLIENT_CHARGE_ASSOCIATIONS_TRANSACTIONS)); + } + ApiParameterHelper.excludeAssociationsForResponseIfProvided(uriInfo.getQueryParameters(), associationParameters); + if (associationParameters.contains(ClientApiConstants.CLIENT_CHARGE_ASSOCIATIONS_TRANSACTIONS)) { + Collection clientTransactionDatas = this.clientTransactionReadPlatformService + .retrieveAllTransactions(clientId, chargeId); + if (CollectionUtils.isEmpty(clientTransactionDatas)) { + clientTransactionDatas = null; + } + clientCharge = ClientChargeData.addAssociations(clientCharge, clientTransactionDatas); + } + } final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); return this.toApiJsonSerializer.serialize(settings, clientCharge, ClientApiConstants.CLIENT_CHARGES_RESPONSE_DATA_PARAMETERS); @@ -132,7 +164,7 @@ public class ClientChargesApiResource { @Path("{chargeId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - public String payOrWaiveSavingsAccountCharge(@PathParam("clientId") final Long clientId, @PathParam("chargeId") final Long chargeId, + public String payOrWaiveClientCharge(@PathParam("clientId") final Long clientId, @PathParam("chargeId") final Long chargeId, @QueryParam("command") final String commandParam, final String apiRequestBodyAsJson) { String json = ""; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientTransactionsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientTransactionsApiResource.java index 1ad271c2d..1382dfe59 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientTransactionsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientTransactionsApiResource.java @@ -9,15 +9,22 @@ import java.util.Collection; 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.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.exception.UnrecognizedQueryParamException; import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; @@ -80,4 +87,30 @@ public class ClientTransactionsApiResource { ClientApiConstants.CLIENT_TRANSACTION_RESPONSE_DATA_PARAMETERS); } + @POST + @Path("{transactionId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String undoClientTransaction(@PathParam("clientId") final Long clientId, @PathParam("transactionId") final Long transactionId, + @QueryParam("command") final String commandParam, final String apiRequestBodyAsJson) { + + String json = ""; + if (is(commandParam, ClientApiConstants.CLIENT_TRANSACTION_COMMAND_UNDO)) { + final CommandWrapper commandRequest = new CommandWrapperBuilder().undoClientTransaction(clientId, transactionId) + .withJson(apiRequestBodyAsJson).build(); + + final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + + json = this.toApiJsonSerializer.serialize(result); + } else { + throw new UnrecognizedQueryParamException("command", commandParam, ClientApiConstants.CLIENT_TRANSACTION_COMMAND_UNDO); + } + + return json; + } + + private boolean is(final String commandParam, final String commandValue) { + return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue); + } + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/data/ClientChargeData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/data/ClientChargeData.java index b52b9ecfe..02a0bcafb 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/data/ClientChargeData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/data/ClientChargeData.java @@ -46,17 +46,31 @@ public class ClientChargeData { private final Boolean isActive; + private final Boolean isPaid; + private final LocalDate inactivationDate; private final Collection chargeOptions; + private final Collection clientTransactionDatas; + public static ClientChargeData instance(Long id, Long clientId, Long chargeId, String name, EnumOptionData chargeTimeType, LocalDate dueDate, EnumOptionData chargeCalculationType, CurrencyData currency, BigDecimal amount, BigDecimal amountPaid, - BigDecimal amountWaived, BigDecimal amountWrittenOff, BigDecimal amountOutstanding, boolean penalty, Boolean isActive, - LocalDate inactivationDate, Collection chargeOptions) { - + BigDecimal amountWaived, BigDecimal amountWrittenOff, BigDecimal amountOutstanding, boolean penalty, Boolean isPaid, + Boolean isActive, LocalDate inactivationDate, Collection chargeOptions) { + Collection clientTransactionDatas = null; return new ClientChargeData(id, clientId, chargeId, name, chargeTimeType, dueDate, chargeCalculationType, currency, amount, - amountPaid, amountWaived, amountWrittenOff, amountOutstanding, penalty, isActive, inactivationDate, chargeOptions); + amountPaid, amountWaived, amountWrittenOff, amountOutstanding, penalty, isPaid, isActive, inactivationDate, chargeOptions, + clientTransactionDatas); + } + + public static ClientChargeData addAssociations(ClientChargeData clientChargeData, + Collection clientTransactionDatas) { + return new ClientChargeData(clientChargeData.id, clientChargeData.clientId, clientChargeData.chargeId, clientChargeData.name, + clientChargeData.chargeTimeType, clientChargeData.dueDate, clientChargeData.chargeCalculationType, + clientChargeData.currency, clientChargeData.amount, clientChargeData.amountPaid, clientChargeData.amountWaived, + clientChargeData.amountWrittenOff, clientChargeData.amountOutstanding, clientChargeData.penalty, clientChargeData.isPaid, + clientChargeData.isActive, clientChargeData.inactivationDate, clientChargeData.chargeOptions, clientTransactionDatas); } public static ClientChargeData template(final Collection chargeOptions) { @@ -74,17 +88,20 @@ public class ClientChargeData { final BigDecimal amountWrittenOff = null; final BigDecimal amountOutstanding = null; final Boolean penalty = false; + final Boolean isPaid = null; final Boolean isActive = null; final LocalDate inactivationDate = null; + final Collection clientTransactionDatas = null; return new ClientChargeData(id, clientId, chargeId, name, chargeTimeType, dueDate, chargeCalculationType, currency, amount, - amountPaid, amountWaived, amountWrittenOff, amountOutstanding, penalty, isActive, inactivationDate, chargeOptions); + amountPaid, amountWaived, amountWrittenOff, amountOutstanding, penalty, isPaid, isActive, inactivationDate, chargeOptions, + clientTransactionDatas); } private ClientChargeData(Long id, Long clientId, Long chargeId, String name, EnumOptionData chargeTimeType, LocalDate dueDate, EnumOptionData chargeCalculationType, CurrencyData currency, BigDecimal amount, BigDecimal amountPaid, BigDecimal amountWaived, - BigDecimal amountWrittenOff, BigDecimal amountOutstanding, boolean penalty, Boolean isActive, LocalDate inactivationDate, - Collection chargeOptions) { + BigDecimal amountWrittenOff, BigDecimal amountOutstanding, boolean penalty, Boolean isPaid, Boolean isActive, + LocalDate inactivationDate, Collection chargeOptions, Collection clientTransactionDatas) { super(); this.id = id; this.clientId = clientId; @@ -100,11 +117,15 @@ public class ClientChargeData { this.amountWrittenOff = amountWrittenOff; this.amountOutstanding = amountOutstanding; this.penalty = penalty; + this.isPaid = isPaid; this.isActive = isActive; this.inactivationDate = inactivationDate; // template related fields this.chargeOptions = chargeOptions; + + /// associations + this.clientTransactionDatas = clientTransactionDatas; } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientCharge.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientCharge.java index 9475b6c59..6c6b53f5b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientCharge.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientCharge.java @@ -83,7 +83,7 @@ public class ClientCharge extends AbstractPersistable { @Transient private OrganisationCurrency currency; - + protected ClientCharge() { // } @@ -117,15 +117,43 @@ public class ClientCharge extends AbstractPersistable { this.status = status; } - public Money pay(final MonetaryCurrency currency, final Money amountPaid) { - Money amountPaidToDate = Money.of(currency, this.amountPaid); - Money amountOutstanding = Money.of(currency, this.amountOutstanding); + public Money pay(final Money amountPaid) { + Money amountPaidToDate = Money.of(this.getCurrency(), this.amountPaid); + Money amountOutstanding = Money.of(this.getCurrency(), this.amountOutstanding); amountPaidToDate = amountPaidToDate.plus(amountPaid); amountOutstanding = amountOutstanding.minus(amountPaid); this.amountPaid = amountPaidToDate.getAmount(); this.amountOutstanding = amountOutstanding.getAmount(); this.paid = determineIfFullyPaid(); - return Money.of(currency, this.amountOutstanding); + return Money.of(this.getCurrency(), this.amountOutstanding); + } + + public void undoPayment(final Money transactionAmount) { + Money amountPaid = getAmountPaid(); + amountPaid = amountPaid.minus(transactionAmount); + this.amountPaid = amountPaid.getAmount(); + this.amountOutstanding = calculateOutstanding(); + this.paid = false; + this.status = true; + } + + public Money waive() { + Money amountWaivedToDate = getAmountWaived(); + Money amountOutstanding = getAmountOutstanding(); + Money totalAmountWaived = amountWaivedToDate.plus(amountOutstanding); + this.amountWaived = totalAmountWaived.getAmount(); + this.amountOutstanding = BigDecimal.ZERO; + this.waived = true; + return totalAmountWaived; + } + + public void undoWaiver(final Money transactionAmount) { + Money amountWaived = getAmountWaived(); + amountWaived = amountWaived.minus(transactionAmount); + this.amountWaived = amountWaived.getAmount(); + this.amountOutstanding = calculateOutstanding(); + this.waived = false; + this.status = true; } private void populateDerivedFields(final BigDecimal amount) { @@ -158,7 +186,6 @@ public class ClientCharge extends AbstractPersistable { } private BigDecimal calculateOutstanding() { - BigDecimal amountPaidLocal = BigDecimal.ZERO; if (this.amountPaid != null) { amountPaidLocal = this.amountPaid; @@ -248,28 +275,28 @@ public class ClientCharge extends AbstractPersistable { } public boolean isPaidOrPartiallyPaid(final MonetaryCurrency currency) { - final Money amountWaivedOrWrittenOff = getAmountWaived(currency).plus(getAmountWrittenOff(currency)); + final Money amountWaivedOrWrittenOff = getAmountWaived().plus(getAmountWrittenOff()); return Money.of(currency, this.amountPaid).plus(amountWaivedOrWrittenOff).isGreaterThanZero(); } - public Money getAmount(final MonetaryCurrency currency) { - return Money.of(currency, this.amount); + public Money getAmount() { + return Money.of(getCurrency(), this.amount); } - public Money getAmountPaid(final MonetaryCurrency currency) { - return Money.of(currency, this.amountPaid); + public Money getAmountPaid() { + return Money.of(getCurrency(), this.amountPaid); } - public Money getAmountWaived(final MonetaryCurrency currency) { - return Money.of(currency, this.amountWaived); + public Money getAmountWaived() { + return Money.of(getCurrency(), this.amountWaived); } - public Money getAmountWrittenOff(final MonetaryCurrency currency) { - return Money.of(currency, this.amountWrittenOff); + public Money getAmountWrittenOff() { + return Money.of(getCurrency(), this.amountWrittenOff); } - public Money getAmountOutstanding(final MonetaryCurrency currency) { - return Money.of(currency, this.amountOutstanding); + public Money getAmountOutstanding() { + return Money.of(getCurrency(), this.amountOutstanding); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientChargePaidBy.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientChargePaidBy.java index 51876d535..31e4bfcba 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientChargePaidBy.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientChargePaidBy.java @@ -45,4 +45,20 @@ public class ClientChargePaidBy extends AbstractPersistable { this.amount = amount; } + public ClientTransaction getClientTransaction() { + return this.clientTransaction; + } + + public void setClientTransaction(ClientTransaction clientTransaction) { + this.clientTransaction = clientTransaction; + } + + public ClientCharge getClientCharge() { + return this.clientCharge; + } + + public void setClientCharge(ClientCharge clientCharge) { + this.clientCharge = clientCharge; + } + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientEnumerations.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientEnumerations.java index 8f256070d..ea7b52448 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientEnumerations.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientEnumerations.java @@ -8,7 +8,6 @@ package org.mifosplatform.portfolio.client.domain; import java.util.ArrayList; import java.util.List; -import org.mifosplatform.accounting.glaccount.domain.GLAccountType; import org.mifosplatform.infrastructure.core.data.EnumOptionData; public class ClientEnumerations { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientTransaction.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientTransaction.java index b3ffe2a57..63e8abd45 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientTransaction.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientTransaction.java @@ -19,14 +19,17 @@ import javax.persistence.OneToMany; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; +import javax.persistence.Transient; import javax.persistence.UniqueConstraint; import org.hibernate.annotations.LazyCollection; import org.hibernate.annotations.LazyCollectionOption; import org.joda.time.LocalDate; import org.mifosplatform.infrastructure.core.service.DateUtils; +import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.organisation.office.domain.Office; +import org.mifosplatform.organisation.office.domain.OrganisationCurrency; import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail; import org.mifosplatform.useradministration.domain.AppUser; import org.springframework.data.jpa.domain.AbstractPersistable; @@ -78,6 +81,11 @@ public class ClientTransaction extends AbstractPersistable { @OneToMany(cascade = CascadeType.ALL, mappedBy = "clientTransaction", orphanRemoval = true) private Set clientChargePaidByCollection = new HashSet<>(); + @Transient + private OrganisationCurrency currency; + + protected ClientTransaction() {} + public static ClientTransaction payCharge(final Client client, final Office office, PaymentDetail paymentDetail, final LocalDate date, final Money amount, final String currencyCode, final AppUser appUser) { final boolean isReversed = false; @@ -91,7 +99,7 @@ public class ClientTransaction extends AbstractPersistable { final boolean isReversed = false; final String externalId = null; final PaymentDetail paymentDetail = null; - return new ClientTransaction(client, office, paymentDetail, ClientTransactionType.PAY_CHARGE.getValue(), date, amount, isReversed, + return new ClientTransaction(client, office, paymentDetail, ClientTransactionType.WAIVE_CHARGE.getValue(), date, amount, isReversed, externalId, DateUtils.getDateOfTenant(), currencyCode, appUser); } @@ -111,8 +119,44 @@ public class ClientTransaction extends AbstractPersistable { this.appUser = appUser; } + public void reverse() { + this.reversed = true; + } + + public boolean isPayChargeTransaction() { + return ClientTransactionType.PAY_CHARGE.getValue().equals(this.typeOf); + } + + public boolean isWaiveChargeTransaction() { + return ClientTransactionType.WAIVE_CHARGE.getValue().equals(this.typeOf); + } + public Set getClientChargePaidByCollection() { return this.clientChargePaidByCollection; } + public Long getClientId() { + return client.getId(); + } + + public Money getAmount() { + return Money.of(getCurrency(), this.amount); + } + + public MonetaryCurrency getCurrency() { + return this.currency.toMonetaryCurrency(); + } + + public void setCurrency(OrganisationCurrency currency) { + this.currency = currency; + } + + public String getCurrencyCode() { + return this.currencyCode; + } + + public boolean isReversed() { + return this.reversed; + } + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientTransactionRepositoryWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientTransactionRepositoryWrapper.java new file mode 100644 index 000000000..94a791629 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientTransactionRepositoryWrapper.java @@ -0,0 +1,47 @@ +/** + * 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.client.domain; + +import org.mifosplatform.organisation.office.domain.OrganisationCurrencyRepositoryWrapper; +import org.mifosplatform.portfolio.client.exception.ClientTransactionNotFoundException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Repository; + +@Repository +public class ClientTransactionRepositoryWrapper { + + private final ClientTransactionRepository repository; + private final OrganisationCurrencyRepositoryWrapper organisationCurrencyRepository; + + @Autowired + public ClientTransactionRepositoryWrapper(final ClientTransactionRepository repository, + final OrganisationCurrencyRepositoryWrapper currencyRepositoryWrapper) { + this.repository = repository; + this.organisationCurrencyRepository = currencyRepositoryWrapper; + } + + public ClientTransaction findOneWithNotFoundDetection(final Long clientId, final Long transactionId) { + final ClientTransaction clientTransaction = this.repository.findOne(transactionId); + if (clientTransaction == null + || clientTransaction.getClientId() != clientId) { throw new ClientTransactionNotFoundException(clientId, transactionId); } + // enrich Client charge with details of Organizational currency + clientTransaction.setCurrency(organisationCurrencyRepository.findOneWithNotFoundDetection(clientTransaction.getCurrencyCode())); + return clientTransaction; + } + + public void save(final ClientTransaction clientTransaction) { + this.repository.save(clientTransaction); + } + + public void saveAndFlush(final ClientTransaction clientTransaction) { + this.repository.saveAndFlush(clientTransaction); + } + + public void delete(final ClientTransaction clientTransaction) { + this.repository.delete(clientTransaction); + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/exception/ClientTransactionCannotBeUndoneException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/exception/ClientTransactionCannotBeUndoneException.java new file mode 100644 index 000000000..9ef98807d --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/exception/ClientTransactionCannotBeUndoneException.java @@ -0,0 +1,19 @@ +/** + * 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.client.exception; + +import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException; + +/** + * A {@link RuntimeException} thrown when attempting to delete clients + */ +public class ClientTransactionCannotBeUndoneException extends AbstractPlatformDomainRuleException { + + public ClientTransactionCannotBeUndoneException(final long clientId, final Long transactionId) { + super("error.msg.clients.transaction.cannot.be.undone", "Client transaction with identifier " + transactionId + + " for client with identifier " + clientId + " has already been reversed", transactionId); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/exception/ClientTransactionNotFoundException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/exception/ClientTransactionNotFoundException.java new file mode 100644 index 000000000..adc98b6e3 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/exception/ClientTransactionNotFoundException.java @@ -0,0 +1,18 @@ +/** + * 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.client.exception; + +import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException; + +public class ClientTransactionNotFoundException extends AbstractPlatformDomainRuleException { + + public ClientTransactionNotFoundException(final Long clientId, final Long transactionId) { + super("error.msg.client.transaction.not.found.exception", + "The Transaction with id `" + transactionId + "` does not exist for a Client with id `" + clientId, transactionId, + clientId); + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/handler/DeleteClientChargeCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/handler/DeleteClientChargeCommandHandler.java index 0d69b5207..2992a850d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/handler/DeleteClientChargeCommandHandler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/handler/DeleteClientChargeCommandHandler.java @@ -29,6 +29,6 @@ public class DeleteClientChargeCommandHandler implements NewCommandSourceHandler @Transactional @Override public CommandProcessingResult processCommand(final JsonCommand command) { - return this.writePlatformService.deleteCharge(command.getClientId(), command.entityId(), command); + return this.writePlatformService.deleteCharge(command.getClientId(), command.entityId()); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/handler/UndoClientTransactionCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/handler/UndoClientTransactionCommandHandler.java new file mode 100644 index 000000000..33ed3ebe0 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/handler/UndoClientTransactionCommandHandler.java @@ -0,0 +1,34 @@ +/** + * 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.client.handler; + +import org.mifosplatform.commands.annotation.CommandType; +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.portfolio.client.api.ClientApiConstants; +import org.mifosplatform.portfolio.client.service.ClientTransactionWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@CommandType(entity = ClientApiConstants.CLIENT_RESOURCE_NAME, action = ClientApiConstants.CLIENT_TRANSACTION_ACTION_UNDO) +public class UndoClientTransactionCommandHandler implements NewCommandSourceHandler { + + private final ClientTransactionWritePlatformService writePlatformService; + + @Autowired + public UndoClientTransactionCommandHandler(final ClientTransactionWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + return this.writePlatformService.undo(command.getClientId(), command.entityId()); + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientChargeReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientChargeReadPlatformService.java index c2073be5a..0a2acb80e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientChargeReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientChargeReadPlatformService.java @@ -7,14 +7,11 @@ package org.mifosplatform.portfolio.client.service; import java.util.Collection; -import org.mifosplatform.portfolio.charge.data.ChargeData; import org.mifosplatform.portfolio.client.data.ClientChargeData; public interface ClientChargeReadPlatformService { - ChargeData retrieveClientChargeTemplate(); - - Collection retrieveClientCharges(Long clientId, String status); + Collection retrieveClientCharges(Long clientId, String status, Boolean isPaid); ClientChargeData retrieveClientCharge(Long clientId, Long clientChargeId); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientChargeReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientChargeReadPlatformServiceImpl.java index 7acb033f0..c97c1dd06 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientChargeReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientChargeReadPlatformServiceImpl.java @@ -17,11 +17,10 @@ import org.mifosplatform.infrastructure.core.service.RoutingDataSource; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.organisation.monetary.data.CurrencyData; import org.mifosplatform.portfolio.charge.data.ChargeData; -import org.mifosplatform.portfolio.charge.service.ChargeDropdownReadPlatformService; import org.mifosplatform.portfolio.charge.service.ChargeEnumerations; +import org.mifosplatform.portfolio.client.api.ClientApiConstants; import org.mifosplatform.portfolio.client.data.ClientChargeData; import org.mifosplatform.portfolio.client.exception.ClientChargeNotFoundException; -import org.mifosplatform.portfolio.common.service.DropdownReadPlatformService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; @@ -33,17 +32,11 @@ public class ClientChargeReadPlatformServiceImpl implements ClientChargeReadPlat private final JdbcTemplate jdbcTemplate; private final PlatformSecurityContext context; - private final ChargeDropdownReadPlatformService chargeDropdownReadPlatformService; - private final DropdownReadPlatformService dropdownReadPlatformService; @Autowired - public ClientChargeReadPlatformServiceImpl(final PlatformSecurityContext context, - final ChargeDropdownReadPlatformService chargeDropdownReadPlatformService, final RoutingDataSource dataSource, - final DropdownReadPlatformService dropdownReadPlatformService) { + public ClientChargeReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource) { this.context = context; - this.chargeDropdownReadPlatformService = chargeDropdownReadPlatformService; this.jdbcTemplate = new JdbcTemplate(dataSource); - this.dropdownReadPlatformService = dropdownReadPlatformService; } public static final class ClientChargeMapper implements RowMapper { @@ -76,13 +69,15 @@ public class ClientChargeReadPlatformServiceImpl implements ClientChargeReadPlat final int chargeCalculation = rs.getInt("chargeCalculation"); final EnumOptionData chargeCalculationType = ChargeEnumerations.chargeCalculationType(chargeCalculation); final boolean penalty = rs.getBoolean("penalty"); + final Boolean isPaid = rs.getBoolean("isPaid"); final Boolean isActive = rs.getBoolean("isActive"); final LocalDate inactivationDate = JdbcSupport.getLocalDate(rs, "inactivationDate"); final Collection chargeOptions = null; return ClientChargeData.instance(id, clientId, chargeId, name, chargeTimeType, dueDate, chargeCalculationType, currency, amount, - amountPaid, amountWaived, amountWrittenOff, amountOutstanding, penalty, isActive, inactivationDate, chargeOptions); + amountPaid, amountWaived, amountWrittenOff, amountOutstanding, penalty, isPaid, isActive, inactivationDate, + chargeOptions); } @@ -91,7 +86,7 @@ public class ClientChargeReadPlatformServiceImpl implements ClientChargeReadPlat + "cc.amount_paid_derived as amountPaid, cc.amount_waived_derived as amountWaived, " + "cc.amount_writtenoff_derived as amountWrittenOff, cc.amount_outstanding_derived as amountOutstanding, " + "cc.charge_time_enum as chargeTime, cc.is_penalty as penalty, cc.charge_due_date as dueAsOfDate, " - + "cc.charge_calculation_enum as chargeCalculation, " + + "cc.charge_calculation_enum as chargeCalculation, cc.is_paid_derived as isPaid, cc.waived as waived, " + "cc.is_active as isActive, cc.inactivated_on_date as inactivationDate, " + "c.currency_code as currencyCode, oc.name as currencyName, " + "oc.decimal_places as currencyDecimalPlaces, oc.currency_multiplesof as inMultiplesOf, oc.display_symbol as currencyDisplaySymbol, " @@ -102,15 +97,25 @@ public class ClientChargeReadPlatformServiceImpl implements ClientChargeReadPlat } @Override - public Collection retrieveClientCharges(Long clientId, String status) { + public Collection retrieveClientCharges(Long clientId, String status, Boolean isPaid) { final ClientChargeMapper rm = new ClientChargeMapper(); final StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("select ").append(rm.schema()).append(" where cc.client_id=? "); - if (status.equalsIgnoreCase("active")) { + + // filter for active charges + if (status.equalsIgnoreCase(ClientApiConstants.CLIENT_CHARGE_QUERY_PARAM_STATUS_VALUE_ACTIVE)) { sqlBuilder.append(" and cc.is_active = 1 "); - } else if (status.equalsIgnoreCase("inactive")) { + } else if (status.equalsIgnoreCase(ClientApiConstants.CLIENT_CHARGE_QUERY_PARAM_STATUS_VALUE_INACTIVE)) { sqlBuilder.append(" and cc.is_active = 0 "); } + + // filter for paid charges + if (isPaid != null && isPaid) { + sqlBuilder.append(" and ( cc.is_paid_derived = 1 or cc.waived = 1) "); + } else if (isPaid != null && !isPaid) { + sqlBuilder.append(" and (cc.is_paid_derived = 0 and cc.waived = 0) "); + } + sqlBuilder.append(" order by cc.charge_time_enum ASC, cc.charge_due_date ASC, cc.is_penalty ASC"); return this.jdbcTemplate.query(sqlBuilder.toString(), rm, new Object[] { clientId }); @@ -131,10 +136,4 @@ public class ClientChargeReadPlatformServiceImpl implements ClientChargeReadPlat } } - @Override - public ChargeData retrieveClientChargeTemplate() { - // TODO Auto-generated method stub - return null; - } - } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientChargeWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientChargeWritePlatformService.java index 7e197f96d..7af79f0cb 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientChargeWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientChargeWritePlatformService.java @@ -18,7 +18,7 @@ public interface ClientChargeWritePlatformService { CommandProcessingResult updateCharge(Long clientId, JsonCommand command); @Transactional - CommandProcessingResult deleteCharge(Long clientId, Long clientChargeId, JsonCommand command); + CommandProcessingResult deleteCharge(Long clientId, Long clientChargeId); @Transactional CommandProcessingResult waiveCharge(Long clientId, Long clientChargeId); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientChargeWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientChargeWritePlatformServiceJpaRepositoryImpl.java index de9535bf4..44f9eb85e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientChargeWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientChargeWritePlatformServiceJpaRepositoryImpl.java @@ -120,11 +120,13 @@ public class ClientChargeWritePlatformServiceJpaRepositoryImpl implements Client final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); final LocalDate transactionDate = command.localDateValueOfParameterNamed(ClientApiConstants.transactionDateParamName); final BigDecimal amountPaid = command.bigDecimalValueOfParameterNamed(ClientApiConstants.amountParamName); + final Money chargePaid = Money.of(clientCharge.getCurrency(), amountPaid); - final Money chargePaid = validatePaymentDateAndAmount(client, clientCharge, fmt, transactionDate, amountPaid); + // Validate business rules for payment + validatePaymentTransaction(client, clientCharge, fmt, transactionDate, amountPaid); // pay the charge - clientCharge.pay(clientCharge.getCurrency(), chargePaid); + clientCharge.pay(chargePaid); // create Payment Transaction final Map changes = new LinkedHashMap<>(); @@ -146,6 +148,52 @@ public class ClientChargeWritePlatformServiceJpaRepositoryImpl implements Client } + @Override + public CommandProcessingResult waiveCharge(Long clientId, Long clientChargeId) { + final Client client = this.clientRepository.getActiveClient(clientId); + final ClientCharge clientCharge = this.clientChargeRepository.findOneWithNotFoundDetection(clientChargeId); + final LocalDate transactionDate = DateUtils.getLocalDateOfTenant(); + + // Validate business rules for payment + validateWaiverTransaction(client, clientCharge); + + // waive the charge + Money waivedAmount = clientCharge.waive(); + + // create Waiver Transaction + ClientTransaction clientTransaction = ClientTransaction.waiver(client, client.getOffice(), transactionDate, waivedAmount, + clientCharge.getCurrency().getCode(), getAppUserIfPresent()); + this.clientTransactionRepository.save(clientTransaction); + + // update charge paid by associations + final ClientChargePaidBy chargePaidBy = ClientChargePaidBy.instance(clientTransaction, clientCharge, waivedAmount.getAmount()); + clientTransaction.getClientChargePaidByCollection().add(chargePaidBy); + + return new CommandProcessingResultBuilder() // + .withEntityId(clientCharge.getId()) // + .withOfficeId(clientCharge.getClient().getOffice().getId()) // + .withClientId(clientCharge.getClient().getId()) // + .build(); + } + + @Override + public CommandProcessingResult deleteCharge(Long clientId, Long clientChargeId) { + final Client client = this.clientRepository.getActiveClient(clientId); + final ClientCharge clientCharge = this.clientChargeRepository.findOneWithNotFoundDetection(clientChargeId); + + // Validate business rules for charge deletion + validateChargeDeletion(client, clientCharge); + + // delete the charge + clientChargeRepository.delete(clientCharge); + + return new CommandProcessingResultBuilder() // + .withEntityId(clientCharge.getId()) // + .withOfficeId(clientCharge.getClient().getOffice().getId()) // + .withClientId(clientCharge.getClient().getId()) // + .build(); + } + /** * Validates transaction to ensure that
* charge is active
@@ -159,31 +207,39 @@ public class ClientChargeWritePlatformServiceJpaRepositoryImpl implements Client * @param fmt * @param transactionDate * @param amountPaid + * @param requiresTransactionDateValidation + * if set to false, transaction date specific validation is + * skipped + * @param requiresTransactionAmountValidation + * if set to false transaction amount validation is skipped * @return */ - private Money validatePaymentDateAndAmount(final Client client, final ClientCharge clientCharge, final DateTimeFormatter fmt, - final LocalDate transactionDate, final BigDecimal amountPaid) { + private void validatePaymentDateAndAmount(final Client client, final ClientCharge clientCharge, final DateTimeFormatter fmt, + final LocalDate transactionDate, final BigDecimal amountPaid, final boolean requiresTransactionDateValidation, + final boolean requiresTransactionAmountValidation) { final List dataValidationErrors = new ArrayList<>(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) .resource(ClientApiConstants.CLIENT_CHARGES_RESOURCE_NAME); - validateTransactionDateOnWorkingDay(transactionDate, clientCharge, fmt); - if (clientCharge.isNotActive()) { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("charge.is.not.active"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - if (client.getActivationLocalDate() != null && transactionDate.isBefore(client.getActivationLocalDate())) { - baseDataValidator.reset().parameter(ClientApiConstants.transactionDateParamName).value(transactionDate.toString(fmt)) - .failWithCodeNoParameterAddedToErrorCode("transaction.before.activationDate"); - throw new PlatformApiDataValidationException(dataValidationErrors); - } + if (requiresTransactionDateValidation) { + validateTransactionDateOnWorkingDay(transactionDate, clientCharge, fmt); - if (DateUtils.isDateInTheFuture(transactionDate)) { - baseDataValidator.reset().parameter(ClientApiConstants.transactionDateParamName).value(transactionDate.toString(fmt)) - .failWithCodeNoParameterAddedToErrorCode("transaction.is.futureDate"); - throw new PlatformApiDataValidationException(dataValidationErrors); + if (client.getActivationLocalDate() != null && transactionDate.isBefore(client.getActivationLocalDate())) { + baseDataValidator.reset().parameter(ClientApiConstants.transactionDateParamName).value(transactionDate.toString(fmt)) + .failWithCodeNoParameterAddedToErrorCode("transaction.before.activationDate"); + throw new PlatformApiDataValidationException(dataValidationErrors); + } + + if (DateUtils.isDateInTheFuture(transactionDate)) { + baseDataValidator.reset().parameter(ClientApiConstants.transactionDateParamName).value(transactionDate.toString(fmt)) + .failWithCodeNoParameterAddedToErrorCode("transaction.is.futureDate"); + throw new PlatformApiDataValidationException(dataValidationErrors); + } } // validate charge is not already paid or waived @@ -195,12 +251,41 @@ public class ClientChargeWritePlatformServiceJpaRepositoryImpl implements Client if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - final Money chargePaid = Money.of(clientCharge.getCurrency(), amountPaid); - if (!clientCharge.getAmountOutstanding(clientCharge.getCurrency()).isGreaterThanOrEqualTo(chargePaid)) { - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.charge.amount.paid.in.access"); - if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + if (requiresTransactionAmountValidation) { + final Money chargePaid = Money.of(clientCharge.getCurrency(), amountPaid); + if (!clientCharge.getAmountOutstanding().isGreaterThanOrEqualTo(chargePaid)) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.charge.amount.paid.in.access"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } } - return chargePaid; + } + + public void validateWaiverTransaction(final Client client, final ClientCharge clientCharge) { + DateTimeFormatter fmt = null; + LocalDate transactionDate = null; + BigDecimal amountPaid = null; + boolean requiresTransactionDateValidation = false; + boolean requiresTransactionAmountValidation = false; + validatePaymentDateAndAmount(client, clientCharge, fmt, transactionDate, amountPaid, requiresTransactionDateValidation, + requiresTransactionAmountValidation); + } + + public void validatePaymentTransaction(final Client client, final ClientCharge clientCharge, final DateTimeFormatter fmt, + final LocalDate transactionDate, final BigDecimal amountPaid) { + boolean requiresTransactionDateValidation = true; + boolean requiresTransactionAmountValidation = true; + validatePaymentDateAndAmount(client, clientCharge, fmt, transactionDate, amountPaid, requiresTransactionDateValidation, + requiresTransactionAmountValidation); + } + + public void validateChargeDeletion(final Client client, final ClientCharge clientCharge) { + DateTimeFormatter fmt = null; + LocalDate transactionDate = null; + BigDecimal amountPaid = null; + boolean requiresTransactionDateValidation = false; + boolean requiresTransactionAmountValidation = false; + validatePaymentDateAndAmount(client, clientCharge, fmt, transactionDate, amountPaid, requiresTransactionDateValidation, + requiresTransactionAmountValidation); } /** @@ -210,19 +295,6 @@ public class ClientChargeWritePlatformServiceJpaRepositoryImpl implements Client @Override public CommandProcessingResult updateCharge(Long clientId, JsonCommand command) { this.clientChargeDataValidator.validateAdd(command.json()); - final Client client = clientRepository.getActiveClient(clientId); - return null; - } - - @Override - public CommandProcessingResult deleteCharge(Long clientId, Long clientChargeId, JsonCommand command) { - // TODO Auto-generated method stub - return null; - } - - @Override - public CommandProcessingResult waiveCharge(Long clientId, Long clientChargeId) { - // TODO Auto-generated method stub return null; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientTransactionReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientTransactionReadPlatformService.java index 99b4decfc..6d98fa13b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientTransactionReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientTransactionReadPlatformService.java @@ -13,7 +13,10 @@ import org.springframework.transaction.annotation.Transactional; public interface ClientTransactionReadPlatformService { @Transactional(readOnly = true) - public Collection retrieveAllTransactions(final Long clientId); + public Collection retrieveAllTransactions(Long clientId); + + @Transactional(readOnly = true) + public Collection retrieveAllTransactions(final Long clientId, final Long chargeId); @Transactional(readOnly = true) public ClientTransactionData retrieveTransaction(Long clientId, Long transactionId); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientTransactionReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientTransactionReadPlatformServiceImpl.java index 44a05cd1b..1960ed6bf 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientTransactionReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientTransactionReadPlatformServiceImpl.java @@ -18,9 +18,11 @@ import org.mifosplatform.organisation.monetary.data.CurrencyData; import org.mifosplatform.portfolio.client.data.ClientTransactionData; import org.mifosplatform.portfolio.client.domain.ClientEnumerations; import org.mifosplatform.portfolio.client.domain.ClientTransactionType; +import org.mifosplatform.portfolio.client.exception.ClientTransactionNotFoundException; import org.mifosplatform.portfolio.paymentdetail.data.PaymentDetailData; import org.mifosplatform.portfolio.paymenttype.data.PaymentTypeData; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Service; @@ -49,7 +51,7 @@ public class ClientTransactionReadPlatformServiceImpl implements ClientTransacti sqlBuilder.append("tr.transaction_date as transactionDate, tr.amount as transactionAmount, "); sqlBuilder.append("tr.created_date as submittedOnDate, tr.is_reversed as reversed, "); sqlBuilder.append("tr.external_id as externalId, o.name as officeName, o.id as officeId, "); - sqlBuilder.append("c.id as clientId, c.account_no as accountNo, "); + sqlBuilder.append("c.id as clientId, c.account_no as accountNo, ccpb.client_charge_id as clientChargeId, "); sqlBuilder.append("pd.payment_type_id as paymentType,pd.account_number as accountNumber,pd.check_number as checkNumber, "); sqlBuilder.append("pd.receipt_number as receiptNumber, pd.bank_number as bankNumber,pd.routing_code as routingCode, "); sqlBuilder.append( @@ -63,6 +65,7 @@ public class ClientTransactionReadPlatformServiceImpl implements ClientTransacti sqlBuilder.append("left join m_payment_detail pd on tr.payment_detail_id = pd.id "); sqlBuilder.append("left join m_payment_type pt on pd.payment_type_id = pt.id "); sqlBuilder.append("left join m_office o on o.id = tr.office_id "); + sqlBuilder.append("left join m_client_charge_paid_by ccpb on ccpb.client_transaction_id = tr.id "); this.schemaSql = sqlBuilder.toString(); } @@ -116,15 +119,32 @@ public class ClientTransactionReadPlatformServiceImpl implements ClientTransacti @Override public Collection retrieveAllTransactions(Long clientId) { - final String sql = "select " + this.clientTransactionMapper.schema() - + " where c.id = ? order by tr.transaction_date DESC, tr.created_date DESC, tr.id DESC"; - return this.jdbcTemplate.query(sql, this.clientTransactionMapper, new Object[] { clientId }); + Long chargeId = null; + return retrieveAllTransactions(clientId, chargeId); + } + + @Override + public Collection retrieveAllTransactions(Long clientId, Long chargeId) { + Object[] parameters = new Object[1]; + String sql = "select " + this.clientTransactionMapper.schema() + " where c.id = ? "; + if (chargeId != null) { + parameters = new Object[2]; + parameters[1] = chargeId; + sql = sql + " and ccpb.client_charge_id = ?"; + } + parameters[0] = clientId; + sql = sql + " order by tr.transaction_date DESC, tr.created_date DESC, tr.id DESC"; + return this.jdbcTemplate.query(sql, this.clientTransactionMapper, parameters); } @Override public ClientTransactionData retrieveTransaction(Long clientId, Long transactionId) { - final String sql = "select " + this.clientTransactionMapper.schema() + " where c.id = ? and tr.id= ?"; - return this.jdbcTemplate.queryForObject(sql, this.clientTransactionMapper, new Object[] { clientId, transactionId }); + try { + final String sql = "select " + this.clientTransactionMapper.schema() + " where c.id = ? and tr.id= ?"; + return this.jdbcTemplate.queryForObject(sql, this.clientTransactionMapper, new Object[] { clientId, transactionId }); + } catch (final EmptyResultDataAccessException e) { + throw new ClientTransactionNotFoundException(clientId, transactionId); + } } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientTransactionWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientTransactionWritePlatformService.java new file mode 100644 index 000000000..9bb31f809 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientTransactionWritePlatformService.java @@ -0,0 +1,16 @@ +/** + * 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.client.service; + +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.springframework.transaction.annotation.Transactional; + +public interface ClientTransactionWritePlatformService { + + @Transactional + CommandProcessingResult undo(Long clientId, Long transactionId); + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientTransactionWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientTransactionWritePlatformServiceJpaRepositoryImpl.java new file mode 100644 index 000000000..6496294c5 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientTransactionWritePlatformServiceJpaRepositoryImpl.java @@ -0,0 +1,76 @@ +/** + * 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.client.service; + +import java.util.Set; + +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; +import org.mifosplatform.organisation.office.domain.OrganisationCurrencyRepositoryWrapper; +import org.mifosplatform.portfolio.client.domain.Client; +import org.mifosplatform.portfolio.client.domain.ClientCharge; +import org.mifosplatform.portfolio.client.domain.ClientChargePaidBy; +import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper; +import org.mifosplatform.portfolio.client.domain.ClientTransaction; +import org.mifosplatform.portfolio.client.domain.ClientTransactionRepositoryWrapper; +import org.mifosplatform.portfolio.client.exception.ClientTransactionCannotBeUndoneException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class ClientTransactionWritePlatformServiceJpaRepositoryImpl implements ClientTransactionWritePlatformService { + + private final ClientTransactionRepositoryWrapper clientTransactionRepository; + + private final ClientRepositoryWrapper clientRepository; + private final OrganisationCurrencyRepositoryWrapper organisationCurrencyRepository; + + @Autowired + public ClientTransactionWritePlatformServiceJpaRepositoryImpl(final ClientTransactionRepositoryWrapper clientTransactionRepository, + final ClientRepositoryWrapper clientRepositoryWrapper, + final OrganisationCurrencyRepositoryWrapper organisationCurrencyRepositoryWrapper) { + this.clientTransactionRepository = clientTransactionRepository; + this.clientRepository = clientRepositoryWrapper; + this.organisationCurrencyRepository = organisationCurrencyRepositoryWrapper; + } + + @Override + public CommandProcessingResult undo(Long clientId, Long transactionId) { + + final Client client = this.clientRepository.getActiveClient(clientId); + + final ClientTransaction clientTransaction = this.clientTransactionRepository.findOneWithNotFoundDetection(clientId, transactionId); + + // validate that transaction can be undone + if (clientTransaction.isReversed()) { throw new ClientTransactionCannotBeUndoneException(clientId, transactionId); } + + // mark transaction as reversed + clientTransaction.reverse(); + + // revert any charges paid back to their original state + if (clientTransaction.isPayChargeTransaction() || clientTransaction.isWaiveChargeTransaction()) { + // undo charge + final Set chargesPaidBy = clientTransaction.getClientChargePaidByCollection(); + for (final ClientChargePaidBy clientChargePaidBy : chargesPaidBy) { + final ClientCharge clientCharge = clientChargePaidBy.getClientCharge(); + clientCharge.setCurrency( + organisationCurrencyRepository.findOneWithNotFoundDetection(clientCharge.getCharge().getCurrencyCode())); + if (clientTransaction.isPayChargeTransaction()) { + clientCharge.undoPayment(clientTransaction.getAmount()); + } else if (clientTransaction.isWaiveChargeTransaction()) { + clientCharge.undoWaiver(clientTransaction.getAmount()); + } + } + } + + return new CommandProcessingResultBuilder() // + .withEntityId(transactionId) // + .withOfficeId(client.officeId()) // + .withClientId(clientId) // + .build(); + } + +} diff --git a/mifosng-provider/src/main/resources/sql/migrations/core_db/V267__client_transaction_permissions.sql b/mifosng-provider/src/main/resources/sql/migrations/core_db/V267__client_transaction_permissions.sql new file mode 100644 index 000000000..1e03340cf --- /dev/null +++ b/mifosng-provider/src/main/resources/sql/migrations/core_db/V267__client_transaction_permissions.sql @@ -0,0 +1,3 @@ +INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('transaction_client', 'READTRANSACTION_CLIENT', 'CLIENT', 'READTRANSACTION', 0); +INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('transaction_client', 'UNDOTRANSACTION_CLIENT', 'CLIENT', 'UNDOTRANSACTION', 0); +INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('transaction_client', 'UNDOTRANSACTION_CLIENT_CHECKER', 'CLIENT', 'UNDOTRANSACTION_CHECKER', 0); \ No newline at end of file