From e95e981bcd923b77f8549c843e777341744ca53e Mon Sep 17 00:00:00 2001 From: Sonali Likhitkar Date: Mon, 19 Oct 2026 10:55:48 +0530 Subject: [PATCH] feat(loan-products): Gold and Warehouse Receipt product fields with validation - 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 --- .../domain/LoanProductAdvanceExt.java | 110 +++++++ .../service/LoanProductAdvanceExtService.java | 285 +++++++++++++++++- .../db/changelog/tenant/changelog-tenant.xml | 4 + .../0604_add_gold_loan_product_fields.xml | 43 +++ .../parts/0605_add_gold_revaluation_rules.xml | 34 +++ ...6_add_gold_servicing_and_auction_rules.xml | 39 +++ ...7_add_warehouse_receipt_product_fields.xml | 47 +++ 7 files changed, 561 insertions(+), 1 deletion(-) create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant/parts/0604_add_gold_loan_product_fields.xml create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant/parts/0605_add_gold_revaluation_rules.xml create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant/parts/0606_add_gold_servicing_and_auction_rules.xml create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant/parts/0607_add_warehouse_receipt_product_fields.xml diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductAdvanceExt.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductAdvanceExt.java index 0fb10b667..bc1ee3522 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductAdvanceExt.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductAdvanceExt.java @@ -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; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductAdvanceExtService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductAdvanceExtService.java index cdc8465e2..c6fa70495 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductAdvanceExtService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductAdvanceExtService.java @@ -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 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 WR_RECEIPT_TYPES = java.util.Set.of("e_nwr", "paper_nwr", "both"); + private static final java.util.Set WR_COMMODITY_GROUPS = java.util.Set.of("cereals", "pulses", "oilseeds", "spices", + "others"); + private static final java.util.Set 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 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 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()); diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml index 04282767c..539be1edb 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml @@ -630,6 +630,10 @@ + + + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0604_add_gold_loan_product_fields.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0604_add_gold_loan_product_fields.xml new file mode 100644 index 000000000..4864a0fe3 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0604_add_gold_loan_product_fields.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0605_add_gold_revaluation_rules.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0605_add_gold_revaluation_rules.xml new file mode 100644 index 000000000..473e3a5dd --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0605_add_gold_revaluation_rules.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0606_add_gold_servicing_and_auction_rules.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0606_add_gold_servicing_and_auction_rules.xml new file mode 100644 index 000000000..ee8423eb9 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0606_add_gold_servicing_and_auction_rules.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0607_add_warehouse_receipt_product_fields.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0607_add_warehouse_receipt_product_fields.xml new file mode 100644 index 000000000..1b798c23a --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0607_add_warehouse_receipt_product_fields.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + +