669 lines
23 KiB
Markdown
669 lines
23 KiB
Markdown
# Savings Product Payload Implementation Guide
|
|
|
|
## Overview
|
|
Your UI has been updated to send a comprehensive new payload with additional fields for the `/savingsproducts` POST endpoint. This document outlines **all changes required** in the backend to support these new fields.
|
|
|
|
---
|
|
|
|
## 🔍 New Fields in Your Payload
|
|
|
|
### **Simple Fields (Direct Columns in m_savings_product table)**
|
|
| Field | Type | Example | Description |
|
|
|-------|------|---------|-------------|
|
|
| accountType | String | "Small Saving Account" | Type of savings account |
|
|
| customerSegment | String | "Individual" | Customer classification |
|
|
| productCode | String | "SB-IND-001" | Product identifier code |
|
|
| effectiveDate | Date | "10 April 2026" | When the product becomes effective |
|
|
| effectiveTo | Date | "10 April 2026" | Validity end date |
|
|
| endDate | Date | "10 April 2026" | Product end date |
|
|
| introducerRequired | Boolean | true | If introducer/referrer is mandatory |
|
|
| shareLinkingEnabled | Boolean | true | If share account linking allowed |
|
|
| nominationFacility | Boolean | true | If nomination facility available |
|
|
| dormancyAlert | Boolean | true | If dormancy alerts enabled |
|
|
| interestCalculateSelector | Integer | 2 | Interest calculation method selector |
|
|
| averagePeriod | String | "Monthly" | Period for averaging balance |
|
|
| autoPost | Boolean | true | Auto-post interest |
|
|
| frequencyFixDay | Integer | 30 | Fixed day for frequency |
|
|
| lastInterestDate | Date | "10 April 2026" | Last interest posting date |
|
|
| enableLockinPeriod | Boolean | true | If lock-in period enforced |
|
|
|
|
### **Complex Objects (Require Separate Tables)**
|
|
|
|
#### 1️⃣ **accountFacilityConfig** (Single object with facility booleans)
|
|
```json
|
|
{
|
|
"atmFacility": true,
|
|
"atmWithdrawalFacility": true,
|
|
"upiFacility": true,
|
|
"posTransactionsFacility": true,
|
|
"chequeBook": true,
|
|
"impsFacility": true,
|
|
"smsAlerts": true,
|
|
"bbpsFacility": true,
|
|
"insuranceCover": true,
|
|
"bepgFacility": true,
|
|
"neftFacility": true,
|
|
"rtgsFacility": true,
|
|
"interBankTotalCapFacility": true,
|
|
"withdrawalFacility": true,
|
|
"depositFacilityKyc": true,
|
|
"virtualDebitCard": true
|
|
}
|
|
```
|
|
→ **Table:** `m_savings_product_facility_config` (One-to-One relationship)
|
|
|
|
#### 2️⃣ **accountFacilityLimits** (Array of limit objects)
|
|
```json
|
|
[
|
|
{"limitType": "ATM Withdrawal", "generalLimit": 40000, "minorAccountLimit": 5000, "smallSavingLimit": 10000},
|
|
{"limitType": "POS Transactions", "generalLimit": 100000, "minorAccountLimit": 2000, "smallSavingLimit": 10000},
|
|
...
|
|
]
|
|
```
|
|
→ **Table:** `m_savings_product_facility_limits` (One-to-Many relationship)
|
|
|
|
#### 3️⃣ **depositLimitKyc** (Nested KYC limit rules)
|
|
```json
|
|
{
|
|
"general": {"kycType": "Full KYC", "limit": "No Limit"},
|
|
"minor": {"kycType": "Guardian KYC", "limit": 100000},
|
|
"smallSaving": {"kycType": "Min KYC", "limit": 50000}
|
|
}
|
|
```
|
|
→ **Table:** `m_savings_product_deposit_limit_kyc` (One-to-One relationship, or JSON column)
|
|
|
|
---
|
|
|
|
## 📋 Database Changes Required
|
|
|
|
### **1. Alter m_savings_product table**
|
|
```sql
|
|
ALTER TABLE m_savings_product ADD COLUMN (
|
|
account_type VARCHAR(50),
|
|
customer_segment VARCHAR(50),
|
|
product_code VARCHAR(20) UNIQUE,
|
|
effective_date DATE,
|
|
effective_to DATE,
|
|
end_date DATE,
|
|
introducer_required BOOLEAN DEFAULT FALSE,
|
|
share_linking_enabled BOOLEAN DEFAULT FALSE,
|
|
nomination_facility BOOLEAN DEFAULT FALSE,
|
|
dormancy_alert BOOLEAN DEFAULT FALSE,
|
|
interest_calculate_selector INT,
|
|
average_period VARCHAR(20),
|
|
auto_post BOOLEAN DEFAULT FALSE,
|
|
frequency_fix_day INT,
|
|
last_interest_date DATE,
|
|
enable_lockin_period BOOLEAN DEFAULT FALSE
|
|
);
|
|
```
|
|
|
|
### **2. Create m_savings_product_facility_config table** (One-to-One)
|
|
```sql
|
|
CREATE TABLE m_savings_product_facility_config (
|
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
|
savings_product_id BIGINT NOT NULL UNIQUE,
|
|
atm_facility BOOLEAN DEFAULT FALSE,
|
|
atm_withdrawal_facility BOOLEAN DEFAULT FALSE,
|
|
upi_facility BOOLEAN DEFAULT FALSE,
|
|
pos_transactions_facility BOOLEAN DEFAULT FALSE,
|
|
cheque_book BOOLEAN DEFAULT FALSE,
|
|
imps_facility BOOLEAN DEFAULT FALSE,
|
|
sms_alerts BOOLEAN DEFAULT FALSE,
|
|
bbps_facility BOOLEAN DEFAULT FALSE,
|
|
insurance_cover BOOLEAN DEFAULT FALSE,
|
|
bepg_facility BOOLEAN DEFAULT FALSE,
|
|
neft_facility BOOLEAN DEFAULT FALSE,
|
|
rtgs_facility BOOLEAN DEFAULT FALSE,
|
|
inter_bank_total_cap_facility BOOLEAN DEFAULT FALSE,
|
|
withdrawal_facility BOOLEAN DEFAULT FALSE,
|
|
deposit_facility_kyc BOOLEAN DEFAULT FALSE,
|
|
virtual_debit_card BOOLEAN DEFAULT FALSE,
|
|
created_date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (savings_product_id) REFERENCES m_savings_product(id) ON DELETE CASCADE
|
|
);
|
|
```
|
|
|
|
### **3. Create m_savings_product_facility_limits table** (One-to-Many)
|
|
```sql
|
|
CREATE TABLE m_savings_product_facility_limits (
|
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
|
savings_product_id BIGINT NOT NULL,
|
|
limit_type VARCHAR(100) NOT NULL,
|
|
general_limit VARCHAR(50), -- Can be decimal or "N/A", "UNLIMITED"
|
|
minor_account_limit VARCHAR(50),
|
|
small_saving_limit VARCHAR(50),
|
|
created_date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (savings_product_id) REFERENCES m_savings_product(id) ON DELETE CASCADE,
|
|
UNIQUE KEY unique_product_limit_type (savings_product_id, limit_type)
|
|
);
|
|
```
|
|
|
|
### **4. Create m_savings_product_deposit_limit_kyc table** (One-to-One)
|
|
```sql
|
|
CREATE TABLE m_savings_product_deposit_limit_kyc (
|
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
|
savings_product_id BIGINT NOT NULL UNIQUE,
|
|
general_kyc_type VARCHAR(50),
|
|
general_limit VARCHAR(50), -- "No Limit" or numeric
|
|
minor_kyc_type VARCHAR(50),
|
|
minor_limit BIGINT,
|
|
small_saving_kyc_type VARCHAR(50),
|
|
small_saving_limit BIGINT,
|
|
created_date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (savings_product_id) REFERENCES m_savings_product(id) ON DELETE CASCADE
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## 🏗️ Java Entity Classes to Create/Update
|
|
|
|
### **1. Update SavingsProduct Entity**
|
|
Add these fields to `fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProduct.java`:
|
|
|
|
```java
|
|
@Column(name = "account_type", length = 50)
|
|
private String accountType;
|
|
|
|
@Column(name = "customer_segment", length = 50)
|
|
private String customerSegment;
|
|
|
|
@Column(name = "product_code", length = 20, unique = true)
|
|
private String productCode;
|
|
|
|
@Column(name = "effective_date")
|
|
private LocalDate effectiveDate;
|
|
|
|
@Column(name = "effective_to")
|
|
private LocalDate effectiveTo;
|
|
|
|
@Column(name = "end_date")
|
|
private LocalDate endDate;
|
|
|
|
@Column(name = "introducer_required")
|
|
private boolean introducerRequired;
|
|
|
|
@Column(name = "share_linking_enabled")
|
|
private boolean shareLinkingEnabled;
|
|
|
|
@Column(name = "nomination_facility")
|
|
private boolean nominationFacility;
|
|
|
|
@Column(name = "dormancy_alert")
|
|
private boolean dormancyAlert;
|
|
|
|
@Column(name = "interest_calculate_selector")
|
|
private Integer interestCalculateSelector;
|
|
|
|
@Column(name = "average_period", length = 20)
|
|
private String averagePeriod;
|
|
|
|
@Column(name = "auto_post")
|
|
private boolean autoPost;
|
|
|
|
@Column(name = "frequency_fix_day")
|
|
private Integer frequencyFixDay;
|
|
|
|
@Column(name = "last_interest_date")
|
|
private LocalDate lastInterestDate;
|
|
|
|
@Column(name = "enable_lockin_period")
|
|
private boolean enableLockinPeriod;
|
|
|
|
// One-to-One relationship
|
|
@OneToOne(cascade = CascadeType.ALL, mappedBy = "savingsProduct", orphanRemoval = true)
|
|
private SavingsProductFacilityConfig facilityConfig;
|
|
|
|
// One-to-Many relationship
|
|
@OneToMany(cascade = CascadeType.ALL, mappedBy = "savingsProduct", orphanRemoval = true)
|
|
private Set<SavingsProductFacilityLimit> facilityLimits = new HashSet<>();
|
|
|
|
// One-to-One relationship
|
|
@OneToOne(cascade = CascadeType.ALL, mappedBy = "savingsProduct", orphanRemoval = true)
|
|
private SavingsProductDepositLimitKyc depositLimitKyc;
|
|
```
|
|
|
|
### **2. Create SavingsProductFacilityConfig Entity**
|
|
`fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProductFacilityConfig.java`:
|
|
|
|
```java
|
|
@Entity
|
|
@Table(name = "m_savings_product_facility_config")
|
|
public class SavingsProductFacilityConfig extends AbstractPersistableCustom<Long> {
|
|
|
|
@OneToOne(fetch = FetchType.LAZY)
|
|
@JoinColumn(name = "savings_product_id", nullable = false)
|
|
private SavingsProduct savingsProduct;
|
|
|
|
@Column(name = "atm_facility")
|
|
private Boolean atmFacility = false;
|
|
|
|
@Column(name = "atm_withdrawal_facility")
|
|
private Boolean atmWithdrawalFacility = false;
|
|
|
|
@Column(name = "upi_facility")
|
|
private Boolean upiFacility = false;
|
|
|
|
@Column(name = "pos_transactions_facility")
|
|
private Boolean posTransactionsFacility = false;
|
|
|
|
@Column(name = "cheque_book")
|
|
private Boolean chequeBook = false;
|
|
|
|
@Column(name = "imps_facility")
|
|
private Boolean impsFacility = false;
|
|
|
|
@Column(name = "sms_alerts")
|
|
private Boolean smsAlerts = false;
|
|
|
|
@Column(name = "bbps_facility")
|
|
private Boolean bbpsFacility = false;
|
|
|
|
@Column(name = "insurance_cover")
|
|
private Boolean insuranceCover = false;
|
|
|
|
@Column(name = "bepg_facility")
|
|
private Boolean bepgFacility = false;
|
|
|
|
@Column(name = "neft_facility")
|
|
private Boolean neftFacility = false;
|
|
|
|
@Column(name = "rtgs_facility")
|
|
private Boolean rtgsFacility = false;
|
|
|
|
@Column(name = "inter_bank_total_cap_facility")
|
|
private Boolean interBankTotalCapFacility = false;
|
|
|
|
@Column(name = "withdrawal_facility")
|
|
private Boolean withdrawalFacility = false;
|
|
|
|
@Column(name = "deposit_facility_kyc")
|
|
private Boolean depositFacilityKyc = false;
|
|
|
|
@Column(name = "virtual_debit_card")
|
|
private Boolean virtualDebitCard = false;
|
|
|
|
// Getters and setters
|
|
}
|
|
```
|
|
|
|
### **3. Create SavingsProductFacilityLimit Entity**
|
|
`fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProductFacilityLimit.java`:
|
|
|
|
```java
|
|
@Entity
|
|
@Table(name = "m_savings_product_facility_limits")
|
|
public class SavingsProductFacilityLimit extends AbstractPersistableCustom<Long> {
|
|
|
|
@ManyToOne(fetch = FetchType.LAZY)
|
|
@JoinColumn(name = "savings_product_id", nullable = false)
|
|
private SavingsProduct savingsProduct;
|
|
|
|
@Column(name = "limit_type", length = 100, nullable = false)
|
|
private String limitType;
|
|
|
|
@Column(name = "general_limit", length = 50)
|
|
private String generalLimit; // Can be "UNLIMITED", "N/A", or numeric value
|
|
|
|
@Column(name = "minor_account_limit", length = 50)
|
|
private String minorAccountLimit;
|
|
|
|
@Column(name = "small_saving_limit", length = 50)
|
|
private String smallSavingLimit;
|
|
|
|
// Getters and setters
|
|
|
|
public static SavingsProductFacilityLimit create(
|
|
SavingsProduct savingsProduct,
|
|
String limitType,
|
|
String generalLimit,
|
|
String minorAccountLimit,
|
|
String smallSavingLimit) {
|
|
|
|
SavingsProductFacilityLimit limit = new SavingsProductFacilityLimit();
|
|
limit.savingsProduct = savingsProduct;
|
|
limit.limitType = limitType;
|
|
limit.generalLimit = generalLimit;
|
|
limit.minorAccountLimit = minorAccountLimit;
|
|
limit.smallSavingLimit = smallSavingLimit;
|
|
return limit;
|
|
}
|
|
}
|
|
```
|
|
|
|
### **4. Create SavingsProductDepositLimitKyc Entity**
|
|
`fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProductDepositLimitKyc.java`:
|
|
|
|
```java
|
|
@Entity
|
|
@Table(name = "m_savings_product_deposit_limit_kyc")
|
|
public class SavingsProductDepositLimitKyc extends AbstractPersistableCustom<Long> {
|
|
|
|
@OneToOne(fetch = FetchType.LAZY)
|
|
@JoinColumn(name = "savings_product_id", nullable = false)
|
|
private SavingsProduct savingsProduct;
|
|
|
|
@Column(name = "general_kyc_type", length = 50)
|
|
private String generalKycType;
|
|
|
|
@Column(name = "general_limit", length = 50)
|
|
private String generalLimit; // "No Limit" or numeric
|
|
|
|
@Column(name = "minor_kyc_type", length = 50)
|
|
private String minorKycType;
|
|
|
|
@Column(name = "minor_limit")
|
|
private Long minorLimit;
|
|
|
|
@Column(name = "small_saving_kyc_type", length = 50)
|
|
private String smallSavingKycType;
|
|
|
|
@Column(name = "small_saving_limit")
|
|
private Long smallSavingLimit;
|
|
|
|
// Getters and setters
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🔑 API Constants to Add
|
|
|
|
Update `fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java`:
|
|
|
|
```java
|
|
// New Savings Product Fields
|
|
public static final String accountTypeParamName = "accountType";
|
|
public static final String customerSegmentParamName = "customerSegment";
|
|
public static final String productCodeParamName = "productCode";
|
|
public static final String effectiveDateParamName = "effectiveDate";
|
|
public static final String effectiveToParamName = "effectiveTo";
|
|
public static final String endDateParamName = "endDate";
|
|
public static final String introducerRequiredParamName = "introducerRequired";
|
|
public static final String shareLinkingEnabledParamName = "shareLinkingEnabled";
|
|
public static final String nominationFacilityParamName = "nominationFacility";
|
|
public static final String dormancyAlertParamName = "dormancyAlert";
|
|
public static final String interestCalculateSelectorParamName = "interestCalculateSelector";
|
|
public static final String averagePeriodParamName = "averagePeriod";
|
|
public static final String autoPostParamName = "autoPost";
|
|
public static final String frequencyFixDayParamName = "frequencyFixDay";
|
|
public static final String lastInterestDateParamName = "lastInterestDate";
|
|
public static final String enableLockinPeriodParamName = "enableLockinPeriod";
|
|
|
|
// Complex objects
|
|
public static final String accountFacilityConfigParamName = "accountFacilityConfig";
|
|
public static final String accountFacilityLimitsParamName = "accountFacilityLimits";
|
|
public static final String depositLimitKycParamName = "depositLimitKyc";
|
|
|
|
// Facility config fields
|
|
public static final String atmFacilityParamName = "atmFacility";
|
|
public static final String atmWithdrawalFacilityParamName = "atmWithdrawalFacility";
|
|
public static final String upiFacilityParamName = "upiFacility";
|
|
// ... etc for all facility fields
|
|
|
|
// Limit fields
|
|
public static final String limitTypeParamName = "limitType";
|
|
public static final String generalLimitParamName = "generalLimit";
|
|
public static final String minorAccountLimitParamName = "minorAccountLimit";
|
|
public static final String smallSavingLimitParamName = "smallSavingLimit";
|
|
```
|
|
|
|
---
|
|
|
|
## ✅ Validator Updates
|
|
|
|
Update `fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductDataValidator.java`:
|
|
|
|
1. **Add to parameter whitelist:**
|
|
```java
|
|
private static final Set<String> SAVINGS_PRODUCT_REQUEST_DATA_PARAMETERS =
|
|
new HashSet<>(Arrays.asList(
|
|
// ... existing parameters ...
|
|
"accountType",
|
|
"customerSegment",
|
|
"productCode",
|
|
"effectiveDate",
|
|
"effectiveTo",
|
|
"endDate",
|
|
"introducerRequired",
|
|
"shareLinkingEnabled",
|
|
"nominationFacility",
|
|
"dormancyAlert",
|
|
"interestCalculateSelector",
|
|
"averagePeriod",
|
|
"autoPost",
|
|
"frequencyFixDay",
|
|
"lastInterestDate",
|
|
"enableLockinPeriod",
|
|
"accountFacilityConfig",
|
|
"accountFacilityLimits",
|
|
"depositLimitKyc"
|
|
));
|
|
```
|
|
|
|
2. **Add validation logic in validateForCreate() and validateForUpdate():**
|
|
```java
|
|
if (command.stringValueOfParameterNamedAllowingNull("productCode") != null) {
|
|
final String productCode = command.stringValueOfParameterNamed("productCode");
|
|
if (StringUtils.isBlank(productCode)) {
|
|
baseDataValidator.reset().parameter("productCode").value(productCode).notBlank();
|
|
}
|
|
}
|
|
|
|
if (command.hasParameter("effectiveDate")) {
|
|
final LocalDate effectiveDate = command.localDateValueOfParameterNamed("effectiveDate");
|
|
baseDataValidator.reset().parameter("effectiveDate").value(effectiveDate).notNull();
|
|
}
|
|
|
|
// Similar for other date fields
|
|
if (command.hasParameter("effectiveTo") && command.hasParameter("effectiveDate")) {
|
|
final LocalDate effectiveDate = command.localDateValueOfParameterNamed("effectiveDate");
|
|
final LocalDate effectiveTo = command.localDateValueOfParameterNamed("effectiveTo");
|
|
if (effectiveTo.isBefore(effectiveDate)) {
|
|
baseDataValidator.reset().parameter("effectiveTo").value(effectiveTo)
|
|
.failWithCode("error.msg.effectiveto.cannot.be.before.effectivedate");
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🔧 Assembler Updates
|
|
|
|
Update `fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProductAssembler.java`:
|
|
|
|
Add helper methods to extract and assemble nested objects:
|
|
|
|
```java
|
|
public SavingsProductFacilityConfig assembleAccountFacilityConfig(final JsonCommand command, final SavingsProduct product) {
|
|
final JsonObject jsonObject = command.parsedJson();
|
|
|
|
if (!jsonObject.has("accountFacilityConfig")) {
|
|
return null;
|
|
}
|
|
|
|
final JsonObject facilityConfigJson = jsonObject.getAsJsonObject("accountFacilityConfig");
|
|
SavingsProductFacilityConfig config = new SavingsProductFacilityConfig();
|
|
config.setSavingsProduct(product);
|
|
config.setAtmFacility(facilityConfigJson.has("atmFacility") ?
|
|
facilityConfigJson.getAsJsonPrimitive("atmFacility").getAsBoolean() : false);
|
|
config.setAtmWithdrawalFacility(facilityConfigJson.has("atmWithdrawalFacility") ?
|
|
facilityConfigJson.getAsJsonPrimitive("atmWithdrawalFacility").getAsBoolean() : false);
|
|
// ... set other facilities ...
|
|
return config;
|
|
}
|
|
|
|
public Set<SavingsProductFacilityLimit> assembleAccountFacilityLimits(final JsonCommand command, final SavingsProduct product) {
|
|
final JsonObject jsonObject = command.parsedJson();
|
|
final Set<SavingsProductFacilityLimit> limits = new HashSet<>();
|
|
|
|
if (!jsonObject.has("accountFacilityLimits")) {
|
|
return limits;
|
|
}
|
|
|
|
final JsonArray limitsArray = jsonObject.getAsJsonArray("accountFacilityLimits");
|
|
for (JsonElement element : limitsArray) {
|
|
final JsonObject limitJson = element.getAsJsonObject();
|
|
SavingsProductFacilityLimit limit = SavingsProductFacilityLimit.create(
|
|
product,
|
|
limitJson.has("limitType") ? limitJson.getAsJsonPrimitive("limitType").getAsString() : null,
|
|
limitJson.has("generalLimit") ? limitJson.getAsJsonPrimitive("generalLimit").getAsString() : null,
|
|
limitJson.has("minorAccountLimit") ? limitJson.getAsJsonPrimitive("minorAccountLimit").getAsString() : null,
|
|
limitJson.has("smallSavingLimit") ? limitJson.getAsJsonPrimitive("smallSavingLimit").getAsString() : null
|
|
);
|
|
limits.add(limit);
|
|
}
|
|
return limits;
|
|
}
|
|
|
|
public SavingsProductDepositLimitKyc assembleDepositLimitKyc(final JsonCommand command, final SavingsProduct product) {
|
|
final JsonObject jsonObject = command.parsedJson();
|
|
|
|
if (!jsonObject.has("depositLimitKyc")) {
|
|
return null;
|
|
}
|
|
|
|
final JsonObject kycJson = jsonObject.getAsJsonObject("depositLimitKyc");
|
|
SavingsProductDepositLimitKyc kyc = new SavingsProductDepositLimitKyc();
|
|
kyc.setSavingsProduct(product);
|
|
|
|
if (kycJson.has("general")) {
|
|
final JsonObject generalJson = kycJson.getAsJsonObject("general");
|
|
kyc.setGeneralKycType(generalJson.has("kycType") ?
|
|
generalJson.getAsJsonPrimitive("kycType").getAsString() : null);
|
|
kyc.setGeneralLimit(generalJson.has("limit") ?
|
|
generalJson.getAsJsonPrimitive("limit").getAsString() : null);
|
|
}
|
|
|
|
// ... handle minor and smallSaving similarly ...
|
|
|
|
return kyc;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 💾 Repository Classes to Create
|
|
|
|
### **SavingsProductFacilityConfigRepository.java**
|
|
```java
|
|
@Repository
|
|
public interface SavingsProductFacilityConfigRepository extends
|
|
JpaRepository<SavingsProductFacilityConfig, Long> {
|
|
SavingsProductFacilityConfig findBySavingsProductId(Long savingsProductId);
|
|
}
|
|
```
|
|
|
|
### **SavingsProductFacilityLimitRepository.java**
|
|
```java
|
|
@Repository
|
|
public interface SavingsProductFacilityLimitRepository extends
|
|
JpaRepository<SavingsProductFacilityLimit, Long> {
|
|
List<SavingsProductFacilityLimit> findBySavingsProductId(Long savingsProductId);
|
|
}
|
|
```
|
|
|
|
### **SavingsProductDepositLimitKycRepository.java**
|
|
```java
|
|
@Repository
|
|
public interface SavingsProductDepositLimitKycRepository extends
|
|
JpaRepository<SavingsProductDepositLimitKyc, Long> {
|
|
SavingsProductDepositLimitKyc findBySavingsProductId(Long savingsProductId);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🚀 Service Updates
|
|
|
|
Update `SavingsProductWritePlatformServiceImpl.create()` method:
|
|
|
|
```java
|
|
public CommandProcessingResult create(final JsonCommand command) {
|
|
try {
|
|
this.savingsProductDataValidator.validateForCreate(command.json());
|
|
|
|
// ... existing code ...
|
|
|
|
// Assemble new objects
|
|
final SavingsProductFacilityConfig facilityConfig =
|
|
this.savingsProductAssembler.assembleAccountFacilityConfig(command, product);
|
|
final Set<SavingsProductFacilityLimit> facilityLimits =
|
|
this.savingsProductAssembler.assembleAccountFacilityLimits(command, product);
|
|
final SavingsProductDepositLimitKyc depositLimitKyc =
|
|
this.savingsProductAssembler.assembleDepositLimitKyc(command, product);
|
|
|
|
if (facilityConfig != null) {
|
|
product.setFacilityConfig(facilityConfig);
|
|
facilityConfigRepository.saveAndFlush(facilityConfig);
|
|
}
|
|
|
|
if (!facilityLimits.isEmpty()) {
|
|
product.setFacilityLimits(facilityLimits);
|
|
facilityLimitRepository.saveAll(facilityLimits);
|
|
}
|
|
|
|
if (depositLimitKyc != null) {
|
|
product.setDepositLimitKyc(depositLimitKyc);
|
|
depositLimitKycRepository.saveAndFlush(depositLimitKyc);
|
|
}
|
|
|
|
// Save main product
|
|
final SavingsProduct productSaved = this.savingProductRepository.saveAndFlush(product);
|
|
|
|
return new CommandProcessingResultBuilder()
|
|
.withCommandId(command.commandId())
|
|
.withEntityId(productSaved.getId())
|
|
.build();
|
|
|
|
} catch (final DataAccessException e) {
|
|
// error handling
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 📊 Read Service Updates
|
|
|
|
Update `SavingsProductReadPlatformService` to include new fields in queries and DTO.
|
|
|
|
---
|
|
|
|
## 🎯 Summary of Changes
|
|
|
|
| Component | Type | Files to Modify/Create |
|
|
|-----------|------|----------------------|
|
|
| Database | Migration | SQL scripts |
|
|
| Entities | Create/Modify | 4 entity classes |
|
|
| Constants | Modify | SavingsApiConstants.java |
|
|
| Validator | Modify | SavingsProductDataValidator.java |
|
|
| Assembler | Modify | SavingsProductAssembler.java |
|
|
| Repositories | Create | 3 repository interfaces |
|
|
| Services | Modify | SavingsProductWrite/ReadPlatformService |
|
|
| DAO | Modify | SQL queries in read service |
|
|
|
|
---
|
|
|
|
## 📌 Implementation Priority
|
|
|
|
1. **Phase 1:** Create database tables
|
|
2. **Phase 2:** Create entity classes
|
|
3. **Phase 3:** Add API constants and validator rules
|
|
4. **Phase 4:** Create repositories and update service layer
|
|
5. **Phase 5:** Update read service and DTOs
|
|
6. **Phase 6:** Test with your POST endpoint
|
|
|
|
---
|
|
|
|
## ⚠️ Important Notes
|
|
|
|
- ✅ Fields like `otherGlMappings`, `isDormancyTrackingActive`, etc. **already exist** in the system
|
|
- ✅ Ensure backward compatibility - make all new fields optional in the validator
|
|
- ✅ Use cascade = CascadeType.ALL and orphanRemoval = true for nested entities
|
|
- ✅ Add unique constraints where needed (e.g., productCode, limit combinations)
|
|
- ✅ Consider adding indexes on frequently queried fields
|
|
- ✅ Update DTO classes to include these new fields in API responses
|
|
|