feat(loan-products): Gold and Warehouse Receipt product fields with validation
Fineract Build & Cucumber tests (without E2E tests) / verify (main, build-core) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, cucumber) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (push) Canceled after 0s
Fineract Docker Builds / build (false, docker-compose-postgresql.yml, postgresql) (push) Canceled after 0s
Fineract Docker Builds / build (true, docker-compose.yml, mariadb) (push) Canceled after 0s
Fineract Documentation build / build (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 1 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 10 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 2 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 3 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 4 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 5 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 6 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 7 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 8 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 9 of 10) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-3) (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with ActiveMQ (push) Canceled after 0s
Fineract Liquibase Only mode - PostgreSQL / test (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with Kafka (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, build-core) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, cucumber) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (push) Canceled after 0s
Fineract Docker Builds / build (false, docker-compose-postgresql.yml, postgresql) (push) Canceled after 0s
Fineract Docker Builds / build (true, docker-compose.yml, mariadb) (push) Canceled after 0s
Fineract Documentation build / build (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 1 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 10 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 2 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 3 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 4 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 5 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 6 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 7 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 8 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 9 of 10) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-3) (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with ActiveMQ (push) Canceled after 0s
Fineract Liquibase Only mode - PostgreSQL / test (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with Kafka (push) Canceled after 0s
- Liquibase 0604 to 0607: columns on m_product_loan_advance_ext for Loan against Gold (LTV cap, purity, rate source, weights, appraiser rule, packet prefix, auction notice, revaluation on NPA and its charge, partial release, third-party gold, disbursement mode and cash limit, LTV breach alert, auction reserve price, redeem before auction) and for Warehouse Receipt (receipt type, WDRA and assay rules, tenure within receipt validity, repository pledge, commodity groups, valuation price source, max loan per quintal, stock inspection, LTV alert, partial release, disposal notice and reserve price). - LoanProductAdvanceExt and LoanProductAdvanceExtService: the new fields are accepted, saved and read back (so Edit and View can use them). - Validation on the merged state: the LTV cap must cover the LTV the margin gives, the breach alert may not sit below the cap, tenure is at most 12 months, redeem-before-sale cannot be switched off, a gold cash disbursement limit is at most 20,000, a warehouse needs a WDRA-registered warehouse and an assay certificate, and the receipt type, commodity groups and price source must be known values. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
+110
@@ -145,6 +145,116 @@ public class LoanProductAdvanceExt {
|
||||
@Column(name = "stock_statement_validity_days")
|
||||
private Integer stockStatementValidityDays;
|
||||
|
||||
// ---- Loan against Gold (pledged ornaments) ---------------------------
|
||||
@Column(name = "gold_max_ltv_pct")
|
||||
private BigDecimal goldMaxLtvPct;
|
||||
|
||||
@Column(name = "gold_min_purity_carat")
|
||||
private Integer goldMinPurityCarat;
|
||||
|
||||
@Column(name = "gold_rate_source")
|
||||
private String goldRateSource;
|
||||
|
||||
@Column(name = "gold_max_per_gram")
|
||||
private BigDecimal goldMaxPerGram;
|
||||
|
||||
@Column(name = "gold_min_net_weight_g")
|
||||
private BigDecimal goldMinNetWeightG;
|
||||
|
||||
@Column(name = "gold_max_net_weight_per_customer_g")
|
||||
private BigDecimal goldMaxNetWeightPerCustomerG;
|
||||
|
||||
@Column(name = "gold_allowed_items")
|
||||
private String goldAllowedItems;
|
||||
|
||||
@Column(name = "gold_appraiser_required")
|
||||
private Boolean goldAppraiserRequired;
|
||||
|
||||
@Column(name = "gold_valuation_certificate_required")
|
||||
private Boolean goldValuationCertificateRequired;
|
||||
|
||||
@Column(name = "gold_max_valuation_per_appraiser")
|
||||
private BigDecimal goldMaxValuationPerAppraiser;
|
||||
|
||||
@Column(name = "gold_packet_prefix")
|
||||
private String goldPacketPrefix;
|
||||
|
||||
@Column(name = "gold_auction_notice_days")
|
||||
private Integer goldAuctionNoticeDays;
|
||||
|
||||
@Column(name = "gold_revalue_on_npa")
|
||||
private Boolean goldRevalueOnNpa;
|
||||
|
||||
@Column(name = "gold_revaluation_charge_applicable")
|
||||
private Boolean goldRevaluationChargeApplicable;
|
||||
|
||||
@Column(name = "gold_partial_release_allowed")
|
||||
private Boolean goldPartialReleaseAllowed;
|
||||
|
||||
@Column(name = "gold_third_party_allowed")
|
||||
private Boolean goldThirdPartyAllowed;
|
||||
|
||||
@Column(name = "gold_disbursement_mode")
|
||||
private String goldDisbursementMode;
|
||||
|
||||
@Column(name = "gold_cash_disbursement_limit")
|
||||
private BigDecimal goldCashDisbursementLimit;
|
||||
|
||||
@Column(name = "gold_ltv_breach_alert_pct")
|
||||
private BigDecimal goldLtvBreachAlertPct;
|
||||
|
||||
@Column(name = "gold_auction_reserve_pct")
|
||||
private BigDecimal goldAuctionReservePct;
|
||||
|
||||
@Column(name = "gold_redeem_before_auction")
|
||||
private Boolean goldRedeemBeforeAuction;
|
||||
|
||||
// ---- Loan against Warehouse Receipt (pledged commodity) ----------------
|
||||
@Column(name = "wr_max_ltv_pct")
|
||||
private BigDecimal wrMaxLtvPct;
|
||||
|
||||
@Column(name = "wr_receipt_type")
|
||||
private String wrReceiptType;
|
||||
|
||||
@Column(name = "wr_wdra_registered")
|
||||
private Boolean wrWdraRegistered;
|
||||
|
||||
@Column(name = "wr_assay_certificate_required")
|
||||
private Boolean wrAssayCertificateRequired;
|
||||
|
||||
@Column(name = "wr_tenure_within_receipt_validity")
|
||||
private Boolean wrTenureWithinReceiptValidity;
|
||||
|
||||
@Column(name = "wr_pledge_marked_before_disbursement")
|
||||
private Boolean wrPledgeMarkedBeforeDisbursement;
|
||||
|
||||
@Column(name = "wr_commodity_groups")
|
||||
private String wrCommodityGroups;
|
||||
|
||||
@Column(name = "wr_valuation_price_source")
|
||||
private String wrValuationPriceSource;
|
||||
|
||||
@Column(name = "wr_max_loan_per_quintal")
|
||||
private BigDecimal wrMaxLoanPerQuintal;
|
||||
|
||||
@Column(name = "wr_stock_inspection_months")
|
||||
private Integer wrStockInspectionMonths;
|
||||
|
||||
@Column(name = "wr_ltv_breach_alert_pct")
|
||||
private BigDecimal wrLtvBreachAlertPct;
|
||||
|
||||
@Column(name = "wr_partial_release_allowed")
|
||||
private Boolean wrPartialReleaseAllowed;
|
||||
|
||||
@Column(name = "wr_disposal_notice_days")
|
||||
private Integer wrDisposalNoticeDays;
|
||||
|
||||
@Column(name = "wr_disposal_reserve_pct")
|
||||
private BigDecimal wrDisposalReservePct;
|
||||
|
||||
@Column(name = "wr_redeem_before_disposal")
|
||||
private Boolean wrRedeemBeforeDisposal;
|
||||
|
||||
// ---- common eligibility & document checklist (Advances and Loans) ----
|
||||
@Column(name = "elig_member_required")
|
||||
private Boolean memberRequired;
|
||||
|
||||
+284
-1
@@ -9,11 +9,15 @@ package org.apache.fineract.portfolio.loanproduct.service;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductAdvanceExt;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductAdvanceExtRepository;
|
||||
@@ -81,7 +85,13 @@ public class LoanProductAdvanceExtService {
|
||||
// SICR & LGD (RBI 2026 ECL framework)
|
||||
"sicrDaysThreshold", "sicrRevolvingDaysThreshold", "sicrRatingDowngradeNotches", "sicrPricingIncreaseTriggerPct",
|
||||
"sicrAssessmentBasis", "sicrRebuttalAllowed", "npaOutOfOrderBasis", "cropDuration", "useOwnLgdModel", "lgdSecuredPct",
|
||||
"lgdUnsecuredPct", "eligibleCollateralType" };
|
||||
"lgdUnsecuredPct", "eligibleCollateralType",
|
||||
// Loan against Gold
|
||||
"goldMaxLtv", "goldMinPurityCarat", "goldRateSource", "goldMaxPerGram", "goldMinNetWeight", "goldMaxNetWeightPerCustomer", "goldAllowedItems", "goldAppraiserRequired", "goldValuationCertificateRequired", "goldMaxValuationPerAppraiser", "goldPacketPrefix", "goldAuctionNoticeDays", "goldRevalueOnNpa", "goldRevaluationChargeApplicable",
|
||||
"goldPartialReleaseAllowed", "goldThirdPartyAllowed", "goldDisbursementMode", "goldCashDisbursementLimit",
|
||||
"goldLtvBreachAlert", "goldAuctionReservePct", "goldRedeemBeforeAuction",
|
||||
// Loan against Warehouse Receipt
|
||||
"wrMaxLtv", "wrReceiptType", "wrWdraRegistered", "wrAssayCertificateRequired", "wrTenureWithinReceiptValidity", "wrPledgeMarkedBeforeDisbursement", "wrCommodityGroups", "wrValuationPriceSource", "wrMaxLoanPerQuintal", "wrStockInspectionMonths", "wrLtvBreachAlert", "wrPartialReleaseAllowed", "wrDisposalNoticeDays", "wrDisposalReservePct", "wrRedeemBeforeDisposal" };
|
||||
|
||||
/**
|
||||
* Insert or partial-update the satellite for this product. Returns the fields that actually changed so the
|
||||
@@ -95,10 +105,205 @@ public class LoanProductAdvanceExtService {
|
||||
final LoanProductAdvanceExt ext = this.repository.findByLoanProductId(loanProduct.getId())
|
||||
.orElseGet(() -> new LoanProductAdvanceExt(loanProduct));
|
||||
applyScalarFields(ext, command, changes);
|
||||
validateGold(ext, command);
|
||||
validateWarehouse(ext, command);
|
||||
this.repository.save(ext);
|
||||
return changes;
|
||||
}
|
||||
|
||||
/** Longest tenure of a bullet Gold loan, in months. */
|
||||
private static final int GOLD_MAX_TENURE_MONTHS = 12;
|
||||
|
||||
/** The most that may be paid out in cash against one gold loan (Income Tax Sec 269SS). */
|
||||
private static final BigDecimal GOLD_CASH_LIMIT = BigDecimal.valueOf(20000);
|
||||
|
||||
/**
|
||||
* Loan against Gold rules, checked on the merged state (what is stored plus what this command changes), so a
|
||||
* partial edit cannot leave the product inconsistent: the LTV cap must cover the LTV the margin gives, the
|
||||
* purity floor is 18 to 24 carat, and a bullet gold loan runs at most 12 months.
|
||||
*/
|
||||
private void validateGold(final LoanProductAdvanceExt ext, final JsonCommand command) {
|
||||
if (!"gold".equals(ext.getProductTypeSubGroup())) {
|
||||
return;
|
||||
}
|
||||
final List<ApiParameterError> errors = new ArrayList<>();
|
||||
final BigDecimal hundred = BigDecimal.valueOf(100);
|
||||
final BigDecimal cap = ext.getGoldMaxLtvPct();
|
||||
if (cap == null || cap.signum() <= 0 || cap.compareTo(hundred) > 0) {
|
||||
errors.add(ApiParameterError.parameterError("validation.msg.loanproduct.gold.maxLtv.invalid",
|
||||
"Maximum LTV cap is required and must be above 0 and at most 100", "goldMaxLtv"));
|
||||
} else if (ext.getMarginPct() != null && hundred.subtract(ext.getMarginPct()).compareTo(cap) > 0) {
|
||||
errors.add(ApiParameterError.parameterError("validation.msg.loanproduct.gold.margin.ltvAboveCap",
|
||||
"Margin gives an LTV above the Maximum LTV cap; raise the margin or the cap", "margin"));
|
||||
}
|
||||
final Integer purity = ext.getGoldMinPurityCarat();
|
||||
if (purity == null || purity < 18 || purity > 24) {
|
||||
errors.add(ApiParameterError.parameterError("validation.msg.loanproduct.gold.minPurity.invalid",
|
||||
"Minimum purity must be between 18 and 24 carat", "goldMinPurityCarat"));
|
||||
}
|
||||
if (isBlank(ext.getGoldRateSource())) {
|
||||
errors.add(ApiParameterError.parameterError("validation.msg.loanproduct.gold.rateSource.required",
|
||||
"Valuation rate source is required", "goldRateSource"));
|
||||
}
|
||||
if (isBlank(ext.getGoldAllowedItems())) {
|
||||
errors.add(ApiParameterError.parameterError("validation.msg.loanproduct.gold.allowedItems.required",
|
||||
"Allowed items is required", "goldAllowedItems"));
|
||||
}
|
||||
if (ext.getGoldAuctionNoticeDays() == null || ext.getGoldAuctionNoticeDays() < 0) {
|
||||
errors.add(ApiParameterError.parameterError("validation.msg.loanproduct.gold.auctionNotice.invalid",
|
||||
"Auction notice period is required and cannot be negative", "goldAuctionNoticeDays"));
|
||||
}
|
||||
if (ext.getGoldMinNetWeightG() != null && ext.getGoldMaxNetWeightPerCustomerG() != null
|
||||
&& ext.getGoldMinNetWeightG().compareTo(ext.getGoldMaxNetWeightPerCustomerG()) > 0) {
|
||||
errors.add(ApiParameterError.parameterError("validation.msg.loanproduct.gold.netWeight.range",
|
||||
"Minimum net weight cannot exceed the maximum per customer", "goldMinNetWeight"));
|
||||
}
|
||||
final BigDecimal alert = ext.getGoldLtvBreachAlertPct();
|
||||
if (alert == null || alert.signum() <= 0 || alert.compareTo(hundred) > 0) {
|
||||
errors.add(ApiParameterError.parameterError("validation.msg.loanproduct.gold.ltvAlert.invalid",
|
||||
"LTV breach alert is required and must be above 0 and at most 100", "goldLtvBreachAlert"));
|
||||
} else if (cap != null && alert.compareTo(cap) < 0) {
|
||||
errors.add(ApiParameterError.parameterError("validation.msg.loanproduct.gold.ltvAlert.belowCap",
|
||||
"LTV breach alert cannot be below the Maximum LTV cap", "goldLtvBreachAlert"));
|
||||
}
|
||||
final BigDecimal reserve = ext.getGoldAuctionReservePct();
|
||||
if (reserve == null || reserve.signum() <= 0 || reserve.compareTo(hundred) > 0) {
|
||||
errors.add(ApiParameterError.parameterError("validation.msg.loanproduct.gold.auctionReserve.invalid",
|
||||
"Auction reserve price is required and must be above 0 and at most 100 percent of market value",
|
||||
"goldAuctionReservePct"));
|
||||
}
|
||||
if (!Boolean.TRUE.equals(ext.getGoldRedeemBeforeAuction())) {
|
||||
errors.add(ApiParameterError.parameterError("validation.msg.loanproduct.gold.redeem.mandatory",
|
||||
"The customer's right to redeem before the auction cannot be switched off", "goldRedeemBeforeAuction"));
|
||||
}
|
||||
final String mode = ext.getGoldDisbursementMode();
|
||||
if (!"account_credit".equals(mode) && !"account_credit_cash_limit".equals(mode)) {
|
||||
errors.add(ApiParameterError.parameterError("validation.msg.loanproduct.gold.disbursementMode.invalid",
|
||||
"Disbursement mode must be account credit, or account credit with a cash limit",
|
||||
"goldDisbursementMode"));
|
||||
} else if ("account_credit_cash_limit".equals(mode)) {
|
||||
final BigDecimal cashLimit = ext.getGoldCashDisbursementLimit();
|
||||
if (cashLimit == null || cashLimit.signum() < 0 || cashLimit.compareTo(GOLD_CASH_LIMIT) > 0) {
|
||||
errors.add(ApiParameterError.parameterError("validation.msg.loanproduct.gold.cashLimit.invalid",
|
||||
"Cash disbursement limit must be between 0 and 20,000 (Income Tax Sec 269SS)",
|
||||
"goldCashDisbursementLimit"));
|
||||
}
|
||||
}
|
||||
if (command.parameterExists("maxNumberOfRepayments")) {
|
||||
final Integer maxTenure = command.integerValueOfParameterNamed("maxNumberOfRepayments");
|
||||
if (maxTenure != null && maxTenure > GOLD_MAX_TENURE_MONTHS) {
|
||||
errors.add(ApiParameterError.parameterError("validation.msg.loanproduct.gold.tenure.exceeds",
|
||||
"A gold loan runs at most " + GOLD_MAX_TENURE_MONTHS + " months", "maxNumberOfRepayments"));
|
||||
}
|
||||
}
|
||||
if (!errors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException(errors);
|
||||
}
|
||||
}
|
||||
|
||||
/** Longest tenure of a warehouse receipt loan, in months. */
|
||||
private static final int WAREHOUSE_MAX_TENURE_MONTHS = 12;
|
||||
|
||||
private static final java.util.Set<String> WR_RECEIPT_TYPES = java.util.Set.of("e_nwr", "paper_nwr", "both");
|
||||
private static final java.util.Set<String> WR_COMMODITY_GROUPS = java.util.Set.of("cereals", "pulses", "oilseeds", "spices",
|
||||
"others");
|
||||
private static final java.util.Set<String> WR_PRICE_SOURCES = java.util.Set.of("mandi_apmc", "msp", "exchange_ncdex",
|
||||
"bank_assessed");
|
||||
|
||||
/**
|
||||
* Loan against Warehouse Receipt rules, checked on the merged state like the gold rules: the LTV cap must cover the
|
||||
* LTV the margin gives, the alert level must not sit below the cap, the receipt must come from a WDRA-registered
|
||||
* warehouse with an assay certificate, the customer's right to redeem stays on, and the loan runs at most 12 months.
|
||||
*/
|
||||
private void validateWarehouse(final LoanProductAdvanceExt ext, final JsonCommand command) {
|
||||
if (!"warehouse".equals(ext.getProductTypeSubGroup())) {
|
||||
return;
|
||||
}
|
||||
final List<ApiParameterError> errors = new ArrayList<>();
|
||||
final BigDecimal hundred = BigDecimal.valueOf(100);
|
||||
final BigDecimal cap = ext.getWrMaxLtvPct();
|
||||
if (cap == null || cap.signum() <= 0 || cap.compareTo(hundred) > 0) {
|
||||
errors.add(ApiParameterError.parameterError("validation.msg.loanproduct.warehouse.maxLtv.invalid",
|
||||
"Maximum LTV cap is required and must be above 0 and at most 100", "wrMaxLtv"));
|
||||
} else if (ext.getMarginPct() != null && hundred.subtract(ext.getMarginPct()).compareTo(cap) > 0) {
|
||||
errors.add(ApiParameterError.parameterError("validation.msg.loanproduct.warehouse.margin.ltvAboveCap",
|
||||
"Margin gives an LTV above the Maximum LTV cap; raise the margin or the cap", "margin"));
|
||||
}
|
||||
if (!WR_RECEIPT_TYPES.contains(ext.getWrReceiptType())) {
|
||||
errors.add(ApiParameterError.parameterError("validation.msg.loanproduct.warehouse.receiptType.invalid",
|
||||
"Receipt type must be e-NWR, paper NWR or both", "wrReceiptType"));
|
||||
}
|
||||
if (!Boolean.TRUE.equals(ext.getWrWdraRegistered())) {
|
||||
errors.add(ApiParameterError.parameterError("validation.msg.loanproduct.warehouse.wdra.mandatory",
|
||||
"The warehouse must be WDRA-registered; this cannot be switched off", "wrWdraRegistered"));
|
||||
}
|
||||
if (!Boolean.TRUE.equals(ext.getWrAssayCertificateRequired())) {
|
||||
errors.add(ApiParameterError.parameterError("validation.msg.loanproduct.warehouse.assay.mandatory",
|
||||
"A quality / assay certificate is mandatory; this cannot be switched off", "wrAssayCertificateRequired"));
|
||||
}
|
||||
if (isBlank(ext.getWrCommodityGroups())) {
|
||||
errors.add(ApiParameterError.parameterError("validation.msg.loanproduct.warehouse.commodityGroups.required",
|
||||
"Pick at least one eligible commodity group", "wrCommodityGroups"));
|
||||
} else {
|
||||
for (final String group : ext.getWrCommodityGroups().split("\\|")) {
|
||||
if (!WR_COMMODITY_GROUPS.contains(group)) {
|
||||
errors.add(ApiParameterError.parameterError("validation.msg.loanproduct.warehouse.commodityGroups.invalid",
|
||||
"Unknown commodity group: " + group, "wrCommodityGroups"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!WR_PRICE_SOURCES.contains(ext.getWrValuationPriceSource())) {
|
||||
errors.add(ApiParameterError.parameterError("validation.msg.loanproduct.warehouse.priceSource.invalid",
|
||||
"Valuation price source is required", "wrValuationPriceSource"));
|
||||
}
|
||||
if (ext.getWrMaxLoanPerQuintal() != null && ext.getWrMaxLoanPerQuintal().signum() <= 0) {
|
||||
errors.add(ApiParameterError.parameterError("validation.msg.loanproduct.warehouse.maxPerQuintal.invalid",
|
||||
"Max loan per quintal must be above 0", "wrMaxLoanPerQuintal"));
|
||||
}
|
||||
if (ext.getWrStockInspectionMonths() == null || ext.getWrStockInspectionMonths() < 1) {
|
||||
errors.add(ApiParameterError.parameterError("validation.msg.loanproduct.warehouse.inspection.invalid",
|
||||
"Stock inspection frequency must be at least 1 month", "wrStockInspectionMonths"));
|
||||
}
|
||||
final BigDecimal alert = ext.getWrLtvBreachAlertPct();
|
||||
if (alert == null || alert.signum() <= 0 || alert.compareTo(hundred) > 0) {
|
||||
errors.add(ApiParameterError.parameterError("validation.msg.loanproduct.warehouse.ltvAlert.invalid",
|
||||
"LTV breach alert is required and must be above 0 and at most 100", "wrLtvBreachAlert"));
|
||||
} else if (cap != null && alert.compareTo(cap) < 0) {
|
||||
errors.add(ApiParameterError.parameterError("validation.msg.loanproduct.warehouse.ltvAlert.belowCap",
|
||||
"LTV breach alert cannot be below the Maximum LTV cap", "wrLtvBreachAlert"));
|
||||
}
|
||||
if (ext.getWrDisposalNoticeDays() == null || ext.getWrDisposalNoticeDays() < 0) {
|
||||
errors.add(ApiParameterError.parameterError("validation.msg.loanproduct.warehouse.disposalNotice.invalid",
|
||||
"Disposal notice period is required and cannot be negative", "wrDisposalNoticeDays"));
|
||||
}
|
||||
final BigDecimal reserve = ext.getWrDisposalReservePct();
|
||||
if (reserve == null || reserve.signum() <= 0 || reserve.compareTo(hundred) > 0) {
|
||||
errors.add(ApiParameterError.parameterError("validation.msg.loanproduct.warehouse.disposalReserve.invalid",
|
||||
"Disposal reserve price is required and must be above 0 and at most 100 percent of market value",
|
||||
"wrDisposalReservePct"));
|
||||
}
|
||||
if (!Boolean.TRUE.equals(ext.getWrRedeemBeforeDisposal())) {
|
||||
errors.add(ApiParameterError.parameterError("validation.msg.loanproduct.warehouse.redeem.mandatory",
|
||||
"The customer's right to redeem before the disposal cannot be switched off", "wrRedeemBeforeDisposal"));
|
||||
}
|
||||
if (command.parameterExists("maxNumberOfRepayments")) {
|
||||
final Integer maxTenure = command.integerValueOfParameterNamed("maxNumberOfRepayments");
|
||||
if (maxTenure != null && maxTenure > WAREHOUSE_MAX_TENURE_MONTHS) {
|
||||
errors.add(ApiParameterError.parameterError("validation.msg.loanproduct.warehouse.tenure.exceeds",
|
||||
"A warehouse receipt loan runs at most " + WAREHOUSE_MAX_TENURE_MONTHS + " months",
|
||||
"maxNumberOfRepayments"));
|
||||
}
|
||||
}
|
||||
if (!errors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException(errors);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isBlank(final String value) {
|
||||
return value == null || value.isBlank();
|
||||
}
|
||||
|
||||
public Map<String, Object> retrieveAsMap(final Long loanProductId) {
|
||||
return this.repository.findByLoanProductId(loanProductId).map(LoanProductAdvanceExtService::toMap)
|
||||
.orElseGet(LinkedHashMap::new);
|
||||
@@ -176,6 +381,48 @@ public class LoanProductAdvanceExtService {
|
||||
setString(command, "preclosureAllowed", ext::getPreclosureAllowed, ext::setPreclosureAllowed, changes);
|
||||
setInteger(command, "lockInMonths", ext::getLockInMonths, ext::setLockInMonths, changes);
|
||||
setString(command, "interestOnPreclosure", ext::getInterestOnPreclosure, ext::setInterestOnPreclosure, changes);
|
||||
setDecimal(command, "goldMaxLtv", ext::getGoldMaxLtvPct, ext::setGoldMaxLtvPct, changes);
|
||||
setInteger(command, "goldMinPurityCarat", ext::getGoldMinPurityCarat, ext::setGoldMinPurityCarat, changes);
|
||||
setString(command, "goldRateSource", ext::getGoldRateSource, ext::setGoldRateSource, changes);
|
||||
setDecimal(command, "goldMaxPerGram", ext::getGoldMaxPerGram, ext::setGoldMaxPerGram, changes);
|
||||
setDecimal(command, "goldMinNetWeight", ext::getGoldMinNetWeightG, ext::setGoldMinNetWeightG, changes);
|
||||
setDecimal(command, "goldMaxNetWeightPerCustomer", ext::getGoldMaxNetWeightPerCustomerG, ext::setGoldMaxNetWeightPerCustomerG, changes);
|
||||
setString(command, "goldAllowedItems", ext::getGoldAllowedItems, ext::setGoldAllowedItems, changes);
|
||||
setBoolean(command, "goldAppraiserRequired", ext::getGoldAppraiserRequired, ext::setGoldAppraiserRequired, changes);
|
||||
setBoolean(command, "goldValuationCertificateRequired", ext::getGoldValuationCertificateRequired, ext::setGoldValuationCertificateRequired, changes);
|
||||
setDecimal(command, "goldMaxValuationPerAppraiser", ext::getGoldMaxValuationPerAppraiser, ext::setGoldMaxValuationPerAppraiser, changes);
|
||||
setString(command, "goldPacketPrefix", ext::getGoldPacketPrefix, ext::setGoldPacketPrefix, changes);
|
||||
setInteger(command, "goldAuctionNoticeDays", ext::getGoldAuctionNoticeDays, ext::setGoldAuctionNoticeDays, changes);
|
||||
setBoolean(command, "goldRevalueOnNpa", ext::getGoldRevalueOnNpa, ext::setGoldRevalueOnNpa, changes);
|
||||
setBoolean(command, "goldRevaluationChargeApplicable", ext::getGoldRevaluationChargeApplicable,
|
||||
ext::setGoldRevaluationChargeApplicable, changes);
|
||||
setBoolean(command, "goldPartialReleaseAllowed", ext::getGoldPartialReleaseAllowed,
|
||||
ext::setGoldPartialReleaseAllowed, changes);
|
||||
setBoolean(command, "goldThirdPartyAllowed", ext::getGoldThirdPartyAllowed, ext::setGoldThirdPartyAllowed,
|
||||
changes);
|
||||
setString(command, "goldDisbursementMode", ext::getGoldDisbursementMode, ext::setGoldDisbursementMode, changes);
|
||||
setDecimal(command, "goldCashDisbursementLimit", ext::getGoldCashDisbursementLimit,
|
||||
ext::setGoldCashDisbursementLimit, changes);
|
||||
setDecimal(command, "goldLtvBreachAlert", ext::getGoldLtvBreachAlertPct, ext::setGoldLtvBreachAlertPct, changes);
|
||||
setDecimal(command, "goldAuctionReservePct", ext::getGoldAuctionReservePct, ext::setGoldAuctionReservePct,
|
||||
changes);
|
||||
setBoolean(command, "goldRedeemBeforeAuction", ext::getGoldRedeemBeforeAuction, ext::setGoldRedeemBeforeAuction,
|
||||
changes);
|
||||
setDecimal(command, "wrMaxLtv", ext::getWrMaxLtvPct, ext::setWrMaxLtvPct, changes);
|
||||
setString(command, "wrReceiptType", ext::getWrReceiptType, ext::setWrReceiptType, changes);
|
||||
setBoolean(command, "wrWdraRegistered", ext::getWrWdraRegistered, ext::setWrWdraRegistered, changes);
|
||||
setBoolean(command, "wrAssayCertificateRequired", ext::getWrAssayCertificateRequired, ext::setWrAssayCertificateRequired, changes);
|
||||
setBoolean(command, "wrTenureWithinReceiptValidity", ext::getWrTenureWithinReceiptValidity, ext::setWrTenureWithinReceiptValidity, changes);
|
||||
setBoolean(command, "wrPledgeMarkedBeforeDisbursement", ext::getWrPledgeMarkedBeforeDisbursement, ext::setWrPledgeMarkedBeforeDisbursement, changes);
|
||||
setString(command, "wrCommodityGroups", ext::getWrCommodityGroups, ext::setWrCommodityGroups, changes);
|
||||
setString(command, "wrValuationPriceSource", ext::getWrValuationPriceSource, ext::setWrValuationPriceSource, changes);
|
||||
setDecimal(command, "wrMaxLoanPerQuintal", ext::getWrMaxLoanPerQuintal, ext::setWrMaxLoanPerQuintal, changes);
|
||||
setInteger(command, "wrStockInspectionMonths", ext::getWrStockInspectionMonths, ext::setWrStockInspectionMonths, changes);
|
||||
setDecimal(command, "wrLtvBreachAlert", ext::getWrLtvBreachAlertPct, ext::setWrLtvBreachAlertPct, changes);
|
||||
setBoolean(command, "wrPartialReleaseAllowed", ext::getWrPartialReleaseAllowed, ext::setWrPartialReleaseAllowed, changes);
|
||||
setInteger(command, "wrDisposalNoticeDays", ext::getWrDisposalNoticeDays, ext::setWrDisposalNoticeDays, changes);
|
||||
setDecimal(command, "wrDisposalReservePct", ext::getWrDisposalReservePct, ext::setWrDisposalReservePct, changes);
|
||||
setBoolean(command, "wrRedeemBeforeDisposal", ext::getWrRedeemBeforeDisposal, ext::setWrRedeemBeforeDisposal, changes);
|
||||
setInteger(command, "revaluationFreqMonths", ext::getRevaluationFreqMonths, ext::setRevaluationFreqMonths,
|
||||
changes);
|
||||
setBoolean(command, "insuranceRequired", ext::getInsuranceRequired, ext::setInsuranceRequired, changes);
|
||||
@@ -445,6 +692,42 @@ public class LoanProductAdvanceExtService {
|
||||
map.put("preclosureAllowed", ext.getPreclosureAllowed());
|
||||
map.put("lockInMonths", ext.getLockInMonths());
|
||||
map.put("interestOnPreclosure", ext.getInterestOnPreclosure());
|
||||
map.put("goldMaxLtv", ext.getGoldMaxLtvPct());
|
||||
map.put("goldMinPurityCarat", ext.getGoldMinPurityCarat());
|
||||
map.put("goldRateSource", ext.getGoldRateSource());
|
||||
map.put("goldMaxPerGram", ext.getGoldMaxPerGram());
|
||||
map.put("goldMinNetWeight", ext.getGoldMinNetWeightG());
|
||||
map.put("goldMaxNetWeightPerCustomer", ext.getGoldMaxNetWeightPerCustomerG());
|
||||
map.put("goldAllowedItems", ext.getGoldAllowedItems());
|
||||
map.put("goldAppraiserRequired", ext.getGoldAppraiserRequired());
|
||||
map.put("goldValuationCertificateRequired", ext.getGoldValuationCertificateRequired());
|
||||
map.put("goldMaxValuationPerAppraiser", ext.getGoldMaxValuationPerAppraiser());
|
||||
map.put("goldPacketPrefix", ext.getGoldPacketPrefix());
|
||||
map.put("goldAuctionNoticeDays", ext.getGoldAuctionNoticeDays());
|
||||
map.put("goldRevalueOnNpa", ext.getGoldRevalueOnNpa());
|
||||
map.put("goldRevaluationChargeApplicable", ext.getGoldRevaluationChargeApplicable());
|
||||
map.put("goldPartialReleaseAllowed", ext.getGoldPartialReleaseAllowed());
|
||||
map.put("goldThirdPartyAllowed", ext.getGoldThirdPartyAllowed());
|
||||
map.put("goldDisbursementMode", ext.getGoldDisbursementMode());
|
||||
map.put("goldCashDisbursementLimit", ext.getGoldCashDisbursementLimit());
|
||||
map.put("goldLtvBreachAlert", ext.getGoldLtvBreachAlertPct());
|
||||
map.put("goldAuctionReservePct", ext.getGoldAuctionReservePct());
|
||||
map.put("goldRedeemBeforeAuction", ext.getGoldRedeemBeforeAuction());
|
||||
map.put("wrMaxLtv", ext.getWrMaxLtvPct());
|
||||
map.put("wrReceiptType", ext.getWrReceiptType());
|
||||
map.put("wrWdraRegistered", ext.getWrWdraRegistered());
|
||||
map.put("wrAssayCertificateRequired", ext.getWrAssayCertificateRequired());
|
||||
map.put("wrTenureWithinReceiptValidity", ext.getWrTenureWithinReceiptValidity());
|
||||
map.put("wrPledgeMarkedBeforeDisbursement", ext.getWrPledgeMarkedBeforeDisbursement());
|
||||
map.put("wrCommodityGroups", ext.getWrCommodityGroups());
|
||||
map.put("wrValuationPriceSource", ext.getWrValuationPriceSource());
|
||||
map.put("wrMaxLoanPerQuintal", ext.getWrMaxLoanPerQuintal());
|
||||
map.put("wrStockInspectionMonths", ext.getWrStockInspectionMonths());
|
||||
map.put("wrLtvBreachAlert", ext.getWrLtvBreachAlertPct());
|
||||
map.put("wrPartialReleaseAllowed", ext.getWrPartialReleaseAllowed());
|
||||
map.put("wrDisposalNoticeDays", ext.getWrDisposalNoticeDays());
|
||||
map.put("wrDisposalReservePct", ext.getWrDisposalReservePct());
|
||||
map.put("wrRedeemBeforeDisposal", ext.getWrRedeemBeforeDisposal());
|
||||
map.put("revaluationFreqMonths", ext.getRevaluationFreqMonths());
|
||||
map.put("insuranceRequired", ext.getInsuranceRequired());
|
||||
map.put("stockStatementFreqMonths", ext.getStockStatementFreqMonths());
|
||||
|
||||
@@ -630,6 +630,10 @@
|
||||
<include file="parts/0599_add_delinquency_range_stage.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0600_create_prudential_floor_master.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0601_add_loan_product_advance_ext_sicr_lgd_fields.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0604_add_gold_loan_product_fields.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0605_add_gold_revaluation_rules.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0606_add_gold_servicing_and_auction_rules.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0607_add_warehouse_receipt_product_fields.xml" relativeToChangelogFile="true"/>
|
||||
|
||||
<!-- DDS: the scheme's own tables, settings and permissions. Kept together and last, because
|
||||
each one builds on the core the block above sets up and none of it builds on DDS. -->
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
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.1.xsd">
|
||||
|
||||
<!-- Loan against Gold: valuation basis, appraiser rule, safe custody and auction notice. Pledge (not lien) security. -->
|
||||
<changeSet author="cbsnexora" id="0604_add_gold_loan_product_fields">
|
||||
<addColumn tableName="m_product_loan_advance_ext">
|
||||
<column name="gold_max_ltv_pct" type="DECIMAL(6,2)"/>
|
||||
<column name="gold_min_purity_carat" type="INT"/>
|
||||
<column name="gold_rate_source" type="VARCHAR(40)"/>
|
||||
<column name="gold_max_per_gram" type="DECIMAL(19,2)"/>
|
||||
<column name="gold_min_net_weight_g" type="DECIMAL(10,2)"/>
|
||||
<column name="gold_max_net_weight_per_customer_g" type="DECIMAL(10,2)"/>
|
||||
<column name="gold_allowed_items" type="VARCHAR(40)"/>
|
||||
<column name="gold_appraiser_required" type="BOOLEAN"/>
|
||||
<column name="gold_valuation_certificate_required" type="BOOLEAN"/>
|
||||
<column name="gold_max_valuation_per_appraiser" type="DECIMAL(19,2)"/>
|
||||
<column name="gold_packet_prefix" type="VARCHAR(20)"/>
|
||||
<column name="gold_auction_notice_days" type="INT"/>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
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.1.xsd">
|
||||
|
||||
<!-- Gold loans are short term, so the gold is not revalued on a schedule: it is revalued when the loan turns NPA,
|
||||
and the customer may be charged for it. -->
|
||||
<changeSet author="cbsnexora" id="0605_add_gold_revaluation_rules">
|
||||
<addColumn tableName="m_product_loan_advance_ext">
|
||||
<column name="gold_revalue_on_npa" type="BOOLEAN"/>
|
||||
<column name="gold_revaluation_charge_applicable" type="BOOLEAN"/>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
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.1.xsd">
|
||||
|
||||
<!-- Loan against Gold: servicing rules (partial release, third-party gold), disbursement mode with the cash limit,
|
||||
the LTV level that flags a falling gold rate, and the auction reserve price / right to redeem. -->
|
||||
<changeSet author="cbsnexora" id="0606_add_gold_servicing_and_auction_rules">
|
||||
<addColumn tableName="m_product_loan_advance_ext">
|
||||
<column name="gold_partial_release_allowed" type="BOOLEAN"/>
|
||||
<column name="gold_third_party_allowed" type="BOOLEAN"/>
|
||||
<column name="gold_disbursement_mode" type="VARCHAR(40)"/>
|
||||
<column name="gold_cash_disbursement_limit" type="DECIMAL(19,2)"/>
|
||||
<column name="gold_ltv_breach_alert_pct" type="DECIMAL(6,2)"/>
|
||||
<column name="gold_auction_reserve_pct" type="DECIMAL(6,2)"/>
|
||||
<column name="gold_redeem_before_auction" type="BOOLEAN"/>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
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.1.xsd">
|
||||
|
||||
<!-- Loan against Warehouse Receipt: the receipt rules (type, WDRA warehouse, assay, validity, repository pledge),
|
||||
the commodity groups and valuation price, stock inspection, LTV alert, and the disposal rules after NPA. -->
|
||||
<changeSet author="cbsnexora" id="0607_add_warehouse_receipt_product_fields">
|
||||
<addColumn tableName="m_product_loan_advance_ext">
|
||||
<column name="wr_max_ltv_pct" type="DECIMAL(6,2)"/>
|
||||
<column name="wr_receipt_type" type="VARCHAR(20)"/>
|
||||
<column name="wr_wdra_registered" type="BOOLEAN"/>
|
||||
<column name="wr_assay_certificate_required" type="BOOLEAN"/>
|
||||
<column name="wr_tenure_within_receipt_validity" type="BOOLEAN"/>
|
||||
<column name="wr_pledge_marked_before_disbursement" type="BOOLEAN"/>
|
||||
<column name="wr_commodity_groups" type="VARCHAR(200)"/>
|
||||
<column name="wr_valuation_price_source" type="VARCHAR(30)"/>
|
||||
<column name="wr_max_loan_per_quintal" type="DECIMAL(19,2)"/>
|
||||
<column name="wr_stock_inspection_months" type="INT"/>
|
||||
<column name="wr_ltv_breach_alert_pct" type="DECIMAL(6,2)"/>
|
||||
<column name="wr_partial_release_allowed" type="BOOLEAN"/>
|
||||
<column name="wr_disposal_notice_days" type="INT"/>
|
||||
<column name="wr_disposal_reserve_pct" type="DECIMAL(6,2)"/>
|
||||
<column name="wr_redeem_before_disposal" type="BOOLEAN"/>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
Reference in New Issue
Block a user