Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ public class ClientApiConstants {
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 isActiveParamName = "isActive";
public static final String inactivationDateParamName = "inactivationDate";
public static final String CLIENT_TRANSACTION_COMMAND_UNDO = "undo";

public static final String CLIENT_CLOSURE_REASON = "ClientClosureReason";
Expand Down Expand Up @@ -199,10 +201,11 @@ public class ClientApiConstants {
staffOptionsParamName, dateOfBirthParamName, genderParamName, clientTypeParamName, clientClassificationParamName,
legalFormParamName, clientNonPersonDetailsParamName, isStaffParamName, legalFormParamName));

protected static final Set<String> CLIENT_CHARGES_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(chargeIdParamName,
clientIdParamName, chargeNameParamName, penaltyParamName, chargeTimeTypeParamName, dueAsOfDateParamName,
chargeCalculationTypeParamName, currencyParamName, amountWaivedParamName, amountWrittenOffParamName, amountOutstandingParamName,
amountOrPercentageParamName, amountParamName, amountPaidParamName, chargeOptionsParamName, transactionsParamName));
protected static final Set<String> CLIENT_CHARGES_RESPONSE_DATA_PARAMETERS = new HashSet<>(
Arrays.asList(chargeIdParamName, clientIdParamName, chargeNameParamName, penaltyParamName, chargeTimeTypeParamName,
dueAsOfDateParamName, chargeCalculationTypeParamName, currencyParamName, amountWaivedParamName,
amountWrittenOffParamName, amountOutstandingParamName, amountOrPercentageParamName, amountParamName,
amountPaidParamName, chargeOptionsParamName, transactionsParamName, isActiveParamName, inactivationDateParamName));

protected static final Set<String> CLIENT_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(idParamName,
transactionAmountParamName, paymentDetailDataParamName, reversedParamName, dateParamName, officeIdParamName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ public String applyClientCharge(@PathParam("clientId") @Parameter(description =
@Path("{chargeId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Pay a Client Charge | Waive a Client Charge", operationId = "payOrWaiveClientCharge", description = "Pay a Client Charge:\n\n"
@Operation(summary = "Pay a Client Charge | Waive a Client Charge | Inactivate a Client Charge", operationId = "payOrWaiveClientCharge", description = "Pay a Client Charge:\n\n"
+ "Mandatory Fields:" + "transactionDate and amount " + ""
+ "\"Pay either a part of or the entire due amount for a charge.(command=paycharge)\n" + "\n" + "Waive a Client Charge:\n"
+ "\n" + "\n" + "This API provides the facility of waiving off the remaining amount on a client charge (command=waive)\n\n"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,10 @@ public void undoPayment(final Money transactionAmount) {
this.amountPaid = amountPaid.getAmount();
this.amountOutstanding = calculateOutstanding();
this.paid = false;
this.status = true;
// Only restore active status if charge was never explicitly inactivated
if (this.inactivationDate == null) {
this.status = true;
}
}

public Money waive() {
Expand All @@ -159,7 +162,15 @@ public void undoWaiver(final Money transactionAmount) {
this.amountWaived = amountWaived.getAmount();
this.amountOutstanding = calculateOutstanding();
this.waived = false;
this.status = true;
// Only restore active status if charge was never explicitly inactivated
if (this.inactivationDate == null) {
this.status = true;
}
}

public void inactivate(final LocalDate inactivationOnDate) {
this.status = false;
this.inactivationDate = inactivationOnDate;
}

private void populateDerivedFields(final BigDecimal amount) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.client.handler;

import org.apache.fineract.commands.annotation.CommandType;
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.portfolio.client.api.ClientApiConstants;
import org.apache.fineract.portfolio.client.service.ClientChargeWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@CommandType(entity = ClientApiConstants.CLIENT_CHARGES_RESOURCE_NAME, action = ClientApiConstants.CLIENT_CHARGE_ACTION_INACTIVATE)
public class InactivateClientChargeCommandHandler implements NewCommandSourceHandler {

private final ClientChargeWritePlatformService writePlatformService;

@Autowired
public InactivateClientChargeCommandHandler(final ClientChargeWritePlatformService writePlatformService) {
this.writePlatformService = writePlatformService;
}

@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.writePlatformService.inactivateCharge(command.getClientId(), command.entityId());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -353,10 +353,50 @@ public CommandProcessingResult updateCharge(@SuppressWarnings("unused") Long cli
}

@Override
@SuppressWarnings("unused")
public CommandProcessingResult inactivateCharge(Long clientId, Long clientChargeId) {
// functionality not yet supported
return null;
try {
final Client client = this.clientRepository.getActiveClientInUserScope(clientId);
final ClientCharge clientCharge = this.clientChargeRepository.findOneWithNotFoundDetection(clientChargeId);

final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(ClientApiConstants.CLIENT_CHARGES_RESOURCE_NAME);

if (clientCharge.isNotActive()) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("charge.is.already.inactive");
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}

if (clientCharge.isWaived()) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("charge.is.already.waived");
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}

if (clientCharge.isPaid()) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("charge.is.already.paid");
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}

final LocalDate inactivationOnDate = DateUtils.getBusinessLocalDate();
clientCharge.inactivate(inactivationOnDate);
this.clientChargeRepository.saveAndFlush(clientCharge);

return new CommandProcessingResultBuilder() //
.withEntityId(clientCharge.getId()) //
.withOfficeId(client.getOffice().getId()) //
.withClientId(client.getId()) //
.build();
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
final Throwable throwable = dve.getMostSpecificCause();
handleDataIntegrityIssues(clientId, clientChargeId, throwable, dve);
return CommandProcessingResult.empty();
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,19 @@
*/
package org.apache.fineract.portfolio.client.service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.organisation.monetary.domain.OrganisationCurrencyRepositoryWrapper;
import org.apache.fineract.portfolio.client.api.ClientApiConstants;
import org.apache.fineract.portfolio.client.domain.Client;
import org.apache.fineract.portfolio.client.domain.ClientCharge;
import org.apache.fineract.portfolio.client.domain.ClientChargePaidBy;
Expand Down Expand Up @@ -67,6 +73,16 @@ public CommandProcessingResult undo(Long clientId, Long transactionId) {
final ClientCharge clientCharge = clientChargePaidBy.getClientCharge();
clientCharge.setCurrency(
organisationCurrencyRepository.findOneWithNotFoundDetection(clientCharge.getCharge().getCurrencyCode()));

// Cannot undo a transaction on an explicitly inactivated charge
if (clientCharge.isNotActive()) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(ClientApiConstants.CLIENT_CHARGES_RESOURCE_NAME);
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.undo.not.allowed.charge.is.inactive");
throw new PlatformApiDataValidationException(dataValidationErrors);
}

if (clientTransaction.isPayChargeTransaction()) {
clientCharge.undoPayment(clientTransaction.getAmount());
} else if (clientTransaction.isWaiveChargeTransaction()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,100 @@ public void clientChargeTest() {

}

@Test
public void clientChargeInactivateTest() {

// Create a charge definition applicable to clients
final Integer chargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec,
ChargesHelper.getChargeSpecifiedDueDateJSON());
Assertions.assertNotNull(chargeId);

// Create a client with activation date
final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 October 2011");
Assertions.assertNotNull(clientId);

// Associate the charge with the client
final Integer clientChargeId = ClientHelper.addChargesForClient(this.requestSpec, this.responseSpec, clientId,
ClientHelper.getSpecifiedDueDateChargesClientAsJSON(chargeId.toString(), "29 October 2011"));
Assertions.assertNotNull(clientChargeId);

// Inactivate the charge - this is the core operation under test
final Integer inactivatedChargeId = ClientHelper.inactivateChargesForClients(this.requestSpec, this.responseSpec, clientId,
clientChargeId);
Assertions.assertNotNull(inactivatedChargeId);
Assertions.assertEquals(clientChargeId, inactivatedChargeId);

// Retrieve and assert the charge is now inactive
// The JSON field name matches the Java field name in ClientChargeData: isActive
final Object isActive = ClientHelper.getClientChargeField(requestSpec, responseSpec, clientId.toString(), clientChargeId.toString(),
"isActive");
Assertions.assertEquals(Boolean.FALSE, isActive);

// Attempting to inactivate an already inactive charge must fail with 400
final ResponseSpecification responseSpecFailure = new ResponseSpecBuilder().expectStatusCode(400).build();
final Integer duplicateInactivate = ClientHelper.inactivateChargesForClients(this.requestSpec, responseSpecFailure, clientId,
clientChargeId);
Assertions.assertNull(duplicateInactivate);
}

@Test
public void clientChargeInactivateBlocksUndoTest() {

// Create charge and client
final Integer chargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec,
ChargesHelper.getChargeSpecifiedDueDateJSON());
Assertions.assertNotNull(chargeId);
final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 October 2011");
Assertions.assertNotNull(clientId);
final Integer clientChargeId = ClientHelper.addChargesForClient(this.requestSpec, this.responseSpec, clientId,
ClientHelper.getSpecifiedDueDateChargesClientAsJSON(chargeId.toString(), "29 October 2011"));
Assertions.assertNotNull(clientChargeId);

// Pay the charge partially
final String transactionId = ClientHelper.payChargesForClients(this.requestSpec, this.responseSpec, clientId, clientChargeId,
ClientHelper.getPayChargeJSON("25 August 2015", "10"));
Assertions.assertNotNull(transactionId);

// Inactivate the charge
final Integer inactivatedId = ClientHelper.inactivateChargesForClients(this.requestSpec, this.responseSpec, clientId,
clientChargeId);
Assertions.assertNotNull(inactivatedId);

// Attempt to undo the payment - must fail with 400 because charge is inactive
final ResponseSpecification responseSpecFailure = new ResponseSpecBuilder().expectStatusCode(400).build();
final Integer undoResult = ClientHelper.revertClientChargeTransaction(this.requestSpec, responseSpecFailure, clientId.toString(),
transactionId);
Assertions.assertNull(undoResult);
}

@Test
public void clientChargeInactivateFieldsFilterTest() {

// Create charge and client
final Integer chargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec,
ChargesHelper.getChargeSpecifiedDueDateJSON());
Assertions.assertNotNull(chargeId);
final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 October 2011");
Assertions.assertNotNull(clientId);
final Integer clientChargeId = ClientHelper.addChargesForClient(this.requestSpec, this.responseSpec, clientId,
ClientHelper.getSpecifiedDueDateChargesClientAsJSON(chargeId.toString(), "29 October 2011"));
Assertions.assertNotNull(clientChargeId);

// Inactivate the charge
ClientHelper.inactivateChargesForClients(this.requestSpec, this.responseSpec, clientId, clientChargeId);

// Assert isActive is returned correctly in full response
final Object isActive = ClientHelper.getClientChargeField(requestSpec, responseSpec, clientId.toString(), clientChargeId.toString(),
"isActive");
Assertions.assertEquals(Boolean.FALSE, isActive);

// Assert isActive is returned when using explicit ?fields= filter
// This validates CLIENT_CHARGES_RESPONSE_DATA_PARAMETERS includes isActive
final Object isActiveFiltered = ClientHelper.getClientChargeFieldWithFilter(requestSpec, responseSpec, clientId.toString(),
clientChargeId.toString(), "isActive", "isActive");
Assertions.assertEquals(Boolean.FALSE, isActiveFiltered);
}

/**
* It checks whether the client charge transaction is reversed or not.
*
Expand Down
Loading
Loading