feat(investment): non-SLR rating, 10% limits and debt-only mutual funds
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 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 8 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 E2E Tests / E2E Tests (Shard 9 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-4) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - PostgreSQL / 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 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 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 8 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 E2E Tests / E2E Tests (Shard 9 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-4) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - PostgreSQL / 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 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:
+25
-1
@@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.investment.api;
|
package org.apache.fineract.portfolio.investment.api;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@@ -47,6 +48,26 @@ public final class InvestmentApiConstants {
|
|||||||
public static final Map<String, String> SCRIPT_CODE_PREFIX = Map.of("GOVT_SECURITY", "G", "NON_SLR", "N", "BOND", "B", "MUTUAL_FUND",
|
public static final Map<String, String> SCRIPT_CODE_PREFIX = Map.of("GOVT_SECURITY", "G", "NON_SLR", "N", "BOND", "B", "MUTUAL_FUND",
|
||||||
"M", "FD", "F", "SHARE", "S");
|
"M", "FD", "F", "SHARE", "S");
|
||||||
|
|
||||||
|
/** Bond types that are non-SLR investments, counted against the RBI limit of 10% of deposits. */
|
||||||
|
public static final Set<String> NON_SLR_BOND_TYPES = Set.of("NON_SLR", "BOND", "MUTUAL_FUND");
|
||||||
|
|
||||||
|
/** Bond types that must carry a credit rating: corporate bonds, CPs and CDs. */
|
||||||
|
public static final Set<String> RATED_BOND_TYPES = Set.of("NON_SLR", "BOND");
|
||||||
|
|
||||||
|
/** Credit ratings on the Script Master: long-term (bonds) and short-term (CP / CD) scales, best first. */
|
||||||
|
public static final List<String> RATINGS = List.of("AAA", "AA+", "AA", "AA-", "A+", "A", "A-", "BBB+", "BBB", "BBB-", "BB+", "BB",
|
||||||
|
"BB-", "B+", "B", "B-", "C", "D", "A1+", "A1", "A2+", "A2", "A3+", "A3", "A4+", "A4", "UNRATED");
|
||||||
|
|
||||||
|
/** RBI: corporate bonds rated "A" or higher, CPs and CDs rated "A2" or higher. */
|
||||||
|
public static final Set<String> ELIGIBLE_RATINGS = Set.of("AAA", "AA+", "AA", "AA-", "A+", "A", "A-", "A1+", "A1", "A2+", "A2");
|
||||||
|
|
||||||
|
/** Credit rating agencies registered with SEBI. */
|
||||||
|
public static final List<String> RATING_AGENCIES = List.of("CRISIL", "ICRA", "CARE", "INDIA_RATINGS", "ACUITE", "BRICKWORK",
|
||||||
|
"INFOMERICS");
|
||||||
|
|
||||||
|
/** RBI: only debt and money-market mutual funds; their sub types start with this. */
|
||||||
|
public static final String DEBT_MF_SUB_TYPE_PREFIX = "DEBT";
|
||||||
|
|
||||||
public static final String PARTY_CODE_PREFIX = "PTY-";
|
public static final String PARTY_CODE_PREFIX = "PTY-";
|
||||||
|
|
||||||
// Common parameters
|
// Common parameters
|
||||||
@@ -87,6 +108,9 @@ public final class InvestmentApiConstants {
|
|||||||
public static final String MATURITY_DATE = "maturityDate";
|
public static final String MATURITY_DATE = "maturityDate";
|
||||||
public static final String COMPANY_NAME = "companyName";
|
public static final String COMPANY_NAME = "companyName";
|
||||||
public static final String CORP_CODE = "corpCode";
|
public static final String CORP_CODE = "corpCode";
|
||||||
|
public static final String RATING = "rating";
|
||||||
|
public static final String RATING_AGENCY = "ratingAgency";
|
||||||
|
public static final String LISTED = "listed";
|
||||||
|
|
||||||
public static final Set<String> PARTY_PARAMS = Set.of(BOND_TYPE, PARTY_NAME, ADDRESS1, ADDRESS2, ADDRESS3, CITY, PINCODE, PHONE, FAX,
|
public static final Set<String> PARTY_PARAMS = Set.of(BOND_TYPE, PARTY_NAME, ADDRESS1, ADDRESS2, ADDRESS3, CITY, PINCODE, PHONE, FAX,
|
||||||
BANK_TYPE, GL_ACCOUNT_ID, INTEREST_GL_ACCOUNT_ID, RBI_CODE, EXPOSURE_LIMIT, LEI_NO, CP_BANK_NAME, CP_BRANCH_NAME, CP_ACCOUNT_NO, CP_IFSC, ACTIVE,
|
BANK_TYPE, GL_ACCOUNT_ID, INTEREST_GL_ACCOUNT_ID, RBI_CODE, EXPOSURE_LIMIT, LEI_NO, CP_BANK_NAME, CP_BRANCH_NAME, CP_ACCOUNT_NO, CP_IFSC, ACTIVE,
|
||||||
@@ -94,5 +118,5 @@ public final class InvestmentApiConstants {
|
|||||||
|
|
||||||
public static final Set<String> SCRIPT_PARAMS = Set.of(BOND_TYPE, SLR_TYPE, SCRIPT_DESC, GL_ACCOUNT_ID, ISIN, FACE_VALUE_PER_UNIT,
|
public static final Set<String> SCRIPT_PARAMS = Set.of(BOND_TYPE, SLR_TYPE, SCRIPT_DESC, GL_ACCOUNT_ID, ISIN, FACE_VALUE_PER_UNIT,
|
||||||
INT_RATE, ISSUE_DATE, MATURITY_DATE, COMPANY_NAME, ADDRESS1, ADDRESS2, ADDRESS3, CITY, PINCODE, PHONE, FAX, CORP_CODE,
|
INT_RATE, ISSUE_DATE, MATURITY_DATE, COMPANY_NAME, ADDRESS1, ADDRESS2, ADDRESS3, CITY, PINCODE, PHONE, FAX, CORP_CODE,
|
||||||
ACTIVE, LOCALE, DATE_FORMAT);
|
RATING, RATING_AGENCY, LISTED, ACTIVE, LOCALE, DATE_FORMAT);
|
||||||
}
|
}
|
||||||
|
|||||||
+19
@@ -30,6 +30,7 @@ import jakarta.ws.rs.PathParam;
|
|||||||
import jakarta.ws.rs.Produces;
|
import jakarta.ws.rs.Produces;
|
||||||
import jakarta.ws.rs.QueryParam;
|
import jakarta.ws.rs.QueryParam;
|
||||||
import jakarta.ws.rs.core.MediaType;
|
import jakarta.ws.rs.core.MediaType;
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -39,10 +40,13 @@ import org.apache.fineract.commands.domain.CommandWrapper;
|
|||||||
import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
||||||
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
|
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
import org.apache.fineract.portfolio.investment.data.InvestmentInterestScheduleData;
|
import org.apache.fineract.portfolio.investment.data.InvestmentInterestScheduleData;
|
||||||
|
import org.apache.fineract.portfolio.investment.data.InvestmentNonSlrLimitData;
|
||||||
import org.apache.fineract.portfolio.investment.data.InvestmentPurchaseData;
|
import org.apache.fineract.portfolio.investment.data.InvestmentPurchaseData;
|
||||||
import org.apache.fineract.portfolio.investment.data.InvestmentSaleData;
|
import org.apache.fineract.portfolio.investment.data.InvestmentSaleData;
|
||||||
|
import org.apache.fineract.portfolio.investment.service.InvestmentNonSlrService;
|
||||||
import org.apache.fineract.portfolio.investment.service.InvestmentPurchaseReadPlatformService;
|
import org.apache.fineract.portfolio.investment.service.InvestmentPurchaseReadPlatformService;
|
||||||
import org.apache.fineract.portfolio.investment.service.InvestmentSaleReadPlatformService;
|
import org.apache.fineract.portfolio.investment.service.InvestmentSaleReadPlatformService;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
@@ -57,6 +61,7 @@ public class InvestmentPurchaseApiResource {
|
|||||||
private final InvestmentPurchaseReadPlatformService readPlatformService;
|
private final InvestmentPurchaseReadPlatformService readPlatformService;
|
||||||
private final InvestmentSaleReadPlatformService saleReadPlatformService;
|
private final InvestmentSaleReadPlatformService saleReadPlatformService;
|
||||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||||
|
private final InvestmentNonSlrService nonSlrService;
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Produces({ MediaType.APPLICATION_JSON })
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
@@ -66,6 +71,20 @@ public class InvestmentPurchaseApiResource {
|
|||||||
return this.readPlatformService.retrieveAll();
|
return this.readPlatformService.retrieveAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("non-slr-limit")
|
||||||
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
|
@Operation(summary = "RBI non-SLR limits for a purchase", description = "Where a non-SLR purchase of amount (book value) in scriptId "
|
||||||
|
+ "on dealDate (dd-MM-yyyy, default today) stands against the 10% limits; any of them may be left out.")
|
||||||
|
public InvestmentNonSlrLimitData nonSlrLimit(@QueryParam("scriptId") @Parameter(description = "scriptId") final Long scriptId,
|
||||||
|
@QueryParam("dealDate") @Parameter(description = "dd-MM-yyyy") final String dealDate,
|
||||||
|
@QueryParam("amount") @Parameter(description = "amount") final BigDecimal amount) {
|
||||||
|
this.context.authenticatedUser().validateHasReadPermission(InvestmentPurchaseApiConstants.ENTITY);
|
||||||
|
final LocalDate date = StringUtils.isBlank(dealDate) ? DateUtils.getBusinessLocalDate()
|
||||||
|
: LocalDate.parse(dealDate.trim(), DateTimeFormatter.ofPattern("dd-MM-yyyy"));
|
||||||
|
return this.nonSlrService.limits(date, scriptId, amount);
|
||||||
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("{purchaseId}")
|
@Path("{purchaseId}")
|
||||||
@Produces({ MediaType.APPLICATION_JSON })
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
|
|||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/** Where a new non-SLR purchase stands against the RBI limits on non-SLR and unlisted non-SLR investments. */
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
public class InvestmentNonSlrLimitData {
|
||||||
|
|
||||||
|
/** The 31 March the limits are based on. */
|
||||||
|
private final String asOnDate;
|
||||||
|
/** Total deposits as on that date, as entered for the FD limits; empty until someone enters it. */
|
||||||
|
private final BigDecimal totalDeposits;
|
||||||
|
/** 10% of total deposits. */
|
||||||
|
private final BigDecimal nonSlrLimit;
|
||||||
|
/** Book value of non-SLR investments held or pending settlement, before this deal. */
|
||||||
|
private final BigDecimal nonSlrOutstanding;
|
||||||
|
private final BigDecimal nonSlrAfter;
|
||||||
|
/** Book value of non-SLR investments held on that 31 March. */
|
||||||
|
private final BigDecimal nonSlrBase;
|
||||||
|
/** 10% of the non-SLR investments held on that 31 March. */
|
||||||
|
private final BigDecimal unlistedLimit;
|
||||||
|
private final BigDecimal unlistedOutstanding;
|
||||||
|
private final BigDecimal unlistedAfter;
|
||||||
|
private final BigDecimal newAmount;
|
||||||
|
/** Whether this deal's security is listed or proposed to be listed; unlisted ones also count against the unlisted limit. */
|
||||||
|
private final boolean listed;
|
||||||
|
private final boolean withinLimits;
|
||||||
|
private final String message;
|
||||||
|
}
|
||||||
+5
-1
@@ -54,6 +54,9 @@ public class InvestmentScriptData {
|
|||||||
private final String phone;
|
private final String phone;
|
||||||
private final String fax;
|
private final String fax;
|
||||||
private final String corpCode;
|
private final String corpCode;
|
||||||
|
private final String rating;
|
||||||
|
private final String ratingAgency;
|
||||||
|
private final boolean listed;
|
||||||
private final boolean active;
|
private final boolean active;
|
||||||
|
|
||||||
public static InvestmentScriptData from(final InvestmentScript s) {
|
public static InvestmentScriptData from(final InvestmentScript s) {
|
||||||
@@ -63,7 +66,8 @@ public class InvestmentScriptData {
|
|||||||
.faceValuePerUnit(s.getFaceValuePerUnit()).intRate(s.getIntRate()).issueDate(format(s.getIssueDate()))
|
.faceValuePerUnit(s.getFaceValuePerUnit()).intRate(s.getIntRate()).issueDate(format(s.getIssueDate()))
|
||||||
.maturityDate(format(s.getMaturityDate())).companyName(s.getCompanyName()).address1(s.getAddress1())
|
.maturityDate(format(s.getMaturityDate())).companyName(s.getCompanyName()).address1(s.getAddress1())
|
||||||
.address2(s.getAddress2()).address3(s.getAddress3()).city(s.getCity()).pincode(s.getPincode()).phone(s.getPhone())
|
.address2(s.getAddress2()).address3(s.getAddress3()).city(s.getCity()).pincode(s.getPincode()).phone(s.getPhone())
|
||||||
.fax(s.getFax()).corpCode(s.getCorpCode()).active(s.isActive()).build();
|
.fax(s.getFax()).corpCode(s.getCorpCode()).rating(s.getRating()).ratingAgency(s.getRatingAgency()).listed(s.isListed())
|
||||||
|
.active(s.isActive()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String format(final LocalDate date) {
|
private static String format(final LocalDate date) {
|
||||||
|
|||||||
+11
@@ -102,6 +102,17 @@ public class InvestmentScript extends AbstractAuditableWithUTCDateTimeCustom<Lon
|
|||||||
@Column(name = "corp_code", length = 30)
|
@Column(name = "corp_code", length = 30)
|
||||||
private String corpCode;
|
private String corpCode;
|
||||||
|
|
||||||
|
/** Credit rating, long-term (AAA … D) for bonds or short-term (A1+ … A4) for CPs / CDs; UNRATED when none. */
|
||||||
|
@Column(name = "rating", length = 10)
|
||||||
|
private String rating;
|
||||||
|
|
||||||
|
@Column(name = "rating_agency", length = 30)
|
||||||
|
private String ratingAgency;
|
||||||
|
|
||||||
|
/** Listed on an exchange, or proposed to be listed (RBI counts those as listed when bought). */
|
||||||
|
@Column(name = "is_listed", nullable = false)
|
||||||
|
private boolean listed = true;
|
||||||
|
|
||||||
@Column(name = "is_active", nullable = false)
|
@Column(name = "is_active", nullable = false)
|
||||||
private boolean active = true;
|
private boolean active = true;
|
||||||
}
|
}
|
||||||
|
|||||||
+25
@@ -41,11 +41,17 @@ import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstant
|
|||||||
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.ISIN;
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.ISIN;
|
||||||
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.ISSUE_DATE;
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.ISSUE_DATE;
|
||||||
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.LEI_NO;
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.LEI_NO;
|
||||||
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.LISTED;
|
||||||
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.MATURITY_DATE;
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.MATURITY_DATE;
|
||||||
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.PARTY_NAME;
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.PARTY_NAME;
|
||||||
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.PARTY_PARAMS;
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.PARTY_PARAMS;
|
||||||
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.PHONE;
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.PHONE;
|
||||||
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.PINCODE;
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.PINCODE;
|
||||||
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.RATED_BOND_TYPES;
|
||||||
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.RATING;
|
||||||
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.RATINGS;
|
||||||
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.RATING_AGENCIES;
|
||||||
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.RATING_AGENCY;
|
||||||
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.RBI_CODE;
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.RBI_CODE;
|
||||||
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.RESOURCE_PARTY;
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.RESOURCE_PARTY;
|
||||||
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.RESOURCE_SCRIPT;
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.RESOURCE_SCRIPT;
|
||||||
@@ -200,10 +206,29 @@ public final class InvestmentMasterCommandFromApiJsonDeserializer {
|
|||||||
|
|
||||||
validateOptionalString(element, v, COMPANY_NAME, 150);
|
validateOptionalString(element, v, COMPANY_NAME, 150);
|
||||||
validateOptionalString(element, v, CORP_CODE, 30);
|
validateOptionalString(element, v, CORP_CODE, 30);
|
||||||
|
validateRating(element, v, create && bondType != null && RATED_BOND_TYPES.contains(bondType));
|
||||||
validateAddress(element, v);
|
validateAddress(element, v);
|
||||||
throwIfErrors(errors);
|
throwIfErrors(errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Corporate bonds, CPs and CDs carry their credit rating, so a purchase can be checked against the RBI minimum. */
|
||||||
|
private void validateRating(final JsonElement element, final DataValidatorBuilder v, final boolean required) {
|
||||||
|
final String rating = this.fromApiJsonHelper.extractStringNamed(RATING, element);
|
||||||
|
if (required) {
|
||||||
|
v.reset().parameter(RATING).value(rating).notBlank();
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank(rating)) {
|
||||||
|
v.reset().parameter(RATING).value(rating).isOneOfTheseValues(RATINGS.toArray());
|
||||||
|
}
|
||||||
|
final String agency = this.fromApiJsonHelper.extractStringNamed(RATING_AGENCY, element);
|
||||||
|
if (StringUtils.isNotBlank(agency)) {
|
||||||
|
v.reset().parameter(RATING_AGENCY).value(agency).isOneOfTheseValues(RATING_AGENCIES.toArray());
|
||||||
|
}
|
||||||
|
if (this.fromApiJsonHelper.parameterExists(LISTED, element)) {
|
||||||
|
v.reset().parameter(LISTED).value(this.fromApiJsonHelper.extractBooleanNamed(LISTED, element)).notNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------- shared --------------------
|
// -------------------- shared --------------------
|
||||||
|
|
||||||
private void validateBondType(final JsonElement element, final DataValidatorBuilder v, final boolean required) {
|
private void validateBondType(final JsonElement element, final DataValidatorBuilder v, final boolean required) {
|
||||||
|
|||||||
+8
@@ -40,11 +40,14 @@ import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstant
|
|||||||
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.ISIN;
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.ISIN;
|
||||||
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.ISSUE_DATE;
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.ISSUE_DATE;
|
||||||
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.LEI_NO;
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.LEI_NO;
|
||||||
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.LISTED;
|
||||||
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.MATURITY_DATE;
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.MATURITY_DATE;
|
||||||
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.PARTY_CODE_PREFIX;
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.PARTY_CODE_PREFIX;
|
||||||
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.PARTY_NAME;
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.PARTY_NAME;
|
||||||
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.PHONE;
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.PHONE;
|
||||||
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.PINCODE;
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.PINCODE;
|
||||||
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.RATING;
|
||||||
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.RATING_AGENCY;
|
||||||
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.RBI_CODE;
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.RBI_CODE;
|
||||||
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.SCRIPT_CODE_PREFIX;
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.SCRIPT_CODE_PREFIX;
|
||||||
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.SCRIPT_DESC;
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.SCRIPT_DESC;
|
||||||
@@ -288,6 +291,11 @@ public class InvestmentMasterWritePlatformServiceImpl implements InvestmentMaste
|
|||||||
setIfPresent(command, PHONE, s::setPhone);
|
setIfPresent(command, PHONE, s::setPhone);
|
||||||
setIfPresent(command, FAX, s::setFax);
|
setIfPresent(command, FAX, s::setFax);
|
||||||
setIfPresent(command, CORP_CODE, s::setCorpCode);
|
setIfPresent(command, CORP_CODE, s::setCorpCode);
|
||||||
|
setIfPresent(command, RATING, s::setRating);
|
||||||
|
setIfPresent(command, RATING_AGENCY, s::setRatingAgency);
|
||||||
|
if (command.parameterExists(LISTED)) {
|
||||||
|
s.setListed(command.booleanPrimitiveValueOfParameterNamed(LISTED));
|
||||||
|
}
|
||||||
if (command.parameterExists(FACE_VALUE_PER_UNIT) && command.bigDecimalValueOfParameterNamed(FACE_VALUE_PER_UNIT) != null) {
|
if (command.parameterExists(FACE_VALUE_PER_UNIT) && command.bigDecimalValueOfParameterNamed(FACE_VALUE_PER_UNIT) != null) {
|
||||||
s.setFaceValuePerUnit(command.bigDecimalValueOfParameterNamed(FACE_VALUE_PER_UNIT));
|
s.setFaceValuePerUnit(command.bigDecimalValueOfParameterNamed(FACE_VALUE_PER_UNIT));
|
||||||
}
|
}
|
||||||
|
|||||||
+200
@@ -0,0 +1,200 @@
|
|||||||
|
/**
|
||||||
|
* 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 static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.DEBT_MF_SUB_TYPE_PREFIX;
|
||||||
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.ELIGIBLE_RATINGS;
|
||||||
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.NON_SLR_BOND_TYPES;
|
||||||
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.RATED_BOND_TYPES;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
|
||||||
|
import org.apache.fineract.portfolio.investment.api.InvestmentPurchaseApiConstants;
|
||||||
|
import org.apache.fineract.portfolio.investment.api.InvestmentSaleApiConstants;
|
||||||
|
import org.apache.fineract.portfolio.investment.data.InvestmentNonSlrLimitData;
|
||||||
|
import org.apache.fineract.portfolio.investment.domain.InvestmentDepositBase;
|
||||||
|
import org.apache.fineract.portfolio.investment.domain.InvestmentDepositBaseRepository;
|
||||||
|
import org.apache.fineract.portfolio.investment.domain.InvestmentPurchase;
|
||||||
|
import org.apache.fineract.portfolio.investment.domain.InvestmentPurchaseRepository;
|
||||||
|
import org.apache.fineract.portfolio.investment.domain.InvestmentSale;
|
||||||
|
import org.apache.fineract.portfolio.investment.domain.InvestmentSaleRepository;
|
||||||
|
import org.apache.fineract.portfolio.investment.domain.InvestmentScript;
|
||||||
|
import org.apache.fineract.portfolio.investment.domain.InvestmentScriptRepository;
|
||||||
|
import org.apache.fineract.portfolio.investment.exception.InvestmentScriptNotFoundException;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RBI rules on non-SLR investments (corporate bonds, CPs, CDs and mutual funds). What may be bought: bonds rated "A" or
|
||||||
|
* higher, CPs and CDs rated "A2" or higher, and only debt or money-market mutual funds. How much: non-SLR investments
|
||||||
|
* within 10% of total deposits as on the previous 31 March, and unlisted ones within 10% of the non-SLR investments held
|
||||||
|
* on that date; while unlisted ones are above their limit, no further non-SLR investment is made.
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class InvestmentNonSlrService {
|
||||||
|
|
||||||
|
private static final DateTimeFormatter DATE = DateTimeFormatter.ofPattern("dd-MM-yyyy");
|
||||||
|
private static final BigDecimal LIMIT_PERCENT = BigDecimal.TEN;
|
||||||
|
private static final String MUTUAL_FUND = "MUTUAL_FUND";
|
||||||
|
|
||||||
|
private final InvestmentPurchaseRepository purchaseRepository;
|
||||||
|
private final InvestmentSaleRepository saleRepository;
|
||||||
|
private final InvestmentDepositBaseRepository depositBaseRepository;
|
||||||
|
private final InvestmentScriptRepository scriptRepository;
|
||||||
|
|
||||||
|
/** Blocks a non-SLR purchase the RBI does not permit: a bond, CP or CD below the minimum rating, or a non-debt mutual fund. */
|
||||||
|
public void requireEligible(final InvestmentScript script, final String subType) {
|
||||||
|
if (RATED_BOND_TYPES.contains(script.getBondType())) {
|
||||||
|
if (StringUtils.isBlank(script.getRating())) {
|
||||||
|
throw rule("rating.missing", "Enter the credit rating of script " + script.getScriptCode() + " in Script Master first; "
|
||||||
|
+ "RBI allows bonds rated A or higher and CP / CD rated A2 or higher");
|
||||||
|
}
|
||||||
|
if (!ELIGIBLE_RATINGS.contains(script.getRating())) {
|
||||||
|
throw rule("rating.below.minimum", "Script " + script.getScriptCode() + " is rated " + script.getRating()
|
||||||
|
+ "; RBI allows bonds rated A or higher and CP / CD rated A2 or higher");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (MUTUAL_FUND.equals(script.getBondType()) && !StringUtils.startsWithIgnoreCase(subType, DEBT_MF_SUB_TYPE_PREFIX)) {
|
||||||
|
throw rule("mf.not.debt", "RBI allows only debt and money-market mutual funds; choose a DEBT sub type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The RBI limits before a purchase is saved, for the Purchase Entry screen; the script and amount may be left out. */
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public InvestmentNonSlrLimitData limits(final LocalDate dealDate, final Long scriptId, final BigDecimal amount) {
|
||||||
|
final InvestmentScript script = scriptId == null ? null
|
||||||
|
: this.scriptRepository.findById(scriptId).orElseThrow(() -> new InvestmentScriptNotFoundException(scriptId));
|
||||||
|
return limits(dealDate, script, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The RBI limits for a non-SLR purchase of {@code amount} (book value) in {@code script} (either may be empty) on {@code dealDate}. */
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public InvestmentNonSlrLimitData limits(final LocalDate dealDate, final InvestmentScript script, final BigDecimal amount) {
|
||||||
|
final LocalDate base = InvestmentFdService.previousYearEnd(dealDate);
|
||||||
|
final BigDecimal total = this.depositBaseRepository.findByAsOnDate(base).map(InvestmentDepositBase::getTotalDeposits).orElse(null);
|
||||||
|
final BigDecimal newAmount = amount == null ? BigDecimal.ZERO : amount;
|
||||||
|
final boolean listed = script == null || !unlisted(script);
|
||||||
|
|
||||||
|
BigDecimal outstanding = BigDecimal.ZERO;
|
||||||
|
BigDecimal unlistedOutstanding = BigDecimal.ZERO;
|
||||||
|
BigDecimal nonSlrBase = BigDecimal.ZERO;
|
||||||
|
for (final InvestmentPurchase p : this.purchaseRepository.findAll()) {
|
||||||
|
if (!NON_SLR_BOND_TYPES.contains(p.getBondType())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final BigDecimal now = bookNow(p);
|
||||||
|
outstanding = outstanding.add(now);
|
||||||
|
if (unlisted(p.getScript())) {
|
||||||
|
unlistedOutstanding = unlistedOutstanding.add(now);
|
||||||
|
}
|
||||||
|
nonSlrBase = nonSlrBase.add(bookOn(p, base));
|
||||||
|
}
|
||||||
|
final BigDecimal nonSlrAfter = outstanding.add(newAmount);
|
||||||
|
final BigDecimal unlistedAfter = listed ? unlistedOutstanding : unlistedOutstanding.add(newAmount);
|
||||||
|
final BigDecimal unlistedLimit = percent(nonSlrBase);
|
||||||
|
final InvestmentNonSlrLimitData.InvestmentNonSlrLimitDataBuilder b = InvestmentNonSlrLimitData.builder().asOnDate(base.format(DATE))
|
||||||
|
.totalDeposits(total).nonSlrOutstanding(outstanding).nonSlrAfter(nonSlrAfter).nonSlrBase(nonSlrBase)
|
||||||
|
.unlistedLimit(unlistedLimit).unlistedOutstanding(unlistedOutstanding).unlistedAfter(unlistedAfter).newAmount(newAmount)
|
||||||
|
.listed(listed);
|
||||||
|
if (total == null) {
|
||||||
|
return b.withinLimits(false).message("Enter the bank's total deposits as on " + base.format(DATE)
|
||||||
|
+ " first; the RBI limit on non-SLR investments is worked out from it").build();
|
||||||
|
}
|
||||||
|
final BigDecimal nonSlrLimit = percent(total);
|
||||||
|
b.nonSlrLimit(nonSlrLimit);
|
||||||
|
if (nonSlrAfter.compareTo(nonSlrLimit) > 0) {
|
||||||
|
return b.withinLimits(false).message("Non-SLR investments would be " + plain(nonSlrAfter) + ", above the RBI limit of "
|
||||||
|
+ plain(nonSlrLimit) + " (10% of total deposits " + plain(total) + " as on " + base.format(DATE) + ")").build();
|
||||||
|
}
|
||||||
|
if (unlistedOutstanding.compareTo(unlistedLimit) > 0) {
|
||||||
|
return b.withinLimits(false).message("Unlisted non-SLR investments are " + plain(unlistedOutstanding)
|
||||||
|
+ ", above the RBI limit of " + plain(unlistedLimit) + " (10% of non-SLR investments " + plain(nonSlrBase) + " as on "
|
||||||
|
+ base.format(DATE) + "); no further non-SLR investment until they are within it").build();
|
||||||
|
}
|
||||||
|
if (!listed && unlistedAfter.compareTo(unlistedLimit) > 0) {
|
||||||
|
return b.withinLimits(false)
|
||||||
|
.message("Unlisted non-SLR investments would be " + plain(unlistedAfter) + ", above the RBI limit of "
|
||||||
|
+ plain(unlistedLimit) + " (10% of non-SLR investments " + plain(nonSlrBase) + " as on " + base.format(DATE)
|
||||||
|
+ ")")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
return b.withinLimits(true).message("Within the RBI limits").build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Only bonds, CPs and CDs can be unlisted for the RBI limit; mutual fund units are not reckoned there. */
|
||||||
|
private static boolean unlisted(final InvestmentScript s) {
|
||||||
|
return RATED_BOND_TYPES.contains(s.getBondType()) && !s.isListed();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Book value now of a deal held or pending settlement: cost less what settled sales, amortisation and write-offs took off. */
|
||||||
|
private BigDecimal bookNow(final InvestmentPurchase p) {
|
||||||
|
if (InvestmentPurchaseApiConstants.STATUS_PENDING_SETTLEMENT.equals(p.getStatus())) {
|
||||||
|
return p.getBookValue();
|
||||||
|
}
|
||||||
|
if (!InvestmentPurchaseApiConstants.STATUS_ACTIVE.equals(p.getStatus())) {
|
||||||
|
return BigDecimal.ZERO;
|
||||||
|
}
|
||||||
|
BigDecimal book = p.getBookValue();
|
||||||
|
for (final InvestmentSale s : this.saleRepository.findByPurchaseIdOrderByIdAsc(p.getId())) {
|
||||||
|
if (InvestmentSaleApiConstants.STATUS_SETTLED.equals(s.getStatus()) && s.getBookValueSold() != null) {
|
||||||
|
book = book.subtract(s.getBookValueSold());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return book;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Book value of a deal on {@code date}: settled by then, with units sold or redeemed after it added back. */
|
||||||
|
private BigDecimal bookOn(final InvestmentPurchase p, final LocalDate date) {
|
||||||
|
if (InvestmentPurchaseApiConstants.STATUS_DROPPED.equals(p.getStatus())
|
||||||
|
|| InvestmentPurchaseApiConstants.STATUS_PENDING_SETTLEMENT.equals(p.getStatus()) || p.getSettlementDate().isAfter(date)) {
|
||||||
|
return BigDecimal.ZERO;
|
||||||
|
}
|
||||||
|
BigDecimal units = p.getBalanceUnits();
|
||||||
|
BigDecimal book = p.getBookValue();
|
||||||
|
for (final InvestmentSale s : this.saleRepository.findByPurchaseIdOrderByIdAsc(p.getId())) {
|
||||||
|
if (!InvestmentSaleApiConstants.STATUS_SETTLED.equals(s.getStatus())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (s.getSettlementDate().isAfter(date)) {
|
||||||
|
units = units.add(s.getUnits());
|
||||||
|
} else if (s.getBookValueSold() != null) {
|
||||||
|
book = book.subtract(s.getBookValueSold());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return units.signum() > 0 ? book : BigDecimal.ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BigDecimal percent(final BigDecimal total) {
|
||||||
|
return total.multiply(LIMIT_PERCENT).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String plain(final BigDecimal v) {
|
||||||
|
return v.setScale(2, RoundingMode.HALF_UP).toPlainString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GeneralPlatformDomainRuleException rule(final String code, final String message) {
|
||||||
|
return new GeneralPlatformDomainRuleException("error.msg.investment.nonslr." + code, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
+13
@@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.investment.service;
|
package org.apache.fineract.portfolio.investment.service;
|
||||||
|
|
||||||
|
import static org.apache.fineract.portfolio.investment.api.InvestmentApiConstants.NON_SLR_BOND_TYPES;
|
||||||
import static org.apache.fineract.portfolio.investment.api.InvestmentPurchaseApiConstants.BOND_TYPE;
|
import static org.apache.fineract.portfolio.investment.api.InvestmentPurchaseApiConstants.BOND_TYPE;
|
||||||
import static org.apache.fineract.portfolio.investment.api.InvestmentPurchaseApiConstants.BROKEN_PERIOD_INTEREST_GL_CODE;
|
import static org.apache.fineract.portfolio.investment.api.InvestmentPurchaseApiConstants.BROKEN_PERIOD_INTEREST_GL_CODE;
|
||||||
import static org.apache.fineract.portfolio.investment.api.InvestmentPurchaseApiConstants.BROKERAGE;
|
import static org.apache.fineract.portfolio.investment.api.InvestmentPurchaseApiConstants.BROKERAGE;
|
||||||
@@ -60,6 +61,7 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
|||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||||
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
|
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
import org.apache.fineract.portfolio.investment.data.InvestmentNonSlrLimitData;
|
||||||
import org.apache.fineract.portfolio.investment.domain.InvestmentParty;
|
import org.apache.fineract.portfolio.investment.domain.InvestmentParty;
|
||||||
import org.apache.fineract.portfolio.investment.domain.InvestmentPartyRepository;
|
import org.apache.fineract.portfolio.investment.domain.InvestmentPartyRepository;
|
||||||
import org.apache.fineract.portfolio.investment.domain.InvestmentPurchase;
|
import org.apache.fineract.portfolio.investment.domain.InvestmentPurchase;
|
||||||
@@ -91,6 +93,7 @@ public class InvestmentPurchaseWritePlatformServiceImpl implements InvestmentPur
|
|||||||
private final GLAccountRepositoryWrapper glAccountRepository;
|
private final GLAccountRepositoryWrapper glAccountRepository;
|
||||||
private final JdbcTemplate jdbcTemplate;
|
private final JdbcTemplate jdbcTemplate;
|
||||||
private final InvestmentJournalPoster journalPoster;
|
private final InvestmentJournalPoster journalPoster;
|
||||||
|
private final InvestmentNonSlrService nonSlrService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
@@ -119,6 +122,10 @@ public class InvestmentPurchaseWritePlatformServiceImpl implements InvestmentPur
|
|||||||
if (!party.isActive()) {
|
if (!party.isActive()) {
|
||||||
throw rule("party.inactive", "Party " + party.getPartyCode() + " is inactive");
|
throw rule("party.inactive", "Party " + party.getPartyCode() + " is inactive");
|
||||||
}
|
}
|
||||||
|
final boolean nonSlr = NON_SLR_BOND_TYPES.contains(bondType);
|
||||||
|
if (nonSlr) {
|
||||||
|
this.nonSlrService.requireEligible(script, command.stringValueOfParameterNamedAllowingNull(SUB_TYPE));
|
||||||
|
}
|
||||||
|
|
||||||
final LocalDate dealDate = command.localDateValueOfParameterNamed(DEAL_DATE);
|
final LocalDate dealDate = command.localDateValueOfParameterNamed(DEAL_DATE);
|
||||||
if (script.getMaturityDate() != null && dealDate.isAfter(script.getMaturityDate())) {
|
if (script.getMaturityDate() != null && dealDate.isAfter(script.getMaturityDate())) {
|
||||||
@@ -157,6 +164,12 @@ public class InvestmentPurchaseWritePlatformServiceImpl implements InvestmentPur
|
|||||||
computeSecurity(p, command, script);
|
computeSecurity(p, command, script);
|
||||||
}
|
}
|
||||||
p.setSettlementAmount(p.getBookValue().add(p.getAccruedInterest()).add(totalCharges(p)));
|
p.setSettlementAmount(p.getBookValue().add(p.getAccruedInterest()).add(totalCharges(p)));
|
||||||
|
if (nonSlr) {
|
||||||
|
final InvestmentNonSlrLimitData limit = this.nonSlrService.limits(dealDate, script, p.getBookValue());
|
||||||
|
if (!limit.isWithinLimits()) {
|
||||||
|
throw rule("nonslr.limit.exceeded", limit.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final InvestmentPurchase saved = this.purchaseRepository.saveAndFlush(p);
|
final InvestmentPurchase saved = this.purchaseRepository.saveAndFlush(p);
|
||||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(saved.getId())
|
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(saved.getId())
|
||||||
|
|||||||
@@ -200,4 +200,5 @@
|
|||||||
<include file="parts/0606_add_ctr_monthly_report_job.xml" relativeToChangelogFile="true"/>
|
<include file="parts/0606_add_ctr_monthly_report_job.xml" relativeToChangelogFile="true"/>
|
||||||
<include file="parts/0607_cc_relevant_risk_rules.xml" relativeToChangelogFile="true"/>
|
<include file="parts/0607_cc_relevant_risk_rules.xml" relativeToChangelogFile="true"/>
|
||||||
<include file="parts/0608_apply_rd_base_chart_to_rd_products.xml" relativeToChangelogFile="true"/>
|
<include file="parts/0608_apply_rd_base_chart_to_rd_products.xml" relativeToChangelogFile="true"/>
|
||||||
|
<include file="parts/0609_investment_script_rating.xml" relativeToChangelogFile="true"/>
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.9.xsd">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
RBI: a UCB may buy corporate bonds rated "A" or higher and CPs / CDs rated "A2" or higher, and unlisted non-SLR
|
||||||
|
securities are limited to 10% of its non-SLR investments. The Script Master records each security's credit
|
||||||
|
rating, the agency that gave it and whether it is listed (or proposed to be listed), so a purchase can be checked.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<changeSet author="cbs-anshul" id="0609-1-investment-script-rating">
|
||||||
|
<preConditions onFail="MARK_RAN">
|
||||||
|
<not><columnExists tableName="m_investment_script" columnName="rating"/></not>
|
||||||
|
</preConditions>
|
||||||
|
<addColumn tableName="m_investment_script">
|
||||||
|
<column name="rating" type="VARCHAR(10)"/>
|
||||||
|
<column name="rating_agency" type="VARCHAR(30)"/>
|
||||||
|
<column name="is_listed" type="BOOLEAN" defaultValueBoolean="true">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
|
</databaseChangeLog>
|
||||||
Reference in New Issue
Block a user