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 @@ + + + + + + + + + + + + + + + + + + + + + + + + +