From 8ad90f0b0017d076a12a25b2a64d1e19fce212ff Mon Sep 17 00:00:00 2001 From: anshul Date: Wed, 14 Oct 2026 17:40:06 +0530 Subject: [PATCH] feat(investment): posted interest in transaction log --- .../api/InvestmentInterestApiResource.java | 10 ++++ .../data/InvestmentInterestPostingData.java | 51 +++++++++++++++++++ ...InvestmentPurchaseReadPlatformService.java | 20 ++++++++ 3 files changed, 81 insertions(+) create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/investment/data/InvestmentInterestPostingData.java diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/investment/api/InvestmentInterestApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/investment/api/InvestmentInterestApiResource.java index 945c73ff9..11d20681c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/investment/api/InvestmentInterestApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/investment/api/InvestmentInterestApiResource.java @@ -30,6 +30,7 @@ import java.util.List; import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.investment.data.InvestmentInterestDueData; +import org.apache.fineract.portfolio.investment.data.InvestmentInterestPostingData; import org.apache.fineract.portfolio.investment.service.InvestmentPurchaseReadPlatformService; import org.springframework.stereotype.Component; @@ -42,6 +43,15 @@ public class InvestmentInterestApiResource { private final PlatformSecurityContext context; private final InvestmentPurchaseReadPlatformService readPlatformService; + @GET + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Posted interest lines", description = "Coupons and closing provisions already posted to GL with their " + + "voucher, newest first.") + public List retrieveAll() { + this.context.authenticatedUser().validateHasReadPermission(InvestmentInterestApiConstants.ENTITY); + return this.readPlatformService.retrieveInterestPostings(); + } + @GET @Path("due") @Produces({ MediaType.APPLICATION_JSON }) diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/investment/data/InvestmentInterestPostingData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/investment/data/InvestmentInterestPostingData.java new file mode 100644 index 000000000..2d68a7f3e --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/investment/data/InvestmentInterestPostingData.java @@ -0,0 +1,51 @@ +/** + * 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.investment.data; + +import java.math.BigDecimal; +import lombok.Builder; +import lombok.Getter; + +/** An interest line (coupon or closing provision) already posted to GL with its voucher — for the transaction log. */ +@Getter +@Builder +public class InvestmentInterestPostingData { + + private final Long id; + private final Long purchaseId; + private final String dealNo; + private final String certNo; + private final String bondType; + private final String scriptDesc; + private final String lineType; + private final String intDate; + private final String fromDate; + private final String toDate; + private final BigDecimal grossInterest; + private final BigDecimal alreadyBooked; + private final BigDecimal amount; + /** Debit side: the bank for a coupon, interest receivable for a provision. */ + private final String drGlCode; + private final String drGlName; + private final String incomeGlCode; + private final String incomeGlName; + private final String glTransactionId; + private final String voucherNo; + private final String postedOnDate; +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/investment/service/InvestmentPurchaseReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/investment/service/InvestmentPurchaseReadPlatformService.java index cce167f75..3160d3b36 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/investment/service/InvestmentPurchaseReadPlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/investment/service/InvestmentPurchaseReadPlatformService.java @@ -31,6 +31,7 @@ import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.portfolio.investment.api.InvestmentInterestApiConstants; import org.apache.fineract.portfolio.investment.api.InvestmentPurchaseApiConstants; import org.apache.fineract.portfolio.investment.data.InvestmentInterestDueData; +import org.apache.fineract.portfolio.investment.data.InvestmentInterestPostingData; import org.apache.fineract.portfolio.investment.data.InvestmentInterestScheduleData; import org.apache.fineract.portfolio.investment.data.InvestmentPurchaseData; import org.apache.fineract.portfolio.investment.domain.InvestmentInterestPosting; @@ -136,6 +137,25 @@ public class InvestmentPurchaseReadPlatformService { return due; } + /** Interest lines already posted to GL, newest first — they appear in the transaction log beside the purchases. */ + public List retrieveInterestPostings() { + final GLAccount receivableGl = this.glAccountRepository + .findOneByGlCodeWithNotFoundDetection(InvestmentInterestApiConstants.INTEREST_RECEIVABLE_GL_CODE); + return this.postingRepository.findAll(Sort.by(Sort.Direction.DESC, "intDate", "id")).stream().map(x -> { + final InvestmentPurchase p = x.getPurchase(); + final GLAccount incomeGl = this.glAccountRepository + .findOneByGlCodeWithNotFoundDetection(InvestmentInterestApiConstants.interestIncomeGlCode(p.getBondType())); + final GLAccount drGl = x.getBankGlAccount() != null ? x.getBankGlAccount() : receivableGl; + return InvestmentInterestPostingData.builder().id(x.getId()).purchaseId(p.getId()).dealNo(p.getDealNo()).certNo(p.getCertNo()) + .bondType(p.getBondType()).scriptDesc(p.getScript().getScriptDesc()).lineType(x.getLineType()) + .intDate(format(x.getIntDate())).fromDate(format(x.getFromDate())).toDate(format(x.getToDate())) + .grossInterest(x.getGrossInterest()).alreadyBooked(x.getAlreadyBooked()).amount(x.getAmount()) + .drGlCode(drGl.getGlCode()).drGlName(drGl.getName()).incomeGlCode(incomeGl.getGlCode()) + .incomeGlName(incomeGl.getName()).glTransactionId(x.getGlTransactionId()).voucherNo(x.getVoucherNo()) + .postedOnDate(format(x.getPostedOnDate())).build(); + }).toList(); + } + private static Optional postingFor(final List posted, final InvestmentInterestCalculator.ScheduleRow r) { return posted.stream().filter(x -> x.getLineType().equals(r.type()) && x.getIntDate().equals(r.date())).findFirst();