FINERACT-1760: Enhanced external id support for loan charges

This commit is contained in:
Adam Saghy
2022-11-28 12:02:00 +01:00
committed by Arnold Gálovics
parent 2f4dcbdd6c
commit 25c0b6a08d
16 changed files with 879 additions and 296 deletions
@@ -28,6 +28,7 @@ import java.util.Map;
import lombok.Getter;
import org.apache.fineract.accounting.glaccount.data.GLAccountData;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
@@ -308,7 +309,7 @@ public final class ChargeData implements Comparable<ChargeData>, Serializable {
}
return LoanChargeData.newLoanChargeDetails(this.id, this.name, this.currency, this.amount, percentage, this.chargeTimeType,
this.chargeCalculationType, this.penalty, this.chargePaymentMode, this.minCap, this.maxCap);
this.chargeCalculationType, this.penalty, this.chargePaymentMode, this.minCap, this.maxCap, ExternalId.empty());
}
public SavingsAccountChargeData toSavingsAccountChargeData() {
@@ -43,15 +43,18 @@ import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.commands.domain.CommandWrapper;
import org.apache.fineract.commands.service.CommandWrapperBuilder;
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.apache.fineract.infrastructure.core.service.CommandParameterUtil;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.charge.data.ChargeData;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
@@ -59,49 +62,37 @@ import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
import org.apache.fineract.portfolio.loanaccount.data.LoanInstallmentChargeData;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Path("/loans/{loanId}/charges")
@Path("/loans")
@Component
@Scope("singleton")
@Tag(name = "Loan Charges", description = "Its typical for MFIs to add extra costs for their loan products. They can be either Fees or Penalties.\n"
+ "\n"
+ "Loan Charges are instances of Charges and represent either fees and penalties for loan products. Refer Charges for documentation of the various properties of a charge, Only additional properties ( specific to the context of a Charge being associated with a Loan) are described here")
@RequiredArgsConstructor
public class LoanChargesApiResource {
private static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(
Arrays.asList("id", "chargeId", "name", "penalty", "chargeTimeType", "dueAsOfDate", "chargeCalculationType", "percentage",
"amountPercentageAppliedTo", "currency", "amountWaived", "amountWrittenOff", "amountOutstanding", "amountOrPercentage",
"amount", "amountPaid", "chargeOptions", "installmentChargeData"));
private static final String RESOURCE_NAME_FOR_PERMISSIONS = "LOAN";
public static final String COMMAND_PAY = "pay";
public static final String COMMAND_WAIVE = "waive";
public static final String COMMAND_ADJUSTMENT = "adjustment";
private static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(
Arrays.asList("id", "chargeId", "name", "penalty", "chargeTimeType", "dueAsOfDate", "chargeCalculationType", "percentage",
"amountPercentageAppliedTo", "currency", "amountWaived", "amountWrittenOff", "amountOutstanding", "amountOrPercentage",
"amount", "amountPaid", "chargeOptions", "installmentChargeData", "externalId"));
private static final String RESOURCE_NAME_FOR_PERMISSIONS = "LOAN";
private final PlatformSecurityContext context;
private final ChargeReadPlatformService chargeReadPlatformService;
private final LoanChargeReadPlatformService loanChargeReadPlatformService;
private final DefaultToApiJsonSerializer<LoanChargeData> toApiJsonSerializer;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
@Autowired
public LoanChargesApiResource(final PlatformSecurityContext context, final ChargeReadPlatformService chargeReadPlatformService,
final LoanChargeReadPlatformService loanChargeReadPlatformService,
final DefaultToApiJsonSerializer<LoanChargeData> toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper,
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
this.context = context;
this.chargeReadPlatformService = chargeReadPlatformService;
this.loanChargeReadPlatformService = loanChargeReadPlatformService;
this.toApiJsonSerializer = toApiJsonSerializer;
this.apiRequestParameterHelper = apiRequestParameterHelper;
this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
}
private final LoanReadPlatformService loanReadPlatformService;
@GET
@Path("{loanId}/charges")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "List Loan Charges", description = "It lists all the Loan Charges specific to a Loan \n\n" + "Example Requests:\n"
@@ -111,16 +102,26 @@ public class LoanChargesApiResource {
public String retrieveAllLoanCharges(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
@Context final UriInfo uriInfo) {
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
final Collection<LoanChargeData> loanCharges = this.loanChargeReadPlatformService.retrieveLoanCharges(loanId);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, loanCharges, RESPONSE_DATA_PARAMETERS);
return retrieveAllLoanCharges(loanId, null, uriInfo);
}
@GET
@Path("template")
@Path("external-id/{loanExternalId}/charges")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "List Loan Charges", description = "It lists all the Loan Charges specific to a Loan \n\n" + "Example Requests:\n"
+ "\n" + "loans/1/charges\n" + "\n" + "\n" + "loans/1/charges?fields=name,amountOrPercentage")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = LoanChargesApiResourceSwagger.GetLoansLoanIdChargesChargeIdResponse.class)))) })
public String retrieveAllLoanCharges(
@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId,
@Context final UriInfo uriInfo) {
return retrieveAllLoanCharges(null, loanExternalId, uriInfo);
}
@GET
@Path("{loanId}/charges/template")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve Loan Charges Template", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:\n"
@@ -130,44 +131,87 @@ public class LoanChargesApiResource {
public String retrieveTemplate(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
@Context final UriInfo uriInfo) {
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
final Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveLoanAccountApplicableCharges(loanId,
new ChargeTimeType[] { ChargeTimeType.OVERDUE_INSTALLMENT });
final LoanChargeData loanChargeTemplate = LoanChargeData.template(chargeOptions);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, loanChargeTemplate, RESPONSE_DATA_PARAMETERS);
return retrieveTemplate(loanId, null, uriInfo);
}
@GET
@Path("{chargeId}")
@Path("external-id/{loanExternalId}/charges/template")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve a Loan Charge", description = "Retrieves Loan Charge according to the Loan ID and Charge ID"
@Operation(summary = "Retrieve Loan Charges Template", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:\n"
+ "\n" + "Field Defaults\n" + "Allowed description Lists\n" + "Example Request:\n" + "\n" + "loans/1/charges/template\n" + "\n")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.GetLoansLoanIdChargesTemplateResponse.class))) })
public String retrieveTemplate(@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId,
@Context final UriInfo uriInfo) {
return retrieveTemplate(null, loanExternalId, uriInfo);
}
@GET
@Path("{loanId}/charges/{loanChargeId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve a Loan Charge", description = "Retrieves Loan Charge according to the Loan ID and Loan Charge ID"
+ "Example Requests:\n" + "\n" + "/loans/1/charges/1\n" + "\n" + "\n" + "/loans/1/charges/1?fields=name,amountOrPercentage")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.GetLoansLoanIdChargesChargeIdResponse.class))) })
public String retrieveLoanCharge(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
@PathParam("chargeId") @Parameter(description = "chargeId") final Long loanChargeId, @Context final UriInfo uriInfo) {
@PathParam("loanChargeId") @Parameter(description = "loanChargeId") final Long loanChargeId, @Context final UriInfo uriInfo) {
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
return retrieveLoanCharge(loanId, null, loanChargeId, null, uriInfo);
}
final LoanChargeData loanCharge = this.loanChargeReadPlatformService.retrieveLoanChargeDetails(loanChargeId, loanId);
@GET
@Path("{loanId}/charges/external-id/{loanChargeExternalId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve a Loan Charge", description = "Retrieves Loan Charge according to the Loan ID and Loan Charge External ID"
+ "Example Requests:\n" + "\n" + "/loans/1/charges/1\n" + "\n" + "\n"
+ "/loans/1/charges/external-id/1?fields=name,amountOrPercentage")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.GetLoansLoanIdChargesChargeIdResponse.class))) })
public String retrieveLoanCharge(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
@PathParam("loanChargeExternalId") @Parameter(description = "loanChargeExternalId") final String loanChargeExternalId,
@Context final UriInfo uriInfo) {
final Collection<LoanInstallmentChargeData> installmentChargeData = this.loanChargeReadPlatformService
.retrieveInstallmentLoanCharges(loanChargeId, true);
return retrieveLoanCharge(loanId, null, null, loanChargeExternalId, uriInfo);
}
final LoanChargeData loanChargeData = new LoanChargeData(loanCharge, installmentChargeData);
@GET
@Path("external-id/{loanExternalId}/charges/{loanChargeId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve a Loan Charge", description = "Retrieves Loan Charge according to the Loan external ID and Loan Charge ID"
+ "Example Requests:\n" + "\n" + "/loans/1/charges/1\n" + "\n" + "\n" + "/loans/1/charges/1?fields=name,amountOrPercentage")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.GetLoansLoanIdChargesChargeIdResponse.class))) })
public String retrieveLoanCharge(@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId,
@PathParam("loanChargeId") @Parameter(description = "loanChargeId") final Long loanChargeId, @Context final UriInfo uriInfo) {
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, loanChargeData, RESPONSE_DATA_PARAMETERS);
return retrieveLoanCharge(null, loanExternalId, loanChargeId, null, uriInfo);
}
@GET
@Path("external-id/{loanExternalId}/charges/external-id/{loanChargeExternalId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve a Loan Charge", description = "Retrieves Loan Charge according to the Loan External ID and Loan Charge External ID"
+ "Example Requests:\n" + "\n" + "/loans/1/charges/1\n" + "\n" + "\n" + "/loans/1/charges/1?fields=name,amountOrPercentage")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.GetLoansLoanIdChargesChargeIdResponse.class))) })
public String retrieveLoanCharge(@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId,
@PathParam("loanChargeExternalId") @Parameter(description = "loanChargeExternalId") final String loanChargeExternalId,
@Context final UriInfo uriInfo) {
return retrieveLoanCharge(null, loanExternalId, null, loanChargeExternalId, uriInfo);
}
@POST
@Path("{loanId}/charges")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Create a Loan Charge", description = "It Creates a Loan Charge")
@Operation(summary = "Create a Loan Charge (no command provided) or Pay a charge (command=pay)", description = "Creates a Loan Charge | Pay a Loan Charge")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PostLoansLoanIdChargesRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PostLoansLoanIdChargesResponse.class))) })
@@ -175,22 +219,90 @@ public class LoanChargesApiResource {
@QueryParam("command") @Parameter(description = "command") final String commandParam,
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
CommandProcessingResult result;
if (CommandParameterUtil.is(commandParam, COMMAND_PAY)) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().payLoanCharge(loanId, null).withJson(apiRequestBodyAsJson)
.build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
} else {
final CommandWrapper commandRequest = new CommandWrapperBuilder().createLoanCharge(loanId).withJson(apiRequestBodyAsJson)
.build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
}
return handleExecuteLoanCharge(loanId, null, commandParam, apiRequestBodyAsJson);
}
return this.toApiJsonSerializer.serialize(result);
@POST
@Path("external-id/{loanExternalId}/charges")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Create a Loan Charge (no command provided) or Pay a charge (command=pay)", description = "Creates a Loan Charge | Pay a Loan Charge")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PostLoansLoanIdChargesRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PostLoansLoanIdChargesResponse.class))) })
public String executeLoanCharge(@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId,
@QueryParam("command") @Parameter(description = "command") final String commandParam,
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
return handleExecuteLoanCharge(null, loanExternalId, commandParam, apiRequestBodyAsJson);
}
@POST
@Path("{loanId}/charges/{loanChargeId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Pay / Waive / Adjustment for Loan Charge", description = "Loan Charge will be paid if the loan is linked with a savings account | Waive Loan Charge | Add Charge Adjustment")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PostLoansLoanIdChargesChargeIdRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PostLoansLoanIdChargesChargeIdResponse.class))) })
public String executeLoanCharge(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
@PathParam("loanChargeId") @Parameter(description = "loanChargeId") final Long loanChargeId,
@QueryParam("command") @Parameter(description = "command") final String commandParam,
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
return handleExecuteLoanCharge(loanId, null, loanChargeId, null, commandParam, apiRequestBodyAsJson);
}
@POST
@Path("{loanId}/charges/external-id/{loanChargeExternalId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Pay / Waive / Adjustment for Loan Charge", description = "Loan Charge will be paid if the loan is linked with a savings account | Waive Loan Charge | Add Charge Adjustment")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PostLoansLoanIdChargesChargeIdRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PostLoansLoanIdChargesChargeIdResponse.class))) })
public String executeLoanCharge(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
@PathParam("loanChargeExternalId") @Parameter(description = "loanChargeExternalId") final String loanChargeExternalId,
@QueryParam("command") @Parameter(description = "command") final String commandParam,
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
return handleExecuteLoanCharge(loanId, null, null, loanChargeExternalId, commandParam, apiRequestBodyAsJson);
}
@POST
@Path("external-id/{loanExternalId}/charges/{loanChargeId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Pay / Waive / Adjustment for Loan Charge", description = "Loan Charge will be paid if the loan is linked with a savings account | Waive Loan Charge | Add Charge Adjustment")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PostLoansLoanIdChargesChargeIdRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PostLoansLoanIdChargesChargeIdResponse.class))) })
public String executeLoanCharge(@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId,
@PathParam("loanChargeId") @Parameter(description = "loanChargeId") final Long loanChargeId,
@QueryParam("command") @Parameter(description = "command") final String commandParam,
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
return handleExecuteLoanCharge(null, loanExternalId, loanChargeId, null, commandParam, apiRequestBodyAsJson);
}
@POST
@Path("external-id/{loanExternalId}/charges/external-id/{loanChargeExternalId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Pay / Waive / Adjustment for Loan Charge", description = "Loan Charge will be paid if the loan is linked with a savings account | Waive Loan Charge | Add Charge Adjustment")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PostLoansLoanIdChargesChargeIdRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PostLoansLoanIdChargesChargeIdResponse.class))) })
public String executeLoanCharge(@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId,
@PathParam("loanChargeExternalId") @Parameter(description = "loanChargeExternalId") final String loanChargeExternalId,
@QueryParam("command") @Parameter(description = "command") final String commandParam,
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
return handleExecuteLoanCharge(null, loanExternalId, null, loanChargeExternalId, commandParam, apiRequestBodyAsJson);
}
@PUT
@Path("{chargeId}")
@Path("{loanId}/charges/{loanChargeId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Update a Loan Charge", description = "Currently Loan Charges may be updated only if the Loan is not yet approved")
@@ -198,40 +310,186 @@ public class LoanChargesApiResource {
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PutLoansLoanIdChargesChargeIdResponse.class))) })
public String updateLoanCharge(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
@PathParam("chargeId") @Parameter(description = "chargeId") final Long loanChargeId,
@PathParam("loanChargeId") @Parameter(description = "loanChargeId") final Long loanChargeId,
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateLoanCharge(loanId, loanChargeId)
.withJson(apiRequestBodyAsJson).build();
return updateLoanCharge(loanId, null, loanChargeId, null, apiRequestBodyAsJson);
}
@PUT
@Path("{loanId}/charges/external-id/{loanChargeExternalId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Update a Loan Charge", description = "Currently Loan Charges may be updated only if the Loan is not yet approved")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PutLoansLoanIdChargesChargeIdRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PutLoansLoanIdChargesChargeIdResponse.class))) })
public String updateLoanCharge(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
@PathParam("loanChargeExternalId") @Parameter(description = "loanChargeExternalId") final String loanChargeExternalId,
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
return updateLoanCharge(loanId, null, null, loanChargeExternalId, apiRequestBodyAsJson);
}
@PUT
@Path("external-id/{loanExternalId}/charges/{loanChargeId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Update a Loan Charge", description = "Currently Loan Charges may be updated only if the Loan is not yet approved")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PutLoansLoanIdChargesChargeIdRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PutLoansLoanIdChargesChargeIdResponse.class))) })
public String updateLoanCharge(@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId,
@PathParam("loanChargeId") @Parameter(description = "loanChargeId") final Long loanChargeId,
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
return updateLoanCharge(null, loanExternalId, loanChargeId, null, apiRequestBodyAsJson);
}
@PUT
@Path("external-id/{loanExternalId}/charges/external-id/{loanChargeExternalId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Update a Loan Charge", description = "Currently Loan Charges may be updated only if the Loan is not yet approved")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PutLoansLoanIdChargesChargeIdRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PutLoansLoanIdChargesChargeIdResponse.class))) })
public String updateLoanCharge(@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId,
@PathParam("loanChargeExternalId") @Parameter(description = "loanChargeExternalId") final String loanChargeExternalId,
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
return updateLoanCharge(null, loanExternalId, null, loanChargeExternalId, apiRequestBodyAsJson);
}
@DELETE
@Path("{loanId}/charges/{loanChargeId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Delete a Loan Charge", description = "Note: Currently, A Loan Charge may only be removed from Loans that are not yet approved.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.DeleteLoansLoanIdChargesChargeIdResponse.class))) })
public String deleteLoanCharge(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
@PathParam("loanChargeId") @Parameter(description = "loanChargeId") final Long loanChargeId) {
return deleteLoanCharge(loanId, null, loanChargeId, null);
}
@DELETE
@Path("{loanId}/charges/external-id/{loanChargeExternalId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Delete a Loan Charge", description = "Note: Currently, A Loan Charge may only be removed from Loans that are not yet approved.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.DeleteLoansLoanIdChargesChargeIdResponse.class))) })
public String deleteLoanCharge(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
@PathParam("loanChargeExternalId") @Parameter(description = "loanChargeExternalId") final String loanChargeExternalId) {
return deleteLoanCharge(loanId, null, null, loanChargeExternalId);
}
@DELETE
@Path("external-id/{loanExternalId}/charges/{loanChargeId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Delete a Loan Charge", description = "Note: Currently, A Loan Charge may only be removed from Loans that are not yet approved.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.DeleteLoansLoanIdChargesChargeIdResponse.class))) })
public String deleteLoanCharge(@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId,
@PathParam("loanChargeId") @Parameter(description = "loanChargeId") final Long loanChargeId) {
return deleteLoanCharge(null, loanExternalId, loanChargeId, null);
}
@DELETE
@Path("external-id/{loanExternalId}/charges/external-id/{loanChargeExternalId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Delete a Loan Charge", description = "Note: Currently, A Loan Charge may only be removed from Loans that are not yet approved.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.DeleteLoansLoanIdChargesChargeIdResponse.class))) })
public String deleteLoanCharge(@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId,
@PathParam("loanChargeExternalId") @Parameter(description = "loanChargeExternalId") final String loanChargeExternalId) {
return deleteLoanCharge(null, loanExternalId, null, loanChargeExternalId);
}
private String deleteLoanCharge(final Long loanId, final String loanExternalIdStr, final Long loanChargeId,
final String loanChargeExternalIdStr) {
ExternalId loanExternalId = ExternalIdFactory.produce(loanExternalIdStr);
ExternalId loanChargeExternalId = ExternalIdFactory.produce(loanChargeExternalIdStr);
Long resolvedLoanId = getResolvedLoanId(loanId, loanExternalId);
Long resolvedLoanChargeId = getResolvedLoanChargeId(loanChargeId, loanChargeExternalId);
final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteLoanCharge(resolvedLoanId, resolvedLoanChargeId).build();
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
return this.toApiJsonSerializer.serialize(result);
}
@POST
@Path("{chargeId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Pay Loan Charge", description = "Loan Charge will be paid if the loan is linked with a savings account")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PostLoansLoanIdChargesChargeIdRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PostLoansLoanIdChargesChargeIdResponse.class))) })
public String executeLoanCharge(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
@PathParam("chargeId") @Parameter(description = "chargeId") final Long loanChargeId,
@QueryParam("command") @Parameter(description = "command") final String commandParam,
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
private String retrieveLoanCharge(final Long loanId, final String loanExternalIdStr, final Long loanChargeId,
final String loanChargeExternalIdStr, final UriInfo uriInfo) {
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
ExternalId loanExternalId = ExternalIdFactory.produce(loanExternalIdStr);
ExternalId loanChargeExternalId = ExternalIdFactory.produce(loanChargeExternalIdStr);
Long resolvedLoanId = getResolvedLoanId(loanId, loanExternalId);
Long resolvedLoanChargeId = getResolvedLoanChargeId(loanChargeId, loanChargeExternalId);
final LoanChargeData loanCharge = this.loanChargeReadPlatformService.retrieveLoanChargeDetails(resolvedLoanChargeId,
resolvedLoanId);
final Collection<LoanInstallmentChargeData> installmentChargeData = this.loanChargeReadPlatformService
.retrieveInstallmentLoanCharges(resolvedLoanChargeId, true);
final LoanChargeData loanChargeData = new LoanChargeData(loanCharge, installmentChargeData);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, loanChargeData, RESPONSE_DATA_PARAMETERS);
}
private String handleExecuteLoanCharge(final Long loanId, final String loanExternalIdStr, final String commandParam,
final String apiRequestBodyAsJson) {
ExternalId loanExternalId = ExternalIdFactory.produce(loanExternalIdStr);
Long resolvedLoanId = getResolvedLoanId(loanId, loanExternalId);
CommandProcessingResult result;
if (CommandParameterUtil.is(commandParam, COMMAND_PAY)) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().payLoanCharge(resolvedLoanId, null)
.withJson(apiRequestBodyAsJson).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
} else {
final CommandWrapper commandRequest = new CommandWrapperBuilder().createLoanCharge(resolvedLoanId)
.withJson(apiRequestBodyAsJson).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
}
return this.toApiJsonSerializer.serialize(result);
}
private String handleExecuteLoanCharge(final Long loanId, final String loanExternalIdStr, final Long loanChargeId,
final String loanChargeExternalIdStr, final String commandParam, final String apiRequestBodyAsJson) {
ExternalId loanExternalId = ExternalIdFactory.produce(loanExternalIdStr);
ExternalId loanChargeExternalId = ExternalIdFactory.produce(loanChargeExternalIdStr);
Long resolvedLoanId = getResolvedLoanId(loanId, loanExternalId);
Long resolvedLoanChargeId = getResolvedLoanChargeId(loanChargeId, loanChargeExternalId);
final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
CommandProcessingResult result;
if (CommandParameterUtil.is(commandParam, COMMAND_WAIVE)) {
final CommandWrapper commandRequest = builder.waiveLoanCharge(loanId, loanChargeId).build();
final CommandWrapper commandRequest = builder.waiveLoanCharge(resolvedLoanId, resolvedLoanChargeId).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
} else if (CommandParameterUtil.is(commandParam, COMMAND_PAY)) {
final CommandWrapper commandRequest = builder.payLoanCharge(loanId, loanChargeId).build();
final CommandWrapper commandRequest = builder.payLoanCharge(resolvedLoanId, resolvedLoanChargeId).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
} else if (CommandParameterUtil.is(commandParam, COMMAND_ADJUSTMENT)) {
final CommandWrapper commandRequest = builder.adjustmentForLoanCharge(loanId, loanChargeId).build();
final CommandWrapper commandRequest = builder.adjustmentForLoanCharge(resolvedLoanId, resolvedLoanChargeId).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
} else {
throw new UnrecognizedQueryParamException("command", commandParam);
@@ -243,20 +501,64 @@ public class LoanChargesApiResource {
return this.toApiJsonSerializer.serialize(result);
}
@DELETE
@Path("{chargeId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Delete a Loan Charge", description = "Note: Currently, A Loan Charge may only be removed from Loans that are not yet approved.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.DeleteLoansLoanIdChargesChargeIdResponse.class))) })
public String deleteLoanCharge(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
@PathParam("chargeId") @Parameter(description = "chargeId") final Long loanChargeId) {
private String updateLoanCharge(final Long loanId, final String loanExternalIdStr, final Long loanChargeId,
final String loanChargeExternalIdStr, final String apiRequestBodyAsJson) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteLoanCharge(loanId, loanChargeId).build();
ExternalId loanExternalId = ExternalIdFactory.produce(loanExternalIdStr);
ExternalId loanChargeExternalId = ExternalIdFactory.produce(loanChargeExternalIdStr);
Long resolvedLoanId = getResolvedLoanId(loanId, loanExternalId);
Long resolvedLoanChargeId = getResolvedLoanChargeId(loanChargeId, loanChargeExternalId);
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateLoanCharge(resolvedLoanId, resolvedLoanChargeId)
.withJson(apiRequestBodyAsJson).build();
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
return this.toApiJsonSerializer.serialize(result);
}
private String retrieveAllLoanCharges(final Long loanId, final String loanExternalIdStr, final UriInfo uriInfo) {
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
ExternalId loanExternalId = ExternalIdFactory.produce(loanExternalIdStr);
Long resolvedLoanId = getResolvedLoanId(loanId, loanExternalId);
final Collection<LoanChargeData> loanCharges = this.loanChargeReadPlatformService.retrieveLoanCharges(resolvedLoanId);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, loanCharges, RESPONSE_DATA_PARAMETERS);
}
private String retrieveTemplate(final Long loanId, final String loanExternalIdStr, final UriInfo uriInfo) {
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
ExternalId loanExternalId = ExternalIdFactory.produce(loanExternalIdStr);
Long resolvedLoanId = getResolvedLoanId(loanId, loanExternalId);
final Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveLoanAccountApplicableCharges(resolvedLoanId,
new ChargeTimeType[] { ChargeTimeType.OVERDUE_INSTALLMENT });
final LoanChargeData loanChargeTemplate = LoanChargeData.template(chargeOptions);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, loanChargeTemplate, RESPONSE_DATA_PARAMETERS);
}
private Long getResolvedLoanChargeId(final Long loanChargeId, final ExternalId loanChargeExternalId) {
Long resolvedLoanChargeId = loanChargeId;
if (resolvedLoanChargeId == null) {
loanChargeExternalId.throwExceptionIfEmpty();
resolvedLoanChargeId = this.loanChargeReadPlatformService.retrieveLoanChargeIdByExternalId(loanChargeExternalId);
}
return resolvedLoanChargeId;
}
private Long getResolvedLoanId(final Long loanId, final ExternalId loanExternalId) {
Long resolvedLoanId = loanId;
if (resolvedLoanId == null) {
loanExternalId.throwExceptionIfEmpty();
resolvedLoanId = this.loanReadPlatformService.retrieveLoanIdByExternalId(loanExternalId.getValue());
}
return resolvedLoanId;
}
}
@@ -105,6 +105,8 @@ final class LoanChargesApiResourceSwagger {
public Boolean penalty;
@Schema(example = "27 March 2013")
public LocalDate submittedOnDate;
@Schema(example = "95174ff9-1a75-4d72-a413-6f9b1cb988b7")
public String externalId;
}
@Schema(description = "GetLoansLoanIdChargesTemplateResponse")
@@ -199,6 +201,8 @@ final class LoanChargesApiResourceSwagger {
public Long loanId;
@Schema(example = "31")
public Long resourceId;
@Schema(example = "95174ff9-1a75-4d72-a413-6f9b1cb988b7")
public String resourceExternalId;
}
@Schema(description = " PutLoansLoanIdChargesChargeIdRequest")
@@ -229,6 +233,8 @@ final class LoanChargesApiResourceSwagger {
public Long loanId;
@Schema(example = "6")
public Long resourceId;
@Schema(example = "95174ff9-1a75-4d72-a413-6f9b1cb988b7")
public String resourceExternalId;
public PutLoansLoanIdChargesChargeIdRequest changes;
}
@@ -325,5 +331,7 @@ final class LoanChargesApiResourceSwagger {
public Long loanId;
@Schema(example = "2")
public Long resourceId;
@Schema(example = "95174ff9-1a75-4d72-a413-6f9b1cb988b7")
public String resourceExternalId;
}
}
@@ -25,6 +25,7 @@ import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.portfolio.charge.data.ChargeData;
import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
@@ -89,11 +90,11 @@ public class LoanChargeData {
private BigDecimal amountUnrecognized;
private String externalId;
private final ExternalId externalId;
public static LoanChargeData template(final Collection<ChargeData> chargeOptions) {
return new LoanChargeData(null, null, null, null, null, null, null, null, chargeOptions, false, null, false, false, null, null,
null, null, null);
null, null, null, ExternalId.empty());
}
/**
@@ -102,9 +103,9 @@ public class LoanChargeData {
public static LoanChargeData newLoanChargeDetails(final Long chargeId, final String name, final CurrencyData currency,
final BigDecimal amount, final BigDecimal percentage, final EnumOptionData chargeTimeType,
final EnumOptionData chargeCalculationType, final boolean penalty, final EnumOptionData chargePaymentMode,
final BigDecimal minCap, final BigDecimal maxCap) {
final BigDecimal minCap, final BigDecimal maxCap, final ExternalId externalId) {
return new LoanChargeData(null, chargeId, name, currency, amount, percentage, chargeTimeType, chargeCalculationType, null, penalty,
chargePaymentMode, false, false, null, minCap, maxCap, null, null);
chargePaymentMode, false, false, null, minCap, maxCap, null, null, externalId);
}
public LoanChargeData(final Long id, final Long chargeId, final String name, final CurrencyData currency, final BigDecimal amount,
@@ -113,7 +114,7 @@ public class LoanChargeData {
final LocalDate dueDate, final EnumOptionData chargeCalculationType, final BigDecimal percentage,
final BigDecimal amountPercentageAppliedTo, final boolean penalty, final EnumOptionData chargePaymentMode, final boolean paid,
final boolean waived, final Long loanId, final BigDecimal minCap, final BigDecimal maxCap, final BigDecimal amountOrPercentage,
Collection<LoanInstallmentChargeData> installmentChargeData, final String externalId) {
Collection<LoanInstallmentChargeData> installmentChargeData, final ExternalId externalId) {
this.id = id;
this.chargeId = chargeId;
this.name = name;
@@ -158,7 +159,7 @@ public class LoanChargeData {
final BigDecimal percentage, final EnumOptionData chargeTimeType, final EnumOptionData chargeCalculationType,
final Collection<ChargeData> chargeOptions, final boolean penalty, final EnumOptionData chargePaymentMode, final boolean paid,
final boolean waived, final Long loanId, final BigDecimal minCap, final BigDecimal maxCap, final BigDecimal amountOrPercentage,
Collection<LoanInstallmentChargeData> installmentChargeData) {
Collection<LoanInstallmentChargeData> installmentChargeData, final ExternalId externalId) {
this.id = id;
this.chargeId = chargeId;
this.name = name;
@@ -197,10 +198,11 @@ public class LoanChargeData {
this.installmentChargeData = installmentChargeData;
this.amountAccrued = null;
this.amountUnrecognized = null;
this.externalId = externalId;
}
public LoanChargeData(final Long id, final LocalDate dueAsOfDate, final BigDecimal amountOutstanding, EnumOptionData chargeTimeType,
final Long loanId, Collection<LoanInstallmentChargeData> installmentChargeData) {
final Long loanId, Collection<LoanInstallmentChargeData> installmentChargeData, final ExternalId externalId) {
this.id = id;
this.chargeId = null;
this.name = null;
@@ -229,10 +231,12 @@ public class LoanChargeData {
this.installmentChargeData = installmentChargeData;
this.amountAccrued = null;
this.amountUnrecognized = null;
this.externalId = externalId;
}
public LoanChargeData(final Long id, final Long chargeId, final LocalDate dueAsOfDate, EnumOptionData chargeTimeType,
final BigDecimal amount, final BigDecimal amountAccrued, final BigDecimal amountWaived, final boolean penalty) {
final BigDecimal amount, final BigDecimal amountAccrued, final BigDecimal amountWaived, final boolean penalty,
final ExternalId externalId) {
this.id = id;
this.chargeId = chargeId;
this.name = null;
@@ -261,6 +265,7 @@ public class LoanChargeData {
this.installmentChargeData = null;
this.amountAccrued = amountAccrued;
this.amountUnrecognized = null;
this.externalId = externalId;
}
public LoanChargeData(final BigDecimal amountUnrecognized, final LoanChargeData chargeData) {
@@ -292,6 +297,7 @@ public class LoanChargeData {
this.installmentChargeData = null;
this.amountAccrued = chargeData.amountAccrued;
this.amountUnrecognized = amountUnrecognized;
this.externalId = chargeData.externalId;
}
public LoanChargeData(LoanChargeData chargeData, Collection<LoanInstallmentChargeData> installmentChargeData) {
@@ -356,6 +362,7 @@ public class LoanChargeData {
this.installmentChargeData = null;
this.amountAccrued = null;
this.amountUnrecognized = null;
this.externalId = ExternalId.empty();
}
public LoanChargeData(final Long id, final LocalDate dueAsOfDate, final BigDecimal amountOrPercentage) {
@@ -387,6 +394,7 @@ public class LoanChargeData {
this.installmentChargeData = null;
this.amountAccrued = null;
this.amountUnrecognized = null;
this.externalId = ExternalId.empty();
}
public boolean isChargePayable() {
@@ -1950,9 +1950,12 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
this.disbursementDetails.add(disbursementDetails);
for (LoanTrancheCharge trancheCharge : trancheCharges) {
Charge chargeDefinition = trancheCharge.getCharge();
final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, principal, null, null, null,
expectedDisbursementDate, null, null);
loanCharge.update(this);
ExternalId externalId = ExternalId.empty();
if (TemporaryConfigurationServiceContainer.isExternalIdAutoGenerationEnabled()) {
externalId = ExternalId.generate();
}
final LoanCharge loanCharge = new LoanCharge(this, chargeDefinition, principal, null, null, null, expectedDisbursementDate,
null, null, BigDecimal.ZERO, externalId);
LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge,
disbursementDetails);
loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge);
@@ -27,7 +27,6 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@@ -44,6 +43,7 @@ import javax.persistence.UniqueConstraint;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
@@ -131,7 +131,7 @@ public class LoanCharge extends AbstractPersistableCustom {
private boolean active = true;
@Column(name = "external_id")
private String externalId;
private ExternalId externalId;
@OneToOne(mappedBy = "loancharge", cascade = CascadeType.ALL, optional = true, orphanRemoval = true, fetch = FetchType.EAGER)
private LoanOverdueInstallmentCharge overdueInstallmentCharge;
@@ -142,99 +142,14 @@ public class LoanCharge extends AbstractPersistableCustom {
@OneToMany(mappedBy = "loanCharge", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
private Set<LoanChargePaidBy> loanChargePaidBySet;
public static LoanCharge createNewFromJson(final Loan loan, final Charge chargeDefinition, final JsonCommand command) {
final LocalDate dueDate = command.localDateValueOfParameterNamed("dueDate");
if (chargeDefinition.getChargeTimeType().equals(ChargeTimeType.SPECIFIED_DUE_DATE.getValue()) && dueDate == null) {
final String defaultUserMessage = "Loan charge is missing due date.";
throw new LoanChargeWithoutMandatoryFieldException("loanCharge", "dueDate", defaultUserMessage, chargeDefinition.getId(),
chargeDefinition.getName());
}
return createNewFromJson(loan, chargeDefinition, command, dueDate);
}
public static LoanCharge createNewFromJson(final Loan loan, final Charge chargeDefinition, final JsonCommand command,
final LocalDate dueDate) {
final Locale locale = command.extractLocale();
final BigDecimal amount = command.bigDecimalValueOfParameterNamed("amount", locale);
final ChargeTimeType chargeTime = null;
final ChargeCalculationType chargeCalculation = null;
final ChargePaymentMode chargePaymentMode = null;
BigDecimal amountPercentageAppliedTo = BigDecimal.ZERO;
switch (ChargeCalculationType.fromInt(chargeDefinition.getChargeCalculation())) {
case PERCENT_OF_AMOUNT:
if (command.hasParameter("principal")) {
amountPercentageAppliedTo = command.bigDecimalValueOfParameterNamed("principal");
} else {
amountPercentageAppliedTo = loan.getPrincipal().getAmount();
}
break;
case PERCENT_OF_AMOUNT_AND_INTEREST:
if (command.hasParameter("principal") && command.hasParameter("interest")) {
amountPercentageAppliedTo = command.bigDecimalValueOfParameterNamed("principal")
.add(command.bigDecimalValueOfParameterNamed("interest"));
} else {
amountPercentageAppliedTo = loan.getPrincipal().getAmount().add(loan.getTotalInterest());
}
break;
case PERCENT_OF_INTEREST:
if (command.hasParameter("interest")) {
amountPercentageAppliedTo = command.bigDecimalValueOfParameterNamed("interest");
} else {
amountPercentageAppliedTo = loan.getTotalInterest();
}
break;
default:
break;
}
BigDecimal loanCharge = BigDecimal.ZERO;
if (ChargeTimeType.fromInt(chargeDefinition.getChargeTimeType()).equals(ChargeTimeType.INSTALMENT_FEE)) {
BigDecimal percentage = amount;
if (percentage == null) {
percentage = chargeDefinition.getAmount();
}
loanCharge = loan.calculatePerInstallmentChargeAmount(ChargeCalculationType.fromInt(chargeDefinition.getChargeCalculation()),
percentage);
}
// If charge type is specified due date and loan is multi disburment
// loan.
// Then we need to get as of this loan charge due date how much amount
// disbursed.
if (chargeDefinition.getChargeTimeType().equals(ChargeTimeType.SPECIFIED_DUE_DATE.getValue()) && loan.isMultiDisburmentLoan()) {
amountPercentageAppliedTo = BigDecimal.ZERO;
for (final LoanDisbursementDetails loanDisbursementDetails : loan.getDisbursementDetails()) {
if (!loanDisbursementDetails.expectedDisbursementDate().isAfter(dueDate)) {
amountPercentageAppliedTo = amountPercentageAppliedTo.add(loanDisbursementDetails.principal());
}
}
}
LoanCharge newLoanCharge = new LoanCharge(loan, chargeDefinition, amountPercentageAppliedTo, amount, chargeTime, chargeCalculation,
dueDate, chargePaymentMode, null, loanCharge);
final String externalId = command.stringValueOfParameterNamedAllowingNull("externalId");
newLoanCharge.setExternalId(externalId);
return newLoanCharge;
}
/*
* loanPrincipal is required for charges that are percentage based
*/
public static LoanCharge createNewWithoutLoan(final Charge chargeDefinition, final BigDecimal loanPrincipal, final BigDecimal amount,
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate,
final ChargePaymentMode chargePaymentMode, final Integer numberOfRepayments) {
return new LoanCharge(null, chargeDefinition, loanPrincipal, amount, chargeTime, chargeCalculation, dueDate, chargePaymentMode,
numberOfRepayments, BigDecimal.ZERO);
}
protected LoanCharge() {
//
}
public LoanCharge(final Loan loan, final Charge chargeDefinition, final BigDecimal loanPrincipal, final BigDecimal amount,
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate,
final ChargePaymentMode chargePaymentMode, final Integer numberOfRepayments, final BigDecimal loanCharge) {
final ChargePaymentMode chargePaymentMode, final Integer numberOfRepayments, final BigDecimal loanCharge,
final ExternalId externalId) {
this.loan = loan;
this.charge = chargeDefinition;
this.submittedOnDate = DateUtils.getBusinessLocalDate();
@@ -278,6 +193,7 @@ public class LoanCharge extends AbstractPersistableCustom {
populateDerivedFields(loanPrincipal, chargeAmount, numberOfRepayments, loanCharge);
this.paid = determineIfFullyPaid();
this.externalId = externalId;
}
private void populateDerivedFields(final BigDecimal amountPercentageAppliedTo, final BigDecimal chargeAmount,
@@ -1093,14 +1009,10 @@ public class LoanCharge extends AbstractPersistableCustom {
this.waived = false;
}
public String getExternalId() {
public ExternalId getExternalId() {
return externalId;
}
public void setExternalId(String externalId) {
this.externalId = externalId;
}
public ChargeTimeType getChargeTimeType() {
return ChargeTimeType.fromInt(this.chargeTime);
}
@@ -18,9 +18,16 @@
*/
package org.apache.fineract.portfolio.loanaccount.domain;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface LoanChargeRepository extends JpaRepository<LoanCharge, Long>, JpaSpecificationExecutor<LoanCharge> {
// no added behaviour
String FIND_ID_BY_EXTERNAL_ID = "SELECT loanCharge.id FROM LoanCharge loanCharge WHERE loanCharge.externalId = :externalId";
@Query(FIND_ID_BY_EXTERNAL_ID)
Long findIdByExternalId(@Param("externalId") ExternalId externalId);
}
@@ -67,7 +67,7 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat
@Override
@Transactional
public void addAccrualAccounting(final Long loanId, final Collection<LoanScheduleAccrualData> loanScheduleAccrualData) {
Collection<LoanChargeData> chargeData = this.loanChargeReadPlatformService.retrieveLoanChargesForAccural(loanId);
Collection<LoanChargeData> chargeData = this.loanChargeReadPlatformService.retrieveLoanChargesForAccrual(loanId);
Collection<LoanSchedulePeriodData> loanWaiverScheduleData = new ArrayList<>(1);
Collection<LoanTransactionData> loanWaiverTransactionData = new ArrayList<>(1);
@@ -87,7 +87,7 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat
public void addPeriodicAccruals(final LocalDate tillDate, Long loanId, Collection<LoanScheduleAccrualData> loanScheduleAccrualData) {
boolean firstTime = true;
LocalDate accruedTill = null;
Collection<LoanChargeData> chargeData = this.loanChargeReadPlatformService.retrieveLoanChargesForAccural(loanId);
Collection<LoanChargeData> chargeData = this.loanChargeReadPlatformService.retrieveLoanChargesForAccrual(loanId);
Collection<LoanSchedulePeriodData> loanWaiverScheduleData = new ArrayList<>(1);
Collection<LoanTransactionData> loanWaiverTransactionData = new ArrayList<>(1);
for (final LoanScheduleAccrualData accrualData : loanScheduleAccrualData) {
@@ -29,14 +29,20 @@ import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.portfolio.charge.domain.Charge;
import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBeAddedException;
import org.apache.fineract.portfolio.charge.exception.LoanChargeWithoutMandatoryFieldException;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
@@ -44,25 +50,17 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTrancheDisbursementC
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class LoanChargeAssembler {
private final FromJsonHelper fromApiJsonHelper;
private final ChargeRepositoryWrapper chargeRepository;
private final LoanChargeRepository loanChargeRepository;
private final LoanProductRepository loanProductRepository;
@Autowired
public LoanChargeAssembler(final FromJsonHelper fromApiJsonHelper, final ChargeRepositoryWrapper chargeRepository,
final LoanChargeRepository loanChargeRepository, final LoanProductRepository loanProductRepository) {
this.fromApiJsonHelper = fromApiJsonHelper;
this.chargeRepository = chargeRepository;
this.loanChargeRepository = loanChargeRepository;
this.loanProductRepository = loanProductRepository;
}
private final ExternalIdFactory externalIdFactory;
public Set<LoanCharge> fromParsedJson(final JsonElement element, List<LoanDisbursementDetails> disbursementDetails) {
JsonArray jsonDisbursement = this.fromApiJsonHelper.extractJsonArrayNamed("disbursementData", element);
@@ -117,6 +115,8 @@ public class LoanChargeAssembler {
locale);
final Integer chargePaymentMode = this.fromApiJsonHelper.extractIntegerNamed("chargePaymentMode", loanChargeElement,
locale);
final String externalIdStr = this.fromApiJsonHelper.extractStringNamed("externalId", loanChargeElement);
final ExternalId externalId = externalIdFactory.create(externalIdStr);
if (id == null) {
final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeId);
@@ -140,8 +140,8 @@ public class LoanChargeAssembler {
chargePaymentModeEnum = ChargePaymentMode.fromInt(chargePaymentMode);
}
if (!isMultiDisbursal) {
final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, principal, amount, chargeTime,
chargeCalculation, dueDate, chargePaymentModeEnum, numberOfRepayments);
final LoanCharge loanCharge = createNewWithoutLoan(chargeDefinition, principal, amount, chargeTime,
chargeCalculation, dueDate, chargePaymentModeEnum, numberOfRepayments, externalId);
loanCharges.add(loanCharge);
} else {
if (topLevelJsonElement.has("disbursementData") && topLevelJsonElement.get("disbursementData").isJsonArray()) {
@@ -159,8 +159,8 @@ public class LoanChargeAssembler {
LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = null;
if (chargeDefinition.isPercentageOfApprovedAmount()
&& disbursementDetail.expectedDisbursementDateAsLocalDate().equals(expectedDisbursementDate)) {
final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, principal, amount,
chargeTime, chargeCalculation, dueDate, chargePaymentModeEnum, numberOfRepayments);
final LoanCharge loanCharge = createNewWithoutLoan(chargeDefinition, principal, amount, chargeTime,
chargeCalculation, dueDate, chargePaymentModeEnum, numberOfRepayments, externalId);
loanCharges.add(loanCharge);
if (loanCharge.isTrancheDisbursementCharge()) {
loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge,
@@ -169,10 +169,10 @@ public class LoanChargeAssembler {
}
} else {
if (disbursementDetail.expectedDisbursementDateAsLocalDate().equals(expectedDisbursementDate)) {
final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition,
final LoanCharge loanCharge = createNewWithoutLoan(chargeDefinition,
disbursementDetail.principal(), amount, chargeTime, chargeCalculation,
disbursementDetail.expectedDisbursementDateAsLocalDate(), chargePaymentModeEnum,
numberOfRepayments);
numberOfRepayments, externalId);
loanCharges.add(loanCharge);
if (loanCharge.isTrancheDisbursementCharge()) {
loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge,
@@ -186,18 +186,18 @@ public class LoanChargeAssembler {
LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = null;
for (LoanDisbursementDetails disbursementDetail : disbursementDetails) {
if (ChargeTimeType.TRANCHE_DISBURSEMENT.getValue().equals(chargeDefinition.getChargeTimeType())) {
final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition,
disbursementDetail.principal(), amount, chargeTime, chargeCalculation,
final LoanCharge loanCharge = createNewWithoutLoan(chargeDefinition, disbursementDetail.principal(),
amount, chargeTime, chargeCalculation,
disbursementDetail.expectedDisbursementDateAsLocalDate(), chargePaymentModeEnum,
numberOfRepayments);
numberOfRepayments, externalId);
loanCharges.add(loanCharge);
loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge, disbursementDetail);
loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge);
}
}
} else {
final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, principal, amount,
chargeTime, chargeCalculation, dueDate, chargePaymentModeEnum, numberOfRepayments);
final LoanCharge loanCharge = createNewWithoutLoan(chargeDefinition, principal, amount, chargeTime,
chargeCalculation, dueDate, chargePaymentModeEnum, numberOfRepayments, externalId);
loanCharges.add(loanCharge);
}
}
@@ -239,4 +239,88 @@ public class LoanChargeAssembler {
}
return associatedChargesForLoan;
}
public LoanCharge createNewFromJson(final Loan loan, final Charge chargeDefinition, final JsonCommand command) {
final LocalDate dueDate = command.localDateValueOfParameterNamed("dueDate");
if (chargeDefinition.getChargeTimeType().equals(ChargeTimeType.SPECIFIED_DUE_DATE.getValue()) && dueDate == null) {
final String defaultUserMessage = "Loan charge is missing due date.";
throw new LoanChargeWithoutMandatoryFieldException("loanCharge", "dueDate", defaultUserMessage, chargeDefinition.getId(),
chargeDefinition.getName());
}
return createNewFromJson(loan, chargeDefinition, command, dueDate);
}
public LoanCharge createNewFromJson(final Loan loan, final Charge chargeDefinition, final JsonCommand command,
final LocalDate dueDate) {
final Locale locale = command.extractLocale();
final BigDecimal amount = command.bigDecimalValueOfParameterNamed("amount", locale);
final ChargeTimeType chargeTime = null;
final ChargeCalculationType chargeCalculation = null;
final ChargePaymentMode chargePaymentMode = null;
BigDecimal amountPercentageAppliedTo = BigDecimal.ZERO;
switch (ChargeCalculationType.fromInt(chargeDefinition.getChargeCalculation())) {
case PERCENT_OF_AMOUNT:
if (command.hasParameter("principal")) {
amountPercentageAppliedTo = command.bigDecimalValueOfParameterNamed("principal");
} else {
amountPercentageAppliedTo = loan.getPrincipal().getAmount();
}
break;
case PERCENT_OF_AMOUNT_AND_INTEREST:
if (command.hasParameter("principal") && command.hasParameter("interest")) {
amountPercentageAppliedTo = command.bigDecimalValueOfParameterNamed("principal")
.add(command.bigDecimalValueOfParameterNamed("interest"));
} else {
amountPercentageAppliedTo = loan.getPrincipal().getAmount().add(loan.getTotalInterest());
}
break;
case PERCENT_OF_INTEREST:
if (command.hasParameter("interest")) {
amountPercentageAppliedTo = command.bigDecimalValueOfParameterNamed("interest");
} else {
amountPercentageAppliedTo = loan.getTotalInterest();
}
break;
default:
break;
}
BigDecimal loanCharge = BigDecimal.ZERO;
if (ChargeTimeType.fromInt(chargeDefinition.getChargeTimeType()).equals(ChargeTimeType.INSTALMENT_FEE)) {
BigDecimal percentage = amount;
if (percentage == null) {
percentage = chargeDefinition.getAmount();
}
loanCharge = loan.calculatePerInstallmentChargeAmount(ChargeCalculationType.fromInt(chargeDefinition.getChargeCalculation()),
percentage);
}
// If charge type is specified due date and loan is multi disburment
// loan.
// Then we need to get as of this loan charge due date how much amount
// disbursed.
if (chargeDefinition.getChargeTimeType().equals(ChargeTimeType.SPECIFIED_DUE_DATE.getValue()) && loan.isMultiDisburmentLoan()) {
amountPercentageAppliedTo = BigDecimal.ZERO;
for (final LoanDisbursementDetails loanDisbursementDetails : loan.getDisbursementDetails()) {
if (!loanDisbursementDetails.expectedDisbursementDate().isAfter(dueDate)) {
amountPercentageAppliedTo = amountPercentageAppliedTo.add(loanDisbursementDetails.principal());
}
}
}
ExternalId externalId = externalIdFactory.createFromCommand(command, "externalId");
return new LoanCharge(loan, chargeDefinition, amountPercentageAppliedTo, amount, chargeTime, chargeCalculation, dueDate,
chargePaymentMode, null, loanCharge, externalId);
}
/*
* loanPrincipal is required for charges that are percentage based
*/
public LoanCharge createNewWithoutLoan(final Charge chargeDefinition, final BigDecimal loanPrincipal, final BigDecimal amount,
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate,
final ChargePaymentMode chargePaymentMode, final Integer numberOfRepayments, final ExternalId externalId) {
return new LoanCharge(null, chargeDefinition, loanPrincipal, amount, chargeTime, chargeCalculation, dueDate, chargePaymentMode,
numberOfRepayments, BigDecimal.ZERO, externalId);
}
}
@@ -19,6 +19,7 @@
package org.apache.fineract.portfolio.loanaccount.service;
import java.util.Collection;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.portfolio.charge.data.ChargeData;
import org.apache.fineract.portfolio.charge.domain.Charge;
import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
@@ -41,8 +42,10 @@ public interface LoanChargeReadPlatformService {
Collection<Integer> retrieveOverdueInstallmentChargeFrequencyNumber(Loan loan, Charge charge, Integer periodNumber);
Collection<LoanChargeData> retrieveLoanChargesForAccural(Long loanId);
Collection<LoanChargeData> retrieveLoanChargesForAccrual(Long loanId);
Collection<LoanChargePaidByData> retrieveLoanChargesPaidBy(Long chargeId, LoanTransactionType transactionType,
Integer installmentNumber);
Long retrieveLoanChargeIdByExternalId(ExternalId loanChargeExternalId);
}
@@ -30,7 +30,9 @@ import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.accounting.glaccount.data.GLAccountData;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.portfolio.charge.data.ChargeData;
import org.apache.fineract.portfolio.charge.domain.Charge;
@@ -42,6 +44,7 @@ import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByData;
import org.apache.fineract.portfolio.loanaccount.data.LoanInstallmentChargeData;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.tax.data.TaxGroupData;
import org.springframework.jdbc.core.JdbcTemplate;
@@ -55,23 +58,24 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
private final JdbcTemplate jdbcTemplate;
private final ChargeDropdownReadPlatformService chargeDropdownReadPlatformService;
private final DropdownReadPlatformService dropdownReadPlatformService;
private final LoanChargeRepository loanChargeRepository;
private static final class LoanChargeMapper implements RowMapper<LoanChargeData> {
public String schema() {
return "lc.id as id, lc.external_id as externalId, c.id as chargeId, c.name as name, lc.submitted_on_date as submittedOnDate, "
+ "lc.amount as amountDue, " + "lc.amount_paid_derived as amountPaid, " + "lc.amount_waived_derived as amountWaived, "
+ "lc.amount_writtenoff_derived as amountWrittenOff, " + "lc.amount_outstanding_derived as amountOutstanding, "
+ "lc.calculation_percentage as percentageOf, lc.calculation_on_amount as amountPercentageAppliedTo, "
+ "lc.charge_time_enum as chargeTime, " + "lc.is_penalty as penalty, "
+ "lc.due_for_collection_as_of_date as dueAsOfDate, " + "lc.charge_calculation_enum as chargeCalculation, "
+ "lc.charge_payment_mode_enum as chargePaymentMode, " + "lc.is_paid_derived as paid, " + "lc.waived as waived, "
+ "lc.min_cap as minCap, lc.max_cap as maxCap, " + "lc.charge_amount_or_percentage as amountOrPercentage, "
+ "lc.loan_id as loanId, c.currency_code as currencyCode, oc.name as currencyName, "
+ "date(coalesce(dd.disbursedon_date,dd.expected_disburse_date)) as disbursementDate, "
+ "oc.decimal_places as currencyDecimalPlaces, oc.currency_multiplesof as inMultiplesOf, oc.display_symbol as currencyDisplaySymbol, "
+ "oc.internationalized_name_code as currencyNameCode from m_charge c "
+ "join m_organisation_currency oc on c.currency_code = oc.code " + "join m_loan_charge lc on lc.charge_id = c.id "
return "lc.id as id, lc.external_id as externalId, c.id as chargeId, c.name as name, lc.submitted_on_date as submittedOnDate, " //
+ "lc.amount as amountDue, lc.amount_paid_derived as amountPaid, lc.amount_waived_derived as amountWaived, " //
+ "lc.amount_writtenoff_derived as amountWrittenOff, lc.amount_outstanding_derived as amountOutstanding, " //
+ "lc.calculation_percentage as percentageOf, lc.calculation_on_amount as amountPercentageAppliedTo, " //
+ "lc.charge_time_enum as chargeTime, lc.is_penalty as penalty, " //
+ "lc.due_for_collection_as_of_date as dueAsOfDate, lc.charge_calculation_enum as chargeCalculation, " //
+ "lc.charge_payment_mode_enum as chargePaymentMode, lc.is_paid_derived as paid, lc.waived as waived, " //
+ "lc.min_cap as minCap, lc.max_cap as maxCap, lc.charge_amount_or_percentage as amountOrPercentage, " //
+ "lc.loan_id as loanId, c.currency_code as currencyCode, oc.name as currencyName, " //
+ "date(coalesce(dd.disbursedon_date,dd.expected_disburse_date)) as disbursementDate, " //
+ "oc.decimal_places as currencyDecimalPlaces, oc.currency_multiplesof as inMultiplesOf, oc.display_symbol as currencyDisplaySymbol, " //
+ "oc.internationalized_name_code as currencyNameCode from m_charge c " //
+ "join m_organisation_currency oc on c.currency_code = oc.code join m_loan_charge lc on lc.charge_id = c.id " //
+ "left join m_loan_tranche_disbursement_charge dc on dc.loan_charge_id=lc.id left join m_loan_disbursement_detail dd on dd.id=dc.disbursement_detail_id ";
}
@@ -122,7 +126,8 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
if (disbursementDate != null) {
dueAsOfDate = disbursementDate;
}
final String externalId = rs.getString("externalId");
final String externalIdStr = rs.getString("externalId");
final ExternalId externalId = ExternalIdFactory.produce(externalIdStr);
return new LoanChargeData(id, chargeId, name, currency, amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding,
chargeTimeType, submittedOnDate, dueAsOfDate, chargeCalculationType, percentageOf, amountPercentageAppliedTo, penalty,
@@ -192,9 +197,9 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
private static final class LoanChargeMapperWithLoanId implements RowMapper<LoanChargeData> {
public String schema() {
return "lc.id as id, lc.due_for_collection_as_of_date as dueAsOfDate, " + "lc.amount_outstanding_derived as amountOutstanding, "
+ "lc.charge_time_enum as chargeTime, " + "loan.id as loanId " + "from m_loan_charge lc "
+ "join m_loan loan on loan.id = lc.loan_id ";
return " lc.id as id, lc.due_for_collection_as_of_date as dueAsOfDate, lc.amount_outstanding_derived as amountOutstanding, "
+ " lc.charge_time_enum as chargeTime, loan.id as loanId, lc.external_id as externalId from m_loan_charge lc "
+ " join m_loan loan on loan.id = lc.loan_id ";
}
@Override
@@ -206,8 +211,10 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
final BigDecimal amountOutstanding = rs.getBigDecimal("amountOutstanding");
final int chargeTime = rs.getInt("chargeTime");
final EnumOptionData chargeTimeType = ChargeEnumerations.chargeTimeType(chargeTime);
final String externalIdStr = rs.getString("externalId");
final ExternalId externalId = ExternalIdFactory.produce(externalIdStr);
return new LoanChargeData(id, dueAsOfDate, amountOutstanding, chargeTimeType, loanId, null);
return new LoanChargeData(id, dueAsOfDate, amountOutstanding, chargeTimeType, loanId, null, externalId);
}
}
@@ -226,8 +233,8 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
public String schema() {
return " lsi.installment as installmentNumber, lsi.duedate as dueAsOfDate, "
+ "lic.amount_outstanding_derived as amountOutstanding," + "lic.amount as amount, " + "lic.is_paid_derived as paid, "
+ "lic.amount_waived_derived as amountWaived, " + "lic.waived as waived " + "from m_loan_installment_charge lic "
+ "lic.amount_outstanding_derived as amountOutstanding, lic.amount as amount, lic.is_paid_derived as paid, "
+ "lic.amount_waived_derived as amountWaived, lic.waived as waived from m_loan_installment_charge lic "
+ "join m_loan_repayment_schedule lsi on lsi.id = lic.loan_schedule_id ";
}
@@ -262,7 +269,7 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
}
@Override
public Collection<LoanChargeData> retrieveLoanChargesForAccural(final Long loanId) {
public Collection<LoanChargeData> retrieveLoanChargesForAccrual(final Long loanId) {
final LoanChargeAccrualMapper rm = new LoanChargeAccrualMapper();
@@ -298,20 +305,20 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
LoanChargeAccrualMapper() {
StringBuilder sb = new StringBuilder(50);
sb.append("lc.id as id, lc.charge_id as chargeId, ");
sb.append("lc.amount as amountDue, ");
sb.append("lc.amount_waived_derived as amountWaived, ");
sb.append("lc.charge_time_enum as chargeTime, ");
sb.append(" lc.id as id, lc.charge_id as chargeId, lc.external_id as externalId, ");
sb.append(" lc.amount as amountDue, ");
sb.append(" lc.amount_waived_derived as amountWaived, ");
sb.append(" lc.charge_time_enum as chargeTime, ");
sb.append(" sum(cp.amount) as amountAccrued, ");
sb.append("lc.is_penalty as penalty, ");
sb.append("lc.due_for_collection_as_of_date as dueAsOfDate ");
sb.append(" lc.is_penalty as penalty, ");
sb.append(" lc.due_for_collection_as_of_date as dueAsOfDate ");
sb.append(" from m_loan_charge lc ");
sb.append("left join (");
sb.append("select lcp.loan_charge_id, lcp.amount");
sb.append(" left join ( ");
sb.append(" select lcp.loan_charge_id, lcp.amount ");
sb.append(" from m_loan_charge_paid_by lcp ");
sb.append(
"inner join m_loan_transaction lt on lt.id = lcp.loan_transaction_id and lt.is_reversed = false and lt.transaction_type_enum = ? and lt.loan_id = ?");
sb.append(") cp on cp.loan_charge_id= lc.id ");
" inner join m_loan_transaction lt on lt.id = lcp.loan_transaction_id and lt.is_reversed = false and lt.transaction_type_enum = ? and lt.loan_id = ? ");
sb.append(" ) cp on cp.loan_charge_id= lc.id ");
schemaSql = sb.toString();
}
@@ -335,7 +342,10 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
final LocalDate dueAsOfDate = JdbcSupport.getLocalDate(rs, "dueAsOfDate");
final boolean penalty = rs.getBoolean("penalty");
return new LoanChargeData(id, chargeId, dueAsOfDate, chargeTimeType, amount, amountAccrued, amountWaived, penalty);
final String externalIdStr = rs.getString("externalId");
final ExternalId externalId = ExternalIdFactory.produce(externalIdStr);
return new LoanChargeData(id, chargeId, dueAsOfDate, chargeTimeType, amount, amountAccrued, amountWaived, penalty, externalId);
}
}
@@ -516,6 +526,11 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
return this.jdbcTemplate.query(sb.toString(), rm, args.toArray());
}
@Override
public Long retrieveLoanChargeIdByExternalId(ExternalId externalId) {
return loanChargeRepository.findIdByExternalId(externalId);
}
private static final class LoanChargesPaidByMapper implements RowMapper<LoanChargePaidByData> {
private final String schemaSql;
@@ -152,8 +152,8 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
private final ConfigurationDomainService configurationDomainService;
private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory;
private final ExternalIdFactory externalIdFactory;
private final AccountTransferDetailRepository accountTransferDetailRepository;
private final LoanChargeAssembler loanChargeAssembler;
private static boolean isPartOfThisInstallment(LoanCharge loanCharge, LoanRepaymentScheduleInstallment e) {
return e.getFromDate().isBefore(loanCharge.getDueDate()) && !loanCharge.getDueDate().isAfter(e.getDueDate());
@@ -185,10 +185,17 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
LocalDate recalculateFrom = loan.fetchInterestRecalculateFromDate();
if (chargeDefinition.isPercentageOfDisbursementAmount()) {
LoanTrancheDisbursementCharge loanTrancheDisbursementCharge;
ExternalId externalId = externalIdFactory.createFromCommand(command, "externalId");
boolean needToGenerateNewExternalId = false;
for (LoanDisbursementDetails disbursementDetail : loanDisburseDetails) {
if (disbursementDetail.actualDisbursementDate() == null) {
loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, disbursementDetail.principal(), null, null, null,
disbursementDetail.expectedDisbursementDateAsLocalDate(), null, null);
// If multiple charges to be applied, only the first one will get the provided externalId, for the
// rest we generate new ones (if needed)
if (needToGenerateNewExternalId) {
externalId = externalIdFactory.create();
}
loanCharge = loanChargeAssembler.createNewWithoutLoan(chargeDefinition, disbursementDetail.principal(), null, null,
null, disbursementDetail.expectedDisbursementDateAsLocalDate(), null, null, externalId);
loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge, disbursementDetail);
loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge);
businessEventNotifierService.notifyPreBusinessEvent(new LoanAddChargeBusinessEvent(loanCharge));
@@ -198,6 +205,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
if (recalculateFrom.isAfter(disbursementDetail.expectedDisbursementDateAsLocalDate())) {
recalculateFrom = disbursementDetail.expectedDisbursementDateAsLocalDate();
}
needToGenerateNewExternalId = true;
}
}
if (loanCharge == null) {
@@ -207,7 +215,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
}
loan.addTrancheLoanCharge(chargeDefinition);
} else {
loanCharge = LoanCharge.createNewFromJson(loan, chargeDefinition, command);
loanCharge = loanChargeAssembler.createNewFromJson(loan, chargeDefinition, command);
businessEventNotifierService.notifyPreBusinessEvent(new LoanAddChargeBusinessEvent(loanCharge));
validateAddLoanCharge(loan, chargeDefinition, loanCharge);
@@ -252,6 +260,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
businessEventNotifierService.notifyPostBusinessEvent(new LoanAddChargeBusinessEvent(loanCharge));
return new CommandProcessingResultBuilder().withCommandId(command.commandId()) //
.withEntityId(loanCharge.getId()) //
.withEntityExternalId(loanCharge.getExternalId()) //
.withOfficeId(loan.getOfficeId()) //
.withClientId(loan.getClientId()) //
.withGroupId(loan.getGroupId()) //
@@ -346,7 +355,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
LoanChargeWaiveCannotBeReversedException.LoanChargeWaiveCannotUndoReason.LOAN_INACTIVE, loanCharge.getId());
}
final Map<String, Object> changes = new LinkedHashMap<>(3);
final Map<String, Object> changes = new LinkedHashMap<>();
businessEventNotifierService.notifyPreBusinessEvent(new LoanWaiveChargeUndoBusinessEvent(loanCharge));
@@ -367,6 +376,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(loanCharge.getId()) //
.withEntityExternalId(loanCharge.getExternalId()) //
.withSubEntityId(loanTransaction.getId()) //
.withSubEntityExternalId(loanTransaction.getExternalId()) //
.withLoanId(loanId) //
@@ -399,6 +409,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(loanChargeId) //
.withEntityExternalId(loanCharge.getExternalId()) //
.withOfficeId(loan.getOfficeId()) //
.withClientId(loan.getClientId()) //
.withGroupId(loan.getGroupId()) //
@@ -458,7 +469,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
loanInstallmentNumber = chargePerInstallment.getRepaymentInstallment().getInstallmentNumber();
}
final Map<String, Object> changes = new LinkedHashMap<>(3);
final Map<String, Object> changes = new LinkedHashMap<>();
changes.put(LoanApiConstants.externalIdParameterName, externalId);
final List<Long> existingTransactionIds = new ArrayList<>();
@@ -490,6 +501,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(loanChargeId) //
.withEntityExternalId(loanCharge.getExternalId()) //
.withSubEntityId(waiveTransaction.getId()) //
.withSubEntityExternalId(waiveTransaction.getExternalId()) //
.withOfficeId(loan.getOfficeId()) //
@@ -523,6 +535,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(loanChargeId) //
.withEntityExternalId(loanCharge.getExternalId()) //
.withOfficeId(loan.getOfficeId()) //
.withClientId(loan.getClientId()) //
.withGroupId(loan.getGroupId()) //
@@ -613,6 +626,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(loanChargeId) //
.withEntityExternalId(loanCharge.getExternalId()) //
.withSubEntityId(loanTransaction.getId()).withSubEntityExternalId(loanTransaction.getExternalId())
.withOfficeId(loan.getOfficeId()) //
.withClientId(loan.getClientId()) //
@@ -629,7 +643,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId);
final LocalDate transactionDate = DateUtils.getBusinessLocalDate();
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("amount");
final ExternalId txnExternalId = externalIdFactory.createFromCommand(command, "externalId");
final ExternalId externalId = externalIdFactory.createFromCommand(command, "externalId");
final String locale = command.locale();
loanChargeAdjustmentEntranceValidation(loanCharge, transactionAmount);
@@ -637,7 +651,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
final CommandProcessingResultBuilder commandProcessingResultBuilder = new CommandProcessingResultBuilder();
LoanTransaction loanTransaction = applyChargeAdjustment(loan, loanCharge, transactionAmount, transactionDate, txnExternalId);
LoanTransaction loanTransaction = applyChargeAdjustment(loan, loanCharge, transactionAmount, transactionDate, externalId);
// Update loan transaction on repayment.
if (AccountType.fromInt(loan.getLoanType()).isIndividualAccount()) {
@@ -658,13 +672,15 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
businessEventNotifierService.notifyPostBusinessEvent(new LoanChargeAdjustmentPostBusinessEvent(loanTransaction));
Map<String, Object> changes = new HashMap<>();
changes.put("externalId", txnExternalId);
changes.put("externalId", externalId);
changes.put("amount", transactionAmount);
changes.put("transactionDate", transactionDate);
changes.put("locale", locale);
return commandProcessingResultBuilder.withCommandId(command.commandId()) //
.withLoanId(loanId) //
.withEntityId(loanChargeId).withSubEntityId(loanTransaction.getId()) //
.withEntityId(loanChargeId) //
.withEntityExternalId(loanCharge.getExternalId()) //
.withSubEntityId(loanTransaction.getId()) //
.withSubEntityExternalId(loanTransaction.getExternalId()) //
.with(changes) //
.build();
@@ -1008,7 +1024,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
businessEventNotifierService.notifyPreBusinessEvent(new LoanApplyOverdueChargeBusinessEvent(loan));
for (Map.Entry<Integer, LocalDate> entry : scheduleDates.entrySet()) {
final LoanCharge loanCharge = LoanCharge.createNewFromJson(loan, chargeDefinition, command, entry.getValue());
final LoanCharge loanCharge = loanChargeAssembler.createNewFromJson(loan, chargeDefinition, command, entry.getValue());
if (BigDecimal.ZERO.compareTo(loanCharge.amount()) == 0) {
continue;
@@ -119,7 +119,6 @@ import org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository;
import org.apache.fineract.portfolio.calendar.domain.CalendarRepository;
import org.apache.fineract.portfolio.calendar.domain.CalendarType;
import org.apache.fineract.portfolio.calendar.exception.CalendarParameterUpdateNotSupportedException;
import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.apache.fineract.portfolio.client.domain.Client;
import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
import org.apache.fineract.portfolio.collateralmanagement.domain.ClientCollateralManagement;
@@ -140,7 +139,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.GroupLoanIndividualMonit
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagement;
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetailsRepository;
@@ -209,8 +207,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final LoanTransactionRepository loanTransactionRepository;
private final LoanTransactionRelationRepository loanTransactionRelationRepository;
private final LoanAssembler loanAssembler;
private final ChargeRepositoryWrapper chargeRepository;
private final LoanChargeRepository loanChargeRepository;
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
private final CalendarInstanceRepository calendarInstanceRepository;
private final PaymentDetailWritePlatformService paymentDetailWritePlatformService;
@@ -220,7 +216,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final AccountTransfersWritePlatformService accountTransfersWritePlatformService;
private final AccountTransfersReadPlatformService accountTransfersReadPlatformService;
private final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService;
private final LoanChargeReadPlatformService loanChargeReadPlatformService;
private final LoanReadPlatformService loanReadPlatformService;
private final FromJsonHelper fromApiJsonHelper;
private final AccountTransferRepository accountTransferRepository;
@@ -245,7 +240,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final LoanRepaymentScheduleInstallmentRepository loanRepaymentScheduleInstallmentRepository;
private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine;
private final LoanAccountLockService loanAccountLockService;
private final ExternalIdFactory externalIdFactory;
@Transactional
@@ -25,6 +25,7 @@ import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Collection;
import java.util.Collections;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.portfolio.charge.domain.Charge;
import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
@@ -83,6 +84,6 @@ public class LoanTest {
private LoanCharge buildLoanCharge() {
return new LoanCharge(mock(Loan.class), mock(Charge.class), new BigDecimal(100), new BigDecimal(100),
ChargeTimeType.TRANCHE_DISBURSEMENT, ChargeCalculationType.FLAT, LocalDate.of(2022, 6, 27), ChargePaymentMode.REGULAR, 1,
new BigDecimal(100));
new BigDecimal(100), ExternalId.generate());
}
}