feat(investment): due date diary and keep dropped deals so their numbers are not reused
Fineract Build & Cucumber tests (without E2E tests) / verify (main, build-core) (push) Waiting to run
Fineract Build & Cucumber tests (without E2E tests) / verify (main, cucumber) (push) Waiting to run
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (push) Waiting to run
Fineract Docker Builds / build (false, docker-compose-postgresql.yml, postgresql) (push) Waiting to run
Fineract Docker Builds / build (true, docker-compose.yml, mariadb) (push) Waiting to run
Fineract Documentation build / build (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 8 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 9 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 1 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 10 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 2 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 3 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 4 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 5 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 6 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 7 of 10) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-2) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-3) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-4) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-5) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-1) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-2) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-4) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-5) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-1) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-2) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-3) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-4) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (push) Waiting to run
Fineract Liquibase Only mode - PostgreSQL / test (push) Waiting to run
Fineract Messaging Smoke Tests / Smoke Test with ActiveMQ (push) Waiting to run
Fineract Messaging Smoke Tests / Smoke Test with Kafka (push) Waiting to run
Fineract Build & Cucumber tests (without E2E tests) / verify (main, build-core) (push) Waiting to run
Fineract Build & Cucumber tests (without E2E tests) / verify (main, cucumber) (push) Waiting to run
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (push) Waiting to run
Fineract Docker Builds / build (false, docker-compose-postgresql.yml, postgresql) (push) Waiting to run
Fineract Docker Builds / build (true, docker-compose.yml, mariadb) (push) Waiting to run
Fineract Documentation build / build (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 8 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 9 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 1 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 10 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 2 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 3 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 4 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 5 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 6 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 7 of 10) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-2) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-3) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-4) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-5) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-1) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-2) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-4) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-5) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-1) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-2) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-3) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-4) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (push) Waiting to run
Fineract Liquibase Only mode - PostgreSQL / test (push) Waiting to run
Fineract Messaging Smoke Tests / Smoke Test with ActiveMQ (push) Waiting to run
Fineract Messaging Smoke Tests / Smoke Test with Kafka (push) Waiting to run
This commit is contained in:
+78
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* 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.api;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.investment.data.InvestmentDiaryData;
|
||||
import org.apache.fineract.portfolio.investment.service.InvestmentDiaryService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Path("/v1/investment/diary")
|
||||
@Component
|
||||
@Tag(name = "Investment Due Date Diary", description = "Coupons, provisions, amortisation, FD payouts and maturities due in a period.")
|
||||
@RequiredArgsConstructor
|
||||
public class InvestmentDiaryApiResource {
|
||||
|
||||
private static final DateTimeFormatter DATE = DateTimeFormatter.ofPattern("dd-MM-yyyy");
|
||||
/** Default window when no dates are given: today and the next 30 days. */
|
||||
private static final int DEFAULT_DAYS = 30;
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final InvestmentDiaryService diaryService;
|
||||
|
||||
@GET
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Due Date Diary for all investments", description = "fromDate / toDate dd-MM-yyyy; default today to 30 days ahead")
|
||||
public InvestmentDiaryData diary(@QueryParam("fromDate") @Parameter(description = "dd-MM-yyyy") final String fromDate,
|
||||
@QueryParam("toDate") @Parameter(description = "dd-MM-yyyy") final String toDate) {
|
||||
this.context.authenticatedUser().validateHasReadPermission(InvestmentPurchaseApiConstants.ENTITY);
|
||||
final LocalDate today = DateUtils.getBusinessLocalDate();
|
||||
final LocalDate from = date(fromDate, today);
|
||||
final LocalDate to = date(toDate, from.plusDays(DEFAULT_DAYS));
|
||||
if (to.isBefore(from)) {
|
||||
throw new GeneralPlatformDomainRuleException("error.msg.investment.diary.dates", "The To date cannot be before the From date");
|
||||
}
|
||||
return this.diaryService.diary(from, to);
|
||||
}
|
||||
|
||||
private static LocalDate date(final String value, final LocalDate fallback) {
|
||||
if (value == null || value.isBlank()) {
|
||||
return fallback;
|
||||
}
|
||||
try {
|
||||
return LocalDate.parse(value.trim(), DATE);
|
||||
} catch (final DateTimeParseException e) {
|
||||
throw new GeneralPlatformDomainRuleException("error.msg.investment.diary.date.invalid", "Enter the date as DD-MM-YYYY", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
@@ -39,6 +39,8 @@ public final class InvestmentPurchaseApiConstants {
|
||||
public static final String STATUS_SOLD = "SOLD";
|
||||
/** Repaid at maturity. */
|
||||
public static final String STATUS_REDEEMED = "REDEEMED";
|
||||
/** Dropped before settlement; kept so its deal and certificate numbers are never given out again. */
|
||||
public static final String STATUS_DROPPED = "DROPPED";
|
||||
|
||||
/** GLs the settlement entry debits besides the script's investment GL. */
|
||||
public static final String BROKEN_PERIOD_INTEREST_GL_CODE = "500606";
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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 java.util.List;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* Due Date Diary: every dated event of the active investments in a period — Govt Security / bond coupons and 31 Mar /
|
||||
* 30 Sep provisions, maturities, premium amortisation, and FD provisions, payouts and maturities.
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
public class InvestmentDiaryData {
|
||||
|
||||
private final String fromDate;
|
||||
private final String toDate;
|
||||
private final String asOnDate;
|
||||
private final int pendingCount;
|
||||
private final int overdueCount;
|
||||
/** Money expected in the period from coupons, FD payouts and maturities not yet received. */
|
||||
private final BigDecimal expectedInflow;
|
||||
private final List<Row> rows;
|
||||
|
||||
@Getter
|
||||
@Builder
|
||||
public static class Row {
|
||||
|
||||
private final String date;
|
||||
/** COUPON, PROVISION, MATURITY, AMORTISATION (Govt Security / bond); FD_PROVISION, FD_PAYOUT, FD_MATURITY. */
|
||||
private final String kind;
|
||||
/** SECURITY or FD — which screen the entry belongs to. */
|
||||
private final String instrument;
|
||||
private final Long refId;
|
||||
private final String dealNo;
|
||||
/** Script, or bank and certificate of an FD. */
|
||||
private final String particulars;
|
||||
private final BigDecimal amount;
|
||||
/** POSTED, OVERDUE, DUE (today), UPCOMING. */
|
||||
private final String status;
|
||||
private final String voucherNo;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -32,5 +32,5 @@ public interface InvestmentPurchaseRepository extends JpaRepository<InvestmentPu
|
||||
@Query("select p from InvestmentPurchase p where p.id = :purchaseId")
|
||||
Optional<InvestmentPurchase> findByIdWithLock(@Param("purchaseId") Long purchaseId);
|
||||
|
||||
boolean existsByScriptId(Long scriptId);
|
||||
boolean existsByScriptIdAndStatusNot(Long scriptId, String status);
|
||||
}
|
||||
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* 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.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.portfolio.investment.api.InvestmentFdApiConstants;
|
||||
import org.apache.fineract.portfolio.investment.api.InvestmentPurchaseApiConstants;
|
||||
import org.apache.fineract.portfolio.investment.data.InvestmentAmortisationData;
|
||||
import org.apache.fineract.portfolio.investment.data.InvestmentDiaryData;
|
||||
import org.apache.fineract.portfolio.investment.data.InvestmentFdScheduleData;
|
||||
import org.apache.fineract.portfolio.investment.data.InvestmentInterestScheduleData;
|
||||
import org.apache.fineract.portfolio.investment.domain.InvestmentFd;
|
||||
import org.apache.fineract.portfolio.investment.domain.InvestmentFdRepository;
|
||||
import org.apache.fineract.portfolio.investment.domain.InvestmentPurchase;
|
||||
import org.apache.fineract.portfolio.investment.domain.InvestmentPurchaseRepository;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Due Date Diary: the dated events of every active investment between two dates, taken from the same schedules the
|
||||
* entry screens use, so the diary always agrees with Interest Entry, Amortize Entry and the FD screens.
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class InvestmentDiaryService {
|
||||
|
||||
private static final DateTimeFormatter DATE = DateTimeFormatter.ofPattern("dd-MM-yyyy");
|
||||
private static final String SECURITY = "SECURITY";
|
||||
private static final String FD = "FD";
|
||||
/** Events that bring money in; provisions and amortisation are book entries only. */
|
||||
private static final Set<String> CASH_KINDS = Set.of("COUPON", "MATURITY", "FD_PAYOUT", "FD_MATURITY");
|
||||
|
||||
private final InvestmentPurchaseRepository purchaseRepository;
|
||||
private final InvestmentFdRepository fdRepository;
|
||||
private final InvestmentPurchaseReadPlatformService purchaseReadPlatformService;
|
||||
private final InvestmentAmortisationService amortisationService;
|
||||
private final InvestmentFdInterestService fdInterestService;
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public InvestmentDiaryData diary(final LocalDate from, final LocalDate to) {
|
||||
final LocalDate today = DateUtils.getBusinessLocalDate();
|
||||
final List<InvestmentDiaryData.Row> rows = new ArrayList<>();
|
||||
|
||||
for (final InvestmentPurchase p : this.purchaseRepository.findAll(Sort.by(Sort.Direction.ASC, "id"))) {
|
||||
if (!InvestmentPurchaseApiConstants.STATUS_ACTIVE.equals(p.getStatus())) {
|
||||
continue;
|
||||
}
|
||||
final InvestmentInterestScheduleData s = this.purchaseReadPlatformService.retrieveInterestSchedule(p.getId(), null);
|
||||
final String particulars = s.getScriptCode() + " " + s.getScriptDesc();
|
||||
for (final InvestmentInterestScheduleData.Row r : s.getRows()) {
|
||||
// A coupon shows the cash coming in (the full coupon); a provision shows what it books.
|
||||
final BigDecimal amount = "COUPON".equals(r.getType()) ? r.getGrossInterest() : r.getAmount();
|
||||
add(rows, from, to, today, r.getIntDate(), r.getType(), SECURITY, p.getId(), p.getDealNo(), particulars, amount,
|
||||
r.isPosted(), r.getVoucherNo());
|
||||
}
|
||||
if (p.getMaturityDate() != null) {
|
||||
add(rows, from, to, today, s.getMaturityDate(), "MATURITY", SECURITY, p.getId(), p.getDealNo(), particulars,
|
||||
s.getFaceValue(), false, null);
|
||||
}
|
||||
if (p.getCategory() != null && p.getCategory().startsWith("HTM") && p.getPremiumDiscount() != null
|
||||
&& p.getPremiumDiscount().signum() > 0) {
|
||||
final InvestmentAmortisationData a = this.amortisationService.schedule(p.getId());
|
||||
for (final InvestmentAmortisationData.Row r : a.getRows()) {
|
||||
if ("YEAR".equals(r.getKind()) && r.getAmount() != null && r.getAmount().signum() > 0) {
|
||||
add(rows, from, to, today, r.getFyEnd(), "AMORTISATION", SECURITY, p.getId(), p.getDealNo(), particulars,
|
||||
r.getAmount(), r.isPosted(), r.getVoucherNo());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (final InvestmentFd f : this.fdRepository.findByStatusOrderByIdAsc(InvestmentFdApiConstants.STATUS_ACTIVE)) {
|
||||
final InvestmentFdScheduleData s = this.fdInterestService.schedule(f.getId());
|
||||
final String particulars = f.getParty().getPartyName() + " · " + f.getCertNo();
|
||||
for (final InvestmentFdScheduleData.Row r : s.getRows()) {
|
||||
final boolean maturity = InvestmentFdApiConstants.LINE_MATURITY.equals(r.getLineType());
|
||||
// Maturity: the maturity amount; a payout: the interest paid (net of TDS); a provision: what it books.
|
||||
final BigDecimal amount = maturity ? f.getExpectedMaturityAmount()
|
||||
: InvestmentFdApiConstants.LINE_PAYOUT.equals(r.getLineType())
|
||||
? r.getGrossInterest().subtract(r.getTds() == null ? BigDecimal.ZERO : r.getTds())
|
||||
: r.getAmount();
|
||||
add(rows, from, to, today, r.getIntDate(), "FD_" + r.getLineType(), FD, f.getId(), f.getDealNo(), particulars, amount,
|
||||
r.isPosted(), r.getVoucherNo());
|
||||
}
|
||||
}
|
||||
|
||||
rows.sort(Comparator.comparing((InvestmentDiaryData.Row r) -> LocalDate.parse(r.getDate(), DATE))
|
||||
.thenComparing(InvestmentDiaryData.Row::getInstrument).thenComparing(InvestmentDiaryData.Row::getDealNo));
|
||||
final BigDecimal inflow = rows.stream().filter(r -> CASH_KINDS.contains(r.getKind()) && !"POSTED".equals(r.getStatus()))
|
||||
.map(InvestmentDiaryData.Row::getAmount).filter(a -> a != null).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
return InvestmentDiaryData.builder().fromDate(from.format(DATE)).toDate(to.format(DATE)).asOnDate(today.format(DATE))
|
||||
.pendingCount((int) rows.stream().filter(r -> !"POSTED".equals(r.getStatus())).count())
|
||||
.overdueCount((int) rows.stream().filter(r -> "OVERDUE".equals(r.getStatus())).count()).expectedInflow(inflow).rows(rows)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static void add(final List<InvestmentDiaryData.Row> rows, final LocalDate from, final LocalDate to, final LocalDate today,
|
||||
final String date, final String kind, final String instrument, final Long refId, final String dealNo, final String particulars,
|
||||
final BigDecimal amount, final boolean posted, final String voucherNo) {
|
||||
if (date == null) {
|
||||
return;
|
||||
}
|
||||
final LocalDate d = LocalDate.parse(date, DATE);
|
||||
if (d.isBefore(from) || d.isAfter(to)) {
|
||||
return;
|
||||
}
|
||||
final String status = posted ? "POSTED" : d.isBefore(today) ? "OVERDUE" : d.isEqual(today) ? "DUE" : "UPCOMING";
|
||||
rows.add(InvestmentDiaryData.Row.builder().date(date).kind(kind).instrument(instrument).refId(refId).dealNo(dealNo)
|
||||
.particulars(particulars).amount(amount).status(status).voucherNo(voucherNo).build());
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -261,7 +261,8 @@ public class InvestmentMasterWritePlatformServiceImpl implements InvestmentMaste
|
||||
&& !Objects.equals(command.localDateValueOfParameterNamed(MATURITY_DATE), s.getMaturityDate())) {
|
||||
changed.add("maturity date");
|
||||
}
|
||||
if (!changed.isEmpty() && this.purchaseRepository.existsByScriptId(s.getId())) {
|
||||
if (!changed.isEmpty() && this.purchaseRepository.existsByScriptIdAndStatusNot(s.getId(),
|
||||
org.apache.fineract.portfolio.investment.api.InvestmentPurchaseApiConstants.STATUS_DROPPED)) {
|
||||
throw new GeneralPlatformDomainRuleException("error.msg.investmentscript.terms.in.use", "Script " + s.getScriptCode()
|
||||
+ " is already used by deals, so its " + String.join(", ", changed) + " cannot be changed", s.getScriptCode());
|
||||
}
|
||||
|
||||
+4
-2
@@ -54,9 +54,11 @@ public class InvestmentPurchaseReadPlatformService {
|
||||
private final InvestmentInterestPostingRepository postingRepository;
|
||||
private final GLAccountRepositoryWrapper glAccountRepository;
|
||||
|
||||
/** All deals, newest first — the Holdings register. */
|
||||
/** All deals, newest first — the Holdings register. Dropped deals only hold their numbers and are left out. */
|
||||
public List<InvestmentPurchaseData> retrieveAll() {
|
||||
return this.purchaseRepository.findAll(Sort.by(Sort.Direction.DESC, "id")).stream().map(InvestmentPurchaseData::from).toList();
|
||||
return this.purchaseRepository.findAll(Sort.by(Sort.Direction.DESC, "id")).stream()
|
||||
.filter(p -> !InvestmentPurchaseApiConstants.STATUS_DROPPED.equals(p.getStatus())).map(InvestmentPurchaseData::from)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public InvestmentPurchaseData retrieveOne(final Long purchaseId) {
|
||||
|
||||
+4
-2
@@ -40,6 +40,7 @@ import static org.apache.fineract.portfolio.investment.api.InvestmentPurchaseApi
|
||||
import static org.apache.fineract.portfolio.investment.api.InvestmentPurchaseApiConstants.STAMP_DUTY;
|
||||
import static org.apache.fineract.portfolio.investment.api.InvestmentPurchaseApiConstants.STAMP_DUTY_GL_CODE;
|
||||
import static org.apache.fineract.portfolio.investment.api.InvestmentPurchaseApiConstants.STATUS_ACTIVE;
|
||||
import static org.apache.fineract.portfolio.investment.api.InvestmentPurchaseApiConstants.STATUS_DROPPED;
|
||||
import static org.apache.fineract.portfolio.investment.api.InvestmentPurchaseApiConstants.STATUS_PENDING_SETTLEMENT;
|
||||
import static org.apache.fineract.portfolio.investment.api.InvestmentPurchaseApiConstants.SUB_TYPE;
|
||||
|
||||
@@ -173,8 +174,9 @@ public class InvestmentPurchaseWritePlatformServiceImpl implements InvestmentPur
|
||||
if (p.getBalanceUnits().compareTo(p.getUnits()) != 0) {
|
||||
throw rule("purchase.partly.sold", "Deal " + p.getDealNo() + " has been partly sold and cannot be dropped");
|
||||
}
|
||||
this.purchaseRepository.delete(p);
|
||||
this.purchaseRepository.flush();
|
||||
// Kept as DROPPED rather than deleted, so the next deal does not reuse its deal or certificate number.
|
||||
p.setStatus(STATUS_DROPPED);
|
||||
this.purchaseRepository.saveAndFlush(p);
|
||||
return new CommandProcessingResultBuilder().withEntityId(purchaseId).withResourceIdAsString(p.getDealNo()).build();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user