Merge remote-tracking branch 'self-hosted/feature/merge-updates' into feature/reports-products

# Conflicts:
#	fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
This commit is contained in:
Aditya
2026-04-29 19:23:21 +05:30
180 changed files with 233213 additions and 270090 deletions
+93
View File
@@ -0,0 +1,93 @@
import hashlib
import json
import os
import sys
import tempfile
from datetime import datetime, timezone
from http.client import HTTPConnection, HTTPException
from pathlib import Path
import traceback
from contextlib import closing
from typing import Optional
WEBSERVER_HOST = "localhost"
WEBSERVER_ENDPOINT = "/api/provenance/call"
PORT_FILE_SUFFIX = "-provenance-port.txt"
class ProvenanceHookError(RuntimeError):
pass
def http_request(method, host, port, location, *, body: Optional[bytes] = None, headers={}, timeout=None) -> bytes:
with closing(HTTPConnection(host, port, timeout=timeout)) as connection:
connection.request(method, location, body=body, headers=headers)
def get_server_port():
claude_root = os.getenv("CLAUDE_PROJECT_DIR")
path_hash = hashlib.md5(claude_root.encode('utf-8')).hexdigest()
port_file = Path(tempfile.gettempdir()) / (path_hash + PORT_FILE_SUFFIX)
return int(port_file.read_text("utf-8").strip())
def send_diff_to_webserver(file_path, timestamp_ms):
try:
port = get_server_port()
except FileNotFoundError as e:
raise ProvenanceHookError(
f"Could not determine API port: {e.filename} does not exist") from e
except Exception as e:
raise ProvenanceHookError("Could not determine API port") from e
url = f"http://{WEBSERVER_HOST}:{port}{WEBSERVER_ENDPOINT}"
try:
payload = {"file_path": file_path, "timestamp": timestamp_ms}
return http_request(
"POST",
WEBSERVER_HOST,
port=port,
location=WEBSERVER_ENDPOINT,
body=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
headers={'Content-Type': 'application/json'},
timeout=0.5
)
except (HTTPException, OSError, ConnectionError) as e:
raise ProvenanceHookError(
f"Network error while sending diff to {url}") from e
except Exception as e:
raise ProvenanceHookError(
f"Unknown error while sending diff to {url}") from e
def extract_file_path(tool_name, tool_input):
if tool_name in ["Write", "Edit", "MultiEdit"]:
return tool_input.get('file_path', 'unknown')
if tool_name == "NotebookEdit":
return tool_input.get('notebook_path', 'unknown')
return 'unknown'
def excepthook(type, value, traceback_):
traceback.print_exception(type, value, traceback_, file=sys.stderr)
sys.exit(1)
def main():
data = json.load(sys.stdin)
tool_name = data.get('tool_name', 'unknown')
modification_tools = [
"Write", "Edit", "MultiEdit", "NotebookEdit"
]
if tool_name in modification_tools:
tool_input = data.get('tool_input', {})
file_path = extract_file_path(tool_name, tool_input)
if file_path:
timestamp_ms = int(datetime.now(timezone.utc).timestamp() * 1000)
send_diff_to_webserver(file_path, timestamp_ms)
if __name__ == "__main__":
sys.excepthook = excepthook
sys.exit(main())
+1
View File
@@ -0,0 +1 @@
{"sessionId":"0fda6e48-a79d-4980-a9ee-31a6d5f735e1","pid":15548,"acquiredAt":1776168814856}
+14 -1
View File
@@ -41,5 +41,18 @@
"additionalDirectories": [ "additionalDirectories": [
"c:\\Users\\adity\\Music\\CBS\\cfcfrms-bank-api" "c:\\Users\\adity\\Music\\CBS\\cfcfrms-bank-api"
] ]
},
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit|NotebookEdit",
"hooks": [
{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/post-tool-call.py\""
}
]
}
]
} }
} }
+33
View File
@@ -0,0 +1,33 @@
{
"permissions": {
"allow": [
"Bash(type *)",
"mcp__idea__get_symbol_info",
"Bash(gradlew *)",
"Bash(select-string *)",
"Bash(select-object *)",
"Bash(dir *)",
"Bash(findstr *)",
"Bash(./gradlew *)",
"Bash(gradlew.bat *)",
"Bash(taskkill *)",
"Bash(timeout *)",
"Bash(.\\gradlew.bat *)",
"Bash(get-content *)",
"Bash(tasklist)",
"Bash(curl *)",
"Bash(netstat *)",
"Bash(curl.exe *)",
"Bash(rmdir *)",
"mcp__idea__build_project",
"Bash(.\\gradlew *)",
"Bash(.\\clean_build.bat)",
"mcp__idea__find_files_by_name_keyword",
"mcp__idea__search_in_files_by_text",
"mcp__idea__get_file_text_by_path",
"mcp__idea__search_in_files_by_regex",
"Bash(powershell *)"
]
},
"$version": 3
}
+7
View File
@@ -0,0 +1,7 @@
{
"permissions": {
"allow": [
"Bash(type *)"
]
}
}
+168
View File
@@ -0,0 +1,168 @@
# SavingsProduct Custom Fields - Implementation Summary
## ✅ Completed Changes
### 1. **Validator Updates**
**File:** `fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductDataValidator.java`
**Changes:**
- Added `balanceFrequency` to `SAVINGS_PRODUCT_REQUEST_DATA_PARAMETERS` whitelist
- Added `accountClosedZeroBalanceInDayClosing` to `SAVINGS_PRODUCT_REQUEST_DATA_PARAMETERS` whitelist
### 2. **API Constants**
**File:** `fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java`
**Changes:**
```java
public static final String balanceFrequencyParamName = "balanceFrequency";
public static final String accountClosedZeroBalanceInDayClosingParamName = "accountClosedZeroBalanceInDayClosing";
```
### 3. **Entity (Domain Model)**
**File:** `fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProduct.java`
**Changes:**
- Added field: `@Column(name = "balance_frequency", length = 50) private String balanceFrequency;`
- Added field: `@Column(name = "account_closed_zero_balance_in_day_closing") private Boolean accountClosedZeroBalanceInDayClosing = false;`
- Added getters: `getBalanceFrequency()`, `getAccountClosedZeroBalanceInDayClosing()`
- Added setters: `setBalanceFrequency()`, `setAccountClosedZeroBalanceInDayClosing()`
- Added update logic in `update()` method to handle changes to these fields
### 4. **Assembler**
**File:** `fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProductAssembler.java`
**Changes:**
```java
if (command.parameterExists("balanceFrequency")) {
product.setBalanceFrequency(command.stringValueOfParameterNamed("balanceFrequency"));
}
if (command.parameterExists("accountClosedZeroBalanceInDayClosing")) {
product.setAccountClosedZeroBalanceInDayClosing(command.booleanObjectValueOfParameterNamed("accountClosedZeroBalanceInDayClosing"));
}
```
### 5. **DTO (Data Transfer Object)**
**File:** `fineract-core/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductData.java`
**Changes:**
```java
@Getter @Setter private String balanceFrequency;
@Getter @Setter private Boolean accountClosedZeroBalanceInDayClosing;
```
### 6. **Read Platform Service**
**File:** `fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsProductReadPlatformServiceImpl.java`
**Changes:**
- SQL Query: Added `sp.balance_frequency as balanceFrequency`
- SQL Query: Added `sp.account_closed_zero_balance_in_day_closing as accountClosedZeroBalanceInDayClosing`
- ResultSet mapping: Added extraction of both fields
### 7. **Database Migration**
**File:** `database_migration_add_custom_fields.sql`
```sql
ALTER TABLE m_savings_product
ADD COLUMN balance_frequency VARCHAR(50) DEFAULT NULL,
ADD COLUMN account_closed_zero_balance_in_day_closing BOOLEAN DEFAULT FALSE;
```
---
## 📋 What These Fields Do
### **balanceFrequency** (String)
- **Purpose:** Defines how often balance is calculated/checked
- **Example Values:** "Daily", "Monthly", "Quarterly", "Yearly"
- **Database Column:** `m_savings_product.balance_frequency`
- **Type:** VARCHAR(50)
### **accountClosedZeroBalanceInDayClosing** (Boolean)
- **Purpose:** Flag to automatically close accounts when balance reaches zero at day end
- **Example Values:** `true` or `false`
- **Default:** `false`
- **Database Column:** `m_savings_product.account_closed_zero_balance_in_day_closing`
- **Type:** BOOLEAN
---
## 🎯 API Usage
### **POST /v1/savingsproducts** (Create)
```json
{
"name": "Saving Product",
"shortName": "SD22",
// ... other fields ...
"balanceFrequency": "Monthly",
"accountClosedZeroBalanceInDayClosing": true,
// ... rest of fields ...
}
```
### **GET /v1/savingsproducts/{productId}** (Read)
Response will include:
```json
{
"id": 1,
"name": "Saving Product",
// ... other fields ...
"balanceFrequency": "Monthly",
"accountClosedZeroBalanceInDayClosing": true,
// ... rest of fields ...
}
```
### **PUT /v1/savingsproducts/{productId}** (Update)
```json
{
"balanceFrequency": "Quarterly",
"accountClosedZeroBalanceInDayClosing": false
}
```
---
## 🗄️ Database Migration Steps
Run this SQL script to add the columns:
```bash
mysql -u root -p your_database < database_migration_add_custom_fields.sql
```
Or manually execute:
```sql
ALTER TABLE m_savings_product
ADD COLUMN balance_frequency VARCHAR(50) DEFAULT NULL,
ADD COLUMN account_closed_zero_balance_in_day_closing BOOLEAN DEFAULT FALSE;
```
---
## ✅ Validation
The fields are now:
- ✅ **Accepted** in CREATE request (POST)
- ✅ **Saved** to database
- ✅ **Returned** in GET response
- ✅ **Updatable** via PUT request
- ✅ **Validated** (no longer rejected as unsupported)
---
## 🚀 Next Steps
1. **Run Database Migration:** Execute the SQL script
2. **Rebuild Application:** `./gradlew clean build`
3. **Restart Server:** Apply the changes
4. **Test API:** Use your payload to create a savings product
---
## 📝 Notes
- Both fields are **optional** (nullable)
- `balanceFrequency` accepts any string value (you can add validation later if needed)
- `accountClosedZeroBalanceInDayClosing` defaults to `false` if not provided
- Both fields support **partial updates** (you can update just one of them)
+668
View File
@@ -0,0 +1,668 @@
# 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
+235
View File
@@ -0,0 +1,235 @@
{
"info": {
"_postman_id": "savings-products-collection",
"name": "Fineract - Savings Products",
"description": "Postman collection for Savings Product APIs.\n\nThis resource exposes 6 endpoints:\n1. POST /v1/savingsproducts\n2. PUT /v1/savingsproducts/{productId}\n3. GET /v1/savingsproducts\n4. GET /v1/savingsproducts/{productId}\n5. GET /v1/savingsproducts/template\n6. DELETE /v1/savingsproducts/{productId}\n\nThe create request below uses the mandatory fields required by the backend validator. If you switch accountingRule to cash-based or accrual-periodic, you must also supply the corresponding accounting mappings.",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"auth": {
"type": "basic",
"basic": [
{
"key": "username",
"value": "{{username}}",
"type": "string"
},
{
"key": "password",
"value": "{{password}}",
"type": "string"
}
]
},
"variable": [
{
"key": "base_url",
"value": "https://localhost:8443/fineract-provider/api/v1",
"description": "Fineract API base URL"
},
{
"key": "tenant_id",
"value": "default",
"description": "Tenant identifier"
},
{
"key": "username",
"value": "mifos",
"description": "API username"
},
{
"key": "password",
"value": "password",
"description": "API password"
},
{
"key": "product_id",
"value": "1",
"description": "Savings product id returned by the create request"
}
],
"item": [
{
"name": "Savings Products",
"item": [
{
"name": "Create Savings Product",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"pm.test('Status code is 200', function () {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test('Resource id returned', function () {",
" var json = pm.response.json();",
" pm.expect(json).to.have.property('resourceId');",
" pm.collectionVariables.set('product_id', json.resourceId);",
"});"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenant_id}}"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"name\": \"Passbook Savings\",\n \"shortName\": \"PBSV\",\n \"description\": \"Daily compounding savings product\",\n \"currencyCode\": \"USD\",\n \"digitsAfterDecimal\": 2,\n \"inMultiplesOf\": 0,\n \"locale\": \"en\",\n \"nominalAnnualInterestRate\": 5.0,\n \"interestCompoundingPeriodType\": 1,\n \"interestPostingPeriodType\": 4,\n \"interestCalculationType\": 1,\n \"interestCalculationDaysInYearType\": 365,\n \"accountingRule\": 1,\n \"withdrawalFeeForTransfers\": false,\n \"enforceMinRequiredBalance\": false,\n \"allowOverdraft\": false,\n \"withHoldTax\": false,\n \"isDormancyTrackingActive\": false,\n \"charges\": [\n {\n \"id\": 1\n }\n ]\n}"
},
"url": {
"raw": "{{base_url}}/savingsproducts",
"host": [
"{{base_url}}"
],
"path": [
"savingsproducts"
]
},
"description": "Create a savings product.\n\nRequired by backend validator:\n- name\n- shortName\n- currencyCode\n- digitsAfterDecimal\n- nominalAnnualInterestRate\n- interestCompoundingPeriodType\n- interestPostingPeriodType\n- interestCalculationType\n- interestCalculationDaysInYearType\n- accountingRule\n\nIf accountingRule = 2 or 3, the backend also expects accounting mappings."
},
"response": []
},
{
"name": "Update Savings Product",
"request": {
"method": "PUT",
"header": [
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenant_id}}"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"description\": \"Updated savings product description\",\n \"locale\": \"en\",\n \"nominalAnnualInterestRate\": 5.75\n}"
},
"url": {
"raw": "{{base_url}}/savingsproducts/{{product_id}}",
"host": [
"{{base_url}}"
],
"path": [
"savingsproducts",
"{{product_id}}"
]
},
"description": "Update an existing savings product by product id."
},
"response": []
},
{
"name": "List Savings Products",
"request": {
"method": "GET",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenant_id}}"
}
],
"url": {
"raw": "{{base_url}}/savingsproducts",
"host": [
"{{base_url}}"
],
"path": [
"savingsproducts"
]
},
"description": "Lists all savings products."
},
"response": []
},
{
"name": "Get Savings Product By ID",
"request": {
"method": "GET",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenant_id}}"
}
],
"url": {
"raw": "{{base_url}}/savingsproducts/{{product_id}}",
"host": [
"{{base_url}}"
],
"path": [
"savingsproducts",
"{{product_id}}"
]
},
"description": "Retrieves a savings product by id. You can also call the same endpoint with ?template=true."
},
"response": []
},
{
"name": "Get Savings Product Template",
"request": {
"method": "GET",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenant_id}}"
}
],
"url": {
"raw": "{{base_url}}/savingsproducts/template",
"host": [
"{{base_url}}"
],
"path": [
"savingsproducts",
"template"
]
},
"description": "Returns template data with dropdown options and default values for savings product creation."
},
"response": []
},
{
"name": "Delete Savings Product",
"request": {
"method": "DELETE",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenant_id}}"
}
],
"url": {
"raw": "{{base_url}}/savingsproducts/{{product_id}}",
"host": [
"{{base_url}}"
],
"path": [
"savingsproducts",
"{{product_id}}"
]
},
"description": "Deletes a savings product by id."
},
"response": []
}
],
"description": "Savings product master APIs."
}
]
}
+1
View File
@@ -41,6 +41,7 @@ buildscript {
'fineract-loan-origination', 'fineract-loan-origination',
'fineract-loan', 'fineract-loan',
'fineract-savings', 'fineract-savings',
'fineract-current-product',
'fineract-report', 'fineract-report',
'fineract-mix', 'fineract-mix',
'integration-tests', 'integration-tests',
+46
View File
@@ -0,0 +1,46 @@
@echo off
echo Stopping any running Gradle daemons...
call gradlew.bat --stop
echo.
echo Deleting corrupted build folders...
if exist fineract-provider\build (
echo Deleting fineract-provider\build...
rmdir /s /q fineract-provider\build
)
if exist fineract-command\build (
echo Deleting fineract-command\build...
rmdir /s /q fineract-command\build
)
if exist fineract-core\build (
echo Deleting fineract-core\build...
rmdir /s /q fineract-core\build
)
if exist fineract-accounting\build (
echo Deleting fineract-accounting\build...
rmdir /s /q fineract-accounting\build
)
if exist fineract-charge\build (
echo Deleting fineract-charge\build...
rmdir /s /q fineract-charge\build
)
if exist fineract-cob\build (
echo Deleting fineract-cob\build...
rmdir /s /q fineract-cob\build
)
if exist fineract-document\build (
echo Deleting fineract-document\build...
rmdir /s /q fineract-document\build
)
if exist fineract-branch\build (
echo Deleting fineract-branch\build...
rmdir /s /q fineract-branch\build
)
if exist fineract-avro-schemas\build (
echo Deleting fineract-avro-schemas\build...
rmdir /s /q fineract-avro-schemas\build
)
echo.
echo Build folders deleted successfully.
echo Now you can run: .\gradlew devRun
echo.
pause
+116
View File
@@ -0,0 +1,116 @@
> Task :buildSrc:extractPluginRequests UP-TO-DATE
> Task :buildSrc:generatePluginAdapters UP-TO-DATE
> Task :buildSrc:compileJava UP-TO-DATE
> Task :buildSrc:compileGroovy UP-TO-DATE
> Task :buildSrc:compileGroovyPlugins UP-TO-DATE
> Task :buildSrc:pluginDescriptors UP-TO-DATE
> Task :buildSrc:processResources UP-TO-DATE
> Task :buildSrc:classes UP-TO-DATE
> Task :buildSrc:jar UP-TO-DATE
> Configure project :
matching ref: COMMIT - 011239f3995f9ea94a893ed4a60251a3b154564c
ref configuration: COMMIT - pattern: null
version: ${describe.tag.version.major}.${describe.tag.version.minor.next}.0-SNAPSHOT
describeTagPattern: .*(\d+\.\d+\.\d+).*
describeTagFirstParent: false
project version: 0.1.0-SNAPSHOT
> Task :fineract-core:processResources UP-TO-DATE
> Task :fineract-tax:processResources UP-TO-DATE
> Task :fineract-accounting:processResources UP-TO-DATE
> Task :spotlessInternalRegisterDependencies UP-TO-DATE
> Task :fineract-document:processResources NO-SOURCE
> Task :fineract-avro-schemas:preprocessAvroSchemas UP-TO-DATE
> Task :fineract-charge:processResources UP-TO-DATE
> Task :fineract-branch:processResources UP-TO-DATE
> Task :fineract-cob:processResources UP-TO-DATE
> Task :fineract-rates:processResources UP-TO-DATE
> Task :fineract-investor:processResources UP-TO-DATE
> Task :fineract-mix:processResources NO-SOURCE
> Task :fineract-loan:processResources UP-TO-DATE
> Task :fineract-loan-origination:processResources UP-TO-DATE
> Task :fineract-progressive-loan:processResources UP-TO-DATE
> Task :fineract-security:processResources NO-SOURCE
> Task :fineract-report:processResources UP-TO-DATE
> Task :fineract-savings:processResources UP-TO-DATE
> Task :fineract-working-capital-loan:processResources UP-TO-DATE
> Task :fineract-validation:compileJava UP-TO-DATE
> Task :fineract-command:compileJava UP-TO-DATE
> Task :fineract-validation:processResources UP-TO-DATE
> Task :fineract-command:processResources UP-TO-DATE
> Task :fineract-command:classes UP-TO-DATE
> Task :fineract-validation:classes UP-TO-DATE
> Task :fineract-command:jar UP-TO-DATE
> Task :fineract-validation:jar UP-TO-DATE
> Task :fineract-avro-schemas:spotlessJson UP-TO-DATE
> Task :fineract-avro-schemas:spotlessJsonApply UP-TO-DATE
> Task :fineract-avro-schemas:buildJavaSdk UP-TO-DATE
> Task :fineract-avro-schemas:compileJava UP-TO-DATE
> Task :fineract-avro-schemas:processResources UP-TO-DATE
> Task :fineract-avro-schemas:classes UP-TO-DATE
> Task :fineract-avro-schemas:jar UP-TO-DATE
> Task :fineract-working-capital-loan:compileJava UP-TO-DATE
> Task :fineract-working-capital-loan:classes UP-TO-DATE
> Task :fineract-working-capital-loan:jar UP-TO-DATE
> Task :fineract-core:compileJava UP-TO-DATE
> Task :fineract-core:classes UP-TO-DATE
> Task :fineract-core:jar UP-TO-DATE
> Task :fineract-mix:compileJava UP-TO-DATE
> Task :fineract-mix:classes UP-TO-DATE
> Task :fineract-mix:jar UP-TO-DATE
> Task :fineract-cob:compileJava UP-TO-DATE
> Task :fineract-cob:classes UP-TO-DATE
> Task :fineract-report:compileJava UP-TO-DATE
> Task :fineract-tax:compileJava UP-TO-DATE
> Task :fineract-rates:compileJava UP-TO-DATE
> Task :fineract-tax:classes UP-TO-DATE
> Task :fineract-rates:classes UP-TO-DATE
> Task :fineract-cob:jar UP-TO-DATE
> Task :fineract-report:classes UP-TO-DATE
> Task :fineract-tax:jar UP-TO-DATE
> Task :fineract-rates:jar UP-TO-DATE
> Task :fineract-report:jar UP-TO-DATE
> Task :fineract-charge:compileJava UP-TO-DATE
> Task :fineract-charge:classes UP-TO-DATE
> Task :fineract-security:compileJava UP-TO-DATE
> Task :fineract-charge:jar UP-TO-DATE
> Task :fineract-security:classes UP-TO-DATE
> Task :fineract-document:compileJava UP-TO-DATE
> Task :fineract-document:classes UP-TO-DATE
> Task :fineract-security:jar UP-TO-DATE
> Task :fineract-document:jar UP-TO-DATE
> Task :fineract-accounting:compileJava UP-TO-DATE
> Task :fineract-accounting:classes UP-TO-DATE
> Task :fineract-accounting:jar UP-TO-DATE
> Task :fineract-branch:compileJava UP-TO-DATE
> Task :fineract-branch:classes UP-TO-DATE
> Task :fineract-branch:jar UP-TO-DATE
> Task :fineract-savings:compileJava UP-TO-DATE
> Task :fineract-loan:compileJava UP-TO-DATE
> Task :fineract-loan:classes UP-TO-DATE
> Task :fineract-savings:classes UP-TO-DATE
> Task :fineract-loan:jar UP-TO-DATE
> Task :fineract-savings:jar UP-TO-DATE
> Task :fineract-progressive-loan:compileJava UP-TO-DATE
> Task :fineract-progressive-loan:classes UP-TO-DATE
> Task :fineract-loan-origination:compileJava UP-TO-DATE
> Task :fineract-loan-origination:classes UP-TO-DATE
> Task :fineract-investor:compileJava UP-TO-DATE
> Task :fineract-investor:classes UP-TO-DATE
> Task :fineract-loan-origination:jar UP-TO-DATE
> Task :fineract-investor:jar UP-TO-DATE
> Task :fineract-progressive-loan:jar UP-TO-DATE
> Task :fineract-provider:compileJava UP-TO-DATE
[Incubating] Problems report is available at: file:///D:/CompanyProject/GitCBS/JAVA-Backend-CBS-MAIN/build/reports/problems/problems-report.html
Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.
You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
For more on this, please refer to https://docs.gradle.org/8.14.3/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
BUILD SUCCESSFUL in 23s
71 actionable tasks: 71 up-to-date
+4
View File
@@ -0,0 +1,4 @@
# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
+81
View File
@@ -0,0 +1,81 @@
-- Complete migration: Add all custom/enhanced fields to m_savings_product table
-- Uses IF NOT EXISTS to safely handle columns that may already exist
DO $$
BEGIN
-- Scalar custom columns
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='account_type') THEN
ALTER TABLE m_savings_product ADD COLUMN account_type VARCHAR(50) DEFAULT NULL;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='customer_segment') THEN
ALTER TABLE m_savings_product ADD COLUMN customer_segment VARCHAR(50) DEFAULT NULL;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='product_code') THEN
ALTER TABLE m_savings_product ADD COLUMN product_code VARCHAR(20) DEFAULT NULL;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='effective_date') THEN
ALTER TABLE m_savings_product ADD COLUMN effective_date DATE DEFAULT NULL;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='effective_to') THEN
ALTER TABLE m_savings_product ADD COLUMN effective_to DATE DEFAULT NULL;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='end_date') THEN
ALTER TABLE m_savings_product ADD COLUMN end_date DATE DEFAULT NULL;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='introducer_required') THEN
ALTER TABLE m_savings_product ADD COLUMN introducer_required BOOLEAN DEFAULT FALSE;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='share_linking_enabled') THEN
ALTER TABLE m_savings_product ADD COLUMN share_linking_enabled BOOLEAN DEFAULT FALSE;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='nomination_facility') THEN
ALTER TABLE m_savings_product ADD COLUMN nomination_facility BOOLEAN DEFAULT FALSE;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='dormancy_alert') THEN
ALTER TABLE m_savings_product ADD COLUMN dormancy_alert BOOLEAN DEFAULT FALSE;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='interest_calculate_selector') THEN
ALTER TABLE m_savings_product ADD COLUMN interest_calculate_selector INT DEFAULT NULL;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='average_period') THEN
ALTER TABLE m_savings_product ADD COLUMN average_period VARCHAR(20) DEFAULT NULL;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='auto_post') THEN
ALTER TABLE m_savings_product ADD COLUMN auto_post BOOLEAN DEFAULT FALSE;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='frequency_fix_day') THEN
ALTER TABLE m_savings_product ADD COLUMN frequency_fix_day INT DEFAULT NULL;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='last_interest_date') THEN
ALTER TABLE m_savings_product ADD COLUMN last_interest_date DATE DEFAULT NULL;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='enable_lockin_period') THEN
ALTER TABLE m_savings_product ADD COLUMN enable_lockin_period BOOLEAN DEFAULT FALSE;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='balance_frequency') THEN
ALTER TABLE m_savings_product ADD COLUMN balance_frequency VARCHAR(50) DEFAULT NULL;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='account_closed_zero_balance_in_day_closing') THEN
ALTER TABLE m_savings_product ADD COLUMN account_closed_zero_balance_in_day_closing BOOLEAN DEFAULT FALSE;
END IF;
END $$;
-- Add comments for documentation (safe to run multiple times)
COMMENT ON COLUMN m_savings_product.account_type IS 'Account type classification';
COMMENT ON COLUMN m_savings_product.customer_segment IS 'Customer segment classification';
COMMENT ON COLUMN m_savings_product.product_code IS 'Unique product code identifier';
COMMENT ON COLUMN m_savings_product.effective_date IS 'Date from which the product becomes effective';
COMMENT ON COLUMN m_savings_product.effective_to IS 'Date until which the product is effective';
COMMENT ON COLUMN m_savings_product.end_date IS 'Product end/expiry date';
COMMENT ON COLUMN m_savings_product.introducer_required IS 'Whether an introducer is required for this product';
COMMENT ON COLUMN m_savings_product.share_linking_enabled IS 'Whether share linking is enabled for this product';
COMMENT ON COLUMN m_savings_product.nomination_facility IS 'Whether nomination facility is available';
COMMENT ON COLUMN m_savings_product.dormancy_alert IS 'Whether dormancy alerts are enabled';
COMMENT ON COLUMN m_savings_product.interest_calculate_selector IS 'Interest calculation selector type';
COMMENT ON COLUMN m_savings_product.average_period IS 'Average period for calculations';
COMMENT ON COLUMN m_savings_product.auto_post IS 'Whether auto posting of interest is enabled';
COMMENT ON COLUMN m_savings_product.frequency_fix_day IS 'Fixed day for frequency calculations';
COMMENT ON COLUMN m_savings_product.last_interest_date IS 'Last date interest was calculated';
COMMENT ON COLUMN m_savings_product.enable_lockin_period IS 'Whether lock-in period is enabled';
COMMENT ON COLUMN m_savings_product.balance_frequency IS 'Frequency for balance calculation (e.g., Daily, Monthly, Quarterly)';
COMMENT ON COLUMN m_savings_product.account_closed_zero_balance_in_day_closing IS 'Flag to indicate if account should be closed when balance reaches zero at day end';
@@ -36,8 +36,13 @@ public interface ProductToGLAccountMappingRepository
@Param("chargeId") Long ChargeId); @Param("chargeId") Long ChargeId);
@Query("select mapping from ProductToGLAccountMapping mapping where mapping.productId =:productId and mapping.productType =:productType and mapping.financialAccountType=:financialAccountType and mapping.paymentType is NULL and mapping.charge is NULL and mapping.chargeOffReason is NULL and mapping.writeOffReason is NULL and mapping.capitalizedIncomeClassification is NULL and mapping.buydownFeeClassification is NULL") @Query("select mapping from ProductToGLAccountMapping mapping where mapping.productId =:productId and mapping.productType =:productType and mapping.financialAccountType=:financialAccountType and mapping.paymentType is NULL and mapping.charge is NULL and mapping.chargeOffReason is NULL and mapping.writeOffReason is NULL and mapping.capitalizedIncomeClassification is NULL and mapping.buydownFeeClassification is NULL")
ProductToGLAccountMapping findCoreProductToFinAccountMapping(@Param("productId") Long productId, @Param("productType") int productType, List<ProductToGLAccountMapping> findCoreProductToFinAccountMappings(@Param("productId") Long productId,
@Param("financialAccountType") int financialAccountType); @Param("productType") int productType, @Param("financialAccountType") int financialAccountType);
default ProductToGLAccountMapping findCoreProductToFinAccountMapping(Long productId, int productType, int financialAccountType) {
List<ProductToGLAccountMapping> results = findCoreProductToFinAccountMappings(productId, productType, financialAccountType);
return results.isEmpty() ? null : results.get(0);
}
/*** /***
* The financial Account Type for a fund source will always be an asset (1) * The financial Account Type for a fund source will always be an asset (1)
@@ -115,6 +115,14 @@ public interface ChargeReadPlatformService {
/** Retrieve savings account charges **/ /** Retrieve savings account charges **/
List<ChargeData> retrieveSavingsAccountApplicableCharges(Long savingsId); List<ChargeData> retrieveSavingsAccountApplicableCharges(Long savingsId);
/**
* Returns all charges applicable for a given current product
*
* @param currentProductId
* @return
*/
List<ChargeData> retrieveCurrentProductCharges(Long currentProductId);
/** /**
* Returns charge definitions which are applicable for shares * Returns charge definitions which are applicable for shares
*/ */
@@ -234,7 +234,7 @@ public class ClientApiConstants {
public static final String clientEntityName = "clients"; public static final String clientEntityName = "clients";
public static final Set<String> CLIENT_ACCOUNTS_DATA_PARAMETERS = new HashSet<>( public static final Set<String> CLIENT_ACCOUNTS_DATA_PARAMETERS = new HashSet<>(
Arrays.asList("loanAccounts", "savingsAccounts", "shareAccounts")); Arrays.asList("loanAccounts", "savingsAccounts", "shareAccounts", "currentAccounts"));
/** /**
* These parameters will match the class level parameters of ClientData. Where possible, we try to get response * These parameters will match the class level parameters of ClientData. Where possible, we try to get response
@@ -292,6 +292,48 @@ public final class DepositsApiConstants {
recurringDepositRequestData.addAll(DEPOSIT_TERM_REQUEST_DATA_PARAMETERS); recurringDepositRequestData.addAll(DEPOSIT_TERM_REQUEST_DATA_PARAMETERS);
recurringDepositRequestData.addAll(RECURRING_DETAILS_REQUEST_DATA_PARAMETERS); recurringDepositRequestData.addAll(RECURRING_DETAILS_REQUEST_DATA_PARAMETERS);
recurringDepositRequestData.add(SavingsApiConstants.minBalanceForInterestCalculationParamName); recurringDepositRequestData.add(SavingsApiConstants.minBalanceForInterestCalculationParamName);
// Custom CBS fields — Details step
recurringDepositRequestData.add("accountType");
recurringDepositRequestData.add("customerSegment");
recurringDepositRequestData.add("productCode");
recurringDepositRequestData.add(SavingsApiConstants.glAccountIdParamName);
recurringDepositRequestData.add("effectiveTo");
recurringDepositRequestData.add("endDate");
recurringDepositRequestData.add("introducerRequired");
recurringDepositRequestData.add("shareLinkingEnabled");
// Custom CBS fields — Currency step
recurringDepositRequestData.add("dormancyAlert");
recurringDepositRequestData.add(SavingsApiConstants.daysToInactiveParamName);
recurringDepositRequestData.add(SavingsApiConstants.daysToDormancyParamName);
recurringDepositRequestData.add(SavingsApiConstants.daysToEscheatParamName);
recurringDepositRequestData.add("balanceSwapOption");
recurringDepositRequestData.add("msme");
recurringDepositRequestData.add("nominationFacility");
recurringDepositRequestData.add("autoDebitFacility");
// Custom CBS fields — Terms step
recurringDepositRequestData.add("effectiveDate");
recurringDepositRequestData.add("interestCalculateSelector");
recurringDepositRequestData.add("averagePeriod");
recurringDepositRequestData.add("autoPost");
recurringDepositRequestData.add("frequencyFixDay");
recurringDepositRequestData.add("lastInterestDate");
// Custom CBS fields — Settings step
recurringDepositRequestData.add("loanFacility");
recurringDepositRequestData.add("prematureWithdrawalOption");
recurringDepositRequestData.add("tenureFlexibility");
recurringDepositRequestData.add("lienMarked");
recurringDepositRequestData.add("withdrawalFeeForTransfers");
recurringDepositRequestData.add("enableLockinPeriod");
recurringDepositRequestData.add("maturityInstructions");
recurringDepositRequestData.add("creditMaturityProceeds");
recurringDepositRequestData.add("penaltyGracePeriodMissedInstallments");
recurringDepositRequestData.add("panelRequestApplicable");
recurringDepositRequestData.add("noPenaltyNomineeHeirClosure");
recurringDepositRequestData.add("deceasedSettlement");
recurringDepositRequestData.add("allowDebitTxnAfterDueDate");
recurringDepositRequestData.add("accountClosedZeroBalanceInDayClosing");
// Custom CBS fields — Interest rate slabs (sent inline but ignored server-side; saved separately)
recurringDepositRequestData.add("rdSlabs");
return recurringDepositRequestData; return recurringDepositRequestData;
} }
@@ -302,6 +344,49 @@ public final class DepositsApiConstants {
recurringDepositRequestData.addAll(DEPOSIT_TERM_RESPONSE_DATA_PARAMETERS); recurringDepositRequestData.addAll(DEPOSIT_TERM_RESPONSE_DATA_PARAMETERS);
recurringDepositRequestData.addAll(RECURRING_DETAILS_RESPONSE_DATA_PARAMETERS); recurringDepositRequestData.addAll(RECURRING_DETAILS_RESPONSE_DATA_PARAMETERS);
recurringDepositRequestData.add(SavingsApiConstants.minBalanceForInterestCalculationParamName); recurringDepositRequestData.add(SavingsApiConstants.minBalanceForInterestCalculationParamName);
// CBS extended fields — Options (for template=true)
recurringDepositRequestData.add("interestCalculationTypeOption");
recurringDepositRequestData.add("periodFrequencyOptions");
recurringDepositRequestData.add("yesNoOptions");
recurringDepositRequestData.add("accountTypeOptions");
recurringDepositRequestData.add("customerSegmentOptions");
// CBS extended fields — Details step
recurringDepositRequestData.add("accountType");
recurringDepositRequestData.add("customerSegment");
recurringDepositRequestData.add("productCode");
recurringDepositRequestData.add(SavingsApiConstants.glAccountIdParamName);
recurringDepositRequestData.add("glAccountName");
recurringDepositRequestData.add("effectiveDate");
recurringDepositRequestData.add("effectiveTo");
recurringDepositRequestData.add("endDate");
recurringDepositRequestData.add("introducerRequired");
recurringDepositRequestData.add("shareLinkingEnabled");
recurringDepositRequestData.add("nominationFacility");
recurringDepositRequestData.add("dormancyAlert");
// CBS extended fields — Terms step
recurringDepositRequestData.add("interestCalculateSelector");
recurringDepositRequestData.add("averagePeriod");
recurringDepositRequestData.add("autoPost");
recurringDepositRequestData.add("frequencyFixDay");
recurringDepositRequestData.add("lastInterestDate");
// CBS extended fields — Settings step
recurringDepositRequestData.add("enableLockinPeriod");
recurringDepositRequestData.add("balanceFrequency");
recurringDepositRequestData.add("accountClosedZeroBalanceInDayClosing");
recurringDepositRequestData.add("balanceSwapOption");
recurringDepositRequestData.add("msme");
recurringDepositRequestData.add("autoDebitFacility");
recurringDepositRequestData.add("loanFacility");
recurringDepositRequestData.add("prematureWithdrawalOption");
recurringDepositRequestData.add("tenureFlexibility");
recurringDepositRequestData.add("lienMarked");
recurringDepositRequestData.add("maturityInstructions");
recurringDepositRequestData.add("creditMaturityProceeds");
recurringDepositRequestData.add("penaltyGracePeriodMissedInstallments");
recurringDepositRequestData.add("panelRequestApplicable");
recurringDepositRequestData.add("noPenaltyNomineeHeirClosure");
recurringDepositRequestData.add("deceasedSettlement");
recurringDepositRequestData.add("allowDebitTxnAfterDueDate");
return recurringDepositRequestData; return recurringDepositRequestData;
} }
@@ -371,6 +456,44 @@ public final class DepositsApiConstants {
recurringDepositResponseData.addAll(PRECLOSURE_RESPONSE_DATA_PARAMETERS); recurringDepositResponseData.addAll(PRECLOSURE_RESPONSE_DATA_PARAMETERS);
recurringDepositResponseData.addAll(DEPOSIT_TERM_RESPONSE_DATA_PARAMETERS); recurringDepositResponseData.addAll(DEPOSIT_TERM_RESPONSE_DATA_PARAMETERS);
recurringDepositResponseData.addAll(RECURRING_DETAILS_RESPONSE_DATA_PARAMETERS); recurringDepositResponseData.addAll(RECURRING_DETAILS_RESPONSE_DATA_PARAMETERS);
// CBS product fields — included in template response when productId is provided
recurringDepositResponseData.add("accountType");
recurringDepositResponseData.add("customerSegment");
recurringDepositResponseData.add("accountTypeOptions");
recurringDepositResponseData.add("customerSegmentOptions");
recurringDepositResponseData.add("productCode");
recurringDepositResponseData.add(SavingsApiConstants.glAccountIdParamName);
recurringDepositResponseData.add("glAccountName");
recurringDepositResponseData.add("effectiveDate");
recurringDepositResponseData.add("effectiveTo");
recurringDepositResponseData.add("endDate");
recurringDepositResponseData.add("introducerRequired");
recurringDepositResponseData.add("shareLinkingEnabled");
recurringDepositResponseData.add("nominationFacility");
recurringDepositResponseData.add("dormancyAlert");
recurringDepositResponseData.add("interestCalculateSelector");
recurringDepositResponseData.add("interestCalculationTypeName");
recurringDepositResponseData.add("averagePeriod");
recurringDepositResponseData.add("autoPost");
recurringDepositResponseData.add("frequencyFixDay");
recurringDepositResponseData.add("lastInterestDate");
recurringDepositResponseData.add("enableLockinPeriod");
recurringDepositResponseData.add("balanceFrequency");
recurringDepositResponseData.add("accountClosedZeroBalanceInDayClosing");
recurringDepositResponseData.add("balanceSwapOption");
recurringDepositResponseData.add("msme");
recurringDepositResponseData.add("autoDebitFacility");
recurringDepositResponseData.add("loanFacility");
recurringDepositResponseData.add("prematureWithdrawalOption");
recurringDepositResponseData.add("tenureFlexibility");
recurringDepositResponseData.add("lienMarked");
recurringDepositResponseData.add("maturityInstructions");
recurringDepositResponseData.add("creditMaturityProceeds");
recurringDepositResponseData.add("penaltyGracePeriodMissedInstallments");
recurringDepositResponseData.add("panelRequestApplicable");
recurringDepositResponseData.add("noPenaltyNomineeHeirClosure");
recurringDepositResponseData.add("deceasedSettlement");
recurringDepositResponseData.add("allowDebitTxnAfterDueDate");
return recurringDepositResponseData; return recurringDepositResponseData;
} }
@@ -25,6 +25,15 @@ public class SavingsApiConstants {
public static final String SAVINGS_ACCOUNT_RESOURCE_NAME = "savingsaccount"; public static final String SAVINGS_ACCOUNT_RESOURCE_NAME = "savingsaccount";
public static final String SAVINGS_ACCOUNT_TRANSACTION_RESOURCE_NAME = "savingsaccount.transaction"; public static final String SAVINGS_ACCOUNT_TRANSACTION_RESOURCE_NAME = "savingsaccount.transaction";
public static final String SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME = "savingsaccountcharge"; public static final String SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME = "savingsaccountcharge";
public static final String YES_NO = "YesNo";
public static final String SAVINGS_PRODUCT_ACCOUNT_TYPE = "SavingsProductAccountType";
public static final String SAVINGS_PRODUCT_CUSTOMER_SEGMENT = "SavingsProductCustomerSegment";
public static final String CURRENT_PRODUCT_ACCOUNT_TYPE = "CurrentProductAccountType";
public static final String CURRENT_PRODUCT_CUSTOMER_SEGMENT = "CurrentProductCustomerSegment";
public static final String RD_PRODUCT_ACCOUNT_TYPE = "RecurringDepositProductAccountType";
public static final String RD_PRODUCT_CUSTOMER_SEGMENT = "RecurringDepositProductCustomerSegment";
public static final String PERIOD_FREQUENCY = "PeriodFrequency";
public static final String INTEREST_CALCULATION_TYPE_OPTION = "InterestCalculationTypeOption";
// actions // actions
public static final String postInterestValidationOnClosure = "postInterestValidationOnClosure"; public static final String postInterestValidationOnClosure = "postInterestValidationOnClosure";
@@ -195,6 +204,7 @@ public class SavingsApiConstants {
public static final String daysToInactiveParamName = "daysToInactive"; public static final String daysToInactiveParamName = "daysToInactive";
public static final String daysToDormancyParamName = "daysToDormancy"; public static final String daysToDormancyParamName = "daysToDormancy";
public static final String daysToEscheatParamName = "daysToEscheat"; public static final String daysToEscheatParamName = "daysToEscheat";
public static final String otherGlMappingsParamName = "otherGlMappings";
public static final String datatables = "datatables"; public static final String datatables = "datatables";
@@ -203,4 +213,99 @@ public class SavingsApiConstants {
public static final String ERROR_MSG_SAVINGS_ACCOUNT_NOT_ACTIVE = "not.in.active.state"; public static final String ERROR_MSG_SAVINGS_ACCOUNT_NOT_ACTIVE = "not.in.active.state";
public static final String accountMappingForPaymentParamName = "accountMappingForPayment"; public static final String accountMappingForPaymentParamName = "accountMappingForPayment";
// Enhanced 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 glAccountIdParamName = "glAccountId";
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";
public static final String posTransactionsFacilityParamName = "posTransactionsFacility";
public static final String chequeBookParamName = "chequeBook";
public static final String impsFacilityParamName = "impsFacility";
public static final String smsAlertsParamName = "smsAlerts";
public static final String bbpsFacilityParamName = "bbpsFacility";
public static final String insuranceCoverParamName = "insuranceCover";
public static final String bepgFacilityParamName = "bepgFacility";
public static final String neftFacilityParamName = "neftFacility";
public static final String rtgsFacilityParamName = "rtgsFacility";
public static final String interBankTotalCapFacilityParamName = "interBankTotalCapFacility";
public static final String withdrawalFacilityParamName = "withdrawalFacility";
public static final String depositFacilityKycParamName = "depositFacilityKyc";
public static final String virtualDebitCardParamName = "virtualDebitCard";
// 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";
// Deposit limit KYC fields
public static final String generalKycTypeParamName = "generalKycType";
public static final String generalKycLimitParamName = "generalLimit";
public static final String minorKycTypeParamName = "minorKycType";
public static final String minorLimitParamName = "minorLimit";
public static final String smallSavingKycTypeParamName = "smallSavingKycType";
public static final String smallSavingKycLimitParamName = "smallSavingLimit";
// Additional custom fields
public static final String balanceFrequencyParamName = "balanceFrequency";
public static final String accountClosedZeroBalanceInDayClosingParamName = "accountClosedZeroBalanceInDayClosing";
// Current Product — new UI fields
public static final String balanceSwapOptionParamName = "balanceSwapOption";
public static final String msmeParamName = "msme";
public static final String autoDebitFacilityParamName = "autoDebitFacility";
public static final String avgBalanceCalcPeriodParamName = "avgBalanceCalcPeriod";
public static final String validForPeriodParamName = "validForPeriod";
public static final String odAccountClosedZeroBalanceInDayClosingParamName = "odAccountClosedZeroBalanceInDayClosing";
// Status of Account fields
public static final String accountStatusParamName = "accountStatus";
public static final String modeOfOperationParamName = "modeOfOperation";
public static final String guardianClientIdParamName = "guardianClientId";
public static final String specialCategoryParamName = "specialCategory";
public static final String weakerSectionParamName = "weakerSection";
public static final String guardianNameParamName = "guardianName";
public static final String guardianCifNumberParamName = "guardianCifNumber";
public static final String guardianRelationshipParamName = "guardianRelationship";
public static final String minorDateOfBirthParamName = "minorDateOfBirth";
// Introducer fields
public static final String introducerIdParamName = "introducerId";
public static final String introducerNameParamName = "introducerName";
public static final String introducerCifNumberParamName = "introducerCifNumber";
// Joint holders
public static final String jointHoldersParamName = "jointHolders";
// Nominee
public static final String nomineeParamName = "nominee";
// Deposit Account Link (DAL)
public static final String linkedAccountIdParamName = "linkedAccountId";
public static final String linkedAccountTypeParamName = "linkedAccountType";
public static final String linkedAccountNoParamName = "linkedAccountNo";
} }
@@ -27,11 +27,13 @@ import java.util.Comparator;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Set; import java.util.Set;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.jersey.serializer.legacy.JsonLocalDateArrayFormat; import org.apache.fineract.infrastructure.core.jersey.serializer.legacy.JsonLocalDateArrayFormat;
import org.apache.fineract.infrastructure.dataqueries.data.DatatableData; import org.apache.fineract.infrastructure.dataqueries.data.DatatableData;
@@ -55,6 +57,7 @@ public final class SavingsAccountData implements Serializable {
private final Long id; private final Long id;
private final String accountNo; private final String accountNo;
private String previewAccountNo;
private final EnumOptionData depositType; private final EnumOptionData depositType;
private final String externalId; private final String externalId;
private final Long groupId; private final Long groupId;
@@ -123,6 +126,66 @@ public final class SavingsAccountData implements Serializable {
private List<DatatableData> datatables = null; private List<DatatableData> datatables = null;
// CBS Account fields (saved per account)
private String cbsAccountType;
private String accountStatus;
private String modeOfOperation;
private String specialCategory;
private String weakerSection;
private String guardianName;
private String guardianCifNumber;
private String guardianRelationship;
private LocalDate minorDateOfBirth;
private Long guardianClientId;
private Long introducerClientId;
private String introducerName;
private String introducerCifNumber;
private Map<String, Object> nomineeData;
private Long linkedAccountId;
private String linkedAccountType;
private String linkedAccountNo;
private List<java.util.Map<String, Object>> jointHolders;
// CBS Product fields (populated in template response from selected product)
private String accountType;
private String customerSegment;
private Collection<CodeValueData> accountTypeOptions;
private Collection<CodeValueData> customerSegmentOptions;
private String productCode;
private LocalDate effectiveDate;
private LocalDate effectiveTo;
private LocalDate endDate;
private Boolean introducerRequired;
private Boolean shareLinkingEnabled;
private Boolean nominationFacility;
private Boolean dormancyAlert;
private Boolean isDormancyTrackingActiveFromProduct;
private Long daysToInactiveFromProduct;
private Long daysToDormancyFromProduct;
private Long daysToEscheatFromProduct;
private String interestCalculationTypeName;
private Boolean enableLockinPeriod;
private String balanceFrequency;
private Boolean accountClosedZeroBalanceInDayClosing;
private Boolean balanceSwapOption;
private Boolean msme;
private Boolean autoDebitFacility;
private String avgBalanceCalcPeriod;
private String validForPeriod;
private Boolean odAccountClosedZeroBalanceInDayClosing;
private LocalDate lastInterestDate;
private Map<String, Object> accountFacilityConfig;
private Collection<Map<String, Object>> accountFacilityLimits;
private Map<String, Object> depositLimitKyc;
// CBS Term fields
private Integer interestCalculateSelector;
private String averagePeriod;
private Boolean autoPost;
private Integer frequencyFixDay;
// CBS dropdown options (from product)
private Collection<EnumOptionData> interestCalculationTypeOption;
private Collection<EnumOptionData> periodFrequencyOptions;
// import field // import field
private Long productId; private Long productId;
private String locale; private String locale;
@@ -688,37 +751,41 @@ public final class SavingsAccountData implements Serializable {
public static SavingsAccountData withTemplateOptions(final SavingsAccountData account, final SavingsAccountData template, public static SavingsAccountData withTemplateOptions(final SavingsAccountData account, final SavingsAccountData template,
final Collection<SavingsAccountTransactionData> transactions, final Collection<SavingsAccountChargeData> charges) { final Collection<SavingsAccountTransactionData> transactions, final Collection<SavingsAccountChargeData> charges) {
if (template == null) { final Collection<SavingsProductData> productOptions = template == null ? null : template.productOptions;
final Collection<SavingsProductData> productOptions = null; final Collection<StaffData> fieldOfficerOptions = template == null ? null : template.fieldOfficerOptions;
final Collection<StaffData> fieldOfficerOptions = null; final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = template == null ? null
final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null; : template.interestCompoundingPeriodTypeOptions;
final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null; final Collection<EnumOptionData> interestPostingPeriodTypeOptions = template == null ? null
final Collection<EnumOptionData> interestCalculationTypeOptions = null; : template.interestPostingPeriodTypeOptions;
final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null; final Collection<EnumOptionData> interestCalculationTypeOptions = template == null ? null
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null; : template.interestCalculationTypeOptions;
final Collection<EnumOptionData> withdrawalFeeTypeOptions = null; final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = template == null ? null
final Collection<ChargeData> chargeOptions = null; : template.interestCalculationDaysInYearTypeOptions;
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = template == null ? null
: template.lockinPeriodFrequencyTypeOptions;
final Collection<EnumOptionData> withdrawalFeeTypeOptions = template == null ? null
: template.withdrawalFeeTypeOptions;
final Collection<ChargeData> chargeOptions = template == null ? null : template.chargeOptions;
return withTemplateOptions(account, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions, final SavingsAccountData result = new SavingsAccountData(account.id, account.accountNo, account.depositType, account.externalId,
interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, account.groupId,
lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, transactions, charges, chargeOptions);
}
return new SavingsAccountData(account.id, account.accountNo, account.depositType, account.externalId, account.groupId,
account.groupName, account.clientId, account.clientName, account.savingsProductId, account.savingsProductName, account.groupName, account.clientId, account.clientName, account.savingsProductId, account.savingsProductName,
account.fieldOfficerId, account.fieldOfficerName, account.status, account.subStatus, account.reasonForBlock, account.fieldOfficerId, account.fieldOfficerName, account.status, account.subStatus, account.reasonForBlock,
account.timeline, account.currency, account.nominalAnnualInterestRate, account.interestCompoundingPeriodType, account.timeline, account.currency, account.nominalAnnualInterestRate, account.interestCompoundingPeriodType,
account.interestPostingPeriodType, account.interestCalculationType, account.interestCalculationDaysInYearType, account.interestPostingPeriodType, account.interestCalculationType, account.interestCalculationDaysInYearType,
account.minRequiredOpeningBalance, account.lockinPeriodFrequency, account.lockinPeriodFrequencyType, account.minRequiredOpeningBalance, account.lockinPeriodFrequency, account.lockinPeriodFrequencyType,
account.withdrawalFeeForTransfers, account.summary, transactions, template.productOptions, template.fieldOfficerOptions, account.withdrawalFeeForTransfers, account.summary, transactions, productOptions, fieldOfficerOptions,
template.interestCompoundingPeriodTypeOptions, template.interestPostingPeriodTypeOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
template.interestCalculationTypeOptions, template.interestCalculationDaysInYearTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
template.lockinPeriodFrequencyTypeOptions, template.withdrawalFeeTypeOptions, charges, template.chargeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions,
account.allowOverdraft, account.overdraftLimit, account.minRequiredBalance, account.enforceMinRequiredBalance, account.allowOverdraft, account.overdraftLimit, account.minRequiredBalance, account.enforceMinRequiredBalance,
account.maxAllowedLienLimit, account.lienAllowed, account.minBalanceForInterestCalculation, account.onHoldFunds, account.maxAllowedLienLimit, account.lienAllowed, account.minBalanceForInterestCalculation, account.onHoldFunds,
account.nominalAnnualInterestRateOverdraft, account.minOverdraftForInterestCalculation, account.withHoldTax, account.nominalAnnualInterestRateOverdraft, account.minOverdraftForInterestCalculation, account.withHoldTax,
account.taxGroup, account.lastActiveTransactionDate, account.isDormancyTrackingActive, account.daysToInactive, account.taxGroup, account.lastActiveTransactionDate, account.isDormancyTrackingActive, account.daysToInactive,
account.daysToDormancy, account.daysToEscheat, account.savingsAmountOnHold); account.daysToDormancy, account.daysToEscheat, account.savingsAmountOnHold);
copyPresentationFields(result, template);
copyPresentationFields(result, account);
return result;
} }
public static SavingsAccountData withTemplateOptions(final SavingsAccountData account, public static SavingsAccountData withTemplateOptions(final SavingsAccountData account,
@@ -731,7 +798,8 @@ public final class SavingsAccountData implements Serializable {
final Collection<SavingsAccountTransactionData> transactions, final Collection<SavingsAccountChargeData> charges, final Collection<SavingsAccountTransactionData> transactions, final Collection<SavingsAccountChargeData> charges,
final Collection<ChargeData> chargeOptions) { final Collection<ChargeData> chargeOptions) {
return new SavingsAccountData(account.id, account.accountNo, account.depositType, account.externalId, account.groupId, final SavingsAccountData result = new SavingsAccountData(account.id, account.accountNo, account.depositType, account.externalId,
account.groupId,
account.groupName, account.clientId, account.clientName, account.savingsProductId, account.savingsProductName, account.groupName, account.clientId, account.clientName, account.savingsProductId, account.savingsProductName,
account.fieldOfficerId, account.fieldOfficerName, account.status, account.subStatus, account.reasonForBlock, account.fieldOfficerId, account.fieldOfficerName, account.status, account.subStatus, account.reasonForBlock,
account.timeline, account.currency, account.nominalAnnualInterestRate, account.interestCompoundingPeriodType, account.timeline, account.currency, account.nominalAnnualInterestRate, account.interestCompoundingPeriodType,
@@ -746,6 +814,223 @@ public final class SavingsAccountData implements Serializable {
account.minOverdraftForInterestCalculation, account.withHoldTax, account.taxGroup, account.lastActiveTransactionDate, account.minOverdraftForInterestCalculation, account.withHoldTax, account.taxGroup, account.lastActiveTransactionDate,
account.isDormancyTrackingActive, account.daysToInactive, account.daysToDormancy, account.daysToEscheat, account.isDormancyTrackingActive, account.daysToInactive, account.daysToDormancy, account.daysToEscheat,
account.savingsAmountOnHold); account.savingsAmountOnHold);
copyPresentationFields(result, account);
return result;
}
private static void copyPresentationFields(final SavingsAccountData target, final SavingsAccountData source) {
if (source == null) {
return;
}
if (source.previewAccountNo != null) {
target.setPreviewAccountNo(source.previewAccountNo);
}
if (source.cbsAccountType != null) {
target.setCbsAccountType(source.cbsAccountType);
}
if (source.customerSegment != null) {
target.setCustomerSegment(source.customerSegment);
}
if (source.accountStatus != null) {
target.setAccountStatus(source.accountStatus);
}
if (source.modeOfOperation != null) {
target.setModeOfOperation(source.modeOfOperation);
}
if (source.specialCategory != null) {
target.setSpecialCategory(source.specialCategory);
}
if (source.weakerSection != null) {
target.setWeakerSection(source.weakerSection);
}
if (source.guardianName != null) {
target.setGuardianName(source.guardianName);
}
if (source.guardianCifNumber != null) {
target.setGuardianCifNumber(source.guardianCifNumber);
}
if (source.guardianRelationship != null) {
target.setGuardianRelationship(source.guardianRelationship);
}
if (source.minorDateOfBirth != null) {
target.setMinorDateOfBirth(source.minorDateOfBirth);
}
if (source.guardianClientId != null) {
target.setGuardianClientId(source.guardianClientId);
}
if (source.introducerClientId != null) {
target.setIntroducerClientId(source.introducerClientId);
}
if (source.introducerName != null) {
target.setIntroducerName(source.introducerName);
}
if (source.introducerCifNumber != null) {
target.setIntroducerCifNumber(source.introducerCifNumber);
}
if (source.nomineeData != null) {
target.setNomineeData(source.nomineeData);
}
if (source.linkedAccountId != null) {
target.setLinkedAccountId(source.linkedAccountId);
}
if (source.linkedAccountType != null) {
target.setLinkedAccountType(source.linkedAccountType);
}
if (source.linkedAccountNo != null) {
target.setLinkedAccountNo(source.linkedAccountNo);
}
if (source.jointHolders != null) {
target.setJointHolders(new ArrayList<>(source.jointHolders));
}
if (source.accountType != null) {
target.setAccountType(source.accountType);
}
if (source.customerSegmentOptions != null) {
target.setCustomerSegmentOptions(source.customerSegmentOptions);
}
if (source.accountTypeOptions != null) {
target.setAccountTypeOptions(source.accountTypeOptions);
}
if (source.productCode != null) {
target.setProductCode(source.productCode);
}
if (source.effectiveDate != null) {
target.setEffectiveDate(source.effectiveDate);
}
if (source.effectiveTo != null) {
target.setEffectiveTo(source.effectiveTo);
}
if (source.endDate != null) {
target.setEndDate(source.endDate);
}
if (source.introducerRequired != null) {
target.setIntroducerRequired(source.introducerRequired);
}
if (source.shareLinkingEnabled != null) {
target.setShareLinkingEnabled(source.shareLinkingEnabled);
}
if (source.nominationFacility != null) {
target.setNominationFacility(source.nominationFacility);
}
if (source.dormancyAlert != null) {
target.setDormancyAlert(source.dormancyAlert);
}
if (source.isDormancyTrackingActiveFromProduct != null) {
target.setIsDormancyTrackingActiveFromProduct(source.isDormancyTrackingActiveFromProduct);
}
if (source.daysToInactiveFromProduct != null) {
target.setDaysToInactiveFromProduct(source.daysToInactiveFromProduct);
}
if (source.daysToDormancyFromProduct != null) {
target.setDaysToDormancyFromProduct(source.daysToDormancyFromProduct);
}
if (source.daysToEscheatFromProduct != null) {
target.setDaysToEscheatFromProduct(source.daysToEscheatFromProduct);
}
if (source.interestCalculationTypeName != null) {
target.setInterestCalculationTypeName(source.interestCalculationTypeName);
}
if (source.enableLockinPeriod != null) {
target.setEnableLockinPeriod(source.enableLockinPeriod);
}
if (source.balanceFrequency != null) {
target.setBalanceFrequency(source.balanceFrequency);
}
if (source.accountClosedZeroBalanceInDayClosing != null) {
target.setAccountClosedZeroBalanceInDayClosing(source.accountClosedZeroBalanceInDayClosing);
}
if (source.balanceSwapOption != null) {
target.setBalanceSwapOption(source.balanceSwapOption);
}
if (source.msme != null) {
target.setMsme(source.msme);
}
if (source.autoDebitFacility != null) {
target.setAutoDebitFacility(source.autoDebitFacility);
}
if (source.avgBalanceCalcPeriod != null) {
target.setAvgBalanceCalcPeriod(source.avgBalanceCalcPeriod);
}
if (source.validForPeriod != null) {
target.setValidForPeriod(source.validForPeriod);
}
if (source.odAccountClosedZeroBalanceInDayClosing != null) {
target.setOdAccountClosedZeroBalanceInDayClosing(source.odAccountClosedZeroBalanceInDayClosing);
}
if (source.lastInterestDate != null) {
target.setLastInterestDate(source.lastInterestDate);
}
if (source.accountFacilityConfig != null) {
target.setAccountFacilityConfig(source.accountFacilityConfig);
}
if (source.accountFacilityLimits != null) {
target.setAccountFacilityLimits(source.accountFacilityLimits);
}
if (source.depositLimitKyc != null) {
target.setDepositLimitKyc(source.depositLimitKyc);
}
if (source.interestCalculateSelector != null) {
target.setInterestCalculateSelector(source.interestCalculateSelector);
}
if (source.averagePeriod != null) {
target.setAveragePeriod(source.averagePeriod);
}
if (source.autoPost != null) {
target.setAutoPost(source.autoPost);
}
if (source.frequencyFixDay != null) {
target.setFrequencyFixDay(source.frequencyFixDay);
}
if (source.interestCalculationTypeOption != null) {
target.setInterestCalculationTypeOption(source.interestCalculationTypeOption);
}
if (source.periodFrequencyOptions != null) {
target.setPeriodFrequencyOptions(source.periodFrequencyOptions);
}
if (source.productId != null) {
target.setProductId(source.productId);
}
if (source.locale != null) {
target.setLocale(source.locale);
}
if (source.dateFormat != null) {
target.setDateFormat(source.dateFormat);
}
if (source.rowIndex != null) {
target.setRowIndex(source.rowIndex);
}
if (source.submittedOnDate != null) {
target.setSubmittedOnDate(source.submittedOnDate);
}
if (source.startInterestCalculationDate != null) {
target.setStartInterestCalculationDate(source.startInterestCalculationDate);
}
if (source.activatedOnDate != null) {
target.setActivatedOnDate(source.activatedOnDate);
}
if (source.lockedInUntilDate != null) {
target.setLockedInUntilDate(source.lockedInUntilDate);
}
if (source.officeId != null) {
target.setOfficeId(source.officeId);
}
if (source.clientData != null) {
target.setClientData(source.clientData);
}
if (source.groupGeneralData != null) {
target.setGroupGeneralData(source.groupGeneralData);
}
if (source.savingsProductData != null) {
target.setSavingsProduct(source.savingsProductData);
}
if (source.savingsAccountSummaryData != null) {
target.setSavingsAccountSummaryData(source.savingsAccountSummaryData);
}
if (source.lastSavingsAccountTransaction != null) {
target.setLastSavingsAccountTransaction(source.lastSavingsAccountTransaction);
}
} }
public static SavingsAccountData withClientTemplate(final Long clientId, final String clientName, final Long groupId, public static SavingsAccountData withClientTemplate(final Long clientId, final String clientName, final Long groupId,
@@ -29,6 +29,7 @@ import org.apache.fineract.accounting.common.AccountingRuleType;
import org.apache.fineract.accounting.glaccount.data.GLAccountData; import org.apache.fineract.accounting.glaccount.data.GLAccountData;
import org.apache.fineract.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper; import org.apache.fineract.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper;
import org.apache.fineract.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper; import org.apache.fineract.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper;
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.portfolio.charge.data.ChargeData; import org.apache.fineract.portfolio.charge.data.ChargeData;
@@ -101,19 +102,62 @@ public final class SavingsProductData implements Serializable {
private final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions; private final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions;
private final Collection<EnumOptionData> interestPostingPeriodTypeOptions; private final Collection<EnumOptionData> interestPostingPeriodTypeOptions;
private final Collection<EnumOptionData> interestCalculationTypeOptions; private final Collection<EnumOptionData> interestCalculationTypeOptions;
private final Collection<EnumOptionData> interestCalculationTypeOption;
private final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions; private final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions;
private final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions; private final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions;
private final Collection<EnumOptionData> withdrawalFeeTypeOptions; private final Collection<EnumOptionData> withdrawalFeeTypeOptions;
private final Collection<EnumOptionData> periodFrequencyOptions;
private final Collection<PaymentTypeData> paymentTypeOptions; private final Collection<PaymentTypeData> paymentTypeOptions;
private final Collection<EnumOptionData> accountingRuleOptions; private final Collection<EnumOptionData> accountingRuleOptions;
private final Map<String, List<GLAccountData>> accountingMappingOptions; private final Map<String, List<GLAccountData>> accountingMappingOptions;
private final Collection<ChargeData> chargeOptions; private final Collection<ChargeData> chargeOptions;
private final Collection<ChargeData> penaltyOptions; private final Collection<ChargeData> penaltyOptions;
private final Collection<TaxGroupData> taxGroupOptions; private final Collection<TaxGroupData> taxGroupOptions;
private final Boolean isDormancyTrackingActive; private final Collection<CodeValueData> yesNoOptions;
private final Long daysToInactive; private final Collection<CodeValueData> accountTypeOptions;
private final Long daysToDormancy; private final Collection<CodeValueData> customerSegmentOptions;
private final Long daysToEscheat; @Getter private final Boolean isDormancyTrackingActive;
@Getter private final Long daysToInactive;
@Getter private final Long daysToDormancy;
@Getter private final Long daysToEscheat;
private final List<Map<String, Object>> otherGlMappings;
// Enhanced Savings Product Fields
@Getter @Setter private String accountType;
@Getter @Setter private String customerSegment;
@Getter @Setter private String productCode;
@Getter @Setter private Long glAccountId;
@Getter @Setter private String glAccountName;
@Getter @Setter private java.time.LocalDate effectiveDate;
@Getter @Setter private java.time.LocalDate effectiveTo;
@Getter @Setter private java.time.LocalDate endDate;
@Getter @Setter private Boolean introducerRequired;
@Getter @Setter private Boolean shareLinkingEnabled;
@Getter @Setter private Boolean nominationFacility;
@Getter @Setter private Boolean dormancyAlert;
@Getter @Setter private Integer interestCalculateSelector;
@Getter @Setter private String interestCalculationTypeName;
@Getter @Setter private String averagePeriod;
@Getter @Setter private Boolean autoPost;
@Getter @Setter private Integer frequencyFixDay;
@Getter @Setter private java.time.LocalDate lastInterestDate;
@Getter @Setter private Boolean enableLockinPeriod;
@Getter @Setter private String balanceFrequency;
@Getter @Setter private Boolean accountClosedZeroBalanceInDayClosing;
// New current product fields
@Getter @Setter private Boolean balanceSwapOption;
@Getter @Setter private Boolean msme;
@Getter @Setter private Boolean autoDebitFacility;
@Getter @Setter private String avgBalanceCalcPeriod;
@Getter @Setter private String validForPeriod;
@Getter @Setter private Boolean odAccountClosedZeroBalanceInDayClosing;
// Complex objects wrapped as Maps/Lists so Jackson converts them into the exact JSON your UI expects
@Getter @Setter private Map<String, Object> accountFacilityConfig;
@Getter @Setter private Collection<Map<String, Object>> accountFacilityLimits;
@Getter @Setter private Map<String, Object> depositLimitKyc;
@Getter @Setter private Map<String, List<Map<String, Object>>> otherGlMappingsMap;
public static SavingsProductData template(final CurrencyData currency, final EnumOptionData interestCompoundingPeriodType, public static SavingsProductData template(final CurrencyData currency, final EnumOptionData interestCompoundingPeriodType,
final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType, final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType,
@@ -121,12 +165,16 @@ public final class SavingsProductData implements Serializable {
final Collection<CurrencyData> currencyOptions, final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions, final Collection<CurrencyData> currencyOptions, final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions,
final Collection<EnumOptionData> interestPostingPeriodTypeOptions, final Collection<EnumOptionData> interestPostingPeriodTypeOptions,
final Collection<EnumOptionData> interestCalculationTypeOptions, final Collection<EnumOptionData> interestCalculationTypeOptions,
final Collection<EnumOptionData> interestCalculationTypeOption,
final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions, final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions, final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
final Collection<PaymentTypeData> paymentTypeOptions, final Collection<EnumOptionData> accountingRuleOptions, final Collection<EnumOptionData> periodFrequencyOptions, final Collection<PaymentTypeData> paymentTypeOptions,
final Collection<EnumOptionData> accountingRuleOptions,
final Map<String, List<GLAccountData>> accountingMappingOptions, final Collection<ChargeData> chargeOptions, final Map<String, List<GLAccountData>> accountingMappingOptions, final Collection<ChargeData> chargeOptions,
final Collection<ChargeData> penaltyOptions, final Collection<TaxGroupData> taxGroupOptions, final Collection<ChargeData> penaltyOptions, final Collection<TaxGroupData> taxGroupOptions,
final String accountMappingForPayment) { final Collection<CodeValueData> yesNoOptions,
final Collection<CodeValueData> accountTypeOptions, final Collection<CodeValueData> customerSegmentOptions,
final String accountMappingForPayment, final List<Map<String, Object>> otherGlMappings) {
final Long id = null; final Long id = null;
final String name = null; final String name = null;
@@ -157,35 +205,66 @@ public final class SavingsProductData implements Serializable {
final Long daysToInactive = null; final Long daysToInactive = null;
final Long daysToDormancy = null; final Long daysToDormancy = null;
final Long daysToEscheat = null; final Long daysToEscheat = null;
final List<Map<String, Object>> otherGlMappingsTemplate = null;
return new SavingsProductData(id, name, shortName, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, return new SavingsProductData(id, name, shortName, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType,
interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, accountingRule, accountingMappings, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, accountingRule, accountingMappings,
paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, interestCalculationTypeOptions, interestCalculationTypeOption, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, withdrawalFeeTypeOptions, periodFrequencyOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, charges, chargeOptions,
penaltyOptions, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings, allowOverdraft, overdraftLimit, penaltyOptions, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings, allowOverdraft, overdraftLimit,
minRequiredBalance, enforceMinRequiredBalance, maxAllowedLienLimit, lienAllowed, minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance, maxAllowedLienLimit, lienAllowed, minBalanceForInterestCalculation,
nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup, taxGroupOptions, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup, taxGroupOptions,
isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, accountMappingForPayment); yesNoOptions, accountTypeOptions, customerSegmentOptions, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat,
accountMappingForPayment, otherGlMappingsTemplate);
}
public static SavingsProductData template(final CurrencyData currency, final EnumOptionData interestCompoundingPeriodType,
final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType,
final EnumOptionData interestCalculationDaysInYearType, final EnumOptionData accountingRule,
final Collection<CurrencyData> currencyOptions, final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions,
final Collection<EnumOptionData> interestPostingPeriodTypeOptions,
final Collection<EnumOptionData> interestCalculationTypeOptions,
final Collection<EnumOptionData> interestCalculationTypeOption,
final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
final Collection<EnumOptionData> periodFrequencyOptions, final Collection<PaymentTypeData> paymentTypeOptions,
final Collection<EnumOptionData> accountingRuleOptions,
final Map<String, List<GLAccountData>> accountingMappingOptions, final Collection<ChargeData> chargeOptions,
final Collection<ChargeData> penaltyOptions, final Collection<TaxGroupData> taxGroupOptions,
final Collection<CodeValueData> yesNoOptions,
final Collection<CodeValueData> accountTypeOptions, final Collection<CodeValueData> customerSegmentOptions,
final String accountMappingForPayment) {
return template(currency, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType,
interestCalculationDaysInYearType, accountingRule, currencyOptions, interestCompoundingPeriodTypeOptions,
interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationTypeOption,
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, periodFrequencyOptions,
paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions, penaltyOptions, taxGroupOptions, yesNoOptions,
accountTypeOptions, customerSegmentOptions, accountMappingForPayment, null);
} }
public static SavingsProductData withCharges(final SavingsProductData product, final Collection<ChargeData> charges) { public static SavingsProductData withCharges(final SavingsProductData product, final Collection<ChargeData> charges) {
return new SavingsProductData(product.id, product.name, product.shortName, product.description, product.currency, SavingsProductData newData = new SavingsProductData(product.id, product.name, product.shortName, product.description, product.currency,
product.nominalAnnualInterestRate, product.interestCompoundingPeriodType, product.interestPostingPeriodType, product.nominalAnnualInterestRate, product.interestCompoundingPeriodType, product.interestPostingPeriodType,
product.interestCalculationType, product.interestCalculationDaysInYearType, product.minRequiredOpeningBalance, product.interestCalculationType, product.interestCalculationDaysInYearType, product.minRequiredOpeningBalance,
product.lockinPeriodFrequency, product.lockinPeriodFrequencyType, product.withdrawalFeeForTransfers, product.accountingRule, product.lockinPeriodFrequency, product.lockinPeriodFrequencyType, product.withdrawalFeeForTransfers, product.accountingRule,
product.accountingMappings, product.paymentChannelToFundSourceMappings, product.currencyOptions, product.accountingMappings, product.paymentChannelToFundSourceMappings, product.currencyOptions,
product.interestCompoundingPeriodTypeOptions, product.interestPostingPeriodTypeOptions, product.interestCompoundingPeriodTypeOptions, product.interestPostingPeriodTypeOptions,
product.interestCalculationTypeOptions, product.interestCalculationDaysInYearTypeOptions, product.interestCalculationTypeOptions, product.interestCalculationTypeOption, product.interestCalculationDaysInYearTypeOptions,
product.lockinPeriodFrequencyTypeOptions, product.withdrawalFeeTypeOptions, product.paymentTypeOptions, product.lockinPeriodFrequencyTypeOptions, product.withdrawalFeeTypeOptions, product.periodFrequencyOptions,
product.paymentTypeOptions,
product.accountingRuleOptions, product.accountingMappingOptions, charges, product.chargeOptions, product.penaltyOptions, product.accountingRuleOptions, product.accountingMappingOptions, charges, product.chargeOptions, product.penaltyOptions,
product.feeToIncomeAccountMappings, product.penaltyToIncomeAccountMappings, product.allowOverdraft, product.overdraftLimit, product.feeToIncomeAccountMappings, product.penaltyToIncomeAccountMappings, product.allowOverdraft, product.overdraftLimit,
product.minRequiredBalance, product.enforceMinRequiredBalance, product.maxAllowedLienLimit, product.lienAllowed, product.minRequiredBalance, product.enforceMinRequiredBalance, product.maxAllowedLienLimit, product.lienAllowed,
product.minBalanceForInterestCalculation, product.nominalAnnualInterestRateOverdraft, product.minBalanceForInterestCalculation, product.nominalAnnualInterestRateOverdraft,
product.minOverdraftForInterestCalculation, product.withHoldTax, product.taxGroup, product.taxGroupOptions, product.minOverdraftForInterestCalculation, product.withHoldTax, product.taxGroup, product.taxGroupOptions,
product.isDormancyTrackingActive, product.daysToInactive, product.daysToDormancy, product.daysToEscheat, product.yesNoOptions, product.accountTypeOptions, product.customerSegmentOptions, product.isDormancyTrackingActive,
product.accountMappingForPayment); product.daysToInactive,
product.daysToDormancy, product.daysToEscheat, product.accountMappingForPayment, product.otherGlMappings);
// Copy enhanced custom fields
copyCustomFields(product, newData);
return newData;
} }
/** /**
@@ -196,27 +275,57 @@ public final class SavingsProductData implements Serializable {
final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions, final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions,
final Collection<EnumOptionData> interestPostingPeriodTypeOptions, final Collection<EnumOptionData> interestPostingPeriodTypeOptions,
final Collection<EnumOptionData> interestCalculationTypeOptions, final Collection<EnumOptionData> interestCalculationTypeOptions,
final Collection<EnumOptionData> interestCalculationTypeOption,
final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions, final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions, final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
final Collection<PaymentTypeData> paymentTypeOptions, final Collection<EnumOptionData> accountingRuleOptions, final Collection<EnumOptionData> periodFrequencyOptions, final Collection<PaymentTypeData> paymentTypeOptions,
final Collection<EnumOptionData> accountingRuleOptions,
final Map<String, List<GLAccountData>> accountingMappingOptions, final Collection<ChargeData> chargeOptions, final Map<String, List<GLAccountData>> accountingMappingOptions, final Collection<ChargeData> chargeOptions,
final Collection<ChargeData> penaltyOptions, Collection<TaxGroupData> taxGroupOptions, final String accountMappingForPayment) { final Collection<ChargeData> penaltyOptions, Collection<TaxGroupData> taxGroupOptions,
final Collection<CodeValueData> yesNoOptions,
final Collection<CodeValueData> accountTypeOptions, final Collection<CodeValueData> customerSegmentOptions,
final String accountMappingForPayment, final List<Map<String, Object>> otherGlMappings) {
return new SavingsProductData(existingProduct.id, existingProduct.name, existingProduct.shortName, existingProduct.description, SavingsProductData newData = new SavingsProductData(existingProduct.id, existingProduct.name, existingProduct.shortName, existingProduct.description,
existingProduct.currency, existingProduct.nominalAnnualInterestRate, existingProduct.interestCompoundingPeriodType, existingProduct.currency, existingProduct.nominalAnnualInterestRate, existingProduct.interestCompoundingPeriodType,
existingProduct.interestPostingPeriodType, existingProduct.interestCalculationType, existingProduct.interestPostingPeriodType, existingProduct.interestCalculationType,
existingProduct.interestCalculationDaysInYearType, existingProduct.minRequiredOpeningBalance, existingProduct.interestCalculationDaysInYearType, existingProduct.minRequiredOpeningBalance,
existingProduct.lockinPeriodFrequency, existingProduct.lockinPeriodFrequencyType, existingProduct.withdrawalFeeForTransfers, existingProduct.lockinPeriodFrequency, existingProduct.lockinPeriodFrequencyType, existingProduct.withdrawalFeeForTransfers,
existingProduct.accountingRule, existingProduct.accountingMappings, existingProduct.paymentChannelToFundSourceMappings, existingProduct.accountingRule, existingProduct.accountingMappings, existingProduct.paymentChannelToFundSourceMappings,
currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, interestCalculationTypeOption, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions,
accountingRuleOptions, accountingMappingOptions, existingProduct.charges, chargeOptions, penaltyOptions, periodFrequencyOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, existingProduct.charges,
chargeOptions, penaltyOptions,
existingProduct.feeToIncomeAccountMappings, existingProduct.penaltyToIncomeAccountMappings, existingProduct.allowOverdraft, existingProduct.feeToIncomeAccountMappings, existingProduct.penaltyToIncomeAccountMappings, existingProduct.allowOverdraft,
existingProduct.overdraftLimit, existingProduct.minRequiredBalance, existingProduct.enforceMinRequiredBalance, existingProduct.overdraftLimit, existingProduct.minRequiredBalance, existingProduct.enforceMinRequiredBalance,
existingProduct.maxAllowedLienLimit, existingProduct.lienAllowed, existingProduct.minBalanceForInterestCalculation, existingProduct.maxAllowedLienLimit, existingProduct.lienAllowed, existingProduct.minBalanceForInterestCalculation,
existingProduct.nominalAnnualInterestRateOverdraft, existingProduct.minOverdraftForInterestCalculation, existingProduct.nominalAnnualInterestRateOverdraft, existingProduct.minOverdraftForInterestCalculation,
existingProduct.withHoldTax, existingProduct.taxGroup, taxGroupOptions, existingProduct.isDormancyTrackingActive, existingProduct.withHoldTax, existingProduct.taxGroup, taxGroupOptions, yesNoOptions, accountTypeOptions,
existingProduct.daysToInactive, existingProduct.daysToDormancy, existingProduct.daysToEscheat, accountMappingForPayment); customerSegmentOptions, existingProduct.isDormancyTrackingActive, existingProduct.daysToInactive, existingProduct.daysToDormancy,
existingProduct.daysToEscheat, accountMappingForPayment, existingProduct.otherGlMappings);
copyCustomFields(existingProduct, newData);
return newData;
}
public static SavingsProductData withTemplate(final SavingsProductData existingProduct, final Collection<CurrencyData> currencyOptions,
final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions,
final Collection<EnumOptionData> interestPostingPeriodTypeOptions,
final Collection<EnumOptionData> interestCalculationTypeOptions,
final Collection<EnumOptionData> interestCalculationTypeOption,
final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
final Collection<EnumOptionData> periodFrequencyOptions, final Collection<PaymentTypeData> paymentTypeOptions,
final Collection<EnumOptionData> accountingRuleOptions,
final Map<String, List<GLAccountData>> accountingMappingOptions, final Collection<ChargeData> chargeOptions,
final Collection<ChargeData> penaltyOptions, Collection<TaxGroupData> taxGroupOptions,
final Collection<CodeValueData> yesNoOptions,
final Collection<CodeValueData> accountTypeOptions, final Collection<CodeValueData> customerSegmentOptions,
final String accountMappingForPayment) {
return withTemplate(existingProduct, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
interestCalculationTypeOptions, interestCalculationTypeOption, interestCalculationDaysInYearTypeOptions,
lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, periodFrequencyOptions, paymentTypeOptions, accountingRuleOptions,
accountingMappingOptions, chargeOptions, penaltyOptions, taxGroupOptions, yesNoOptions, accountTypeOptions,
customerSegmentOptions, accountMappingForPayment, existingProduct.otherGlMappings);
} }
public static SavingsProductData withAccountingDetails(final SavingsProductData existingProduct, public static SavingsProductData withAccountingDetails(final SavingsProductData existingProduct,
@@ -228,31 +337,52 @@ public final class SavingsProductData implements Serializable {
final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null; final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null; final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null;
final Collection<EnumOptionData> interestCalculationTypeOptions = null; final Collection<EnumOptionData> interestCalculationTypeOptions = null;
final Collection<EnumOptionData> interestCalculationTypeOption = null;
final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null; final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null; final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
final Collection<EnumOptionData> withdrawalFeeTypeOptions = null; final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
final Collection<EnumOptionData> periodFrequencyOptions = null;
final Collection<PaymentTypeData> paymentTypeOptions = null; final Collection<PaymentTypeData> paymentTypeOptions = null;
final Collection<EnumOptionData> accountingRuleOptions = null; final Collection<EnumOptionData> accountingRuleOptions = null;
final Map<String, List<GLAccountData>> accountingMappingOptions = null; final Map<String, List<GLAccountData>> accountingMappingOptions = null;
final Collection<ChargeData> chargeOptions = null; final Collection<ChargeData> chargeOptions = null;
final Collection<ChargeData> penaltyOptions = null; final Collection<ChargeData> penaltyOptions = null;
return new SavingsProductData(existingProduct.id, existingProduct.name, existingProduct.shortName, existingProduct.description, SavingsProductData newData = new SavingsProductData(existingProduct.id, existingProduct.name, existingProduct.shortName, existingProduct.description, existingProduct.currency, existingProduct.nominalAnnualInterestRate, existingProduct.interestCompoundingPeriodType, existingProduct.interestPostingPeriodType, existingProduct.interestCalculationType, existingProduct.interestCalculationDaysInYearType, existingProduct.minRequiredOpeningBalance, existingProduct.lockinPeriodFrequency, existingProduct.lockinPeriodFrequencyType, existingProduct.withdrawalFeeForTransfers, existingProduct.accountingRule, accountingMappings, paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationTypeOption, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, periodFrequencyOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, existingProduct.charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings, existingProduct.allowOverdraft, existingProduct.overdraftLimit, existingProduct.minRequiredBalance, existingProduct.enforceMinRequiredBalance, existingProduct.maxAllowedLienLimit, existingProduct.lienAllowed, existingProduct.minBalanceForInterestCalculation, existingProduct.nominalAnnualInterestRateOverdraft, existingProduct.minOverdraftForInterestCalculation, existingProduct.withHoldTax, existingProduct.taxGroup, existingProduct.taxGroupOptions, existingProduct.yesNoOptions, existingProduct.accountTypeOptions, existingProduct.customerSegmentOptions, existingProduct.isDormancyTrackingActive, existingProduct.daysToInactive, existingProduct.daysToDormancy, existingProduct.daysToEscheat, existingProduct.accountMappingForPayment, existingProduct.otherGlMappings); copyCustomFields(existingProduct, newData); return newData;
existingProduct.currency, existingProduct.nominalAnnualInterestRate, existingProduct.interestCompoundingPeriodType, }
existingProduct.interestPostingPeriodType, existingProduct.interestCalculationType,
existingProduct.interestCalculationDaysInYearType, existingProduct.minRequiredOpeningBalance, private static void copyCustomFields(SavingsProductData source, SavingsProductData target) {
existingProduct.lockinPeriodFrequency, existingProduct.lockinPeriodFrequencyType, existingProduct.withdrawalFeeForTransfers, target.setAccountType(source.getAccountType());
existingProduct.accountingRule, accountingMappings, paymentChannelToFundSourceMappings, currencyOptions, target.setCustomerSegment(source.getCustomerSegment());
interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, target.setProductCode(source.getProductCode());
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, target.setGlAccountId(source.getGlAccountId());
accountingRuleOptions, accountingMappingOptions, existingProduct.charges, chargeOptions, penaltyOptions, target.setGlAccountName(source.getGlAccountName());
feeToIncomeAccountMappings, penaltyToIncomeAccountMappings, existingProduct.allowOverdraft, existingProduct.overdraftLimit, target.setEffectiveDate(source.getEffectiveDate());
existingProduct.minRequiredBalance, existingProduct.enforceMinRequiredBalance, existingProduct.maxAllowedLienLimit, target.setEffectiveTo(source.getEffectiveTo());
existingProduct.lienAllowed, existingProduct.minBalanceForInterestCalculation, target.setEndDate(source.getEndDate());
existingProduct.nominalAnnualInterestRateOverdraft, existingProduct.minOverdraftForInterestCalculation, target.setIntroducerRequired(source.getIntroducerRequired());
existingProduct.withHoldTax, existingProduct.taxGroup, existingProduct.taxGroupOptions, target.setShareLinkingEnabled(source.getShareLinkingEnabled());
existingProduct.isDormancyTrackingActive, existingProduct.daysToInactive, existingProduct.daysToDormancy, target.setNominationFacility(source.getNominationFacility());
existingProduct.daysToEscheat, existingProduct.accountMappingForPayment); target.setDormancyAlert(source.getDormancyAlert());
target.setInterestCalculateSelector(source.getInterestCalculateSelector());
target.setInterestCalculationTypeName(source.getInterestCalculationTypeName());
target.setAveragePeriod(source.getAveragePeriod());
target.setAutoPost(source.getAutoPost());
target.setFrequencyFixDay(source.getFrequencyFixDay());
target.setLastInterestDate(source.getLastInterestDate());
target.setEnableLockinPeriod(source.getEnableLockinPeriod());
target.setBalanceFrequency(source.getBalanceFrequency());
target.setAccountClosedZeroBalanceInDayClosing(source.getAccountClosedZeroBalanceInDayClosing());
target.setBalanceSwapOption(source.getBalanceSwapOption());
target.setMsme(source.getMsme());
target.setAutoDebitFacility(source.getAutoDebitFacility());
target.setAvgBalanceCalcPeriod(source.getAvgBalanceCalcPeriod());
target.setValidForPeriod(source.getValidForPeriod());
target.setOdAccountClosedZeroBalanceInDayClosing(source.getOdAccountClosedZeroBalanceInDayClosing());
target.setOtherGlMappingsMap(source.getOtherGlMappingsMap());
target.setAccountFacilityConfig(source.getAccountFacilityConfig());
target.setAccountFacilityLimits(source.getAccountFacilityLimits());
target.setDepositLimitKyc(source.getDepositLimitKyc());
} }
public static SavingsProductData instance(final Long id, final String name, final String shortName, final String description, public static SavingsProductData instance(final Long id, final String name, final String shortName, final String description,
@@ -264,8 +394,10 @@ public final class SavingsProductData implements Serializable {
final BigDecimal minRequiredBalance, final boolean enforceMinRequiredBalance, final BigDecimal maxAllowedLienLimit, final BigDecimal minRequiredBalance, final boolean enforceMinRequiredBalance, final BigDecimal maxAllowedLienLimit,
final boolean lienAllowed, final BigDecimal minBalanceForInterestCalculation, final boolean lienAllowed, final BigDecimal minBalanceForInterestCalculation,
final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation, final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation,
final boolean withHoldTax, final TaxGroupData taxGroup, final Boolean isDormancyTrackingActive, final Long daysToInactive, final boolean withHoldTax, final TaxGroupData taxGroup, final Collection<CodeValueData> yesNoOptions,
final Long daysToDormancy, final Long daysToEscheat) { final Collection<CodeValueData> accountTypeOptions, final Collection<CodeValueData> customerSegmentOptions,
final Boolean isDormancyTrackingActive, final Long daysToInactive, final Long daysToDormancy, final Long daysToEscheat,
final List<Map<String, Object>> otherGlMappings) {
final Map<String, Object> accountingMappings = null; final Map<String, Object> accountingMappings = null;
final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings = null; final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings = null;
@@ -274,9 +406,11 @@ public final class SavingsProductData implements Serializable {
final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null; final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null; final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null;
final Collection<EnumOptionData> interestCalculationTypeOptions = null; final Collection<EnumOptionData> interestCalculationTypeOptions = null;
final Collection<EnumOptionData> interestCalculationTypeOption = null;
final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null; final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null; final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
final Collection<EnumOptionData> withdrawalFeeTypeOptions = null; final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
final Collection<EnumOptionData> periodFrequencyOptions = null;
final Collection<PaymentTypeData> paymentTypeOptions = null; final Collection<PaymentTypeData> paymentTypeOptions = null;
final Collection<EnumOptionData> accountingRuleOptions = null; final Collection<EnumOptionData> accountingRuleOptions = null;
final Map<String, List<GLAccountData>> accountingMappingOptions = null; final Map<String, List<GLAccountData>> accountingMappingOptions = null;
@@ -292,12 +426,14 @@ public final class SavingsProductData implements Serializable {
interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, accountingType, accountingMappings, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, accountingType, accountingMappings,
paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, interestCalculationTypeOptions, interestCalculationTypeOption, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, withdrawalFeeTypeOptions, periodFrequencyOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions,
charges, chargeOptions,
penaltyOptions, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings, allowOverdraft, overdraftLimit, penaltyOptions, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings, allowOverdraft, overdraftLimit,
minRequiredBalance, enforceMinRequiredBalance, maxAllowedLienLimit, lienAllowed, minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance, maxAllowedLienLimit, lienAllowed, minBalanceForInterestCalculation,
nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup, taxGroupOptions, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup, taxGroupOptions,
isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, accountMappingForPayment); yesNoOptions, accountTypeOptions, customerSegmentOptions, isDormancyTrackingActive, daysToInactive, daysToDormancy,
daysToEscheat, accountMappingForPayment, otherGlMappings);
} }
public static SavingsProductData lookup(final Long id, final String name) { public static SavingsProductData lookup(final Long id, final String name) {
@@ -333,9 +469,11 @@ public final class SavingsProductData implements Serializable {
final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null; final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null; final Collection<EnumOptionData> interestPostingPeriodTypeOptions = null;
final Collection<EnumOptionData> interestCalculationTypeOptions = null; final Collection<EnumOptionData> interestCalculationTypeOptions = null;
final Collection<EnumOptionData> interestCalculationTypeOption = null;
final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null; final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null; final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
final Collection<EnumOptionData> withdrawalFeeTypeOptions = null; final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
final Collection<EnumOptionData> periodFrequencyOptions = null;
final Collection<PaymentTypeData> paymentTypeOptions = null; final Collection<PaymentTypeData> paymentTypeOptions = null;
final Collection<EnumOptionData> accountingRuleOptions = null; final Collection<EnumOptionData> accountingRuleOptions = null;
final Map<String, List<GLAccountData>> accountingMappingOptions = null; final Map<String, List<GLAccountData>> accountingMappingOptions = null;
@@ -345,22 +483,28 @@ public final class SavingsProductData implements Serializable {
final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings = null; final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings = null;
final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings = null; final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings = null;
final Collection<TaxGroupData> taxGroupOptions = null; final Collection<TaxGroupData> taxGroupOptions = null;
final Collection<CodeValueData> yesNoOptions = null;
final Collection<CodeValueData> accountTypeOptions = null;
final Collection<CodeValueData> customerSegmentOptions = null;
final Boolean isDormancyTrackingActive = null; final Boolean isDormancyTrackingActive = null;
final Long daysToInactive = null; final Long daysToInactive = null;
final Long daysToDormancy = null; final Long daysToDormancy = null;
final Long daysToEscheat = null; final Long daysToEscheat = null;
final String accountMappingForPayment = null; final String accountMappingForPayment = null;
final List<Map<String, Object>> otherGlMappings = null;
return new SavingsProductData(id, name, shortName, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, return new SavingsProductData(id, name, shortName, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType,
interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, accountingType, accountingMappings, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, accountingType, accountingMappings,
paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, interestCalculationTypeOptions, interestCalculationTypeOption, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, withdrawalFeeTypeOptions, periodFrequencyOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, charges,
chargeOptions,
penaltyOptions, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings, allowOverdraft, overdraftLimit, penaltyOptions, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings, allowOverdraft, overdraftLimit,
minRequiredBalance, enforceMinRequiredBalance, maxAllowedLienLimit, lienAllowed, minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance, maxAllowedLienLimit, lienAllowed, minBalanceForInterestCalculation,
nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup, taxGroupOptions, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup, taxGroupOptions,
isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, accountMappingForPayment); yesNoOptions, accountTypeOptions, customerSegmentOptions, isDormancyTrackingActive, daysToInactive, daysToDormancy,
daysToEscheat, accountMappingForPayment, otherGlMappings);
} }
public static SavingsProductData createForInterestPosting(final Long id, final EnumOptionData accountingRule) { public static SavingsProductData createForInterestPosting(final Long id, final EnumOptionData accountingRule) {
@@ -388,9 +532,11 @@ public final class SavingsProductData implements Serializable {
this.interestCompoundingPeriodTypeOptions = null; this.interestCompoundingPeriodTypeOptions = null;
this.interestPostingPeriodTypeOptions = null; this.interestPostingPeriodTypeOptions = null;
this.interestCalculationTypeOptions = null; this.interestCalculationTypeOptions = null;
this.interestCalculationTypeOption = null;
this.interestCalculationDaysInYearTypeOptions = null; this.interestCalculationDaysInYearTypeOptions = null;
this.lockinPeriodFrequencyTypeOptions = null; this.lockinPeriodFrequencyTypeOptions = null;
this.withdrawalFeeTypeOptions = null; this.withdrawalFeeTypeOptions = null;
this.periodFrequencyOptions = null;
this.paymentTypeOptions = null; this.paymentTypeOptions = null;
this.accountingMappingOptions = null; this.accountingMappingOptions = null;
@@ -419,10 +565,14 @@ public final class SavingsProductData implements Serializable {
this.taxGroup = null; this.taxGroup = null;
this.withHoldTax = false; this.withHoldTax = false;
this.taxGroupOptions = null; this.taxGroupOptions = null;
this.yesNoOptions = null;
this.accountTypeOptions = null;
this.customerSegmentOptions = null;
this.isDormancyTrackingActive = null; this.isDormancyTrackingActive = null;
this.daysToInactive = null; this.daysToInactive = null;
this.daysToDormancy = null; this.daysToDormancy = null;
this.daysToEscheat = null; this.daysToEscheat = null;
this.otherGlMappings = null;
this.accountMappingForPayment = null; this.accountMappingForPayment = null;
} }
@@ -436,9 +586,11 @@ public final class SavingsProductData implements Serializable {
final Collection<CurrencyData> currencyOptions, final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions, final Collection<CurrencyData> currencyOptions, final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions,
final Collection<EnumOptionData> interestPostingPeriodTypeOptions, final Collection<EnumOptionData> interestPostingPeriodTypeOptions,
final Collection<EnumOptionData> interestCalculationTypeOptions, final Collection<EnumOptionData> interestCalculationTypeOptions,
final Collection<EnumOptionData> interestCalculationTypeOption,
final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions, final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions, final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
final Collection<PaymentTypeData> paymentTypeOptions, final Collection<EnumOptionData> accountingRuleOptions, final Collection<EnumOptionData> periodFrequencyOptions, final Collection<PaymentTypeData> paymentTypeOptions,
final Collection<EnumOptionData> accountingRuleOptions,
final Map<String, List<GLAccountData>> accountingMappingOptions, final Collection<ChargeData> charges, final Map<String, List<GLAccountData>> accountingMappingOptions, final Collection<ChargeData> charges,
final Collection<ChargeData> chargeOptions, final Collection<ChargeData> penaltyOptions, final Collection<ChargeData> chargeOptions, final Collection<ChargeData> penaltyOptions,
final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings, final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings,
@@ -447,8 +599,10 @@ public final class SavingsProductData implements Serializable {
final BigDecimal maxAllowedLienLimit, final boolean lienAllowed, final BigDecimal minBalanceForInterestCalculation, final BigDecimal maxAllowedLienLimit, final boolean lienAllowed, final BigDecimal minBalanceForInterestCalculation,
final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation, final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation,
final boolean withHoldTax, final TaxGroupData taxGroup, final Collection<TaxGroupData> taxGroupOptions, final boolean withHoldTax, final TaxGroupData taxGroup, final Collection<TaxGroupData> taxGroupOptions,
final Collection<CodeValueData> yesNoOptions, final Collection<CodeValueData> accountTypeOptions,
final Collection<CodeValueData> customerSegmentOptions,
final Boolean isDormancyTrackingActive, final Long daysToInactive, final Long daysToDormancy, final Long daysToEscheat, final Boolean isDormancyTrackingActive, final Long daysToInactive, final Long daysToDormancy, final Long daysToEscheat,
final String accountMappingForPayment) { final String accountMappingForPayment, final List<Map<String, Object>> otherGlMappings) {
this.id = id; this.id = id;
this.name = name; this.name = name;
this.shortName = shortName; this.shortName = shortName;
@@ -469,9 +623,11 @@ public final class SavingsProductData implements Serializable {
this.interestCompoundingPeriodTypeOptions = interestCompoundingPeriodTypeOptions; this.interestCompoundingPeriodTypeOptions = interestCompoundingPeriodTypeOptions;
this.interestPostingPeriodTypeOptions = interestPostingPeriodTypeOptions; this.interestPostingPeriodTypeOptions = interestPostingPeriodTypeOptions;
this.interestCalculationTypeOptions = interestCalculationTypeOptions; this.interestCalculationTypeOptions = interestCalculationTypeOptions;
this.interestCalculationTypeOption = interestCalculationTypeOption;
this.interestCalculationDaysInYearTypeOptions = interestCalculationDaysInYearTypeOptions; this.interestCalculationDaysInYearTypeOptions = interestCalculationDaysInYearTypeOptions;
this.lockinPeriodFrequencyTypeOptions = lockinPeriodFrequencyTypeOptions; this.lockinPeriodFrequencyTypeOptions = lockinPeriodFrequencyTypeOptions;
this.withdrawalFeeTypeOptions = withdrawalFeeTypeOptions; this.withdrawalFeeTypeOptions = withdrawalFeeTypeOptions;
this.periodFrequencyOptions = periodFrequencyOptions;
this.paymentTypeOptions = paymentTypeOptions; this.paymentTypeOptions = paymentTypeOptions;
this.accountingMappingOptions = accountingMappingOptions; this.accountingMappingOptions = accountingMappingOptions;
@@ -503,11 +659,15 @@ public final class SavingsProductData implements Serializable {
this.taxGroup = taxGroup; this.taxGroup = taxGroup;
this.withHoldTax = withHoldTax; this.withHoldTax = withHoldTax;
this.taxGroupOptions = taxGroupOptions; this.taxGroupOptions = taxGroupOptions;
this.yesNoOptions = yesNoOptions;
this.accountTypeOptions = accountTypeOptions;
this.customerSegmentOptions = customerSegmentOptions;
this.isDormancyTrackingActive = isDormancyTrackingActive; this.isDormancyTrackingActive = isDormancyTrackingActive;
this.daysToInactive = daysToInactive; this.daysToInactive = daysToInactive;
this.daysToDormancy = daysToDormancy; this.daysToDormancy = daysToDormancy;
this.daysToEscheat = daysToEscheat; this.daysToEscheat = daysToEscheat;
this.accountMappingForPayment = accountMappingForPayment; this.accountMappingForPayment = accountMappingForPayment;
this.otherGlMappings = otherGlMappings;
} }
public boolean hasAccountingEnabled() { public boolean hasAccountingEnabled() {
@@ -547,4 +707,8 @@ public final class SavingsProductData implements Serializable {
return AccountingRuleType.ACCRUAL_PERIODIC.getValue().toString().equals(this.accountingRule.getValue()); return AccountingRuleType.ACCRUAL_PERIODIC.getValue().toString().equals(this.accountingRule.getValue());
} }
public List<Map<String, Object>> getOtherGlMappings() {
return this.otherGlMappings;
}
} }
+68
View File
@@ -0,0 +1,68 @@
/**
* 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.
*/
description = 'Fineract Current Product'
compileJava {
dependsOn ':fineract-avro-schemas:buildJavaSdk'
}
configurations {
providedRuntime // needed for Spring Boot executable WAR
providedCompile
compile() {
exclude module: 'hibernate-entitymanager'
exclude module: 'hibernate-validator'
exclude module: 'activation'
exclude module: 'bcmail-jdk14'
exclude module: 'bcprov-jdk14'
exclude module: 'bctsp-jdk14'
exclude module: 'c3p0'
exclude module: 'stax-api'
exclude module: 'jaxb-api'
exclude module: 'jaxb-impl'
exclude module: 'jboss-logging'
exclude module: 'itext-rtf'
exclude module: 'classworlds'
}
runtime
}
apply from: 'dependencies.gradle'
// If we are running Gradle within Eclipse to enhance classes with OpenJPA,
// set the classes directory to point to Eclipse's default build directory
if (project.hasProperty('env') && project.getProperty('env') == 'eclipse') {
sourceSets.main.java.outputDir = new File(rootProject.projectDir, "fineract-current-product/bin/main")
}
eclipse {
project {
buildCommand([ LaunchConfigHandle: "<project>/.externalToolBuilders/OpenJPA Enhance Builder.launch" ], 'org.eclipse.ui.externaltools.ExternalToolBuilder')
}
}
if (!(project.hasProperty('env') && project.getProperty('env') == 'dev')) {
sourceSets {
test {
java {
exclude '**/core/boot/tests/**'
}
}
}
}
@@ -0,0 +1,64 @@
/**
* 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.
*/
dependencies {
// Never use "compile" scope, but make all dependencies either 'implementation', 'runtimeOnly' or 'testCompile'.
implementation(project(path: ':fineract-core'))
implementation(project(path: ':fineract-accounting'))
implementation(project(path: ':fineract-charge'))
implementation(project(path: ':fineract-tax'))
implementation(project(path: ':fineract-savings'))
implementation(
'org.springframework.boot:spring-boot-starter-web',
'org.springframework.boot:spring-boot-starter-security',
'jakarta.ws.rs:jakarta.ws.rs-api',
'com.google.guava:guava',
'com.google.code.gson:gson',
'org.apache.commons:commons-lang3',
'com.jayway.jsonpath:json-path',
'com.github.spotbugs:spotbugs-annotations',
'io.swagger.core.v3:swagger-annotations-jakarta',
'org.mapstruct:mapstruct',
)
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
annotationProcessor 'org.mapstruct:mapstruct-processor'
implementation ('org.springframework.boot:spring-boot-starter-data-jpa') {
exclude group: 'org.hibernate'
}
implementation('org.eclipse.persistence:org.eclipse.persistence.jpa') {
exclude group: 'org.eclipse.persistence', module: 'jakarta.persistence'
}
testImplementation( 'io.github.classgraph:classgraph' )
testImplementation ('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'com.jayway.jsonpath', module: 'json-path'
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
exclude group: 'jakarta.activation'
exclude group: 'javax.activation'
exclude group: 'org.skyscreamer'
}
testImplementation ('org.mockito:mockito-inline')
}
@@ -0,0 +1,386 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.currentaccount.domain;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import lombok.Getter;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
import org.apache.fineract.organisation.office.domain.Office;
import org.apache.fineract.organisation.staff.domain.Staff;
import org.apache.fineract.portfolio.client.domain.Client;
@Entity
@Table(name = "m_current_account")
@Getter
@Setter
public class CurrentAccount extends AbstractAuditableCustom {
@Column(name = "account_no", nullable = false, length = 20, unique = true)
private String accountNo;
@Column(name = "external_id", length = 100)
private String externalId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "client_id")
private Client client;
// Store product_id as plain column to avoid cross-module EclipseLink entity dependency
@Column(name = "product_id", nullable = false)
private Long productId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "office_id", nullable = false)
private Office office;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "staff_id")
private Staff staff;
// Status: 100=Submitted, 200=Approved, 300=Active, 400=Withdrawn, 500=Rejected, 600=Closed
@Column(name = "status_enum", nullable = false)
private Integer statusEnum = 100;
// SubStatus: 0=None, 100=Inactive, 200=Dormant, 300=Block, 400=BlockDebit, 500=BlockCredit
@Column(name = "sub_status_enum", nullable = false)
private Integer subStatusEnum = 0;
// 1=Individual, 2=Group, 3=JLG
@Column(name = "account_type_enum", nullable = false)
private Integer accountTypeEnum = 1;
// Lifecycle dates
@Column(name = "submittedon_date")
private LocalDate submittedOnDate;
@Column(name = "approvedon_date")
private LocalDate approvedOnDate;
@Column(name = "activatedon_date")
private LocalDate activatedOnDate;
@Column(name = "rejectedon_date")
private LocalDate rejectedOnDate;
@Column(name = "withdrawnon_date")
private LocalDate withdrawnOnDate;
@Column(name = "closedon_date")
private LocalDate closedOnDate;
// Currency (copied from product at account creation)
@Column(name = "currency_code", nullable = false, length = 3)
private String currencyCode;
@Column(name = "currency_digits", nullable = false)
private Integer currencyDigits;
@Column(name = "currency_multiplesof")
private Integer currencyMultiplesOf;
// Interest
@Column(name = "nominal_annual_interest_rate", nullable = false, precision = 19, scale = 6)
private BigDecimal nominalAnnualInterestRate = BigDecimal.ZERO;
@Column(name = "interest_compounding_period_enum", nullable = false)
private Integer interestCompoundingPeriodEnum;
@Column(name = "interest_posting_period_enum", nullable = false)
private Integer interestPostingPeriodEnum;
@Column(name = "interest_calculation_type_enum", nullable = false)
private Integer interestCalculationTypeEnum;
@Column(name = "interest_calculation_days_in_year_type_enum", nullable = false)
private Integer interestCalculationDaysInYearTypeEnum;
@Column(name = "min_balance_for_interest_calculation", precision = 19, scale = 6)
private BigDecimal minBalanceForInterestCalculation;
@Column(name = "start_interest_calculation_date")
private LocalDate startInterestCalculationDate;
@Column(name = "accrued_till_date")
private LocalDate accruedTillDate;
@Column(name = "last_interest_calculation_date")
private LocalDate lastInterestCalculationDate;
@Column(name = "last_closed_business_date")
private LocalDate lastClosedBusinessDate;
// Tax
@Column(name = "withhold_tax", nullable = false)
private boolean withholdTax = false;
// Balance rules
@Column(name = "min_required_opening_balance", precision = 19, scale = 6)
private BigDecimal minRequiredOpeningBalance;
@Column(name = "lockin_period_frequency")
private Integer lockinPeriodFrequency;
@Column(name = "lockin_period_frequency_enum")
private Integer lockinPeriodFrequencyEnum;
@Column(name = "lockedin_until_date_derived")
private LocalDate lockedInUntilDate;
@Column(name = "enforce_min_required_balance", nullable = false)
private boolean enforceMinRequiredBalance = false;
@Column(name = "min_required_balance", precision = 19, scale = 6)
private BigDecimal minRequiredBalance;
// Overdraft
@Column(name = "allow_overdraft", nullable = false)
private boolean allowOverdraft = false;
@Column(name = "overdraft_limit", precision = 19, scale = 6)
private BigDecimal overdraftLimit;
@Column(name = "nominal_annual_interest_rate_overdraft", precision = 19, scale = 6)
private BigDecimal nominalAnnualInterestRateOverdraft;
@Column(name = "min_overdraft_for_interest_calculation", precision = 19, scale = 6)
private BigDecimal minOverdraftForInterestCalculation;
// Lien
@Column(name = "is_lien_allowed", nullable = false)
private boolean lienAllowed = false;
@Column(name = "max_allowed_lien_limit", precision = 19, scale = 6)
private BigDecimal maxAllowedLienLimit;
@Column(name = "on_hold_funds_derived", nullable = false, precision = 19, scale = 6)
private BigDecimal onHoldFundsDerived = BigDecimal.ZERO;
// Running balance summary
@Column(name = "account_balance_derived", nullable = false, precision = 19, scale = 6)
private BigDecimal accountBalanceDerived = BigDecimal.ZERO;
@Column(name = "total_deposits_derived", nullable = false, precision = 19, scale = 6)
private BigDecimal totalDepositsDerived = BigDecimal.ZERO;
@Column(name = "total_withdrawals_derived", nullable = false, precision = 19, scale = 6)
private BigDecimal totalWithdrawalsDerived = BigDecimal.ZERO;
@Column(name = "total_interest_earned_derived", nullable = false, precision = 19, scale = 6)
private BigDecimal totalInterestEarnedDerived = BigDecimal.ZERO;
@Column(name = "total_interest_posted_derived", nullable = false, precision = 19, scale = 6)
private BigDecimal totalInterestPostedDerived = BigDecimal.ZERO;
@Column(name = "total_overdraft_interest_derived", nullable = false, precision = 19, scale = 6)
private BigDecimal totalOverdraftInterestDerived = BigDecimal.ZERO;
@Column(name = "total_withhold_tax_derived", nullable = false, precision = 19, scale = 6)
private BigDecimal totalWithholdTaxDerived = BigDecimal.ZERO;
@Column(name = "total_savings_amount_on_hold", nullable = false, precision = 19, scale = 6)
private BigDecimal totalSavingsAmountOnHold = BigDecimal.ZERO;
@Column(name = "withdrawal_fee_for_transfer", nullable = false)
private boolean withdrawalFeeForTransfer = false;
@Column(name = "reason_for_block", length = 500)
private String reasonForBlock;
// Current-account-specific fields
@Column(name = "msme", nullable = false)
private boolean msme = false;
@Column(name = "auto_debit_facility", nullable = false)
private boolean autoDebitFacility = false;
@Column(name = "balance_swap_option", nullable = false)
private boolean balanceSwapOption = false;
@Column(name = "establishment_date")
private LocalDate establishmentDate;
@Column(name = "start_operation_date")
private LocalDate startOperationDate;
@Column(name = "is_non_individual_client", nullable = false)
private boolean nonIndividualClient = false;
@Column(name = "signatory_rights", length = 100)
private String signatoryRights;
// CBS classification
@Column(name = "cbs_account_type", length = 100)
private String cbsAccountType;
@Column(name = "customer_segment", length = 100)
private String customerSegment;
@Column(name = "account_status", length = 50)
private String accountStatus;
@Column(name = "mode_of_operation", length = 50)
private String modeOfOperation;
@Column(name = "special_category", length = 100)
private String specialCategory;
// Introducer
@Column(name = "introducer_client_id")
private Long introducerClientId;
@Column(name = "introducer_name", length = 200)
private String introducerName;
@Column(name = "introducer_cif_number", length = 50)
private String introducerCifNumber;
// Nominee JSON
@Column(name = "nominee_data", columnDefinition = "TEXT")
private String nomineeData;
// Interest calculation settings
@Column(name = "interest_calculate_selector")
private Integer interestCalculateSelector;
@Column(name = "average_period", length = 20)
private String averagePeriod;
@Column(name = "auto_post", nullable = false)
private boolean autoPost = false;
@Column(name = "frequency_fix_day")
private Integer frequencyFixDay;
@Column(name = "enable_lockin_period", nullable = false)
private boolean enableLockinPeriod = false;
@Column(name = "balance_frequency", length = 50)
private String balanceFrequency;
@Column(name = "avg_balance_calc_period", length = 10)
private String avgBalanceCalcPeriod;
// Relationships
@OneToMany(mappedBy = "currentAccount", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private List<CurrentAccountTransaction> transactions = new ArrayList<>();
@OneToMany(mappedBy = "currentAccount", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private Set<CurrentAccountCharge> charges = new HashSet<>();
@OneToOne(mappedBy = "currentAccount", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private CurrentAccountFacilityConfig facilityConfig;
@OneToMany(mappedBy = "currentAccount", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private List<CurrentAccountJointHolder> jointHolders = new ArrayList<>();
@OneToMany(mappedBy = "currentAccount", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private List<CurrentAccountPartner> partners = new ArrayList<>();
@OneToMany(mappedBy = "currentAccount", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private List<CurrentAccountOfficerHistory> officerHistory = new ArrayList<>();
protected CurrentAccount() {}
public static CurrentAccount createNew(Client client, Long productId, Office office,
Staff staff, String accountNo, LocalDate submittedOnDate, Integer accountTypeEnum,
String currencyCode, Integer currencyDigits, Integer currencyMultiplesOf,
BigDecimal nominalAnnualInterestRate, Integer interestCompoundingPeriodEnum,
Integer interestPostingPeriodEnum, Integer interestCalculationTypeEnum,
Integer interestCalculationDaysInYearTypeEnum, boolean allowOverdraft,
BigDecimal overdraftLimit, boolean msme, boolean autoDebitFacility,
boolean balanceSwapOption, String customerSegment, boolean autoPost,
Integer interestCalculateSelector, String averagePeriod, String avgBalanceCalcPeriod) {
CurrentAccount account = new CurrentAccount();
account.client = client;
account.productId = productId;
account.office = office;
account.staff = staff;
account.accountNo = accountNo;
account.submittedOnDate = submittedOnDate;
account.statusEnum = 100;
account.subStatusEnum = 0;
account.accountTypeEnum = accountTypeEnum != null ? accountTypeEnum : 1;
account.currencyCode = currencyCode;
account.currencyDigits = currencyDigits;
account.currencyMultiplesOf = currencyMultiplesOf;
account.nominalAnnualInterestRate = nominalAnnualInterestRate != null ? nominalAnnualInterestRate : BigDecimal.ZERO;
account.interestCompoundingPeriodEnum = interestCompoundingPeriodEnum;
account.interestPostingPeriodEnum = interestPostingPeriodEnum;
account.interestCalculationTypeEnum = interestCalculationTypeEnum;
account.interestCalculationDaysInYearTypeEnum = interestCalculationDaysInYearTypeEnum;
account.allowOverdraft = allowOverdraft;
account.overdraftLimit = overdraftLimit;
account.msme = msme;
account.autoDebitFacility = autoDebitFacility;
account.balanceSwapOption = balanceSwapOption;
account.customerSegment = customerSegment;
account.autoPost = autoPost;
account.interestCalculateSelector = interestCalculateSelector;
account.averagePeriod = averagePeriod;
account.avgBalanceCalcPeriod = avgBalanceCalcPeriod;
return account;
}
public boolean isActive() { return this.statusEnum != null && this.statusEnum == 300; }
public boolean isSubmittedAndPendingApproval() { return this.statusEnum != null && this.statusEnum == 100; }
public boolean isApproved() { return this.statusEnum != null && this.statusEnum == 200; }
public boolean isClosed() { return this.statusEnum != null && this.statusEnum == 600; }
public void updateAccountNo(String accountNo) { this.accountNo = accountNo; }
public void approve(LocalDate approvedOnDate) {
this.statusEnum = 200;
this.approvedOnDate = approvedOnDate;
}
public void activate(LocalDate activatedOnDate) {
this.statusEnum = 300;
this.activatedOnDate = activatedOnDate;
}
public void reject(LocalDate rejectedOnDate) {
this.statusEnum = 500;
this.rejectedOnDate = rejectedOnDate;
}
public void withdraw(LocalDate withdrawnOnDate) {
this.statusEnum = 400;
this.withdrawnOnDate = withdrawnOnDate;
}
public void close(LocalDate closedOnDate) {
this.statusEnum = 600;
this.closedOnDate = closedOnDate;
}
}
@@ -0,0 +1,109 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.currentaccount.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import java.math.BigDecimal;
import java.time.LocalDate;
import lombok.Getter;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.portfolio.charge.domain.Charge;
@Entity
@Table(name = "m_current_account_charge")
@Getter
@Setter
public class CurrentAccountCharge extends AbstractPersistableCustom<Long> {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "current_account_id", nullable = false)
private CurrentAccount currentAccount;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "charge_id", nullable = false)
private Charge charge;
@Column(name = "charge_time_enum", nullable = false)
private Integer chargeTimeEnum;
@Column(name = "charge_due_date")
private LocalDate chargeDueDate;
@Column(name = "fee_on_month")
private Integer feeOnMonth;
@Column(name = "fee_on_day")
private Integer feeOnDay;
@Column(name = "fee_interval")
private Integer feeInterval;
@Column(name = "charge_calculation_enum", nullable = false)
private Integer chargeCalculationEnum;
@Column(name = "calculation_percentage", precision = 19, scale = 6)
private BigDecimal calculationPercentage;
@Column(name = "calculation_on_amount", precision = 19, scale = 6)
private BigDecimal calculationOnAmount;
@Column(name = "amount", nullable = false, precision = 19, scale = 6)
private BigDecimal amount;
@Column(name = "amount_paid_derived", nullable = false, precision = 19, scale = 6)
private BigDecimal amountPaidDerived = BigDecimal.ZERO;
@Column(name = "amount_waived_derived", nullable = false, precision = 19, scale = 6)
private BigDecimal amountWaivedDerived = BigDecimal.ZERO;
@Column(name = "amount_writtenoff_derived", nullable = false, precision = 19, scale = 6)
private BigDecimal amountWrittenOffDerived = BigDecimal.ZERO;
@Column(name = "amount_outstanding_derived", nullable = false, precision = 19, scale = 6)
private BigDecimal amountOutstandingDerived;
@Column(name = "is_paid_derived", nullable = false)
private boolean paid = false;
@Column(name = "waived", nullable = false)
private boolean waived = false;
@Column(name = "is_penalty", nullable = false)
private boolean penalty = false;
@Column(name = "is_active", nullable = false)
private boolean active = true;
@Column(name = "inactivated_on_date")
private LocalDate inactivatedOnDate;
@Column(name = "free_withdrawal_count")
private Integer freeWithdrawalCount;
@Column(name = "charge_reset_date")
private LocalDate chargeResetDate;
protected CurrentAccountCharge() {}
}
@@ -0,0 +1,102 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.currentaccount.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
@Entity
@Table(name = "m_current_account_facility_config")
@Getter
@Setter
public class CurrentAccountFacilityConfig extends AbstractPersistableCustom<Long> {
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "current_account_id", nullable = false, unique = true)
private CurrentAccount currentAccount;
@Column(name = "atm_facility", nullable = false)
private boolean atmFacility = false;
@Column(name = "atm_withdrawal_facility", nullable = false)
private boolean atmWithdrawalFacility = false;
@Column(name = "upi_facility", nullable = false)
private boolean upiFacility = false;
@Column(name = "pos_transactions_facility", nullable = false)
private boolean posTransactionsFacility = false;
@Column(name = "cheque_book", nullable = false)
private boolean chequeBook = false;
@Column(name = "imps_facility", nullable = false)
private boolean impsFacility = false;
@Column(name = "sms_alerts", nullable = false)
private boolean smsAlerts = false;
@Column(name = "bbps_facility", nullable = false)
private boolean bbpsFacility = false;
@Column(name = "insurance_cover", nullable = false)
private boolean insuranceCover = false;
@Column(name = "bepg_facility", nullable = false)
private boolean bepgFacility = false;
@Column(name = "neft_facility", nullable = false)
private boolean neftFacility = false;
@Column(name = "rtgs_facility", nullable = false)
private boolean rtgsFacility = false;
@Column(name = "inter_bank_total_cap_facility", nullable = false)
private boolean interBankTotalCapFacility = false;
@Column(name = "withdrawal_facility", nullable = false)
private boolean withdrawalFacility = false;
@Column(name = "deposit_facility_kyc", nullable = false)
private boolean depositFacilityKyc = false;
@Column(name = "virtual_debit_card", nullable = false)
private boolean virtualDebitCard = false;
@Column(name = "internet_banking", nullable = false)
private boolean internetBanking = false;
@Column(name = "mobile_banking", nullable = false)
private boolean mobileBanking = false;
protected CurrentAccountFacilityConfig() {}
public static CurrentAccountFacilityConfig createFor(CurrentAccount account) {
CurrentAccountFacilityConfig cfg = new CurrentAccountFacilityConfig();
cfg.currentAccount = account;
return cfg;
}
}
@@ -0,0 +1,64 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.currentaccount.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import java.math.BigDecimal;
import lombok.Getter;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
@Entity
@Table(name = "m_current_account_joint_holder")
@Getter
@Setter
public class CurrentAccountJointHolder extends AbstractPersistableCustom<Long> {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "current_account_id", nullable = false)
private CurrentAccount currentAccount;
@Column(name = "client_id")
private Long clientId;
@Column(name = "cif_number", length = 50)
private String cifNumber;
@Column(name = "display_name", length = 200)
private String displayName;
@Column(name = "share_percentage", precision = 5, scale = 2)
private BigDecimal sharePercentage;
@Column(name = "signing_authority", length = 100)
private String signingAuthority;
@Column(name = "role", length = 100)
private String role;
@Column(name = "is_primary", nullable = false)
private boolean primary = false;
public CurrentAccountJointHolder() {}
}
@@ -0,0 +1,57 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.currentaccount.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import java.time.LocalDate;
import lombok.Getter;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.organisation.staff.domain.Staff;
@Entity
@Table(name = "m_current_account_officer_history")
@Getter
@Setter
public class CurrentAccountOfficerHistory extends AbstractPersistableCustom<Long> {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "current_account_id", nullable = false)
private CurrentAccount currentAccount;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "staff_id")
private Staff staff;
@Column(name = "start_date", nullable = false)
private LocalDate startDate;
@Column(name = "end_date")
private LocalDate endDate;
@Column(name = "created_by_id")
private Long createdById;
protected CurrentAccountOfficerHistory() {}
}
@@ -0,0 +1,58 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.currentaccount.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import java.math.BigDecimal;
import lombok.Getter;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
@Entity
@Table(name = "m_current_account_partner")
@Getter
@Setter
public class CurrentAccountPartner extends AbstractPersistableCustom<Long> {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "current_account_id", nullable = false)
private CurrentAccount currentAccount;
@Column(name = "client_id")
private Long clientId;
@Column(name = "client_cif", length = 50)
private String clientCif;
@Column(name = "client_name", length = 200)
private String clientName;
@Column(name = "share_percentage", precision = 5, scale = 2)
private BigDecimal sharePercentage;
@Column(name = "is_mandatory", nullable = false)
private boolean mandatory = false;
public CurrentAccountPartner() {}
}
@@ -0,0 +1,40 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.currentaccount.domain;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface CurrentAccountRepository extends JpaRepository<CurrentAccount, Long> {
Optional<CurrentAccount> findByAccountNo(String accountNo);
Optional<CurrentAccount> findByExternalId(String externalId);
List<CurrentAccount> findByClientId(Long clientId);
@Query("SELECT MAX(ca.id) FROM CurrentAccount ca")
Optional<Long> findMaxId();
@Query("SELECT ca FROM CurrentAccount ca WHERE ca.client.id = :clientId AND ca.statusEnum != 600")
List<CurrentAccount> findActiveByClientId(@Param("clientId") Long clientId);
}
@@ -0,0 +1,137 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.currentaccount.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import lombok.Getter;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.organisation.office.domain.Office;
@Entity
@Table(name = "m_current_account_transaction")
@Getter
@Setter
public class CurrentAccountTransaction extends AbstractPersistableCustom<Long> {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "current_account_id", nullable = false)
private CurrentAccount currentAccount;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "office_id", nullable = false)
private Office office;
// 1=Deposit, 2=Withdrawal, 3=InterestPosting, 4=Charge, 5=ChargeWaiver, 6=Transfer, 7=Reversal
@Column(name = "transaction_type_enum", nullable = false)
private Integer transactionTypeEnum;
@Column(name = "transaction_date", nullable = false)
private LocalDate transactionDate;
@Column(name = "amount", nullable = false, precision = 19, scale = 6)
private BigDecimal amount;
@Column(name = "ref_no", length = 100)
private String refNo;
@Column(name = "remarks", length = 500)
private String remarks;
@Column(name = "is_reversed", nullable = false)
private boolean reversed = false;
@Column(name = "is_reversal", nullable = false)
private boolean reversal = false;
@Column(name = "original_transaction_id")
private Long originalTransactionId;
@Column(name = "is_manual", nullable = false)
private boolean manual = false;
@Column(name = "running_balance_derived", precision = 19, scale = 6)
private BigDecimal runningBalanceDerived;
@Column(name = "cumulative_balance_derived", precision = 19, scale = 6)
private BigDecimal cumulativeBalanceDerived;
@Column(name = "balance_end_date_derived")
private LocalDate balanceEndDateDerived;
@Column(name = "balance_number_of_days_derived")
private Integer balanceNumberOfDaysDerived;
@Column(name = "overdraft_amount_derived", precision = 19, scale = 6)
private BigDecimal overdraftAmountDerived;
@Column(name = "is_lien_transaction", nullable = false)
private boolean lienTransaction = false;
@Column(name = "release_id_of_hold_amount_transaction")
private Long releaseIdOfHoldAmountTransaction;
@Column(name = "reason_for_block", length = 200)
private String reasonForBlock;
@Column(name = "submitted_on_date")
private LocalDate submittedOnDate;
@Column(name = "created_date")
private LocalDateTime createdDate;
@Column(name = "created_by")
private Long createdBy;
protected CurrentAccountTransaction() {}
public static CurrentAccountTransaction deposit(CurrentAccount account, Office office,
BigDecimal amount, LocalDate transactionDate, String refNo) {
CurrentAccountTransaction txn = new CurrentAccountTransaction();
txn.currentAccount = account;
txn.office = office;
txn.transactionTypeEnum = 1;
txn.amount = amount;
txn.transactionDate = transactionDate;
txn.refNo = refNo;
txn.submittedOnDate = transactionDate;
return txn;
}
public static CurrentAccountTransaction withdrawal(CurrentAccount account, Office office,
BigDecimal amount, LocalDate transactionDate, String refNo) {
CurrentAccountTransaction txn = new CurrentAccountTransaction();
txn.currentAccount = account;
txn.office = office;
txn.transactionTypeEnum = 2;
txn.amount = amount;
txn.transactionDate = transactionDate;
txn.refNo = refNo;
txn.submittedOnDate = transactionDate;
return txn;
}
}
@@ -0,0 +1,27 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.currentaccount.domain;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
public interface CurrentAccountTransactionRepository extends JpaRepository<CurrentAccountTransaction, Long> {
List<CurrentAccountTransaction> findByCurrentAccountIdOrderByTransactionDateDescIdDesc(Long currentAccountId);
}
@@ -0,0 +1,29 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.currentaccount.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
public class CurrentAccountNotFoundException extends AbstractPlatformResourceNotFoundException {
public CurrentAccountNotFoundException(Long id) {
super("error.msg.current.account.id.invalid",
"Current account with identifier " + id + " does not exist", id);
}
}
@@ -0,0 +1,92 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.currentaccount.service;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccount;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
/**
* Generates CBS account numbers for current accounts.
* Format: 00 + GL_CODE(6 digits) + OFFICE_ID(2 digits) + ACCOUNT_ID(5 digits) = 15 digits
* GL code is fetched from m_current_product.gl_account_id → acc_gl_account.gl_code
*/
@Component
@RequiredArgsConstructor
public class CurrentAccountNumberGenerator {
private static final String CBS_PREFIX = "00";
private final JdbcTemplate jdbcTemplate;
public String generate(CurrentAccount account) {
Long productId = account.getProductId();
// Fetch gl_code directly from current product → gl account
String glCode = jdbcTemplate.query(
"SELECT ga.gl_code FROM m_current_product cp "
+ "JOIN acc_gl_account ga ON ga.id = cp.gl_account_id "
+ "WHERE cp.id = ?",
rs -> rs.next() ? rs.getString("gl_code") : null,
productId);
if (glCode == null || glCode.isBlank()) glCode = "000000";
// Pad/trim GL code to 6 digits
glCode = glCode.length() > 6
? glCode.substring(glCode.length() - 6)
: StringUtils.leftPad(glCode, 6, '0');
// Office ID — 2 digits
String officeId = StringUtils.leftPad(account.getOffice().getId().toString(), 2, '0');
if (officeId.length() > 2) officeId = officeId.substring(officeId.length() - 2);
// Account ID — 5 digits
String accountId = StringUtils.leftPad(account.getId().toString(), 5, '0');
if (accountId.length() > 5) accountId = accountId.substring(accountId.length() - 5);
return CBS_PREFIX + glCode + officeId + accountId;
}
public String preview(Long productId, Long officeId) {
String glCode = jdbcTemplate.query(
"SELECT ga.gl_code FROM m_current_product cp "
+ "JOIN acc_gl_account ga ON ga.id = cp.gl_account_id "
+ "WHERE cp.id = ?",
rs -> rs.next() ? rs.getString("gl_code") : null,
productId);
if (glCode == null || glCode.isBlank()) glCode = "000000";
glCode = glCode.length() > 6 ? glCode.substring(glCode.length() - 6) : StringUtils.leftPad(glCode, 6, '0');
String officeStr = StringUtils.leftPad(officeId != null ? officeId.toString() : "0", 2, '0');
if (officeStr.length() > 2) officeStr = officeStr.substring(officeStr.length() - 2);
// Next account ID estimate from m_current_account
Long maxId = jdbcTemplate.query(
"SELECT MAX(id) FROM m_current_account",
rs -> rs.next() ? rs.getLong(1) : 0L);
String nextSeq = StringUtils.leftPad(String.valueOf((maxId != null ? maxId : 0L) + 1), 5, '0');
if (nextSeq.length() > 5) nextSeq = nextSeq.substring(nextSeq.length() - 5);
return CBS_PREFIX + glCode + officeStr + nextSeq;
}
}
@@ -0,0 +1,29 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.currentaccount.service;
import java.util.List;
import java.util.Map;
public interface CurrentAccountReadPlatformService {
Map<String, Object> retrieveOne(Long accountId);
List<Map<String, Object>> retrieveAll();
}
@@ -0,0 +1,43 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.currentaccount.service;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
public interface CurrentAccountWritePlatformService {
CommandProcessingResult submitApplication(JsonCommand command);
CommandProcessingResult approveApplication(Long accountId, JsonCommand command);
CommandProcessingResult undoApproval(Long accountId, JsonCommand command);
CommandProcessingResult activate(Long accountId, JsonCommand command);
CommandProcessingResult rejectApplication(Long accountId, JsonCommand command);
CommandProcessingResult withdrawApplication(Long accountId, JsonCommand command);
CommandProcessingResult closeAccount(Long accountId, JsonCommand command);
CommandProcessingResult modifyApplication(Long accountId, JsonCommand command);
CommandProcessingResult deleteApplication(Long accountId);
}
@@ -0,0 +1,455 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.currentproduct.data;
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.CURRENT_PRODUCT_RESOURCE_NAME;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.allowOverdraftParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.currencyCodeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.daysToDormancyParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.daysToEscheatParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.daysToInactiveParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.descriptionParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.digitsAfterDecimalParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.feeAmountParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.inMultiplesOfParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationDaysInYearTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCompoundingPeriodTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestPostingPeriodTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.isDormancyTrackingActiveParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lienAllowedParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.maxAllowedLienLimitParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minBalanceForInterestCalculationParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minOverdraftForInterestCalculationParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredOpeningBalanceParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nameParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateOverdraftParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.overdraftLimitParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.shortNameParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.taxGroupIdParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withHoldTaxParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName;
import com.google.gson.JsonElement;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.time.MonthDay;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.accounting.common.AccountingConstants.SavingProductAccountingParams;
import org.apache.fineract.accounting.common.AccountingValidations;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.portfolio.savings.DepositAccountType;
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProduct;
import org.apache.fineract.portfolio.savings.data.SavingsProductAccountingDataValidator;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class CurrentProductDataValidator {
private final FromJsonHelper fromApiJsonHelper;
private final SavingsProductAccountingDataValidator savingsProductAccountingDataValidator;
private static final Set<String> CURRENT_PRODUCT_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(
SavingsApiConstants.localeParamName, SavingsApiConstants.dateFormatParamName,
SavingsApiConstants.monthDayFormatParamName, nameParamName, shortNameParamName,
descriptionParamName, currencyCodeParamName, digitsAfterDecimalParamName, inMultiplesOfParamName,
nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName,
interestPostingPeriodTypeParamName, interestCalculationTypeParamName,
interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName,
lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName,
SavingsApiConstants.withdrawalFeeAmountParamName, SavingsApiConstants.withdrawalFeeTypeParamName,
withdrawalFeeForTransfersParamName, feeAmountParamName, feeOnMonthDayParamName,
SavingsApiConstants.accountingRuleParamName, SavingsApiConstants.chargesParamName,
SavingsApiConstants.otherGlMappingsParamName,
SavingProductAccountingParams.INCOME_FROM_FEES.getValue(),
SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue(),
SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue(),
SavingProductAccountingParams.PENALTIES_RECEIVABLE.getValue(),
SavingProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(),
SavingProductAccountingParams.SAVINGS_CONTROL.getValue(),
SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue(),
SavingProductAccountingParams.SAVINGS_REFERENCE.getValue(),
SavingProductAccountingParams.FEE_INCOME_ACCOUNT_MAPPING.getValue(),
SavingProductAccountingParams.PENALTY_INCOME_ACCOUNT_MAPPING.getValue(),
SavingProductAccountingParams.FEES_RECEIVABLE.getValue(),
SavingProductAccountingParams.INTEREST_PAYABLE.getValue(),
SavingProductAccountingParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue(),
SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(),
SavingProductAccountingParams.INCOME_FROM_INTEREST.getValue(),
SavingProductAccountingParams.ESCHEAT_LIABILITY.getValue(),
SavingProductAccountingParams.INTEREST_RECEIVABLE.getValue(),
isDormancyTrackingActiveParamName, daysToDormancyParamName, daysToInactiveParamName,
daysToEscheatParamName, allowOverdraftParamName, overdraftLimitParamName,
nominalAnnualInterestRateOverdraftParamName, minOverdraftForInterestCalculationParamName,
SavingsApiConstants.minRequiredBalanceParamName, SavingsApiConstants.enforceMinRequiredBalanceParamName,
SavingsApiConstants.maxAllowedLienLimitParamName, SavingsApiConstants.lienAllowedParamName,
minBalanceForInterestCalculationParamName, withHoldTaxParamName, taxGroupIdParamName,
SavingsApiConstants.accountTypeParamName, SavingsApiConstants.customerSegmentParamName,
SavingsApiConstants.productCodeParamName, SavingsApiConstants.effectiveDateParamName,
SavingsApiConstants.effectiveToParamName, SavingsApiConstants.endDateParamName,
SavingsApiConstants.introducerRequiredParamName, SavingsApiConstants.shareLinkingEnabledParamName,
SavingsApiConstants.nominationFacilityParamName, SavingsApiConstants.dormancyAlertParamName,
SavingsApiConstants.interestCalculateSelectorParamName, SavingsApiConstants.averagePeriodParamName,
SavingsApiConstants.autoPostParamName, SavingsApiConstants.frequencyFixDayParamName,
SavingsApiConstants.lastInterestDateParamName, SavingsApiConstants.enableLockinPeriodParamName,
SavingsApiConstants.accountFacilityConfigParamName, SavingsApiConstants.accountFacilityLimitsParamName,
SavingsApiConstants.depositLimitKycParamName,
SavingsApiConstants.balanceFrequencyParamName, SavingsApiConstants.accountClosedZeroBalanceInDayClosingParamName,
SavingsApiConstants.balanceSwapOptionParamName, SavingsApiConstants.msmeParamName,
SavingsApiConstants.autoDebitFacilityParamName, SavingsApiConstants.avgBalanceCalcPeriodParamName,
SavingsApiConstants.validForPeriodParamName, SavingsApiConstants.odAccountClosedZeroBalanceInDayClosingParamName,
SavingsApiConstants.glAccountIdParamName));
public void validateForCreate(final String json) {
if (StringUtils.isBlank(json)) {
throw new InvalidJsonException();
}
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, CURRENT_PRODUCT_REQUEST_DATA_PARAMETERS);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(CURRENT_PRODUCT_RESOURCE_NAME);
final JsonElement element = this.fromApiJsonHelper.parse(json);
final String name = this.fromApiJsonHelper.extractStringNamed(nameParamName, element);
baseDataValidator.reset().parameter(nameParamName).value(name).notBlank().notExceedingLengthOf(100);
final String shortName = this.fromApiJsonHelper.extractStringNamed(shortNameParamName, element);
baseDataValidator.reset().parameter(shortNameParamName).value(shortName).notBlank().notExceedingLengthOf(4);
final String currencyCode = this.fromApiJsonHelper.extractStringNamed(currencyCodeParamName, element);
baseDataValidator.reset().parameter(currencyCodeParamName).value(currencyCode).notBlank();
final Integer digitsAfterDecimal = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(digitsAfterDecimalParamName, element);
baseDataValidator.reset().parameter(digitsAfterDecimalParamName).value(digitsAfterDecimal).notNull().inMinMaxRange(0, 6);
if (this.fromApiJsonHelper.parameterExists(inMultiplesOfParamName, element)) {
final Integer inMultiplesOf = this.fromApiJsonHelper.extractIntegerNamed(inMultiplesOfParamName, element, Locale.getDefault());
baseDataValidator.reset().parameter(inMultiplesOfParamName).value(inMultiplesOf).ignoreIfNull().integerZeroOrGreater();
}
if (this.fromApiJsonHelper.parameterExists(nominalAnnualInterestRateParamName, element)) {
final BigDecimal nominalAnnualInterestRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(nominalAnnualInterestRateParamName, element);
baseDataValidator.reset().parameter(nominalAnnualInterestRateParamName).value(nominalAnnualInterestRate).zeroOrPositiveAmount();
}
if (this.fromApiJsonHelper.parameterExists(interestCompoundingPeriodTypeParamName, element)) {
final Integer interestCompoundingPeriodType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(interestCompoundingPeriodTypeParamName, element);
baseDataValidator.reset().parameter(interestCompoundingPeriodTypeParamName).value(interestCompoundingPeriodType)
.isOneOfTheseValues(SavingsCompoundingInterestPeriodType.integerValues());
}
final Integer interestPostingPeriodType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(interestPostingPeriodTypeParamName, element);
baseDataValidator.reset().parameter(interestPostingPeriodTypeParamName).value(interestPostingPeriodType).notNull()
.isOneOfTheseValues(SavingsPostingInterestPeriodType.integerValues());
if (this.fromApiJsonHelper.parameterExists(interestCalculationTypeParamName, element)) {
final Integer interestCalculationType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(interestCalculationTypeParamName, element);
baseDataValidator.reset().parameter(interestCalculationTypeParamName).value(interestCalculationType).notNull();
}
if (this.fromApiJsonHelper.parameterExists(interestCalculationDaysInYearTypeParamName, element)) {
final Integer interestCalculationDaysInYearType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(interestCalculationDaysInYearTypeParamName, element);
baseDataValidator.reset().parameter(interestCalculationDaysInYearTypeParamName).value(interestCalculationDaysInYearType)
.isOneOfTheseValues(SavingsInterestCalculationDaysInYearType.integerValues());
}
if (this.fromApiJsonHelper.parameterExists(descriptionParamName, element)) {
final String description = this.fromApiJsonHelper.extractStringNamed(descriptionParamName, element);
baseDataValidator.reset().parameter(descriptionParamName).value(description).ignoreIfNull().notExceedingLengthOf(500);
}
if (this.fromApiJsonHelper.parameterExists(minRequiredOpeningBalanceParamName, element)) {
final BigDecimal minOpeningBalance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minRequiredOpeningBalanceParamName, element);
baseDataValidator.reset().parameter(minRequiredOpeningBalanceParamName).value(minOpeningBalance).zeroOrPositiveAmount();
}
if (this.fromApiJsonHelper.parameterExists(lockinPeriodFrequencyParamName, element)) {
final Integer lockinPeriodFrequency = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(lockinPeriodFrequencyParamName, element);
baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequency).integerZeroOrGreater();
if (lockinPeriodFrequency != null) {
final Integer lockinPeriodFrequencyType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(lockinPeriodFrequencyTypeParamName, element);
baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).notNull().inMinMaxRange(0, 3);
}
}
if (this.fromApiJsonHelper.parameterExists(lockinPeriodFrequencyTypeParamName, element)) {
final Integer lockinPeriodFrequencyType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(lockinPeriodFrequencyTypeParamName, element);
baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).inMinMaxRange(0, 3);
if (lockinPeriodFrequencyType != null) {
final Integer lockinPeriodFrequency = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(lockinPeriodFrequencyParamName, element);
baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequency).notNull().integerZeroOrGreater();
}
}
if (this.fromApiJsonHelper.parameterExists(withdrawalFeeForTransfersParamName, element)) {
final Boolean isWithdrawalFeeApplicableForTransfers = this.fromApiJsonHelper.extractBooleanNamed(withdrawalFeeForTransfersParamName, element);
baseDataValidator.reset().parameter(withdrawalFeeForTransfersParamName).value(isWithdrawalFeeApplicableForTransfers).ignoreIfNull().validateForBooleanValue();
}
// dormancy
Boolean isDormancyActive = this.fromApiJsonHelper.extractBooleanNamed(isDormancyTrackingActiveParamName, element);
if (isDormancyActive == null) {
isDormancyActive = false;
}
if (isDormancyActive) {
final Long daysToInact = this.fromApiJsonHelper.extractLongNamed(daysToInactiveParamName, element);
baseDataValidator.reset().parameter(daysToInactiveParamName).value(daysToInact).notNull().longGreaterThanZero();
final Long daysToDor = this.fromApiJsonHelper.extractLongNamed(daysToDormancyParamName, element);
baseDataValidator.reset().parameter(daysToDormancyParamName).value(daysToDor).notNull().longGreaterThanZero();
final Long daysToEsc = this.fromApiJsonHelper.extractLongNamed(daysToEscheatParamName, element);
baseDataValidator.reset().parameter(daysToEscheatParamName).value(daysToEsc).notNull().longGreaterThanZero();
if (null != daysToInact && null != daysToDor && null != daysToEsc) {
baseDataValidator.reset().parameter(daysToDormancyParamName).value(daysToDor).longGreaterThanNumber(daysToInact);
baseDataValidator.reset().parameter(daysToEscheatParamName).value(daysToEsc).longGreaterThanNumber(daysToDor);
}
}
if (this.fromApiJsonHelper.parameterExists(SavingsApiConstants.effectiveToParamName, element)
&& this.fromApiJsonHelper.parameterExists(SavingsApiConstants.effectiveDateParamName, element)) {
final java.time.LocalDate effectiveDate = this.fromApiJsonHelper.extractLocalDateNamed(SavingsApiConstants.effectiveDateParamName, element);
final java.time.LocalDate effectiveTo = this.fromApiJsonHelper.extractLocalDateNamed(SavingsApiConstants.effectiveToParamName, element);
if (effectiveDate != null && effectiveTo != null && effectiveTo.isBefore(effectiveDate)) {
baseDataValidator.reset().parameter(SavingsApiConstants.effectiveToParamName).value(effectiveTo)
.failWithCode("error.msg.effectiveto.cannot.be.before.effectivedate");
}
}
final Integer accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault());
baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).notNull().inMinMaxRange(1, 3);
if (AccountingValidations.isCashBasedAccounting(accountingRuleType)
|| AccountingValidations.isAccrualPeriodicBasedAccounting(accountingRuleType)) {
savingsProductAccountingDataValidator.evaluateProductAccountingData(accountingRuleType, isDormancyActive, element,
baseDataValidator, DepositAccountType.SAVINGS_DEPOSIT, true);
}
validateOverdraftParams(baseDataValidator, element);
if (this.fromApiJsonHelper.parameterExists(minBalanceForInterestCalculationParamName, element)) {
final BigDecimal minBalanceForInterestCalculation = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minBalanceForInterestCalculationParamName, element);
baseDataValidator.reset().parameter(minBalanceForInterestCalculationParamName).value(minBalanceForInterestCalculation).ignoreIfNull().zeroOrPositiveAmount();
}
validateTaxWithHoldingParams(baseDataValidator, element, true);
validateLienParams(baseDataValidator, element);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateForUpdate(final String json, final CurrentProduct product) {
if (StringUtils.isBlank(json)) {
throw new InvalidJsonException();
}
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, CURRENT_PRODUCT_REQUEST_DATA_PARAMETERS);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(CURRENT_PRODUCT_RESOURCE_NAME);
final JsonElement element = this.fromApiJsonHelper.parse(json);
if (this.fromApiJsonHelper.parameterExists(nameParamName, element)) {
final String name = this.fromApiJsonHelper.extractStringNamed(nameParamName, element);
baseDataValidator.reset().parameter(nameParamName).value(name).notBlank().notExceedingLengthOf(100);
}
if (this.fromApiJsonHelper.parameterExists(shortNameParamName, element)) {
final String shortName = this.fromApiJsonHelper.extractStringNamed(shortNameParamName, element);
baseDataValidator.reset().parameter(shortNameParamName).value(shortName).notBlank().notExceedingLengthOf(4);
}
if (this.fromApiJsonHelper.parameterExists(descriptionParamName, element)) {
final String description = this.fromApiJsonHelper.extractStringNamed(descriptionParamName, element);
baseDataValidator.reset().parameter(descriptionParamName).value(description).notBlank().notExceedingLengthOf(500);
}
if (this.fromApiJsonHelper.parameterExists(currencyCodeParamName, element)) {
final String currencyCode = this.fromApiJsonHelper.extractStringNamed(currencyCodeParamName, element);
baseDataValidator.reset().parameter(currencyCodeParamName).value(currencyCode).notBlank();
}
if (this.fromApiJsonHelper.parameterExists(digitsAfterDecimalParamName, element)) {
final Integer digitsAfterDecimal = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(digitsAfterDecimalParamName, element);
baseDataValidator.reset().parameter(digitsAfterDecimalParamName).value(digitsAfterDecimal).notNull().inMinMaxRange(0, 6);
}
if (this.fromApiJsonHelper.parameterExists(inMultiplesOfParamName, element)) {
final Integer inMultiplesOf = this.fromApiJsonHelper.extractIntegerNamed(inMultiplesOfParamName, element, Locale.getDefault());
baseDataValidator.reset().parameter(inMultiplesOfParamName).value(inMultiplesOf).ignoreIfNull().integerZeroOrGreater();
}
if (this.fromApiJsonHelper.parameterExists(nominalAnnualInterestRateParamName, element)) {
final BigDecimal interestRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(nominalAnnualInterestRateParamName, element);
baseDataValidator.reset().parameter(nominalAnnualInterestRateParamName).value(interestRate).notNull().zeroOrPositiveAmount();
}
if (this.fromApiJsonHelper.parameterExists(interestCompoundingPeriodTypeParamName, element)) {
final Integer interestCompoundingPeriodType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(interestCompoundingPeriodTypeParamName, element);
baseDataValidator.reset().parameter(interestCompoundingPeriodTypeParamName).value(interestCompoundingPeriodType).notNull()
.isOneOfTheseValues(SavingsCompoundingInterestPeriodType.integerValues());
}
if (this.fromApiJsonHelper.parameterExists(interestCalculationTypeParamName, element)) {
final Integer interestCalculationType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(interestCalculationTypeParamName, element);
baseDataValidator.reset().parameter(interestCalculationTypeParamName).value(interestCalculationType).notNull().inMinMaxRange(1, 2);
}
if (this.fromApiJsonHelper.parameterExists(interestCalculationDaysInYearTypeParamName, element)) {
final Integer interestCalculationDaysInYearType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(interestCalculationDaysInYearTypeParamName, element);
baseDataValidator.reset().parameter(interestCalculationDaysInYearTypeParamName).value(interestCalculationDaysInYearType).notNull().isOneOfTheseValues(360, 365);
}
if (this.fromApiJsonHelper.parameterExists(minRequiredOpeningBalanceParamName, element)) {
final BigDecimal minOpeningBalance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minRequiredOpeningBalanceParamName, element);
baseDataValidator.reset().parameter(minRequiredOpeningBalanceParamName).value(minOpeningBalance).ignoreIfNull().zeroOrPositiveAmount();
}
if (this.fromApiJsonHelper.parameterExists(lockinPeriodFrequencyParamName, element)) {
final Integer lockinPeriodFrequency = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(lockinPeriodFrequencyParamName, element);
baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequency).ignoreIfNull().integerZeroOrGreater();
}
if (this.fromApiJsonHelper.parameterExists(lockinPeriodFrequencyTypeParamName, element)) {
final Integer lockinPeriodFrequencyType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(lockinPeriodFrequencyTypeParamName, element);
baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).inMinMaxRange(0, 3);
}
if (this.fromApiJsonHelper.parameterExists(withdrawalFeeForTransfersParamName, element)) {
final Boolean isWithdrawalFeeApplicableForTransfers = this.fromApiJsonHelper.extractBooleanNamed(withdrawalFeeForTransfersParamName, element);
baseDataValidator.reset().parameter(withdrawalFeeForTransfersParamName).value(isWithdrawalFeeApplicableForTransfers).ignoreIfNull().validateForBooleanValue();
}
Integer accountingRuleType = product.getAccountingType();
if (this.fromApiJsonHelper.parameterExists("accountingRule", element)) {
accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault());
baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).notNull().inMinMaxRange(1, 3);
}
final Boolean isDormancyActive = this.fromApiJsonHelper.parameterExists(isDormancyTrackingActiveParamName, element)
? this.fromApiJsonHelper.extractBooleanNamed(isDormancyTrackingActiveParamName, element)
: product.isDormancyTrackingActive();
if (AccountingValidations.isCashBasedAccounting(accountingRuleType)
|| AccountingValidations.isAccrualPeriodicBasedAccounting(accountingRuleType)) {
savingsProductAccountingDataValidator.evaluateProductAccountingData(accountingRuleType, isDormancyActive, element,
baseDataValidator, DepositAccountType.SAVINGS_DEPOSIT, false);
}
if (null != isDormancyActive && isDormancyActive) {
final Long daysToInact = this.fromApiJsonHelper.parameterExists(daysToInactiveParamName, element)
? this.fromApiJsonHelper.extractLongNamed(daysToInactiveParamName, element)
: product.getDaysToInactive();
baseDataValidator.reset().parameter(daysToInactiveParamName).value(daysToInact).notNull().longGreaterThanZero();
final Long daysToDor = this.fromApiJsonHelper.parameterExists(daysToDormancyParamName, element)
? this.fromApiJsonHelper.extractLongNamed(daysToDormancyParamName, element)
: product.getDaysToDormancy();
baseDataValidator.reset().parameter(daysToDormancyParamName).value(daysToDor).notNull().longGreaterThanZero();
final Long daysToEsc = this.fromApiJsonHelper.parameterExists(daysToEscheatParamName, element)
? this.fromApiJsonHelper.extractLongNamed(daysToEscheatParamName, element)
: product.getDaysToEscheat();
baseDataValidator.reset().parameter(daysToEscheatParamName).value(daysToEsc).notNull().longGreaterThanZero();
if (null != daysToInact && null != daysToDor && null != daysToEsc) {
baseDataValidator.reset().parameter(daysToDormancyParamName).value(daysToDor).longGreaterThanNumber(daysToInact);
baseDataValidator.reset().parameter(daysToEscheatParamName).value(daysToEsc).longGreaterThanNumber(daysToDor);
}
}
validateOverdraftParams(baseDataValidator, element);
if (this.fromApiJsonHelper.parameterExists(minBalanceForInterestCalculationParamName, element)) {
final BigDecimal minBalanceForInterestCalculation = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minBalanceForInterestCalculationParamName, element);
baseDataValidator.reset().parameter(minBalanceForInterestCalculationParamName).value(minBalanceForInterestCalculation).ignoreIfNull().zeroOrPositiveAmount();
}
validateTaxWithHoldingParams(baseDataValidator, element, false);
validateLienParams(baseDataValidator, element);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
private void validateOverdraftParams(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
if (this.fromApiJsonHelper.parameterExists(allowOverdraftParamName, element)) {
final Boolean allowOverdraft = this.fromApiJsonHelper.extractBooleanNamed(allowOverdraftParamName, element);
baseDataValidator.reset().parameter(allowOverdraftParamName).value(allowOverdraft).ignoreIfNull().validateForBooleanValue();
}
if (this.fromApiJsonHelper.parameterExists(overdraftLimitParamName, element)) {
final BigDecimal overdraftLimit = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(overdraftLimitParamName, element);
baseDataValidator.reset().parameter(overdraftLimitParamName).value(overdraftLimit).ignoreIfNull().zeroOrPositiveAmount();
}
if (this.fromApiJsonHelper.parameterExists(nominalAnnualInterestRateOverdraftParamName, element)) {
final BigDecimal nominalAnnualInterestRateOverdraft = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(nominalAnnualInterestRateOverdraftParamName, element);
baseDataValidator.reset().parameter(nominalAnnualInterestRateOverdraftParamName).value(nominalAnnualInterestRateOverdraft).ignoreIfNull().zeroOrPositiveAmount();
}
if (this.fromApiJsonHelper.parameterExists(minOverdraftForInterestCalculationParamName, element)) {
final BigDecimal minOverdraftForInterestCalculation = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minOverdraftForInterestCalculationParamName, element);
baseDataValidator.reset().parameter(minOverdraftForInterestCalculationParamName).value(minOverdraftForInterestCalculation).ignoreIfNull().zeroOrPositiveAmount();
}
}
private void validateLienParams(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
if (this.fromApiJsonHelper.parameterExists(lienAllowedParamName, element)) {
final Boolean lienAllowed = this.fromApiJsonHelper.extractBooleanNamed(lienAllowedParamName, element);
baseDataValidator.reset().parameter(lienAllowedParamName).value(lienAllowed).ignoreIfNull().validateForBooleanValue();
}
if (this.fromApiJsonHelper.parameterExists(maxAllowedLienLimitParamName, element)) {
final BigDecimal maxAllowedLienLimit = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(maxAllowedLienLimitParamName, element);
baseDataValidator.reset().parameter(maxAllowedLienLimitParamName).value(maxAllowedLienLimit).ignoreIfNull().zeroOrPositiveAmount();
}
if (BooleanUtils.isTrue(this.fromApiJsonHelper.extractBooleanNamed(lienAllowedParamName, element))
&& BooleanUtils.isTrue(this.fromApiJsonHelper.extractBooleanNamed(allowOverdraftParamName, element))) {
final BigDecimal maxAllowedLienLimit = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(maxAllowedLienLimitParamName, element);
final BigDecimal overdraftLimit = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(overdraftLimitParamName, element);
if (overdraftLimit.compareTo(maxAllowedLienLimit) > 0) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("Overdraft.limit.can.not.be.greater.than.lien.limit");
}
}
}
private void validateTaxWithHoldingParams(final DataValidatorBuilder baseDataValidator, final JsonElement element, final boolean isCreate) {
if (this.fromApiJsonHelper.parameterExists(withHoldTaxParamName, element)) {
final String withHoldTax = this.fromApiJsonHelper.extractStringNamed(withHoldTaxParamName, element);
baseDataValidator.reset().parameter(withHoldTaxParamName).value(withHoldTax).ignoreIfNull().validateForBooleanValue();
}
Boolean withHoldTax = this.fromApiJsonHelper.extractBooleanNamed(withHoldTaxParamName, element);
if (withHoldTax == null) {
withHoldTax = false;
}
if (this.fromApiJsonHelper.parameterExists(taxGroupIdParamName, element)) {
final Long taxGroupId = this.fromApiJsonHelper.extractLongNamed(taxGroupIdParamName, element);
baseDataValidator.reset().parameter(taxGroupIdParamName).value(taxGroupId).ignoreIfNull().longGreaterThanZero();
}
}
}
@@ -0,0 +1,888 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.currentproduct.domain;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountingRuleParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.allowOverdraftParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargesParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.currencyCodeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.daysToDormancyParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.daysToEscheatParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.daysToInactiveParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.descriptionParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.digitsAfterDecimalParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.enforceMinRequiredBalanceParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.inMultiplesOfParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationDaysInYearTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCompoundingPeriodTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestPostingPeriodTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.isDormancyTrackingActiveParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lienAllowedParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.localeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.maxAllowedLienLimitParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minBalanceForInterestCalculationParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minOverdraftForInterestCalculationParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredBalanceParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredOpeningBalanceParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nameParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateOverdraftParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.otherGlMappingsParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.overdraftLimitParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.shortNameParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.taxGroupIdParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withHoldTaxParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.customerSegmentParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.productCodeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.glAccountIdParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.effectiveDateParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.effectiveToParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.endDateParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.introducerRequiredParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.shareLinkingEnabledParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominationFacilityParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.dormancyAlertParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculateSelectorParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.averagePeriodParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.autoPostParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.frequencyFixDayParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lastInterestDateParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.enableLockinPeriodParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.balanceSwapOptionParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.msmeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.autoDebitFacilityParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.avgBalanceCalcPeriodParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.validForPeriodParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.odAccountClosedZeroBalanceInDayClosingParamName;
import com.google.gson.JsonArray;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.Lob;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.apache.fineract.accounting.common.AccountingRuleType;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.charge.domain.Charge;
import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
import org.apache.fineract.portfolio.tax.domain.TaxGroup;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "m_current_product", uniqueConstraints = {
@UniqueConstraint(columnNames = { "name" }, name = "cp_unq_name"),
@UniqueConstraint(columnNames = { "short_name" }, name = "cp_unq_short_name") })
public class CurrentProduct extends AbstractPersistableCustom<Long> {
@Column(name = "name", nullable = false, unique = true)
private String name;
@Column(name = "short_name", nullable = false, unique = true)
private String shortName;
@Column(name = "description", length = 500, nullable = false)
private String description;
@Embedded
private MonetaryCurrency currency;
@Column(name = "nominal_annual_interest_rate", scale = 6, precision = 19, nullable = false)
private BigDecimal nominalAnnualInterestRate;
@Column(name = "interest_compounding_period_enum", nullable = false)
private Integer interestCompoundingPeriodType;
@Column(name = "interest_posting_period_enum", nullable = false)
private Integer interestPostingPeriodType;
@Column(name = "interest_calculation_type_enum", nullable = false)
private Integer interestCalculationType;
@Column(name = "interest_calculation_days_in_year_type_enum", nullable = false)
private Integer interestCalculationDaysInYearType;
@Column(name = "min_required_opening_balance", scale = 6, precision = 19, nullable = true)
private BigDecimal minRequiredOpeningBalance;
@Column(name = "lockin_period_frequency", nullable = true)
private Integer lockinPeriodFrequency;
@Column(name = "lockin_period_frequency_enum", nullable = true)
private Integer lockinPeriodFrequencyType;
@Column(name = "accounting_type", nullable = false)
private Integer accountingRule;
@Column(name = "withdrawal_fee_for_transfer")
private boolean withdrawalFeeApplicableForTransfer;
@ManyToMany
@JoinTable(name = "m_current_product_charge",
joinColumns = @JoinColumn(name = "current_product_id"),
inverseJoinColumns = @JoinColumn(name = "charge_id"))
private Set<Charge> charges;
@Column(name = "allow_overdraft")
private boolean allowOverdraft;
@Column(name = "overdraft_limit", scale = 6, precision = 19, nullable = true)
private BigDecimal overdraftLimit;
@Column(name = "nominal_annual_interest_rate_overdraft", scale = 6, precision = 19, nullable = true)
private BigDecimal nominalAnnualInterestRateOverdraft;
@Column(name = "min_overdraft_for_interest_calculation", scale = 6, precision = 19, nullable = true)
private BigDecimal minOverdraftForInterestCalculation;
@Column(name = "enforce_min_required_balance")
private boolean enforceMinRequiredBalance;
@Column(name = "min_required_balance", scale = 6, precision = 19, nullable = true)
private BigDecimal minRequiredBalance;
@Column(name = "is_lien_allowed", nullable = false)
private boolean lienAllowed;
@Column(name = "max_allowed_lien_limit", scale = 6, precision = 19, nullable = true)
private BigDecimal maxAllowedLienLimit;
@Column(name = "min_balance_for_interest_calculation", scale = 6, precision = 19, nullable = true)
private BigDecimal minBalanceForInterestCalculation;
@Column(name = "withhold_tax", nullable = false)
private boolean withHoldTax;
@ManyToOne
@JoinColumn(name = "tax_group_id")
private TaxGroup taxGroup;
@Column(name = "is_dormancy_tracking_active")
private Boolean isDormancyTrackingActive;
@Column(name = "days_to_inactive")
private Long daysToInactive;
@Column(name = "days_to_dormancy")
private Long daysToDormancy;
@Column(name = "days_to_escheat")
private Long daysToEscheat;
@Lob
@Column(name = "other_gl_mappings")
private String otherGlMappings;
// Enhanced fields
@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 = "gl_account_id")
private Long glAccountId;
@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 = false;
@Column(name = "share_linking_enabled")
private boolean shareLinkingEnabled = false;
@Column(name = "nomination_facility")
private boolean nominationFacility = false;
@Column(name = "dormancy_alert")
private boolean dormancyAlert = false;
@Column(name = "interest_calculate_selector")
private Integer interestCalculateSelector;
@Column(name = "average_period", length = 20)
private String averagePeriod;
@Column(name = "auto_post")
private boolean autoPost = false;
@Column(name = "frequency_fix_day")
private Integer frequencyFixDay;
@Column(name = "last_interest_date")
private LocalDate lastInterestDate;
@Column(name = "enable_lockin_period")
private boolean enableLockinPeriod = false;
@Column(name = "balance_frequency", length = 50)
private String balanceFrequency;
@Column(name = "account_closed_zero_balance_in_day_closing")
private Boolean accountClosedZeroBalanceInDayClosing = false;
// New UI fields (Currency step)
@Column(name = "balance_swap_option")
private boolean balanceSwapOption = false;
@Column(name = "msme")
private boolean msme = false;
@Column(name = "auto_debit_facility")
private boolean autoDebitFacility = false;
// New UI fields (OD section)
@Column(name = "avg_balance_calc_period", length = 10)
private String avgBalanceCalcPeriod;
@Column(name = "valid_for_period", length = 10)
private String validForPeriod;
@Column(name = "od_account_closed_zero_balance_in_day_closing")
private boolean odAccountClosedZeroBalanceInDayClosing = false;
@OneToOne(cascade = CascadeType.ALL, mappedBy = "currentProduct", orphanRemoval = true)
private CurrentProductFacilityConfig facilityConfig;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "currentProduct", orphanRemoval = true)
private Set<CurrentProductFacilityLimit> facilityLimits = new HashSet<>();
@OneToOne(cascade = CascadeType.ALL, mappedBy = "currentProduct", orphanRemoval = true)
private CurrentProductDepositLimitKyc depositLimitKyc;
public static CurrentProduct createNew(final String name, final String shortName, final String description,
final MonetaryCurrency currency, final BigDecimal interestRate,
final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType,
final SavingsPostingInterestPeriodType interestPostingPeriodType,
final SavingsInterestCalculationType interestCalculationType,
final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType,
final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency,
final SavingsPeriodFrequencyType lockinPeriodFrequencyType,
final boolean withdrawalFeeApplicableForTransfer, final AccountingRuleType accountingRuleType,
final Set<Charge> charges, final boolean allowOverdraft, final BigDecimal overdraftLimit,
final boolean enforceMinRequiredBalance, final BigDecimal minRequiredBalance,
final boolean lienAllowed, final BigDecimal maxAllowedLienLimit,
final BigDecimal minBalanceForInterestCalculation, final BigDecimal nominalAnnualInterestRateOverdraft,
final BigDecimal minOverdraftForInterestCalculation, final boolean withHoldTax, final TaxGroup taxGroup,
final String otherGlMappings, final Boolean isDormancyTrackingActive, final Long daysToInactive,
final Long daysToDormancy, final Long daysToEscheat) {
return new CurrentProduct(name, shortName, description, currency, interestRate, interestCompoundingPeriodType,
interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType,
minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType,
withdrawalFeeApplicableForTransfer, accountingRuleType, charges, allowOverdraft, overdraftLimit,
enforceMinRequiredBalance, minRequiredBalance, lienAllowed, maxAllowedLienLimit,
minBalanceForInterestCalculation, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation,
withHoldTax, taxGroup, otherGlMappings, isDormancyTrackingActive, daysToInactive, daysToDormancy,
daysToEscheat);
}
protected CurrentProduct() {
this.name = null;
this.description = null;
}
private CurrentProduct(final String name, final String shortName, final String description,
final MonetaryCurrency currency, final BigDecimal interestRate,
final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType,
final SavingsPostingInterestPeriodType interestPostingPeriodType,
final SavingsInterestCalculationType interestCalculationType,
final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType,
final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency,
final SavingsPeriodFrequencyType lockinPeriodFrequencyType,
final boolean withdrawalFeeApplicableForTransfer, final AccountingRuleType accountingRuleType,
final Set<Charge> charges, final boolean allowOverdraft, final BigDecimal overdraftLimit,
final boolean enforceMinRequiredBalance, final BigDecimal minRequiredBalance,
final boolean lienAllowed, final BigDecimal maxAllowedLienLimit,
final BigDecimal minBalanceForInterestCalculation, final BigDecimal nominalAnnualInterestRateOverdraft,
final BigDecimal minOverdraftForInterestCalculation, final boolean withHoldTax, final TaxGroup taxGroup,
final String otherGlMappings, final Boolean isDormancyTrackingActive, final Long daysToInactive,
final Long daysToDormancy, final Long daysToEscheat) {
this.name = name;
this.shortName = shortName;
this.description = description;
this.currency = currency;
this.nominalAnnualInterestRate = interestRate;
this.interestCompoundingPeriodType = interestCompoundingPeriodType.getValue();
this.interestPostingPeriodType = interestPostingPeriodType.getValue();
this.interestCalculationType = interestCalculationType.getValue();
this.interestCalculationDaysInYearType = interestCalculationDaysInYearType.getValue();
if (minRequiredOpeningBalance != null) {
this.minRequiredOpeningBalance = Money.of(currency, minRequiredOpeningBalance).getAmount();
}
this.lockinPeriodFrequency = lockinPeriodFrequency;
if (lockinPeriodFrequency != null && lockinPeriodFrequencyType != null) {
this.lockinPeriodFrequencyType = lockinPeriodFrequencyType.getValue();
}
this.withdrawalFeeApplicableForTransfer = withdrawalFeeApplicableForTransfer;
if (accountingRuleType != null) {
this.accountingRule = accountingRuleType.getValue();
}
if (charges != null) {
this.charges = charges;
}
validateLockinDetails();
this.allowOverdraft = allowOverdraft;
this.overdraftLimit = overdraftLimit;
this.nominalAnnualInterestRateOverdraft = nominalAnnualInterestRateOverdraft;
this.minOverdraftForInterestCalculation = minOverdraftForInterestCalculation;
ensureOverdraftLimitsSetForOverdraftAccounts();
this.enforceMinRequiredBalance = enforceMinRequiredBalance;
this.minRequiredBalance = minRequiredBalance;
this.lienAllowed = lienAllowed;
this.maxAllowedLienLimit = maxAllowedLienLimit;
this.minBalanceForInterestCalculation = minBalanceForInterestCalculation;
this.withHoldTax = withHoldTax;
this.taxGroup = taxGroup;
this.otherGlMappings = otherGlMappings;
this.isDormancyTrackingActive = isDormancyTrackingActive == null ? Boolean.FALSE : isDormancyTrackingActive;
this.daysToInactive = daysToInactive;
this.daysToDormancy = daysToDormancy;
this.daysToEscheat = daysToEscheat;
}
private void ensureOverdraftLimitsSetForOverdraftAccounts() {
if (this.allowOverdraft) {
this.overdraftLimit = this.overdraftLimit == null ? BigDecimal.ZERO : this.overdraftLimit;
this.nominalAnnualInterestRateOverdraft = this.nominalAnnualInterestRateOverdraft == null ? BigDecimal.ZERO
: this.nominalAnnualInterestRateOverdraft;
this.minOverdraftForInterestCalculation = this.minOverdraftForInterestCalculation == null ? BigDecimal.ZERO
: this.minOverdraftForInterestCalculation;
}
}
private void validateLockinDetails() {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource("currentproduct");
if (this.lockinPeriodFrequency == null) {
baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(this.lockinPeriodFrequencyType)
.ignoreIfNull().inMinMaxRange(0, 3);
}
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
public MonetaryCurrency currency() {
return this.currency.copy();
}
public BigDecimal nominalAnnualInterestRate() {
return this.nominalAnnualInterestRate;
}
public Integer getAccountingType() {
return this.accountingRule;
}
public boolean withHoldTax() {
return this.withHoldTax;
}
public TaxGroup getTaxGroup() {
return this.taxGroup;
}
public void setTaxGroup(final TaxGroup taxGroup) {
this.taxGroup = taxGroup;
}
public CurrentProductFacilityConfig getFacilityConfig() {
return this.facilityConfig;
}
public void setFacilityConfig(CurrentProductFacilityConfig facilityConfig) {
this.facilityConfig = facilityConfig;
}
public Set<CurrentProductFacilityLimit> getFacilityLimits() {
return this.facilityLimits;
}
public void setFacilityLimits(Set<CurrentProductFacilityLimit> facilityLimits) {
this.facilityLimits = facilityLimits;
}
public CurrentProductDepositLimitKyc getDepositLimitKyc() {
return this.depositLimitKyc;
}
public void setDepositLimitKyc(CurrentProductDepositLimitKyc depositLimitKyc) {
this.depositLimitKyc = depositLimitKyc;
}
public boolean update(final Set<Charge> newCharges) {
if (newCharges == null) {
return false;
}
final Set<Charge> currentSetOfCharges = this.charges == null ? new HashSet<>() : this.charges;
if (!currentSetOfCharges.equals(newCharges)) {
this.charges = newCharges;
return true;
}
return false;
}
public Map<String, Object> update(final JsonCommand command) {
final Map<String, Object> actualChanges = new LinkedHashMap<>(10);
final String localeAsInput = command.locale();
if (command.isChangeInStringParameterNamed(nameParamName, this.name)) {
final String newValue = command.stringValueOfParameterNamed(nameParamName);
actualChanges.put(nameParamName, newValue);
this.name = newValue;
}
if (command.isChangeInStringParameterNamed(shortNameParamName, this.shortName)) {
final String newValue = command.stringValueOfParameterNamed(shortNameParamName);
actualChanges.put(shortNameParamName, newValue);
this.shortName = newValue;
}
if (command.isChangeInStringParameterNamed(descriptionParamName, this.description)) {
final String newValue = command.stringValueOfParameterNamed(descriptionParamName);
actualChanges.put(descriptionParamName, newValue);
this.description = newValue;
}
Integer digitsAfterDecimal = this.currency.getDigitsAfterDecimal();
if (command.isChangeInIntegerParameterNamed(digitsAfterDecimalParamName, digitsAfterDecimal)) {
final Integer newValue = command.integerValueOfParameterNamed(digitsAfterDecimalParamName);
actualChanges.put(digitsAfterDecimalParamName, newValue);
actualChanges.put(localeParamName, localeAsInput);
digitsAfterDecimal = newValue;
this.currency = new MonetaryCurrency(this.currency.getCode(), digitsAfterDecimal, this.currency.getInMultiplesOf());
}
String currencyCode = this.currency.getCode();
if (command.isChangeInStringParameterNamed(currencyCodeParamName, currencyCode)) {
final String newValue = command.stringValueOfParameterNamed(currencyCodeParamName);
actualChanges.put(currencyCodeParamName, newValue);
currencyCode = newValue;
this.currency = new MonetaryCurrency(currencyCode, this.currency.getDigitsAfterDecimal(), this.currency.getInMultiplesOf());
}
Integer inMultiplesOf = this.currency.getInMultiplesOf();
if (command.isChangeInIntegerParameterNamed(inMultiplesOfParamName, inMultiplesOf)) {
final Integer newValue = command.integerValueOfParameterNamed(inMultiplesOfParamName);
actualChanges.put(inMultiplesOfParamName, newValue);
actualChanges.put(localeParamName, localeAsInput);
inMultiplesOf = newValue;
this.currency = new MonetaryCurrency(this.currency.getCode(), this.currency.getDigitsAfterDecimal(), inMultiplesOf);
}
if (command.isChangeInBigDecimalParameterNamed(nominalAnnualInterestRateParamName, this.nominalAnnualInterestRate)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(nominalAnnualInterestRateParamName);
actualChanges.put(nominalAnnualInterestRateParamName, newValue);
actualChanges.put(localeParamName, localeAsInput);
this.nominalAnnualInterestRate = newValue;
}
if (command.isChangeInIntegerParameterNamed(interestCompoundingPeriodTypeParamName, this.interestCompoundingPeriodType)) {
final Integer newValue = command.integerValueOfParameterNamed(interestCompoundingPeriodTypeParamName);
actualChanges.put(interestCompoundingPeriodTypeParamName, newValue);
this.interestCompoundingPeriodType = SavingsCompoundingInterestPeriodType.fromInt(newValue).getValue();
}
if (command.isChangeInIntegerParameterNamed(interestPostingPeriodTypeParamName, this.interestPostingPeriodType)) {
final Integer newValue = command.integerValueOfParameterNamed(interestPostingPeriodTypeParamName);
actualChanges.put(interestPostingPeriodTypeParamName, newValue);
this.interestPostingPeriodType = SavingsPostingInterestPeriodType.fromInt(newValue).getValue();
}
if (command.isChangeInIntegerParameterNamed(interestCalculationTypeParamName, this.interestCalculationType)) {
final Integer newValue = command.integerValueOfParameterNamed(interestCalculationTypeParamName);
actualChanges.put(interestCalculationTypeParamName, newValue);
this.interestCalculationType = newValue;
}
if (command.isChangeInIntegerParameterNamed(interestCalculationDaysInYearTypeParamName, this.interestCalculationDaysInYearType)) {
final Integer newValue = command.integerValueOfParameterNamed(interestCalculationDaysInYearTypeParamName);
actualChanges.put(interestCalculationDaysInYearTypeParamName, newValue);
this.interestCalculationDaysInYearType = SavingsInterestCalculationDaysInYearType.fromInt(newValue).getValue();
}
if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(minRequiredOpeningBalanceParamName, this.minRequiredOpeningBalance)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(minRequiredOpeningBalanceParamName);
actualChanges.put(minRequiredOpeningBalanceParamName, newValue);
actualChanges.put(localeParamName, localeAsInput);
this.minRequiredOpeningBalance = newValue;
}
if (command.isChangeInIntegerParameterNamedDefaultingZeroToNull(lockinPeriodFrequencyParamName, this.lockinPeriodFrequency)) {
final Integer newValue = command.integerValueOfParameterNamedDefaultToNullIfZero(lockinPeriodFrequencyParamName);
actualChanges.put(lockinPeriodFrequencyParamName, newValue);
actualChanges.put(localeParamName, localeAsInput);
this.lockinPeriodFrequency = newValue;
}
if (command.isChangeInIntegerParameterNamed(lockinPeriodFrequencyTypeParamName, this.lockinPeriodFrequencyType)) {
final Integer newValue = command.integerValueOfParameterNamed(lockinPeriodFrequencyTypeParamName);
actualChanges.put(lockinPeriodFrequencyTypeParamName, newValue);
this.lockinPeriodFrequencyType = newValue != null ? SavingsPeriodFrequencyType.fromInt(newValue).getValue() : newValue;
}
if (this.lockinPeriodFrequency == null) {
this.lockinPeriodFrequencyType = null;
}
if (command.isChangeInBooleanParameterNamed(withdrawalFeeForTransfersParamName, this.withdrawalFeeApplicableForTransfer)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(withdrawalFeeForTransfersParamName);
actualChanges.put(withdrawalFeeForTransfersParamName, newValue);
this.withdrawalFeeApplicableForTransfer = newValue;
}
if (command.isChangeInIntegerParameterNamed(accountingRuleParamName, this.accountingRule)) {
final Integer newValue = command.integerValueOfParameterNamed(accountingRuleParamName);
actualChanges.put(accountingRuleParamName, newValue);
this.accountingRule = newValue;
}
if (command.hasParameter(chargesParamName)) {
final JsonArray jsonArray = command.arrayOfParameterNamed(chargesParamName);
if (jsonArray != null) {
actualChanges.put(chargesParamName, command.jsonFragment(chargesParamName));
}
}
if (command.isChangeInBooleanParameterNamed(allowOverdraftParamName, this.allowOverdraft)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(allowOverdraftParamName);
actualChanges.put(allowOverdraftParamName, newValue);
this.allowOverdraft = newValue;
}
if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(overdraftLimitParamName, this.overdraftLimit)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(overdraftLimitParamName);
actualChanges.put(overdraftLimitParamName, newValue);
actualChanges.put(localeParamName, localeAsInput);
this.overdraftLimit = newValue;
}
if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(nominalAnnualInterestRateOverdraftParamName, this.nominalAnnualInterestRateOverdraft)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(nominalAnnualInterestRateOverdraftParamName);
actualChanges.put(nominalAnnualInterestRateOverdraftParamName, newValue);
actualChanges.put(localeParamName, localeAsInput);
this.nominalAnnualInterestRateOverdraft = newValue;
}
if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(minOverdraftForInterestCalculationParamName, this.minOverdraftForInterestCalculation)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(minOverdraftForInterestCalculationParamName);
actualChanges.put(minOverdraftForInterestCalculationParamName, newValue);
actualChanges.put(localeParamName, localeAsInput);
this.minOverdraftForInterestCalculation = newValue;
}
if (!this.allowOverdraft) {
this.overdraftLimit = null;
this.nominalAnnualInterestRateOverdraft = null;
this.minOverdraftForInterestCalculation = null;
}
if (command.isChangeInBooleanParameterNamed(enforceMinRequiredBalanceParamName, this.enforceMinRequiredBalance)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(enforceMinRequiredBalanceParamName);
actualChanges.put(enforceMinRequiredBalanceParamName, newValue);
this.enforceMinRequiredBalance = newValue;
}
if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(minRequiredBalanceParamName, this.minRequiredBalance)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(minRequiredBalanceParamName);
actualChanges.put(minRequiredBalanceParamName, newValue);
actualChanges.put(localeParamName, localeAsInput);
this.minRequiredBalance = newValue;
}
if (command.isChangeInBooleanParameterNamed(lienAllowedParamName, this.lienAllowed)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(lienAllowedParamName);
actualChanges.put(lienAllowedParamName, newValue);
this.lienAllowed = newValue;
}
if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(maxAllowedLienLimitParamName, this.maxAllowedLienLimit)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(maxAllowedLienLimitParamName);
actualChanges.put(maxAllowedLienLimitParamName, newValue);
actualChanges.put(localeParamName, localeAsInput);
this.maxAllowedLienLimit = newValue;
}
if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(minBalanceForInterestCalculationParamName, this.minBalanceForInterestCalculation)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(minBalanceForInterestCalculationParamName);
actualChanges.put(minBalanceForInterestCalculationParamName, newValue);
actualChanges.put(localeParamName, localeAsInput);
this.minBalanceForInterestCalculation = newValue;
}
if (command.isChangeInBooleanParameterNamed(withHoldTaxParamName, this.withHoldTax)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(withHoldTaxParamName);
actualChanges.put(withHoldTaxParamName, newValue);
this.withHoldTax = newValue;
}
if (this.withHoldTax) {
if (this.taxGroup == null || command.isChangeInLongParameterNamed(taxGroupIdParamName, this.taxGroup.getId())) {
final Long newValue = command.longValueOfParameterNamed(taxGroupIdParamName);
actualChanges.put(taxGroupIdParamName, newValue);
}
} else {
this.taxGroup = null;
}
if (command.hasParameter(otherGlMappingsParamName)) {
final String newValue = command.hasParameter(otherGlMappingsParamName) ? command.jsonFragment(otherGlMappingsParamName) : null;
actualChanges.put(otherGlMappingsParamName, newValue);
this.otherGlMappings = newValue;
}
if (command.isChangeInBooleanParameterNamed(isDormancyTrackingActiveParamName, this.isDormancyTrackingActive)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(isDormancyTrackingActiveParamName);
actualChanges.put(isDormancyTrackingActiveParamName, newValue);
this.isDormancyTrackingActive = newValue;
}
if (command.isChangeInLongParameterNamed(daysToInactiveParamName, this.daysToInactive)) {
final Long newValue = command.longValueOfParameterNamed(daysToInactiveParamName);
actualChanges.put(daysToInactiveParamName, newValue);
this.daysToInactive = newValue;
}
if (command.isChangeInLongParameterNamed(daysToDormancyParamName, this.daysToDormancy)) {
final Long newValue = command.longValueOfParameterNamed(daysToDormancyParamName);
actualChanges.put(daysToDormancyParamName, newValue);
this.daysToDormancy = newValue;
}
if (command.isChangeInLongParameterNamed(daysToEscheatParamName, this.daysToEscheat)) {
final Long newValue = command.longValueOfParameterNamed(daysToEscheatParamName);
actualChanges.put(daysToEscheatParamName, newValue);
this.daysToEscheat = newValue;
}
// Enhanced fields
if (command.isChangeInStringParameterNamed(accountTypeParamName, this.accountType)) {
final String newValue = command.stringValueOfParameterNamed(accountTypeParamName);
actualChanges.put(accountTypeParamName, newValue);
this.accountType = newValue;
}
if (command.isChangeInStringParameterNamed(customerSegmentParamName, this.customerSegment)) {
final String newValue = command.stringValueOfParameterNamed(customerSegmentParamName);
actualChanges.put(customerSegmentParamName, newValue);
this.customerSegment = newValue;
}
if (command.isChangeInStringParameterNamed(productCodeParamName, this.productCode)) {
final String newValue = command.stringValueOfParameterNamed(productCodeParamName);
actualChanges.put(productCodeParamName, newValue);
this.productCode = newValue;
}
if (command.isChangeInLongParameterNamed(glAccountIdParamName, this.glAccountId)) {
final Long newValue = command.longValueOfParameterNamed(glAccountIdParamName);
actualChanges.put(glAccountIdParamName, newValue);
this.glAccountId = newValue;
}
if (command.isChangeInBooleanParameterNamed(introducerRequiredParamName, this.introducerRequired)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(introducerRequiredParamName);
actualChanges.put(introducerRequiredParamName, newValue);
this.introducerRequired = newValue;
}
if (command.isChangeInBooleanParameterNamed(shareLinkingEnabledParamName, this.shareLinkingEnabled)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(shareLinkingEnabledParamName);
actualChanges.put(shareLinkingEnabledParamName, newValue);
this.shareLinkingEnabled = newValue;
}
if (command.isChangeInBooleanParameterNamed(nominationFacilityParamName, this.nominationFacility)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(nominationFacilityParamName);
actualChanges.put(nominationFacilityParamName, newValue);
this.nominationFacility = newValue;
}
if (command.isChangeInBooleanParameterNamed(dormancyAlertParamName, this.dormancyAlert)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(dormancyAlertParamName);
actualChanges.put(dormancyAlertParamName, newValue);
this.dormancyAlert = newValue;
}
if (command.isChangeInBooleanParameterNamed(autoPostParamName, this.autoPost)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(autoPostParamName);
actualChanges.put(autoPostParamName, newValue);
this.autoPost = newValue;
}
if (command.isChangeInBooleanParameterNamed(enableLockinPeriodParamName, this.enableLockinPeriod)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(enableLockinPeriodParamName);
actualChanges.put(enableLockinPeriodParamName, newValue);
this.enableLockinPeriod = newValue;
}
// New currency-step fields
if (command.isChangeInBooleanParameterNamed(balanceSwapOptionParamName, this.balanceSwapOption)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(balanceSwapOptionParamName);
actualChanges.put(balanceSwapOptionParamName, newValue);
this.balanceSwapOption = newValue;
}
if (command.isChangeInBooleanParameterNamed(msmeParamName, this.msme)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(msmeParamName);
actualChanges.put(msmeParamName, newValue);
this.msme = newValue;
}
if (command.isChangeInBooleanParameterNamed(autoDebitFacilityParamName, this.autoDebitFacility)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(autoDebitFacilityParamName);
actualChanges.put(autoDebitFacilityParamName, newValue);
this.autoDebitFacility = newValue;
}
// New OD-section fields
if (command.isChangeInStringParameterNamed(avgBalanceCalcPeriodParamName, this.avgBalanceCalcPeriod)) {
final String newValue = command.stringValueOfParameterNamed(avgBalanceCalcPeriodParamName);
actualChanges.put(avgBalanceCalcPeriodParamName, newValue);
this.avgBalanceCalcPeriod = newValue;
}
if (command.isChangeInStringParameterNamed(validForPeriodParamName, this.validForPeriod)) {
final String newValue = command.stringValueOfParameterNamed(validForPeriodParamName);
actualChanges.put(validForPeriodParamName, newValue);
this.validForPeriod = newValue;
}
if (command.isChangeInBooleanParameterNamed(odAccountClosedZeroBalanceInDayClosingParamName, this.odAccountClosedZeroBalanceInDayClosing)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(odAccountClosedZeroBalanceInDayClosingParamName);
actualChanges.put(odAccountClosedZeroBalanceInDayClosingParamName, newValue);
this.odAccountClosedZeroBalanceInDayClosing = newValue;
}
// Clear OD-specific fields when overdraft is disabled
if (!this.allowOverdraft) {
this.avgBalanceCalcPeriod = null;
this.validForPeriod = null;
this.odAccountClosedZeroBalanceInDayClosing = false;
}
return actualChanges;
}
public String getName() { return name; }
public String getShortName() { return shortName; }
public boolean isAllowOverdraft() { return allowOverdraft; }
public BigDecimal getOverdraftLimit() { return overdraftLimit; }
public Integer getInterestCompoundingPeriodType() { return interestCompoundingPeriodType; }
public Integer getInterestPostingPeriodType() { return interestPostingPeriodType; }
public Integer getInterestCalculationType() { return interestCalculationType; }
public Integer getInterestCalculationDaysInYearType() { return interestCalculationDaysInYearType; }
// Dormancy/misc getters needed by validator
public Boolean isDormancyTrackingActive() { return this.isDormancyTrackingActive; }
public Long getDaysToInactive() { return this.daysToInactive; }
public Long getDaysToDormancy() { return this.daysToDormancy; }
public Long getDaysToEscheat() { return this.daysToEscheat; }
// Getters and setters for enhanced fields
public String getAccountType() { return accountType; }
public void setAccountType(String accountType) { this.accountType = accountType; }
public String getCustomerSegment() { return customerSegment; }
public void setCustomerSegment(String customerSegment) { this.customerSegment = customerSegment; }
public String getProductCode() { return productCode; }
public void setProductCode(String productCode) { this.productCode = productCode; }
public Long getGlAccountId() { return glAccountId; }
public void setGlAccountId(Long glAccountId) { this.glAccountId = glAccountId; }
public LocalDate getEffectiveDate() { return effectiveDate; }
public void setEffectiveDate(LocalDate effectiveDate) { this.effectiveDate = effectiveDate; }
public LocalDate getEffectiveTo() { return effectiveTo; }
public void setEffectiveTo(LocalDate effectiveTo) { this.effectiveTo = effectiveTo; }
public LocalDate getEndDate() { return endDate; }
public void setEndDate(LocalDate endDate) { this.endDate = endDate; }
public boolean isIntroducerRequired() { return introducerRequired; }
public void setIntroducerRequired(boolean introducerRequired) { this.introducerRequired = introducerRequired; }
public boolean isShareLinkingEnabled() { return shareLinkingEnabled; }
public void setShareLinkingEnabled(boolean shareLinkingEnabled) { this.shareLinkingEnabled = shareLinkingEnabled; }
public boolean isNominationFacility() { return nominationFacility; }
public void setNominationFacility(boolean nominationFacility) { this.nominationFacility = nominationFacility; }
public boolean isDormancyAlert() { return dormancyAlert; }
public void setDormancyAlert(boolean dormancyAlert) { this.dormancyAlert = dormancyAlert; }
public Integer getInterestCalculateSelector() { return interestCalculateSelector; }
public void setInterestCalculateSelector(Integer interestCalculateSelector) { this.interestCalculateSelector = interestCalculateSelector; }
public String getAveragePeriod() { return averagePeriod; }
public void setAveragePeriod(String averagePeriod) { this.averagePeriod = averagePeriod; }
public boolean isAutoPost() { return autoPost; }
public void setAutoPost(boolean autoPost) { this.autoPost = autoPost; }
public Integer getFrequencyFixDay() { return frequencyFixDay; }
public void setFrequencyFixDay(Integer frequencyFixDay) { this.frequencyFixDay = frequencyFixDay; }
public LocalDate getLastInterestDate() { return lastInterestDate; }
public void setLastInterestDate(LocalDate lastInterestDate) { this.lastInterestDate = lastInterestDate; }
public boolean isEnableLockinPeriod() { return enableLockinPeriod; }
public void setEnableLockinPeriod(boolean enableLockinPeriod) { this.enableLockinPeriod = enableLockinPeriod; }
public String getBalanceFrequency() { return balanceFrequency; }
public void setBalanceFrequency(String balanceFrequency) { this.balanceFrequency = balanceFrequency; }
public Boolean getAccountClosedZeroBalanceInDayClosing() { return accountClosedZeroBalanceInDayClosing; }
public void setAccountClosedZeroBalanceInDayClosing(Boolean val) { this.accountClosedZeroBalanceInDayClosing = val; }
// New field getters/setters
public boolean isBalanceSwapOption() { return balanceSwapOption; }
public void setBalanceSwapOption(boolean balanceSwapOption) { this.balanceSwapOption = balanceSwapOption; }
public boolean isMsme() { return msme; }
public void setMsme(boolean msme) { this.msme = msme; }
public boolean isAutoDebitFacility() { return autoDebitFacility; }
public void setAutoDebitFacility(boolean autoDebitFacility) { this.autoDebitFacility = autoDebitFacility; }
public String getAvgBalanceCalcPeriod() { return avgBalanceCalcPeriod; }
public void setAvgBalanceCalcPeriod(String avgBalanceCalcPeriod) { this.avgBalanceCalcPeriod = avgBalanceCalcPeriod; }
public String getValidForPeriod() { return validForPeriod; }
public void setValidForPeriod(String validForPeriod) { this.validForPeriod = validForPeriod; }
public boolean isOdAccountClosedZeroBalanceInDayClosing() { return odAccountClosedZeroBalanceInDayClosing; }
public void setOdAccountClosedZeroBalanceInDayClosing(boolean val) { this.odAccountClosedZeroBalanceInDayClosing = val; }
}
@@ -0,0 +1,433 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.currentproduct.domain;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountFacilityConfigParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountFacilityLimitsParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.allowOverdraftParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.autoPostParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.averagePeriodParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargesParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.currencyCodeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.customerSegmentParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.daysToDormancyParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.daysToEscheatParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.daysToInactiveParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.depositLimitKycParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.descriptionParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.digitsAfterDecimalParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.dormancyAlertParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.effectiveDateParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.effectiveToParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.enableLockinPeriodParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.balanceSwapOptionParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.msmeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.autoDebitFacilityParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.avgBalanceCalcPeriodParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.validForPeriodParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.odAccountClosedZeroBalanceInDayClosingParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.endDateParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.enforceMinRequiredBalanceParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.frequencyFixDayParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.idParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.inMultiplesOfParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculateSelectorParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationDaysInYearTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCompoundingPeriodTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestPostingPeriodTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.introducerRequiredParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.isDormancyTrackingActiveParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lastInterestDateParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lienAllowedParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.maxAllowedLienLimitParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minBalanceForInterestCalculationParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minOverdraftForInterestCalculationParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredBalanceParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredOpeningBalanceParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nameParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominationFacilityParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateOverdraftParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.otherGlMappingsParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.overdraftLimitParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.productCodeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.glAccountIdParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.shareLinkingEnabledParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.shortNameParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.taxGroupIdParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withHoldTaxParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.math.BigDecimal;
import java.util.HashSet;
import java.util.Set;
import org.apache.fineract.accounting.common.AccountingRuleType;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.exception.InvalidCurrencyException;
import org.apache.fineract.portfolio.charge.domain.Charge;
import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.apache.fineract.portfolio.charge.exception.ChargeCannotBeAppliedToException;
import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
import org.apache.fineract.portfolio.tax.domain.TaxGroup;
import org.apache.fineract.portfolio.tax.domain.TaxGroupRepositoryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class CurrentProductAssembler {
private final ChargeRepositoryWrapper chargeRepository;
private final TaxGroupRepositoryWrapper taxGroupRepository;
@Autowired
public CurrentProductAssembler(final ChargeRepositoryWrapper chargeRepository,
final TaxGroupRepositoryWrapper taxGroupRepository) {
this.chargeRepository = chargeRepository;
this.taxGroupRepository = taxGroupRepository;
}
public CurrentProduct assemble(final JsonCommand command) {
final String name = command.stringValueOfParameterNamed(nameParamName);
final String shortName = command.stringValueOfParameterNamed(shortNameParamName);
final String description = command.stringValueOfParameterNamedAllowingNull(descriptionParamName);
final String currencyCode = command.stringValueOfParameterNamed(currencyCodeParamName);
final Integer digitsAfterDecimal = command.integerValueOfParameterNamed(digitsAfterDecimalParamName);
final Integer inMultiplesOf = command.integerValueOfParameterNamed(inMultiplesOfParamName);
final MonetaryCurrency currency = new MonetaryCurrency(currencyCode, digitsAfterDecimal, inMultiplesOf);
BigDecimal interestRate = command.bigDecimalValueOfParameterNamed(nominalAnnualInterestRateParamName);
if (interestRate == null) {
interestRate = BigDecimal.ZERO;
}
SavingsCompoundingInterestPeriodType interestCompoundingPeriodType = SavingsCompoundingInterestPeriodType.DAILY;
final Integer interestPeriodTypeValue = command.integerValueOfParameterNamed(interestCompoundingPeriodTypeParamName);
if (interestPeriodTypeValue != null) {
interestCompoundingPeriodType = SavingsCompoundingInterestPeriodType.fromInt(interestPeriodTypeValue);
}
SavingsPostingInterestPeriodType interestPostingPeriodType = null;
final Integer interestPostingPeriodTypeValue = command.integerValueOfParameterNamed(interestPostingPeriodTypeParamName);
if (interestPostingPeriodTypeValue != null) {
interestPostingPeriodType = SavingsPostingInterestPeriodType.fromInt(interestPostingPeriodTypeValue);
}
SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType.DAILY_BALANCE;
final Integer interestCalculationTypeValue = command.integerValueOfParameterNamed(interestCalculationTypeParamName);
if (interestCalculationTypeValue != null) {
interestCalculationType = SavingsInterestCalculationType.fromInt(interestCalculationTypeValue);
}
SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType = SavingsInterestCalculationDaysInYearType.DAYS_365;
final Integer interestCalculationDaysInYearTypeValue = command.integerValueOfParameterNamed(interestCalculationDaysInYearTypeParamName);
if (interestCalculationDaysInYearTypeValue != null) {
interestCalculationDaysInYearType = SavingsInterestCalculationDaysInYearType.fromInt(interestCalculationDaysInYearTypeValue);
}
final BigDecimal minRequiredOpeningBalance = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(minRequiredOpeningBalanceParamName);
final Integer lockinPeriodFrequency = command.integerValueOfParameterNamedDefaultToNullIfZero(lockinPeriodFrequencyParamName);
SavingsPeriodFrequencyType lockinPeriodFrequencyType = null;
final Integer lockinPeriodFrequencyTypeValue = command.integerValueOfParameterNamed(lockinPeriodFrequencyTypeParamName);
if (lockinPeriodFrequencyTypeValue != null) {
lockinPeriodFrequencyType = SavingsPeriodFrequencyType.fromInt(lockinPeriodFrequencyTypeValue);
}
boolean isWithdrawalFeeApplicableForTransfer = false;
if (command.parameterExists(withdrawalFeeForTransfersParamName)) {
isWithdrawalFeeApplicableForTransfer = command.booleanPrimitiveValueOfParameterNamed(withdrawalFeeForTransfersParamName);
}
final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(command.integerValueOfParameterNamed("accountingRule"));
final Set<Charge> charges = assembleListOfCurrentProductCharges(command, currencyCode);
boolean allowOverdraft = false;
if (command.parameterExists(allowOverdraftParamName)) {
allowOverdraft = command.booleanPrimitiveValueOfParameterNamed(allowOverdraftParamName);
}
BigDecimal overdraftLimit = BigDecimal.ZERO;
if (command.parameterExists(overdraftLimitParamName)) {
overdraftLimit = command.bigDecimalValueOfParameterNamed(overdraftLimitParamName);
}
BigDecimal nominalAnnualInterestRateOverdraft = BigDecimal.ZERO;
if (command.parameterExists(nominalAnnualInterestRateOverdraftParamName)) {
nominalAnnualInterestRateOverdraft = command.bigDecimalValueOfParameterNamed(nominalAnnualInterestRateOverdraftParamName);
}
BigDecimal minOverdraftForInterestCalculation = BigDecimal.ZERO;
if (command.parameterExists(minOverdraftForInterestCalculationParamName)) {
minOverdraftForInterestCalculation = command.bigDecimalValueOfParameterNamed(minOverdraftForInterestCalculationParamName);
}
boolean enforceMinRequiredBalance = false;
if (command.parameterExists(enforceMinRequiredBalanceParamName)) {
enforceMinRequiredBalance = command.booleanPrimitiveValueOfParameterNamed(enforceMinRequiredBalanceParamName);
}
BigDecimal minRequiredBalance = BigDecimal.ZERO;
if (command.parameterExists(minRequiredBalanceParamName)) {
minRequiredBalance = command.bigDecimalValueOfParameterNamed(minRequiredBalanceParamName);
}
boolean lienAllowed = false;
if (command.parameterExists(lienAllowedParamName)) {
lienAllowed = command.booleanPrimitiveValueOfParameterNamed(lienAllowedParamName);
}
BigDecimal maxAllowedLienLimit = BigDecimal.ZERO;
if (command.parameterExists(maxAllowedLienLimitParamName)) {
maxAllowedLienLimit = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(maxAllowedLienLimitParamName);
}
final BigDecimal minBalanceForInterestCalculation = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(minBalanceForInterestCalculationParamName);
boolean withHoldTax = command.booleanPrimitiveValueOfParameterNamed(withHoldTaxParamName);
final TaxGroup taxGroup = assembleTaxGroup(command);
final Boolean isDormancyTrackingActive = command.booleanObjectValueOfParameterNamed(isDormancyTrackingActiveParamName);
final Long daysToInactive = command.longValueOfParameterNamed(daysToInactiveParamName);
final Long daysToDormancy = command.longValueOfParameterNamed(daysToDormancyParamName);
final Long daysToEscheat = command.longValueOfParameterNamed(daysToEscheatParamName);
final String otherGlMappings = command.hasParameter(otherGlMappingsParamName) ? command.jsonFragment(otherGlMappingsParamName) : null;
CurrentProduct product = CurrentProduct.createNew(name, shortName, description, currency, interestRate,
interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType,
interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency,
lockinPeriodFrequencyType, isWithdrawalFeeApplicableForTransfer, accountingRuleType, charges,
allowOverdraft, overdraftLimit, enforceMinRequiredBalance, minRequiredBalance, lienAllowed,
maxAllowedLienLimit, minBalanceForInterestCalculation, nominalAnnualInterestRateOverdraft,
minOverdraftForInterestCalculation, withHoldTax, taxGroup, otherGlMappings,
isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat);
// Set enhanced fields
if (command.parameterExists(accountTypeParamName)) {
product.setAccountType(command.stringValueOfParameterNamed(accountTypeParamName));
}
if (command.parameterExists(customerSegmentParamName)) {
product.setCustomerSegment(command.stringValueOfParameterNamed(customerSegmentParamName));
}
if (command.parameterExists(productCodeParamName)) {
product.setProductCode(command.stringValueOfParameterNamed(productCodeParamName));
}
if (command.parameterExists(glAccountIdParamName)) {
product.setGlAccountId(command.longValueOfParameterNamed(glAccountIdParamName));
}
if (command.parameterExists(effectiveDateParamName)) {
product.setEffectiveDate(command.localDateValueOfParameterNamed(effectiveDateParamName));
}
if (command.parameterExists(effectiveToParamName)) {
product.setEffectiveTo(command.localDateValueOfParameterNamed(effectiveToParamName));
}
if (command.parameterExists(endDateParamName)) {
product.setEndDate(command.localDateValueOfParameterNamed(endDateParamName));
}
if (command.parameterExists(introducerRequiredParamName)) {
product.setIntroducerRequired(command.booleanPrimitiveValueOfParameterNamed(introducerRequiredParamName));
}
if (command.parameterExists(shareLinkingEnabledParamName)) {
product.setShareLinkingEnabled(command.booleanPrimitiveValueOfParameterNamed(shareLinkingEnabledParamName));
}
if (command.parameterExists(nominationFacilityParamName)) {
product.setNominationFacility(command.booleanPrimitiveValueOfParameterNamed(nominationFacilityParamName));
}
if (command.parameterExists(dormancyAlertParamName)) {
product.setDormancyAlert(command.booleanPrimitiveValueOfParameterNamed(dormancyAlertParamName));
}
if (command.parameterExists(interestCalculateSelectorParamName)) {
product.setInterestCalculateSelector(command.integerValueOfParameterNamed(interestCalculateSelectorParamName));
}
if (command.parameterExists(averagePeriodParamName)) {
product.setAveragePeriod(command.stringValueOfParameterNamed(averagePeriodParamName));
}
if (command.parameterExists(autoPostParamName)) {
product.setAutoPost(command.booleanPrimitiveValueOfParameterNamed(autoPostParamName));
}
if (command.parameterExists(frequencyFixDayParamName)) {
product.setFrequencyFixDay(command.integerValueOfParameterNamed(frequencyFixDayParamName));
}
if (command.parameterExists(lastInterestDateParamName)) {
product.setLastInterestDate(command.localDateValueOfParameterNamed(lastInterestDateParamName));
}
if (command.parameterExists(enableLockinPeriodParamName)) {
product.setEnableLockinPeriod(command.booleanPrimitiveValueOfParameterNamed(enableLockinPeriodParamName));
}
if (command.parameterExists("balanceFrequency")) {
product.setBalanceFrequency(command.stringValueOfParameterNamed("balanceFrequency"));
}
if (command.parameterExists("accountClosedZeroBalanceInDayClosing")) {
product.setAccountClosedZeroBalanceInDayClosing(command.booleanObjectValueOfParameterNamed("accountClosedZeroBalanceInDayClosing"));
}
// New currency-step fields
if (command.parameterExists(balanceSwapOptionParamName)) {
product.setBalanceSwapOption(command.booleanPrimitiveValueOfParameterNamed(balanceSwapOptionParamName));
}
if (command.parameterExists(msmeParamName)) {
product.setMsme(command.booleanPrimitiveValueOfParameterNamed(msmeParamName));
}
if (command.parameterExists(autoDebitFacilityParamName)) {
product.setAutoDebitFacility(command.booleanPrimitiveValueOfParameterNamed(autoDebitFacilityParamName));
}
// New OD-section fields (only set when overdraft is allowed)
if (allowOverdraft) {
if (command.parameterExists(avgBalanceCalcPeriodParamName)) {
product.setAvgBalanceCalcPeriod(command.stringValueOfParameterNamed(avgBalanceCalcPeriodParamName));
}
if (command.parameterExists(validForPeriodParamName)) {
product.setValidForPeriod(command.stringValueOfParameterNamed(validForPeriodParamName));
}
if (command.parameterExists(odAccountClosedZeroBalanceInDayClosingParamName)) {
product.setOdAccountClosedZeroBalanceInDayClosing(command.booleanPrimitiveValueOfParameterNamed(odAccountClosedZeroBalanceInDayClosingParamName));
}
}
return product;
}
public Set<Charge> assembleListOfCurrentProductCharges(final JsonCommand command, final String currencyCode) {
final Set<Charge> charges = new HashSet<>();
if (command.parameterExists(chargesParamName)) {
final JsonArray chargesArray = command.arrayOfParameterNamed(chargesParamName);
if (chargesArray != null) {
for (int i = 0; i < chargesArray.size(); i++) {
final JsonObject jsonObject = chargesArray.get(i).getAsJsonObject();
if (jsonObject.has(idParamName)) {
final Long id = jsonObject.get(idParamName).getAsLong();
final Charge charge = this.chargeRepository.findOneWithNotFoundDetection(id);
if (!charge.isSavingsCharge()) {
final String errorMessage = "Charge with identifier " + charge.getId()
+ " cannot be applied to Current product.";
throw new ChargeCannotBeAppliedToException("current.product", errorMessage, charge.getId());
}
if (!currencyCode.equals(charge.getCurrencyCode())) {
final String errorMessage = "Charge and Current Product must have the same currency.";
throw new InvalidCurrencyException("charge", "attach.to.current.product", errorMessage);
}
charges.add(charge);
}
}
}
}
return charges;
}
public TaxGroup assembleTaxGroup(final JsonCommand command) {
final Long taxGroupId = command.longValueOfParameterNamed(taxGroupIdParamName);
TaxGroup taxGroup = null;
if (taxGroupId != null) {
taxGroup = this.taxGroupRepository.findOneWithNotFoundDetection(taxGroupId);
}
return taxGroup;
}
public CurrentProductFacilityConfig assembleAccountFacilityConfig(final JsonCommand command, final CurrentProduct product) {
if (!command.hasParameter(accountFacilityConfigParamName)) {
return null;
}
JsonElement facilityConfigElement = command.parsedJson().getAsJsonObject().get(accountFacilityConfigParamName);
if (facilityConfigElement == null || facilityConfigElement.isJsonNull()) {
return null;
}
final JsonObject facilityConfigJson = facilityConfigElement.getAsJsonObject();
CurrentProductFacilityConfig config = new CurrentProductFacilityConfig(product);
config.setAtmFacility(facilityConfigJson.has("atmFacility") ? facilityConfigJson.get("atmFacility").getAsBoolean() : false);
config.setAtmWithdrawalFacility(facilityConfigJson.has("atmWithdrawalFacility") ? facilityConfigJson.get("atmWithdrawalFacility").getAsBoolean() : false);
config.setUpiFacility(facilityConfigJson.has("upiFacility") ? facilityConfigJson.get("upiFacility").getAsBoolean() : false);
config.setPosTransactionsFacility(facilityConfigJson.has("posTransactionsFacility") ? facilityConfigJson.get("posTransactionsFacility").getAsBoolean() : false);
config.setChequeBook(facilityConfigJson.has("chequeBook") ? facilityConfigJson.get("chequeBook").getAsBoolean() : false);
config.setImpsFacility(facilityConfigJson.has("impsFacility") ? facilityConfigJson.get("impsFacility").getAsBoolean() : false);
config.setSmsAlerts(facilityConfigJson.has("smsAlerts") ? facilityConfigJson.get("smsAlerts").getAsBoolean() : false);
config.setBbpsFacility(facilityConfigJson.has("bbpsFacility") ? facilityConfigJson.get("bbpsFacility").getAsBoolean() : false);
config.setInsuranceCover(facilityConfigJson.has("insuranceCover") ? facilityConfigJson.get("insuranceCover").getAsBoolean() : false);
config.setBepgFacility(facilityConfigJson.has("bepgFacility") ? facilityConfigJson.get("bepgFacility").getAsBoolean() : false);
config.setNeftFacility(facilityConfigJson.has("neftFacility") ? facilityConfigJson.get("neftFacility").getAsBoolean() : false);
config.setRtgsFacility(facilityConfigJson.has("rtgsFacility") ? facilityConfigJson.get("rtgsFacility").getAsBoolean() : false);
config.setInterBankTotalCapFacility(facilityConfigJson.has("interBankTotalCapFacility") ? facilityConfigJson.get("interBankTotalCapFacility").getAsBoolean() : false);
config.setWithdrawalFacility(facilityConfigJson.has("withdrawalFacility") ? facilityConfigJson.get("withdrawalFacility").getAsBoolean() : false);
config.setDepositFacilityKyc(facilityConfigJson.has("depositFacilityKyc") ? facilityConfigJson.get("depositFacilityKyc").getAsBoolean() : false);
config.setVirtualDebitCard(facilityConfigJson.has("virtualDebitCard") ? facilityConfigJson.get("virtualDebitCard").getAsBoolean() : false);
return config;
}
public Set<CurrentProductFacilityLimit> assembleAccountFacilityLimits(final JsonCommand command, final CurrentProduct product) {
final Set<CurrentProductFacilityLimit> limits = new HashSet<>();
if (!command.hasParameter(accountFacilityLimitsParamName)) {
return limits;
}
final JsonArray limitsArray = command.arrayOfParameterNamed(accountFacilityLimitsParamName);
if (limitsArray == null) {
return limits;
}
for (int i = 0; i < limitsArray.size(); i++) {
final JsonObject limitJson = limitsArray.get(i).getAsJsonObject();
limits.add(CurrentProductFacilityLimit.create(product,
limitJson.has("limitType") ? limitJson.get("limitType").getAsString() : null,
limitJson.has("generalLimit") ? limitJson.get("generalLimit").getAsString() : null,
limitJson.has("minorAccountLimit") ? limitJson.get("minorAccountLimit").getAsString() : null,
limitJson.has("smallSavingLimit") ? limitJson.get("smallSavingLimit").getAsString() : null));
}
return limits;
}
public CurrentProductDepositLimitKyc assembleDepositLimitKyc(final JsonCommand command, final CurrentProduct product) {
if (!command.hasParameter(depositLimitKycParamName)) {
return null;
}
JsonElement kycElement = command.parsedJson().getAsJsonObject().get(depositLimitKycParamName);
if (kycElement == null || kycElement.isJsonNull()) {
return null;
}
final JsonObject kycJson = kycElement.getAsJsonObject();
CurrentProductDepositLimitKyc kyc = new CurrentProductDepositLimitKyc(product);
if (kycJson.has("general")) {
final JsonObject generalJson = kycJson.getAsJsonObject("general");
kyc.setGeneralKycType(generalJson.has("kycType") ? generalJson.get("kycType").getAsString() : null);
kyc.setGeneralLimit(generalJson.has("limit") ? generalJson.get("limit").getAsString() : null);
}
if (kycJson.has("minor")) {
final JsonObject minorJson = kycJson.getAsJsonObject("minor");
kyc.setMinorKycType(minorJson.has("kycType") ? minorJson.get("kycType").getAsString() : null);
kyc.setMinorLimit(minorJson.has("limit") ? minorJson.get("limit").getAsLong() : null);
}
if (kycJson.has("smallSaving")) {
final JsonObject smallSavingJson = kycJson.getAsJsonObject("smallSaving");
kyc.setSmallSavingKycType(smallSavingJson.has("kycType") ? smallSavingJson.get("kycType").getAsString() : null);
kyc.setSmallSavingLimit(smallSavingJson.has("limit") ? smallSavingJson.get("limit").getAsLong() : null);
}
return kyc;
}
}
@@ -0,0 +1,92 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.currentproduct.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import java.time.LocalDateTime;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
@Entity
@Table(name = "m_current_product_deposit_limit_kyc")
public class CurrentProductDepositLimitKyc extends AbstractPersistableCustom<Long> {
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "current_product_id", nullable = false)
private CurrentProduct currentProduct;
@Column(name = "general_kyc_type", length = 50)
private String generalKycType;
@Column(name = "general_limit", length = 50)
private String generalLimit;
@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;
@Column(name = "created_date", nullable = false)
private LocalDateTime createdDate;
protected CurrentProductDepositLimitKyc() {
this.createdDate = LocalDateTime.now();
}
public CurrentProductDepositLimitKyc(CurrentProduct currentProduct) {
this();
this.currentProduct = currentProduct;
}
public CurrentProduct getCurrentProduct() { return currentProduct; }
public void setCurrentProduct(CurrentProduct currentProduct) { this.currentProduct = currentProduct; }
public String getGeneralKycType() { return generalKycType; }
public void setGeneralKycType(String generalKycType) { this.generalKycType = generalKycType; }
public String getGeneralLimit() { return generalLimit; }
public void setGeneralLimit(String generalLimit) { this.generalLimit = generalLimit; }
public String getMinorKycType() { return minorKycType; }
public void setMinorKycType(String minorKycType) { this.minorKycType = minorKycType; }
public Long getMinorLimit() { return minorLimit; }
public void setMinorLimit(Long minorLimit) { this.minorLimit = minorLimit; }
public String getSmallSavingKycType() { return smallSavingKycType; }
public void setSmallSavingKycType(String smallSavingKycType) { this.smallSavingKycType = smallSavingKycType; }
public Long getSmallSavingLimit() { return smallSavingLimit; }
public void setSmallSavingLimit(Long smallSavingLimit) { this.smallSavingLimit = smallSavingLimit; }
public LocalDateTime getCreatedDate() { return createdDate; }
public void updateFrom(CurrentProductDepositLimitKyc other) {
this.generalKycType = other.getGeneralKycType();
this.generalLimit = other.getGeneralLimit();
this.minorKycType = other.getMinorKycType();
this.minorLimit = other.getMinorLimit();
this.smallSavingKycType = other.getSmallSavingKycType();
this.smallSavingLimit = other.getSmallSavingLimit();
}
}
@@ -0,0 +1,26 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.currentproduct.domain;
import org.springframework.data.jpa.repository.JpaRepository;
public interface CurrentProductDepositLimitKycRepository extends JpaRepository<CurrentProductDepositLimitKyc, Long> {
CurrentProductDepositLimitKyc findByCurrentProductId(Long currentProductId);
}
@@ -0,0 +1,153 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.currentproduct.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import java.time.LocalDateTime;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
@Entity
@Table(name = "m_current_product_facility_config")
public class CurrentProductFacilityConfig extends AbstractPersistableCustom<Long> {
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "current_product_id", nullable = false)
private CurrentProduct currentProduct;
@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;
@Column(name = "created_date", nullable = false)
private LocalDateTime createdDate;
protected CurrentProductFacilityConfig() {
this.createdDate = LocalDateTime.now();
}
public CurrentProductFacilityConfig(CurrentProduct currentProduct) {
this();
this.currentProduct = currentProduct;
}
public CurrentProduct getCurrentProduct() { return currentProduct; }
public void setCurrentProduct(CurrentProduct currentProduct) { this.currentProduct = currentProduct; }
public Boolean getAtmFacility() { return atmFacility; }
public void setAtmFacility(Boolean v) { this.atmFacility = v != null ? v : false; }
public Boolean getAtmWithdrawalFacility() { return atmWithdrawalFacility; }
public void setAtmWithdrawalFacility(Boolean v) { this.atmWithdrawalFacility = v != null ? v : false; }
public Boolean getUpiFacility() { return upiFacility; }
public void setUpiFacility(Boolean v) { this.upiFacility = v != null ? v : false; }
public Boolean getPosTransactionsFacility() { return posTransactionsFacility; }
public void setPosTransactionsFacility(Boolean v) { this.posTransactionsFacility = v != null ? v : false; }
public Boolean getChequeBook() { return chequeBook; }
public void setChequeBook(Boolean v) { this.chequeBook = v != null ? v : false; }
public Boolean getImpsFacility() { return impsFacility; }
public void setImpsFacility(Boolean v) { this.impsFacility = v != null ? v : false; }
public Boolean getSmsAlerts() { return smsAlerts; }
public void setSmsAlerts(Boolean v) { this.smsAlerts = v != null ? v : false; }
public Boolean getBbpsFacility() { return bbpsFacility; }
public void setBbpsFacility(Boolean v) { this.bbpsFacility = v != null ? v : false; }
public Boolean getInsuranceCover() { return insuranceCover; }
public void setInsuranceCover(Boolean v) { this.insuranceCover = v != null ? v : false; }
public Boolean getBepgFacility() { return bepgFacility; }
public void setBepgFacility(Boolean v) { this.bepgFacility = v != null ? v : false; }
public Boolean getNeftFacility() { return neftFacility; }
public void setNeftFacility(Boolean v) { this.neftFacility = v != null ? v : false; }
public Boolean getRtgsFacility() { return rtgsFacility; }
public void setRtgsFacility(Boolean v) { this.rtgsFacility = v != null ? v : false; }
public Boolean getInterBankTotalCapFacility() { return interBankTotalCapFacility; }
public void setInterBankTotalCapFacility(Boolean v) { this.interBankTotalCapFacility = v != null ? v : false; }
public Boolean getWithdrawalFacility() { return withdrawalFacility; }
public void setWithdrawalFacility(Boolean v) { this.withdrawalFacility = v != null ? v : false; }
public Boolean getDepositFacilityKyc() { return depositFacilityKyc; }
public void setDepositFacilityKyc(Boolean v) { this.depositFacilityKyc = v != null ? v : false; }
public Boolean getVirtualDebitCard() { return virtualDebitCard; }
public void setVirtualDebitCard(Boolean v) { this.virtualDebitCard = v != null ? v : false; }
public LocalDateTime getCreatedDate() { return createdDate; }
public void updateFrom(CurrentProductFacilityConfig other) {
this.atmFacility = other.getAtmFacility();
this.atmWithdrawalFacility = other.getAtmWithdrawalFacility();
this.upiFacility = other.getUpiFacility();
this.posTransactionsFacility = other.getPosTransactionsFacility();
this.chequeBook = other.getChequeBook();
this.impsFacility = other.getImpsFacility();
this.smsAlerts = other.getSmsAlerts();
this.bbpsFacility = other.getBbpsFacility();
this.insuranceCover = other.getInsuranceCover();
this.bepgFacility = other.getBepgFacility();
this.neftFacility = other.getNeftFacility();
this.rtgsFacility = other.getRtgsFacility();
this.interBankTotalCapFacility = other.getInterBankTotalCapFacility();
this.withdrawalFacility = other.getWithdrawalFacility();
this.depositFacilityKyc = other.getDepositFacilityKyc();
this.virtualDebitCard = other.getVirtualDebitCard();
}
}
@@ -0,0 +1,26 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.currentproduct.domain;
import org.springframework.data.jpa.repository.JpaRepository;
public interface CurrentProductFacilityConfigRepository extends JpaRepository<CurrentProductFacilityConfig, Long> {
CurrentProductFacilityConfig findByCurrentProductId(Long currentProductId);
}
@@ -0,0 +1,83 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.currentproduct.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import java.time.LocalDateTime;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
@Entity
@Table(name = "m_current_product_facility_limits")
public class CurrentProductFacilityLimit extends AbstractPersistableCustom<Long> {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "current_product_id", nullable = false)
private CurrentProduct currentProduct;
@Column(name = "limit_type", length = 100, nullable = false)
private String limitType;
@Column(name = "general_limit", length = 50)
private String generalLimit;
@Column(name = "minor_account_limit", length = 50)
private String minorAccountLimit;
@Column(name = "small_saving_limit", length = 50)
private String smallSavingLimit;
@Column(name = "created_date", nullable = false)
private LocalDateTime createdDate;
protected CurrentProductFacilityLimit() {
this.createdDate = LocalDateTime.now();
}
private CurrentProductFacilityLimit(CurrentProduct currentProduct, String limitType,
String generalLimit, String minorAccountLimit, String smallSavingLimit) {
this();
this.currentProduct = currentProduct;
this.limitType = limitType;
this.generalLimit = generalLimit;
this.minorAccountLimit = minorAccountLimit;
this.smallSavingLimit = smallSavingLimit;
}
public static CurrentProductFacilityLimit create(CurrentProduct currentProduct, String limitType,
String generalLimit, String minorAccountLimit, String smallSavingLimit) {
return new CurrentProductFacilityLimit(currentProduct, limitType, generalLimit, minorAccountLimit, smallSavingLimit);
}
public CurrentProduct getCurrentProduct() { return currentProduct; }
public void setCurrentProduct(CurrentProduct currentProduct) { this.currentProduct = currentProduct; }
public String getLimitType() { return limitType; }
public void setLimitType(String limitType) { this.limitType = limitType; }
public String getGeneralLimit() { return generalLimit; }
public void setGeneralLimit(String generalLimit) { this.generalLimit = generalLimit; }
public String getMinorAccountLimit() { return minorAccountLimit; }
public void setMinorAccountLimit(String minorAccountLimit) { this.minorAccountLimit = minorAccountLimit; }
public String getSmallSavingLimit() { return smallSavingLimit; }
public void setSmallSavingLimit(String smallSavingLimit) { this.smallSavingLimit = smallSavingLimit; }
public LocalDateTime getCreatedDate() { return createdDate; }
}
@@ -0,0 +1,31 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.currentproduct.domain;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.transaction.annotation.Transactional;
public interface CurrentProductFacilityLimitRepository extends JpaRepository<CurrentProductFacilityLimit, Long> {
List<CurrentProductFacilityLimit> findByCurrentProductId(Long currentProductId);
@Transactional
void deleteByCurrentProductId(Long currentProductId);
}
@@ -0,0 +1,25 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.currentproduct.domain;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface CurrentProductRepository extends JpaRepository<CurrentProduct, Long>, JpaSpecificationExecutor<CurrentProduct> {
}
@@ -0,0 +1,31 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.currentproduct.service;
import java.util.Collection;
import org.apache.fineract.portfolio.savings.data.SavingsProductData;
public interface CurrentProductReadPlatformService {
Collection<SavingsProductData> retrieveAll();
Collection<SavingsProductData> retrieveAllForLookup();
SavingsProductData retrieveOne(Long productId);
}
@@ -0,0 +1,349 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.currentproduct.service;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.accounting.common.AccountingEnumerations;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductDepositLimitKyc;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductDepositLimitKycRepository;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityConfig;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityConfigRepository;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityLimit;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityLimitRepository;
import org.apache.fineract.portfolio.savings.data.SavingsProductData;
import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException;
import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
import org.apache.fineract.portfolio.tax.data.TaxGroupData;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
@RequiredArgsConstructor
public class CurrentProductReadPlatformServiceImpl implements CurrentProductReadPlatformService {
private final PlatformSecurityContext context;
private final JdbcTemplate jdbcTemplate;
private final CurrentProductFacilityConfigRepository facilityConfigRepository;
private final CurrentProductFacilityLimitRepository facilityLimitRepository;
private final CurrentProductDepositLimitKycRepository depositLimitKycRepository;
private final CurrentProductMapper currentProductRowMapper = new CurrentProductMapper();
private final CurrentProductLookupMapper currentProductLookupsRowMapper = new CurrentProductLookupMapper();
private static final Gson GSON = new Gson();
private static final Type OTHER_GL_MAPPINGS_TYPE = new TypeToken<Map<String, List<Map<String, Object>>>>() {}.getType();
@Override
public Collection<SavingsProductData> retrieveAll() {
this.context.authenticatedUser();
final String sql = "select " + this.currentProductRowMapper.schema();
return this.jdbcTemplate.query(sql, this.currentProductRowMapper); // NOSONAR
}
@Override
public Collection<SavingsProductData> retrieveAllForLookup() {
final String sql = "select " + this.currentProductLookupsRowMapper.schema();
return this.jdbcTemplate.query(sql, this.currentProductLookupsRowMapper); // NOSONAR
}
@Override
public SavingsProductData retrieveOne(final Long productId) {
try {
this.context.authenticatedUser();
final String sql = "select " + this.currentProductRowMapper.schema() + " where cp.id = ?";
SavingsProductData product = this.jdbcTemplate.queryForObject(sql, this.currentProductRowMapper, // NOSONAR
new Object[] { productId });
if (product != null) {
// Fetch Facility Config
CurrentProductFacilityConfig facilityConfig = facilityConfigRepository.findByCurrentProductId(productId);
if (facilityConfig != null) {
Map<String, Object> facilityConfigMap = new HashMap<>();
facilityConfigMap.put("atmFacility", facilityConfig.getAtmFacility());
facilityConfigMap.put("atmWithdrawalFacility", facilityConfig.getAtmWithdrawalFacility());
facilityConfigMap.put("upiFacility", facilityConfig.getUpiFacility());
facilityConfigMap.put("posTransactionsFacility", facilityConfig.getPosTransactionsFacility());
facilityConfigMap.put("chequeBook", facilityConfig.getChequeBook());
facilityConfigMap.put("impsFacility", facilityConfig.getImpsFacility());
facilityConfigMap.put("smsAlerts", facilityConfig.getSmsAlerts());
facilityConfigMap.put("bbpsFacility", facilityConfig.getBbpsFacility());
facilityConfigMap.put("insuranceCover", facilityConfig.getInsuranceCover());
facilityConfigMap.put("bepgFacility", facilityConfig.getBepgFacility());
facilityConfigMap.put("neftFacility", facilityConfig.getNeftFacility());
facilityConfigMap.put("rtgsFacility", facilityConfig.getRtgsFacility());
facilityConfigMap.put("interBankTotalCapFacility", facilityConfig.getInterBankTotalCapFacility());
facilityConfigMap.put("withdrawalFacility", facilityConfig.getWithdrawalFacility());
facilityConfigMap.put("depositFacilityKyc", facilityConfig.getDepositFacilityKyc());
facilityConfigMap.put("virtualDebitCard", facilityConfig.getVirtualDebitCard());
product.setAccountFacilityConfig(facilityConfigMap);
}
// Fetch Facility Limits
List<CurrentProductFacilityLimit> facilityLimits = facilityLimitRepository.findByCurrentProductId(productId);
if (facilityLimits != null && !facilityLimits.isEmpty()) {
List<Map<String, Object>> limits = facilityLimits.stream().map(limit -> {
Map<String, Object> limitMap = new HashMap<>();
limitMap.put("limitType", limit.getLimitType());
limitMap.put("generalLimit", limit.getGeneralLimit());
limitMap.put("minorAccountLimit", limit.getMinorAccountLimit());
limitMap.put("smallSavingLimit", limit.getSmallSavingLimit());
return limitMap;
}).toList();
product.setAccountFacilityLimits(limits);
}
// Fetch Deposit Limit KYC
CurrentProductDepositLimitKyc depositLimitKyc = depositLimitKycRepository.findByCurrentProductId(productId);
if (depositLimitKyc != null) {
Map<String, Object> depositLimitKycMap = new HashMap<>();
depositLimitKycMap.put("general", Map.of(
"kycType", depositLimitKyc.getGeneralKycType() != null ? depositLimitKyc.getGeneralKycType() : "",
"limit", depositLimitKyc.getGeneralLimit() != null ? depositLimitKyc.getGeneralLimit() : ""));
depositLimitKycMap.put("minor", Map.of(
"kycType", depositLimitKyc.getMinorKycType() != null ? depositLimitKyc.getMinorKycType() : "",
"limit", depositLimitKyc.getMinorLimit() != null ? depositLimitKyc.getMinorLimit() : 0L));
depositLimitKycMap.put("smallSaving", Map.of(
"kycType", depositLimitKyc.getSmallSavingKycType() != null ? depositLimitKyc.getSmallSavingKycType() : "",
"limit", depositLimitKyc.getSmallSavingLimit() != null ? depositLimitKyc.getSmallSavingLimit() : 0L));
product.setDepositLimitKyc(depositLimitKycMap);
}
}
return product;
} catch (final EmptyResultDataAccessException e) {
throw new SavingsProductNotFoundException(productId, e);
}
}
private static final class CurrentProductMapper implements RowMapper<SavingsProductData> {
private final String schemaSql;
CurrentProductMapper() {
final StringBuilder sqlBuilder = new StringBuilder(1000);
sqlBuilder.append("cp.id as id, cp.name as name, cp.short_name as shortName, cp.description as description, ");
sqlBuilder.append(
"cp.currency_code as currencyCode, cp.currency_digits as currencyDigits, cp.currency_multiplesof as inMultiplesOf, ");
sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, ");
sqlBuilder.append("cp.nominal_annual_interest_rate as nominalAnnualInterestRate, ");
sqlBuilder.append("cp.interest_compounding_period_enum as compoundingInterestPeriodType, ");
sqlBuilder.append("cp.interest_posting_period_enum as interestPostingPeriodType, ");
sqlBuilder.append("cp.interest_calculation_type_enum as interestCalculationType, ");
sqlBuilder.append("cp.interest_calculation_days_in_year_type_enum as interestCalculationDaysInYearType, ");
sqlBuilder.append("cp.min_required_opening_balance as minRequiredOpeningBalance, ");
sqlBuilder.append("cp.lockin_period_frequency as lockinPeriodFrequency,");
sqlBuilder.append("cp.lockin_period_frequency_enum as lockinPeriodFrequencyType, ");
sqlBuilder.append("cp.withdrawal_fee_for_transfer as withdrawalFeeForTransfers, ");
sqlBuilder.append("cp.allow_overdraft as allowOverdraft, ");
sqlBuilder.append("cp.overdraft_limit as overdraftLimit, ");
sqlBuilder.append("cp.nominal_annual_interest_rate_overdraft as nominalAnnualInterestRateOverdraft, ");
sqlBuilder.append("cp.min_overdraft_for_interest_calculation as minOverdraftForInterestCalculation, ");
sqlBuilder.append("cp.min_required_balance as minRequiredBalance, ");
sqlBuilder.append("cp.enforce_min_required_balance as enforceMinRequiredBalance, ");
sqlBuilder.append("cp.max_allowed_lien_limit as maxAllowedLienLimit, ");
sqlBuilder.append("cp.is_lien_allowed as lienAllowed, ");
sqlBuilder.append("cp.min_balance_for_interest_calculation as minBalanceForInterestCalculation,");
sqlBuilder.append("cp.accounting_type as accountingType, ");
sqlBuilder.append("cp.withhold_tax as withHoldTax,");
sqlBuilder.append("tg.id as taxGroupId, tg.name as taxGroupName, ");
sqlBuilder.append("cp.is_dormancy_tracking_active as isDormancyTrackingActive,");
sqlBuilder.append("cp.days_to_inactive as daysToInactive,");
sqlBuilder.append("cp.days_to_dormancy as daysToDormancy,");
sqlBuilder.append("cp.days_to_escheat as daysToEscheat, ");
sqlBuilder.append("cp.other_gl_mappings as otherGlMappings, ");
// Enhanced fields
sqlBuilder.append("cp.account_type as accountType, cp.customer_segment as customerSegment, cp.product_code as productCode, cp.gl_account_id as glAccountId, gla.name as glAccountName, ");
sqlBuilder.append("cp.effective_date as effectiveDate, cp.effective_to as effectiveTo, cp.end_date as endDate, ");
sqlBuilder.append("cp.introducer_required as introducerRequired, cp.share_linking_enabled as shareLinkingEnabled, ");
sqlBuilder.append("cp.nomination_facility as nominationFacility, cp.dormancy_alert as dormancyAlert, ");
sqlBuilder.append("cp.interest_calculate_selector as interestCalculateSelector, cp.average_period as averagePeriod, ");
sqlBuilder.append("cp.auto_post as autoPost, cp.frequency_fix_day as frequencyFixDay, cp.last_interest_date as lastInterestDate, ");
sqlBuilder.append("cp.enable_lockin_period as enableLockinPeriod, ");
sqlBuilder.append("cp.balance_frequency as balanceFrequency, ");
sqlBuilder.append("cp.account_closed_zero_balance_in_day_closing as accountClosedZeroBalanceInDayClosing, ");
sqlBuilder.append("cp.balance_swap_option as balanceSwapOption, cp.msme as msme, cp.auto_debit_facility as autoDebitFacility, ");
sqlBuilder.append("cp.avg_balance_calc_period as avgBalanceCalcPeriod, cp.valid_for_period as validForPeriod, ");
sqlBuilder.append("cp.od_account_closed_zero_balance_in_day_closing as odAccountClosedZeroBalanceInDayClosing ");
sqlBuilder.append("from m_current_product cp ");
sqlBuilder.append("join m_currency curr on curr.code = cp.currency_code ");
sqlBuilder.append("left join m_tax_group tg on tg.id = cp.tax_group_id ");
sqlBuilder.append("left join acc_gl_account gla on gla.id = cp.gl_account_id ");
this.schemaSql = sqlBuilder.toString();
}
public String schema() {
return this.schemaSql;
}
@Override
public SavingsProductData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final String name = rs.getString("name");
final String shortName = rs.getString("shortName");
final String description = rs.getString("description");
final String currencyCode = rs.getString("currencyCode");
final String currencyName = rs.getString("currencyName");
final String currencyNameCode = rs.getString("currencyNameCode");
final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, currencyDisplaySymbol,
currencyNameCode);
final BigDecimal nominalAnnualInterestRate = rs.getBigDecimal("nominalAnnualInterestRate");
final Integer compoundingInterestPeriodTypeValue = JdbcSupport.getInteger(rs, "compoundingInterestPeriodType");
final EnumOptionData compoundingInterestPeriodType = SavingsEnumerations
.compoundingInterestPeriodType(compoundingInterestPeriodTypeValue);
final Integer interestPostingPeriodTypeValue = JdbcSupport.getInteger(rs, "interestPostingPeriodType");
final EnumOptionData interestPostingPeriodType = SavingsEnumerations.interestPostingPeriodType(interestPostingPeriodTypeValue);
final Integer interestCalculationTypeValue = JdbcSupport.getInteger(rs, "interestCalculationType");
final EnumOptionData interestCalculationType = SavingsEnumerations.interestCalculationType(interestCalculationTypeValue);
EnumOptionData interestCalculationDaysInYearType = null;
final Integer interestCalculationDaysInYearTypeValue = JdbcSupport.getInteger(rs, "interestCalculationDaysInYearType");
if (interestCalculationDaysInYearTypeValue != null) {
interestCalculationDaysInYearType = SavingsEnumerations
.interestCalculationDaysInYearType(interestCalculationDaysInYearTypeValue);
}
final Integer accountingRuleId = JdbcSupport.getInteger(rs, "accountingType");
final EnumOptionData accountingRuleType = AccountingEnumerations.accountingRuleType(accountingRuleId);
final BigDecimal minRequiredOpeningBalance = rs.getBigDecimal("minRequiredOpeningBalance");
final Integer lockinPeriodFrequency = JdbcSupport.getInteger(rs, "lockinPeriodFrequency");
EnumOptionData lockinPeriodFrequencyType = null;
final Integer lockinPeriodFrequencyTypeValue = JdbcSupport.getInteger(rs, "lockinPeriodFrequencyType");
if (lockinPeriodFrequencyTypeValue != null) {
lockinPeriodFrequencyType = SavingsEnumerations.lockinPeriodFrequencyType(lockinPeriodFrequencyTypeValue);
}
final boolean withdrawalFeeForTransfers = rs.getBoolean("withdrawalFeeForTransfers");
final boolean allowOverdraft = rs.getBoolean("allowOverdraft");
final BigDecimal overdraftLimit = rs.getBigDecimal("overdraftLimit");
final BigDecimal nominalAnnualInterestRateOverdraft = rs.getBigDecimal("nominalAnnualInterestRateOverdraft");
final BigDecimal minOverdraftForInterestCalculation = rs.getBigDecimal("minOverdraftForInterestCalculation");
final BigDecimal minRequiredBalance = rs.getBigDecimal("minRequiredBalance");
final boolean enforceMinRequiredBalance = rs.getBoolean("enforceMinRequiredBalance");
final BigDecimal maxAllowedLienLimit = rs.getBigDecimal("maxAllowedLienLimit");
final boolean lienAllowed = rs.getBoolean("lienAllowed");
final BigDecimal minBalanceForInterestCalculation = rs.getBigDecimal("minBalanceForInterestCalculation");
final boolean withHoldTax = rs.getBoolean("withHoldTax");
final Long taxGroupId = JdbcSupport.getLong(rs, "taxGroupId");
final String taxGroupName = rs.getString("taxGroupName");
TaxGroupData taxGroupData = null;
if (taxGroupId != null) {
taxGroupData = TaxGroupData.lookup(taxGroupId, taxGroupName);
}
final Boolean isDormancyTrackingActive = rs.getBoolean("isDormancyTrackingActive");
final Long daysToInactive = JdbcSupport.getLong(rs, "daysToInactive");
final Long daysToDormancy = JdbcSupport.getLong(rs, "daysToDormancy");
final Long daysToEscheat = JdbcSupport.getLong(rs, "daysToEscheat");
final Map<String, List<Map<String, Object>>> otherGlMappings = parseOtherGlMappings(rs.getString("otherGlMappings"));
SavingsProductData productData = SavingsProductData.instance(id, name, shortName, description, currency,
nominalAnnualInterestRate, compoundingInterestPeriodType, interestPostingPeriodType, interestCalculationType,
interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType,
withdrawalFeeForTransfers, accountingRuleType, allowOverdraft, overdraftLimit, minRequiredBalance,
enforceMinRequiredBalance, maxAllowedLienLimit, lienAllowed, minBalanceForInterestCalculation,
nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroupData, null, null, null,
isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, null);
productData.setOtherGlMappingsMap(otherGlMappings);
// Populate Enhanced Fields
productData.setAccountType(rs.getString("accountType"));
productData.setCustomerSegment(rs.getString("customerSegment"));
productData.setProductCode(rs.getString("productCode"));
productData.setGlAccountId(JdbcSupport.getLong(rs, "glAccountId"));
productData.setGlAccountName(rs.getString("glAccountName"));
productData.setEffectiveDate(JdbcSupport.getLocalDate(rs, "effectiveDate"));
productData.setEffectiveTo(JdbcSupport.getLocalDate(rs, "effectiveTo"));
productData.setEndDate(JdbcSupport.getLocalDate(rs, "endDate"));
productData.setIntroducerRequired(rs.getBoolean("introducerRequired"));
productData.setShareLinkingEnabled(rs.getBoolean("shareLinkingEnabled"));
productData.setNominationFacility(rs.getBoolean("nominationFacility"));
productData.setDormancyAlert(rs.getBoolean("dormancyAlert"));
productData.setInterestCalculateSelector(JdbcSupport.getInteger(rs, "interestCalculateSelector"));
productData.setAveragePeriod(rs.getString("averagePeriod"));
productData.setAutoPost(rs.getBoolean("autoPost"));
productData.setFrequencyFixDay(JdbcSupport.getInteger(rs, "frequencyFixDay"));
productData.setLastInterestDate(JdbcSupport.getLocalDate(rs, "lastInterestDate"));
productData.setEnableLockinPeriod(rs.getBoolean("enableLockinPeriod"));
productData.setBalanceFrequency(rs.getString("balanceFrequency"));
productData.setAccountClosedZeroBalanceInDayClosing(rs.getBoolean("accountClosedZeroBalanceInDayClosing"));
productData.setBalanceSwapOption(rs.getBoolean("balanceSwapOption"));
productData.setMsme(rs.getBoolean("msme"));
productData.setAutoDebitFacility(rs.getBoolean("autoDebitFacility"));
productData.setAvgBalanceCalcPeriod(rs.getString("avgBalanceCalcPeriod"));
productData.setValidForPeriod(rs.getString("validForPeriod"));
productData.setOdAccountClosedZeroBalanceInDayClosing(rs.getBoolean("odAccountClosedZeroBalanceInDayClosing"));
return productData;
}
}
private static final class CurrentProductLookupMapper implements RowMapper<SavingsProductData> {
public String schema() {
return " cp.id as id, cp.name as name from m_current_product cp";
}
@Override
public SavingsProductData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final String name = rs.getString("name");
return SavingsProductData.lookup(id, name);
}
}
private static Map<String, List<Map<String, Object>>> parseOtherGlMappings(final String otherGlMappingsJson) {
if (otherGlMappingsJson == null || otherGlMappingsJson.isBlank()) {
return null;
}
return GSON.fromJson(otherGlMappingsJson, OTHER_GL_MAPPINGS_TYPE);
}
}
@@ -0,0 +1,31 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.currentproduct.service;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
public interface CurrentProductWritePlatformService {
CommandProcessingResult create(JsonCommand command);
CommandProcessingResult update(Long productId, JsonCommand command);
CommandProcessingResult delete(Long productId);
}
@@ -0,0 +1,267 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.currentproduct.service;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountingRuleParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargesParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.taxGroupIdParamName;
import jakarta.persistence.PersistenceException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.charge.domain.Charge;
import org.apache.fineract.portfolio.currentproduct.data.CurrentProductDataValidator;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProduct;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductAssembler;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductDepositLimitKyc;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductDepositLimitKycRepository;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityConfig;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityConfigRepository;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityLimit;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityLimitRepository;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductRepository;
import org.apache.fineract.portfolio.savings.DepositsApiConstants;
import org.apache.fineract.portfolio.savings.DepositAccountType;
import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException;
import org.apache.fineract.portfolio.tax.domain.TaxGroup;
import org.springframework.dao.DataAccessException;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@RequiredArgsConstructor
public class CurrentProductWritePlatformServiceJpaRepositoryImpl implements CurrentProductWritePlatformService {
private final PlatformSecurityContext context;
private final CurrentProductRepository currentProductRepository;
private final CurrentProductDataValidator fromApiJsonDataValidator;
private final CurrentProductAssembler currentProductAssembler;
private final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService;
private final CurrentProductFacilityConfigRepository facilityConfigRepository;
private final CurrentProductFacilityLimitRepository facilityLimitRepository;
private final CurrentProductDepositLimitKycRepository depositLimitKycRepository;
private void handleDataIntegrityIssues(final JsonCommand command, final Throwable realCause, final Exception dae) {
String msgCode = "error.msg." + DepositsApiConstants.CURRENT_PRODUCT_RESOURCE_NAME;
String msg = "Unknown data integrity issue with current product.";
String param = null;
Object[] msgArgs;
Throwable checkEx = realCause == null ? dae : realCause;
if (checkEx.getMessage().contains("cp_unq_name")) {
final String name = command.stringValueOfParameterNamed("name");
msgCode += ".duplicate.name";
msg = "Current product with name `" + name + "` already exists";
param = "name";
msgArgs = new Object[] { name, dae };
} else if (checkEx.getMessage().contains("cp_unq_short_name")) {
final String shortName = command.stringValueOfParameterNamed("shortName");
msgCode += ".duplicate.short.name";
msg = "Current product with short name `" + shortName + "` already exists";
param = "shortName";
msgArgs = new Object[] { shortName, dae };
} else if (checkEx.getMessage().contains("cp_unq_product_code")) {
final String productCode = command.stringValueOfParameterNamed("productCode");
msgCode += ".duplicate.product.code";
msg = "Current product with product code `" + productCode + "` already exists";
param = "productCode";
msgArgs = new Object[] { productCode, dae };
} else {
msgCode += ".unknown.data.integrity.issue";
msg = "Unknown data integrity issue with current product. Details: " + checkEx.getMessage();
msgArgs = new Object[] { dae };
}
log.error("Error occured.", dae);
throw ErrorHandler.getMappable(dae, msgCode, msg, param, msgArgs);
}
@Transactional
@Override
public CommandProcessingResult create(final JsonCommand command) {
try {
this.fromApiJsonDataValidator.validateForCreate(command.json());
final CurrentProduct product = this.currentProductAssembler.assemble(command);
final CurrentProductFacilityConfig facilityConfig =
this.currentProductAssembler.assembleAccountFacilityConfig(command, product);
final Set<CurrentProductFacilityLimit> facilityLimits =
this.currentProductAssembler.assembleAccountFacilityLimits(command, product);
final CurrentProductDepositLimitKyc depositLimitKyc =
this.currentProductAssembler.assembleDepositLimitKyc(command, product);
this.currentProductRepository.saveAndFlush(product);
if (facilityConfig != null) {
this.facilityConfigRepository.saveAndFlush(facilityConfig);
}
if (!facilityLimits.isEmpty()) {
this.facilityLimitRepository.saveAll(facilityLimits);
}
if (depositLimitKyc != null) {
this.depositLimitKycRepository.saveAndFlush(depositLimitKyc);
}
this.accountMappingWritePlatformService.createSavingProductToGLAccountMapping(product.getId(), command,
DepositAccountType.SAVINGS_DEPOSIT);
return new CommandProcessingResultBuilder() //
.withEntityId(product.getId()) //
.build();
} catch (final DataAccessException e) {
handleDataIntegrityIssues(command, e.getMostSpecificCause(), e);
return CommandProcessingResult.empty();
} catch (final PersistenceException dve) {
handleDataIntegrityIssues(command, ExceptionUtils.getRootCause(dve.getCause()), dve);
return CommandProcessingResult.empty();
}
}
@Transactional
@Override
public CommandProcessingResult update(final Long productId, final JsonCommand command) {
try {
this.context.authenticatedUser();
final CurrentProduct product = this.currentProductRepository.findById(productId)
.orElseThrow(() -> new SavingsProductNotFoundException(productId));
this.fromApiJsonDataValidator.validateForUpdate(command.json(), product);
final Map<String, Object> changes = product.update(command);
if (changes.containsKey(chargesParamName)) {
final Set<Charge> charges = this.currentProductAssembler.assembleListOfCurrentProductCharges(command,
product.currency().getCode());
final boolean updated = product.update(charges);
if (!updated) {
changes.remove(chargesParamName);
}
}
if (changes.containsKey(taxGroupIdParamName)) {
final TaxGroup taxGroup = this.currentProductAssembler.assembleTaxGroup(command);
product.setTaxGroup(taxGroup);
if (product.withHoldTax() && product.getTaxGroup() == null) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(DepositsApiConstants.CURRENT_PRODUCT_RESOURCE_NAME);
final Long taxGroupId = null;
baseDataValidator.reset().parameter(taxGroupIdParamName).value(taxGroupId).notBlank();
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
// Update facility config
final CurrentProductFacilityConfig existingFacilityConfig = product.getFacilityConfig();
final CurrentProductFacilityConfig newFacilityConfig =
this.currentProductAssembler.assembleAccountFacilityConfig(command, product);
if (newFacilityConfig != null) {
if (existingFacilityConfig != null) {
existingFacilityConfig.updateFrom(newFacilityConfig);
this.facilityConfigRepository.saveAndFlush(existingFacilityConfig);
} else {
this.facilityConfigRepository.saveAndFlush(newFacilityConfig);
}
} else if (existingFacilityConfig != null) {
this.facilityConfigRepository.delete(existingFacilityConfig);
}
// Update facility limits (delete old, insert new)
final Set<CurrentProductFacilityLimit> newFacilityLimits =
this.currentProductAssembler.assembleAccountFacilityLimits(command, product);
if (!newFacilityLimits.isEmpty() || command.hasParameter("accountFacilityLimits")) {
if (product.getFacilityLimits() != null && !product.getFacilityLimits().isEmpty()) {
this.facilityLimitRepository.deleteAll(product.getFacilityLimits());
product.getFacilityLimits().clear();
}
if (!newFacilityLimits.isEmpty()) {
this.facilityLimitRepository.saveAll(newFacilityLimits);
product.setFacilityLimits(newFacilityLimits);
}
}
// Update deposit limit KYC
final CurrentProductDepositLimitKyc existingDepositLimitKyc = product.getDepositLimitKyc();
final CurrentProductDepositLimitKyc newDepositLimitKyc =
this.currentProductAssembler.assembleDepositLimitKyc(command, product);
if (newDepositLimitKyc != null) {
if (existingDepositLimitKyc != null) {
existingDepositLimitKyc.updateFrom(newDepositLimitKyc);
this.depositLimitKycRepository.saveAndFlush(existingDepositLimitKyc);
} else {
this.depositLimitKycRepository.saveAndFlush(newDepositLimitKyc);
}
} else if (existingDepositLimitKyc != null) {
this.depositLimitKycRepository.delete(existingDepositLimitKyc);
}
// accounting related changes
final boolean accountingTypeChanged = changes.containsKey(accountingRuleParamName);
final Map<String, Object> accountingMappingChanges = this.accountMappingWritePlatformService
.updateSavingsProductToGLAccountMapping(product.getId(), command, accountingTypeChanged, product.getAccountingType(),
DepositAccountType.SAVINGS_DEPOSIT);
changes.putAll(accountingMappingChanges);
if (!changes.isEmpty()) {
this.currentProductRepository.saveAndFlush(product);
}
return new CommandProcessingResultBuilder() //
.withEntityId(product.getId()) //
.with(changes).build();
} catch (final DataAccessException e) {
handleDataIntegrityIssues(command, e.getMostSpecificCause(), e);
return CommandProcessingResult.empty();
} catch (final PersistenceException dve) {
handleDataIntegrityIssues(command, ExceptionUtils.getRootCause(dve.getCause()), dve);
return CommandProcessingResult.empty();
}
}
@Transactional
@Override
public CommandProcessingResult delete(final Long productId) {
this.context.authenticatedUser();
final CurrentProduct product = this.currentProductRepository.findById(productId)
.orElseThrow(() -> new SavingsProductNotFoundException(productId));
this.currentProductRepository.delete(product);
return new CommandProcessingResultBuilder() //
.withEntityId(product.getId()) //
.build();
}
}
@@ -0,0 +1,64 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.currentproduct.starter;
import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.currentproduct.data.CurrentProductDataValidator;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductAssembler;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductDepositLimitKycRepository;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityConfigRepository;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityLimitRepository;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductRepository;
import org.apache.fineract.portfolio.currentproduct.service.CurrentProductReadPlatformService;
import org.apache.fineract.portfolio.currentproduct.service.CurrentProductReadPlatformServiceImpl;
import org.apache.fineract.portfolio.currentproduct.service.CurrentProductWritePlatformService;
import org.apache.fineract.portfolio.currentproduct.service.CurrentProductWritePlatformServiceJpaRepositoryImpl;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
@Configuration
public class CurrentProductConfiguration {
@Bean
@ConditionalOnMissingBean(CurrentProductReadPlatformService.class)
public CurrentProductReadPlatformService currentProductReadPlatformService(PlatformSecurityContext context,
JdbcTemplate jdbcTemplate, CurrentProductFacilityConfigRepository currentFacilityConfigRepository,
CurrentProductFacilityLimitRepository currentFacilityLimitRepository,
CurrentProductDepositLimitKycRepository currentDepositLimitKycRepository) {
return new CurrentProductReadPlatformServiceImpl(context, jdbcTemplate, currentFacilityConfigRepository,
currentFacilityLimitRepository, currentDepositLimitKycRepository);
}
@Bean
@ConditionalOnMissingBean(CurrentProductWritePlatformService.class)
public CurrentProductWritePlatformService currentProductWritePlatformService(PlatformSecurityContext context,
CurrentProductRepository currentProductRepository, CurrentProductDataValidator currentProductDataValidator,
CurrentProductAssembler currentProductAssembler,
ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService,
CurrentProductFacilityConfigRepository currentFacilityConfigRepository,
CurrentProductFacilityLimitRepository currentFacilityLimitRepository,
CurrentProductDepositLimitKycRepository currentDepositLimitKycRepository) {
return new CurrentProductWritePlatformServiceJpaRepositoryImpl(context, currentProductRepository, currentProductDataValidator,
currentProductAssembler, accountMappingWritePlatformService, currentFacilityConfigRepository,
currentFacilityLimitRepository, currentDepositLimitKycRepository);
}
}
+5 -2
View File
@@ -368,7 +368,7 @@ tasks.register('devRun', org.springframework.boot.gradle.tasks.run.BootRun) {
description = 'Runs the application quickly for development by skipping quality checks' description = 'Runs the application quickly for development by skipping quality checks'
group = 'Application' group = 'Application'
// Configure the build to skip quality checks // Configure the build to skip quality checks and resolve tasks
gradle.taskGraph.whenReady { graph -> gradle.taskGraph.whenReady { graph ->
if (graph.hasTask(devRun)) { if (graph.hasTask(devRun)) {
tasks.matching { task -> tasks.matching { task ->
@@ -377,7 +377,10 @@ tasks.register('devRun', org.springframework.boot.gradle.tasks.run.BootRun) {
'spotbugsMain', 'spotbugsTest', 'spotbugsMain', 'spotbugsTest',
'javadoc', 'javadocJar', 'javadoc', 'javadocJar',
'modernizer', 'modernizer',
'testClasses'] 'testClasses',
'compileTestJava',
'test',
'resolve']
}.configureEach { }.configureEach {
enabled = false enabled = false
} }
+1
View File
@@ -34,6 +34,7 @@ dependencies {
implementation(project(path: ':fineract-charge')) implementation(project(path: ':fineract-charge'))
implementation(project(path: ':fineract-loan')) implementation(project(path: ':fineract-loan'))
implementation(project(path: ':fineract-savings')) implementation(project(path: ':fineract-savings'))
implementation(project(path: ':fineract-current-product'))
implementation(project(path: ':fineract-branch')) implementation(project(path: ':fineract-branch'))
implementation(project(path: ':fineract-document')) implementation(project(path: ':fineract-document'))
implementation(project(path: ':fineract-progressive-loan')) implementation(project(path: ':fineract-progressive-loan'))
+18
View File
@@ -0,0 +1,18 @@
> Task :buildSrc:extractPluginRequests UP-TO-DATE
> Task :buildSrc:generatePluginAdapters UP-TO-DATE
> Task :buildSrc:compileJava UP-TO-DATE
> Task :buildSrc:compileGroovy UP-TO-DATE
> Task :buildSrc:compileGroovyPlugins UP-TO-DATE
> Task :buildSrc:pluginDescriptors UP-TO-DATE
> Task :buildSrc:processResources UP-TO-DATE
> Task :buildSrc:classes UP-TO-DATE
> Task :buildSrc:jar UP-TO-DATE
> Configure project :
matching ref: COMMIT - d7821c7824067a96d31c4c031ea538fc5e4107fc
ref configuration: COMMIT - pattern: null
version: ${describe.tag.version.major}.${describe.tag.version.minor.next}.0-SNAPSHOT
describeTagPattern: .*(\d+\.\d+\.\d+).*
describeTagFirstParent: false
project version: 0.1.0-SNAPSHOT
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -24,6 +24,7 @@ import java.util.Map;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.accounting.glaccount.domain.GLAccountRepository;
import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormat; import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormat;
import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormatEnumerations.AccountNumberPrefixType; import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormatEnumerations.AccountNumberPrefixType;
import org.apache.fineract.infrastructure.codes.domain.CodeValue; import org.apache.fineract.infrastructure.codes.domain.CodeValue;
@@ -35,9 +36,12 @@ import org.apache.fineract.portfolio.client.domain.ClientRepository;
import org.apache.fineract.portfolio.group.domain.Group; import org.apache.fineract.portfolio.group.domain.Group;
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccount;
import org.apache.fineract.portfolio.savings.domain.SavingsAccount; import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepository; import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepository;
import org.apache.fineract.portfolio.savings.domain.SavingsProductRepository;
import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccount; import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccount;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
@@ -58,10 +62,16 @@ public class AccountNumberGenerator {
private static final String SAVINGS_PRODUCT_SHORT_NAME = "savingsProductShortName"; private static final String SAVINGS_PRODUCT_SHORT_NAME = "savingsProductShortName";
private static final String SHARE_PRODUCT_SHORT_NAME = "sharesProductShortName"; private static final String SHARE_PRODUCT_SHORT_NAME = "sharesProductShortName";
private static final String PREFIX_SHORT_NAME = "prefixShortName"; private static final String PREFIX_SHORT_NAME = "prefixShortName";
private static final int CBS_ACCOUNT_NUMBER_LENGTH = 15;
private static final String CBS_FIXED_PREFIX = "00";
private final ConfigurationReadPlatformService configurationReadPlatformService; private final ConfigurationReadPlatformService configurationReadPlatformService;
private final ClientRepository clientRepository; private final ClientRepository clientRepository;
private final LoanRepository loanRepository; private final LoanRepository loanRepository;
private final SavingsAccountRepository savingsAccountRepository; private final SavingsAccountRepository savingsAccountRepository;
private final GLAccountRepository glAccountRepository;
private final SavingsProductRepository savingsProductRepository;
private final JdbcTemplate jdbcTemplate;
public String generate(Client client, AccountNumberFormat accountNumberFormat) { public String generate(Client client, AccountNumberFormat accountNumberFormat) {
Map<String, String> propertyMap = new HashMap<>(); Map<String, String> propertyMap = new HashMap<>();
@@ -85,12 +95,93 @@ public class AccountNumberGenerator {
} }
public String generate(SavingsAccount savingsAccount, AccountNumberFormat accountNumberFormat) { public String generate(SavingsAccount savingsAccount, AccountNumberFormat accountNumberFormat) {
Map<String, String> propertyMap = new HashMap<>(); return generateCbsAccountNumber(savingsAccount);
propertyMap.put(ID, savingsAccount.getId().toString()); }
propertyMap.put(OFFICE_NAME, savingsAccount.office().getName());
propertyMap.put(SAVINGS_PRODUCT_SHORT_NAME, savingsAccount.savingsProduct().getShortName()); private String generateCbsAccountNumber(SavingsAccount savingsAccount) {
propertyMap.put(ENTITY_TYPE, "savingsAccount"); // Format: [00][GL_CODE 6 digits][OFFICE_ID 2 digits][ACCOUNT_ID 5 digits] = 15 digits
return generateAccountNumber(propertyMap, accountNumberFormat); Long glAccountId = savingsAccount.savingsProduct().getGlAccountId();
String glCode = "000000";
if (glAccountId != null) {
glCode = glAccountRepository.findById(glAccountId)
.map(gl -> StringUtils.leftPad(gl.getGlCode(), 6, '0'))
.orElse("000000");
}
glCode = glCode.length() > 6 ? glCode.substring(glCode.length() - 6) : StringUtils.leftPad(glCode, 6, '0');
String officeId = StringUtils.leftPad(savingsAccount.office().getId().toString(), 2, '0');
if (officeId.length() > 2) {
officeId = officeId.substring(officeId.length() - 2);
}
String accountId = StringUtils.leftPad(savingsAccount.getId().toString(), 5, '0');
if (accountId.length() > 5) {
accountId = accountId.substring(accountId.length() - 5);
}
return CBS_FIXED_PREFIX + glCode + officeId + accountId;
}
/**
* Generate CBS account number for a current account.
* GL code is fetched directly from m_current_product using the current account's product_id.
* Format: 00 + GL_CODE(6) + OFFICE_ID(2) + ACCOUNT_ID(5) = 15 digits
*/
public String generateForCurrentAccount(CurrentAccount currentAccount) {
Long productId = currentAccount.getProductId();
Long glAccountId = jdbcTemplate.query(
"SELECT gl_account_id FROM m_current_product WHERE id = ?",
rs -> rs.next() ? rs.getLong("gl_account_id") : null,
productId);
String glCode = "000000";
if (glAccountId != null && glAccountId > 0) {
glCode = glAccountRepository.findById(glAccountId)
.map(gl -> StringUtils.leftPad(gl.getGlCode(), 6, '0'))
.orElse("000000");
}
glCode = glCode.length() > 6 ? glCode.substring(glCode.length() - 6) : StringUtils.leftPad(glCode, 6, '0');
String officeId = StringUtils.leftPad(currentAccount.getOffice().getId().toString(), 2, '0');
if (officeId.length() > 2) officeId = officeId.substring(officeId.length() - 2);
String accountId = StringUtils.leftPad(currentAccount.getId().toString(), 5, '0');
if (accountId.length() > 5) accountId = accountId.substring(accountId.length() - 5);
return CBS_FIXED_PREFIX + glCode + officeId + accountId;
}
public String previewAccountNumber(Long productId, Long officeId) {
String glCode = "000000";
if (productId != null) {
glCode = savingsProductRepository.findById(productId).map(p -> {
Long glAccId = p.getGlAccountId();
if (glAccId == null) return "000000";
return glAccountRepository.findById(glAccId).map(gl -> gl.getGlCode()).orElse("000000");
}).orElse("000000");
}
return buildPreview(glCode, officeId);
}
public String previewAccountNumberByGlAccountId(Long glAccountId, Long officeId) {
String glCode = "000000";
if (glAccountId != null) {
glCode = glAccountRepository.findById(glAccountId).map(gl -> gl.getGlCode()).orElse("000000");
}
return buildPreview(glCode, officeId);
}
private String buildPreview(String glCode, Long officeId) {
glCode = glCode.length() > 6 ? glCode.substring(glCode.length() - 6) : StringUtils.leftPad(glCode, 6, '0');
String officeIdStr = officeId != null ? StringUtils.leftPad(officeId.toString(), 2, '0') : "00";
if (officeIdStr.length() > 2) officeIdStr = officeIdStr.substring(officeIdStr.length() - 2);
Long maxId = savingsAccountRepository.findMaxId().orElse(0L);
String nextSeq = StringUtils.leftPad(String.valueOf(maxId + 1), 5, '0');
if (nextSeq.length() > 5) nextSeq = nextSeq.substring(nextSeq.length() - 5);
return CBS_FIXED_PREFIX + glCode + officeIdStr + nextSeq;
} }
public String generate(ShareAccount shareaccount, AccountNumberFormat accountNumberFormat) { public String generate(ShareAccount shareaccount, AccountNumberFormat accountNumberFormat) {
@@ -31,6 +31,7 @@ public class AccountSummaryCollectionData {
private final Collection<SavingsAccountSummaryData> savingsAccounts; private final Collection<SavingsAccountSummaryData> savingsAccounts;
private final Collection<ShareAccountSummaryData> shareAccounts; private final Collection<ShareAccountSummaryData> shareAccounts;
private final Collection<GuarantorAccountSummaryData> guarantorAccounts; private final Collection<GuarantorAccountSummaryData> guarantorAccounts;
private final Collection<SavingsAccountSummaryData> currentAccounts;
private final Collection<LoanAccountSummaryData> memberLoanAccounts; private final Collection<LoanAccountSummaryData> memberLoanAccounts;
private final Collection<SavingsAccountSummaryData> memberSavingsAccounts; private final Collection<SavingsAccountSummaryData> memberSavingsAccounts;
@@ -49,6 +50,24 @@ public class AccountSummaryCollectionData {
this.savingsAccounts = defaultSavingsAccountsIfEmpty(savingsAccounts); this.savingsAccounts = defaultSavingsAccountsIfEmpty(savingsAccounts);
this.shareAccounts = defaultShareAccountsIfEmpty(shareAccounts); this.shareAccounts = defaultShareAccountsIfEmpty(shareAccounts);
this.guarantorAccounts = guarantorAccounts; this.guarantorAccounts = guarantorAccounts;
this.currentAccounts = null;
this.memberLoanAccounts = null;
this.memberSavingsAccounts = null;
this.memberGuarantorAccounts = null;
}
public AccountSummaryCollectionData(final Collection<LoanAccountSummaryData> loanAccounts,
final Collection<LoanAccountSummaryData> groupLoanIndividualMonitoringAccounts,
final Collection<SavingsAccountSummaryData> savingsAccounts, final Collection<ShareAccountSummaryData> shareAccounts,
final Collection<GuarantorAccountSummaryData> guarantorAccounts,
final Collection<SavingsAccountSummaryData> currentAccounts) {
this.loanAccounts = defaultLoanAccountsIfEmpty(loanAccounts);
this.groupLoanIndividualMonitoringAccounts = groupLoanIndividualMonitoringAccounts;
this.savingsAccounts = defaultSavingsAccountsIfEmpty(savingsAccounts);
this.shareAccounts = defaultShareAccountsIfEmpty(shareAccounts);
this.guarantorAccounts = guarantorAccounts;
this.currentAccounts = defaultSavingsAccountsIfEmpty(currentAccounts);
this.memberLoanAccounts = null; this.memberLoanAccounts = null;
this.memberSavingsAccounts = null; this.memberSavingsAccounts = null;
this.memberGuarantorAccounts = null; this.memberGuarantorAccounts = null;
@@ -66,6 +85,7 @@ public class AccountSummaryCollectionData {
this.savingsAccounts = defaultSavingsAccountsIfEmpty(savingsAccounts); this.savingsAccounts = defaultSavingsAccountsIfEmpty(savingsAccounts);
this.guarantorAccounts = guarantorAccounts; this.guarantorAccounts = guarantorAccounts;
this.shareAccounts = null; this.shareAccounts = null;
this.currentAccounts = null;
this.memberLoanAccounts = defaultLoanAccountsIfEmpty(memberLoanAccounts); this.memberLoanAccounts = defaultLoanAccountsIfEmpty(memberLoanAccounts);
this.memberSavingsAccounts = defaultSavingsAccountsIfEmpty(memberSavingsAccounts); this.memberSavingsAccounts = defaultSavingsAccountsIfEmpty(memberSavingsAccounts);
this.memberGuarantorAccounts = defaultGuarantorAccountsIfEmpty(memberGuarantorAccounts); this.memberGuarantorAccounts = defaultGuarantorAccountsIfEmpty(memberGuarantorAccounts);
@@ -79,7 +79,8 @@ public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements Accou
final List<ShareAccountSummaryData> shareAccounts = retrieveShareAccountDetails(clientId); final List<ShareAccountSummaryData> shareAccounts = retrieveShareAccountDetails(clientId);
final List<GuarantorAccountSummaryData> guarantorloanAccounts = retrieveGuarantorLoanAccountDetails(guarantorWhereClause, final List<GuarantorAccountSummaryData> guarantorloanAccounts = retrieveGuarantorLoanAccountDetails(guarantorWhereClause,
new Object[] { clientId }); new Object[] { clientId });
return new AccountSummaryCollectionData(loanAccounts, glimAccounts, savingsAccounts, shareAccounts, guarantorloanAccounts); final List<SavingsAccountSummaryData> currentAccounts = retrieveCurrentAccountDetails(clientId);
return new AccountSummaryCollectionData(loanAccounts, glimAccounts, savingsAccounts, shareAccounts, guarantorloanAccounts, currentAccounts);
} }
@Override @Override
@@ -176,6 +177,12 @@ public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements Accou
return retrieveLoanAccountDetails(loanWhereClause, new Object[] { groupId }); return retrieveLoanAccountDetails(loanWhereClause, new Object[] { groupId });
} }
private List<SavingsAccountSummaryData> retrieveCurrentAccountDetails(final Long clientId) {
final CurrentAccountSummaryDataMapper mapper = new CurrentAccountSummaryDataMapper();
final String sql = "select " + mapper.schema() + " where ca.client_id = ? order by ca.status_enum ASC, ca.account_no ASC";
return this.jdbcTemplate.query(sql, mapper, new Object[] { clientId }); // NOSONAR
}
private List<LoanAccountSummaryData> retrieveLoanAccountDetails(final String loanwhereClause, final Object[] inputs) { private List<LoanAccountSummaryData> retrieveLoanAccountDetails(final String loanwhereClause, final Object[] inputs) {
final LoanAccountSummaryDataMapper rm = new LoanAccountSummaryDataMapper(); final LoanAccountSummaryDataMapper rm = new LoanAccountSummaryDataMapper();
final String sql = "select " + rm.loanAccountSummarySchema() + loanwhereClause; final String sql = "select " + rm.loanAccountSummarySchema() + loanwhereClause;
@@ -452,6 +459,119 @@ public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements Accou
} }
} }
private static final class CurrentAccountSummaryDataMapper implements RowMapper<SavingsAccountSummaryData> {
private final String schemaSql;
CurrentAccountSummaryDataMapper() {
final StringBuilder sb = new StringBuilder();
sb.append("ca.id as id, ca.account_no as accountNo, ca.external_id as externalId, ");
sb.append("ca.status_enum as statusEnum, ca.sub_status_enum as subStatusEnum, ");
sb.append("ca.account_type_enum as accountType, ");
sb.append("ca.account_balance_derived as accountBalance, ");
sb.append("ca.on_hold_funds_derived as onHoldFunds, ");
sb.append("ca.submittedon_date as submittedOnDate, ");
sb.append("sbu.username as submittedByUsername, sbu.firstname as submittedByFirstname, sbu.lastname as submittedByLastname, ");
sb.append("ca.rejectedon_date as rejectedOnDate, ");
sb.append("rbu.username as rejectedByUsername, rbu.firstname as rejectedByFirstname, rbu.lastname as rejectedByLastname, ");
sb.append("ca.withdrawnon_date as withdrawnOnDate, ");
sb.append("wbu.username as withdrawnByUsername, wbu.firstname as withdrawnByFirstname, wbu.lastname as withdrawnByLastname, ");
sb.append("ca.approvedon_date as approvedOnDate, ");
sb.append("abu.username as approvedByUsername, abu.firstname as approvedByFirstname, abu.lastname as approvedByLastname, ");
sb.append("ca.activatedon_date as activatedOnDate, ");
sb.append("avbu.username as activatedByUsername, avbu.firstname as activatedByFirstname, avbu.lastname as activatedByLastname, ");
sb.append("ca.closedon_date as closedOnDate, ");
sb.append("cbu.username as closedByUsername, cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname, ");
sb.append("ca.currency_code as currencyCode, ca.currency_digits as currencyDigits, ca.currency_multiplesof as inMultiplesOf, ");
sb.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
sb.append("curr.display_symbol as currencyDisplaySymbol, ");
sb.append("ca.product_id as productId, cp.name as productName, cp.short_name as shortProductName ");
sb.append("from m_current_account ca ");
sb.append("join m_current_product cp on cp.id = ca.product_id ");
sb.append("join m_currency curr on curr.code = ca.currency_code ");
sb.append("left join m_appuser sbu on sbu.id = ca.submittedon_userid ");
sb.append("left join m_appuser rbu on rbu.id = ca.rejectedon_userid ");
sb.append("left join m_appuser wbu on wbu.id = ca.withdrawnon_userid ");
sb.append("left join m_appuser abu on abu.id = ca.approvedon_userid ");
sb.append("left join m_appuser avbu on avbu.id = ca.activatedon_userid ");
sb.append("left join m_appuser cbu on cbu.id = ca.closedon_userid ");
this.schemaSql = sb.toString();
}
public String schema() {
return this.schemaSql;
}
@Override
public SavingsAccountSummaryData mapRow(final ResultSet rs, final int rowNum) throws SQLException {
final Long id = JdbcSupport.getLong(rs, "id");
final String accountNo = rs.getString("accountNo");
final String externalId = rs.getString("externalId");
final Long productId = JdbcSupport.getLong(rs, "productId");
final String productName = rs.getString("productName");
final String shortProductName = rs.getString("shortProductName");
final Integer statusId = JdbcSupport.getInteger(rs, "statusEnum");
final SavingsAccountStatusEnumData status = SavingsEnumerations.status(statusId);
final Integer subStatusEnum = JdbcSupport.getInteger(rs, "subStatusEnum");
final SavingsAccountSubStatusEnumData subStatus = SavingsEnumerations.subStatus(subStatusEnum);
final Integer accountType = JdbcSupport.getInteger(rs, "accountType");
final EnumOptionData accountTypeData = AccountEnumerations.loanType(accountType);
final BigDecimal accountBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accountBalance");
final BigDecimal onHoldFunds = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "onHoldFunds");
final String currencyCode = rs.getString("currencyCode");
final String currencyName = rs.getString("currencyName");
final String currencyNameCode = rs.getString("currencyNameCode");
final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
currencyDisplaySymbol, currencyNameCode);
final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate");
final String submittedByUsername = rs.getString("submittedByUsername");
final String submittedByFirstname = rs.getString("submittedByFirstname");
final String submittedByLastname = rs.getString("submittedByLastname");
final LocalDate rejectedOnDate = JdbcSupport.getLocalDate(rs, "rejectedOnDate");
final String rejectedByUsername = rs.getString("rejectedByUsername");
final String rejectedByFirstname = rs.getString("rejectedByFirstname");
final String rejectedByLastname = rs.getString("rejectedByLastname");
final LocalDate withdrawnOnDate = JdbcSupport.getLocalDate(rs, "withdrawnOnDate");
final String withdrawnByUsername = rs.getString("withdrawnByUsername");
final String withdrawnByFirstname = rs.getString("withdrawnByFirstname");
final String withdrawnByLastname = rs.getString("withdrawnByLastname");
final LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, "approvedOnDate");
final String approvedByUsername = rs.getString("approvedByUsername");
final String approvedByFirstname = rs.getString("approvedByFirstname");
final String approvedByLastname = rs.getString("approvedByLastname");
final LocalDate activatedOnDate = JdbcSupport.getLocalDate(rs, "activatedOnDate");
final String activatedByUsername = rs.getString("activatedByUsername");
final String activatedByFirstname = rs.getString("activatedByFirstname");
final String activatedByLastname = rs.getString("activatedByLastname");
final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedOnDate");
final String closedByUsername = rs.getString("closedByUsername");
final String closedByFirstname = rs.getString("closedByFirstname");
final String closedByLastname = rs.getString("closedByLastname");
final SavingsAccountApplicationTimelineData timeline = new SavingsAccountApplicationTimelineData(submittedOnDate,
submittedByUsername, submittedByFirstname, submittedByLastname, rejectedOnDate, rejectedByUsername,
rejectedByFirstname, rejectedByLastname, withdrawnOnDate, withdrawnByUsername, withdrawnByFirstname,
withdrawnByLastname, approvedOnDate, approvedByUsername, approvedByFirstname, approvedByLastname, activatedOnDate,
activatedByUsername, activatedByFirstname, activatedByLastname, closedOnDate, closedByUsername, closedByFirstname,
closedByLastname);
final EnumOptionData depositTypeData = SavingsEnumerations.depositType(400);
return new SavingsAccountSummaryData(id, accountNo, externalId, productId, productName, shortProductName, status, currency,
accountBalance, onHoldFunds, null, accountBalance, accountTypeData, timeline, depositTypeData, subStatus, activatedOnDate);
}
}
private static final class LoanAccountSummaryDataMapper implements RowMapper<LoanAccountSummaryData> { private static final class LoanAccountSummaryDataMapper implements RowMapper<LoanAccountSummaryData> {
public String loanAccountSummarySchema() { public String loanAccountSummarySchema() {
@@ -304,6 +304,10 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
return chargeSchema() + " join m_share_product_charge mspc on mspc.charge_id = c.id"; return chargeSchema() + " join m_share_product_charge mspc on mspc.charge_id = c.id";
} }
public String currentProductChargeSchema() {
return chargeSchema() + " join m_current_product_charge cpc on cpc.charge_id = c.id";
}
@Override @Override
public ChargeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { public ChargeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long id = rs.getLong("id"); final Long id = rs.getLong("id");
@@ -438,6 +442,17 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
return this.jdbcTemplate.query(sql, rm, new Object[] { shareProductId }); // NOSONAR return this.jdbcTemplate.query(sql, rm, new Object[] { shareProductId }); // NOSONAR
} }
@Override
public List<ChargeData> retrieveCurrentProductCharges(final Long currentProductId) {
final ChargeMapper rm = new ChargeMapper();
String sql = "select " + rm.currentProductChargeSchema()
+ " where c.is_deleted=false and c.is_active=true and cpc.current_product_id=? ";
sql += addInClauseToSQL_toLimitChargesMappedToOffice_ifOfficeSpecificProductsEnabled();
return this.jdbcTemplate.query(sql, rm, new Object[] { currentProductId }); // NOSONAR
}
@Override @Override
public List<ChargeData> retrieveSavingsAccountApplicableCharges(Long savingsAccountId) { public List<ChargeData> retrieveSavingsAccountApplicableCharges(Long savingsAccountId) {
@@ -35,35 +35,26 @@ import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.UriInfo; import jakarta.ws.rs.core.UriInfo;
import java.util.Arrays; import com.google.gson.Gson;
import java.util.Collection; import com.google.gson.GsonBuilder;
import java.util.Set; import com.google.gson.JsonParser;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.commands.domain.CommandWrapper;
import org.apache.fineract.commands.service.CommandWrapperBuilder;
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper; import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException; import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.infrastructure.core.service.SearchParameters;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator; import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.apache.fineract.portfolio.savings.DepositsApiConstants; import org.apache.fineract.portfolio.savings.DepositsApiConstants;
import org.apache.fineract.portfolio.savings.DepositAccountType;
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData;
import org.apache.fineract.portfolio.savings.data.SavingsAccountData; import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData; import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountReadPlatformService;
import org.apache.fineract.portfolio.savings.service.SavingsAccountChargeReadPlatformService; import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountWritePlatformService;
import org.apache.fineract.portfolio.savings.service.SavingsAccountReadPlatformService; import org.apache.fineract.portfolio.savings.service.CurrentAccountTemplateReadPlatformService;
import org.apache.fineract.portfolio.savings.service.SavingsAccountTemplateReadPlatformService;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
@Path("/v1/currentaccounts") @Path("/v1/currentaccounts")
@Component @Component
@@ -71,14 +62,15 @@ import org.springframework.util.CollectionUtils;
@RequiredArgsConstructor @RequiredArgsConstructor
public class CurrentAccountsApiResource { public class CurrentAccountsApiResource {
private final SavingsAccountReadPlatformService savingsAccountReadPlatformService; private static final Gson GSON = new GsonBuilder().serializeNulls().create();
private final SavingsAccountTemplateReadPlatformService savingsAccountTemplateReadPlatformService;
private final CurrentAccountTemplateReadPlatformService currentAccountTemplateReadPlatformService;
private final PlatformSecurityContext context; private final PlatformSecurityContext context;
private final DefaultToApiJsonSerializer<SavingsAccountData> toApiJsonSerializer; private final DefaultToApiJsonSerializer<SavingsAccountData> toApiJsonSerializer;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final ApiRequestParameterHelper apiRequestParameterHelper; private final ApiRequestParameterHelper apiRequestParameterHelper;
private final SavingsAccountChargeReadPlatformService savingsAccountChargeReadPlatformService;
private final SqlValidator sqlValidator; private final SqlValidator sqlValidator;
private final CurrentAccountWritePlatformService currentAccountWritePlatformService;
private final CurrentAccountReadPlatformService currentAccountReadPlatformService;
@GET @GET
@Path("template") @Path("template")
@@ -93,7 +85,7 @@ public class CurrentAccountsApiResource {
@Context final UriInfo uriInfo) { @Context final UriInfo uriInfo) {
context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.CURRENT_ACCOUNT_RESOURCE_NAME); context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.CURRENT_ACCOUNT_RESOURCE_NAME);
final SavingsAccountData savingsAccount = savingsAccountTemplateReadPlatformService.retrieveTemplate(clientId, groupId, productId, final SavingsAccountData savingsAccount = currentAccountTemplateReadPlatformService.retrieveTemplate(clientId, groupId, productId,
staffInSelectedOfficeOnly); staffInSelectedOfficeOnly);
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return toApiJsonSerializer.serialize(settings, savingsAccount, SavingsApiSetConstants.SAVINGS_ACCOUNT_RESPONSE_DATA_PARAMETERS); return toApiJsonSerializer.serialize(settings, savingsAccount, SavingsApiSetConstants.SAVINGS_ACCOUNT_RESPONSE_DATA_PARAMETERS);
@@ -112,14 +104,8 @@ public class CurrentAccountsApiResource {
@QueryParam("sortOrder") final String sortOrder) { @QueryParam("sortOrder") final String sortOrder) {
context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.CURRENT_ACCOUNT_RESOURCE_NAME); context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.CURRENT_ACCOUNT_RESOURCE_NAME);
sqlValidator.validate(orderBy); final List<Map<String, Object>> accounts = currentAccountReadPlatformService.retrieveAll();
sqlValidator.validate(sortOrder); return GSON.toJson(accounts);
sqlValidator.validate(externalId);
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).externalId(externalId).offset(offset)
.orderBy(orderBy).sortOrder(sortOrder).build();
final Page<SavingsAccountData> products = savingsAccountReadPlatformService.retrieveAll(searchParameters);
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return toApiJsonSerializer.serialize(settings, products, SavingsApiSetConstants.SAVINGS_ACCOUNT_RESPONSE_DATA_PARAMETERS);
} }
@POST @POST
@@ -128,8 +114,10 @@ public class CurrentAccountsApiResource {
@Operation(summary = "Submit new current account application") @Operation(summary = "Submit new current account application")
@ApiResponse(responseCode = "200", description = "OK") @ApiResponse(responseCode = "200", description = "OK")
public String submitApplication(@Parameter(hidden = true) final String apiRequestBodyAsJson) { public String submitApplication(@Parameter(hidden = true) final String apiRequestBodyAsJson) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().createCurrentAccount().withJson(apiRequestBodyAsJson).build(); final org.apache.fineract.infrastructure.core.api.JsonCommand command =
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest); org.apache.fineract.infrastructure.core.api.JsonCommand.fromJsonElement(null,
JsonParser.parseString(apiRequestBodyAsJson != null ? apiRequestBodyAsJson : "{}"));
final CommandProcessingResult result = currentAccountWritePlatformService.submitApplication(command);
return toApiJsonSerializer.serialize(result); return toApiJsonSerializer.serialize(result);
} }
@@ -145,13 +133,8 @@ public class CurrentAccountsApiResource {
@Context final UriInfo uriInfo) { @Context final UriInfo uriInfo) {
context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.CURRENT_ACCOUNT_RESOURCE_NAME); context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.CURRENT_ACCOUNT_RESOURCE_NAME);
final Map<String, Object> account = currentAccountReadPlatformService.retrieveOne(accountId);
if (!(is(chargeStatus, "all") || is(chargeStatus, "active") || is(chargeStatus, "inactive"))) { return GSON.toJson(account);
throw new UnrecognizedQueryParamException("status", chargeStatus, new Object[] { "all", "active", "inactive" });
}
final SavingsAccountData savingsAccount = savingsAccountReadPlatformService.retrieveOne(accountId);
return populateTemplateAndAssociations(accountId, savingsAccount, staffInSelectedOfficeOnly, chargeStatus, uriInfo);
} }
@PUT @PUT
@@ -164,16 +147,10 @@ public class CurrentAccountsApiResource {
@Parameter(hidden = true) final String apiRequestBodyAsJson, @Parameter(hidden = true) final String apiRequestBodyAsJson,
@QueryParam("command") final String commandParam) { @QueryParam("command") final String commandParam) {
if (is(commandParam, "updateWithHoldTax")) { final org.apache.fineract.infrastructure.core.api.JsonCommand command =
final CommandWrapper commandRequest = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson) org.apache.fineract.infrastructure.core.api.JsonCommand.fromJsonElement(null,
.updateWithHoldTaxCurrentAccount(accountId).build(); JsonParser.parseString(apiRequestBodyAsJson != null ? apiRequestBodyAsJson : "{}"));
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest); final CommandProcessingResult result = currentAccountWritePlatformService.modifyApplication(accountId, command);
return toApiJsonSerializer.serialize(result);
}
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateCurrentAccount(accountId).withJson(apiRequestBodyAsJson)
.build();
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
return toApiJsonSerializer.serialize(result); return toApiJsonSerializer.serialize(result);
} }
@@ -192,53 +169,28 @@ public class CurrentAccountsApiResource {
jsonApiRequest = "{}"; jsonApiRequest = "{}";
} }
final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(jsonApiRequest); final org.apache.fineract.infrastructure.core.api.JsonCommand command =
org.apache.fineract.infrastructure.core.api.JsonCommand.fromJsonElement(null,
JsonParser.parseString(jsonApiRequest));
CommandProcessingResult result = null; CommandProcessingResult result = null;
if (is(commandParam, "reject")) { if (is(commandParam, "reject")) {
result = commandsSourceWritePlatformService.logCommandSource(builder.rejectCurrentAccountApplication(accountId).build()); result = currentAccountWritePlatformService.rejectApplication(accountId, command);
} else if (is(commandParam, "withdrawnByApplicant")) { } else if (is(commandParam, "withdrawnByApplicant")) {
result = commandsSourceWritePlatformService.logCommandSource(builder.withdrawCurrentAccountApplication(accountId).build()); result = currentAccountWritePlatformService.withdrawApplication(accountId, command);
} else if (is(commandParam, "approve")) { } else if (is(commandParam, "approve")) {
result = commandsSourceWritePlatformService.logCommandSource(builder.approveCurrentAccountApplication(accountId).build()); result = currentAccountWritePlatformService.approveApplication(accountId, command);
} else if (is(commandParam, "undoapproval")) { } else if (is(commandParam, "undoapproval")) {
result = commandsSourceWritePlatformService.logCommandSource(builder.undoCurrentAccountApplication(accountId).build()); result = currentAccountWritePlatformService.undoApproval(accountId, command);
} else if (is(commandParam, "activate")) { } else if (is(commandParam, "activate")) {
result = commandsSourceWritePlatformService.logCommandSource(builder.currentAccountActivation(accountId).build()); result = currentAccountWritePlatformService.activate(accountId, command);
} else if (is(commandParam, "calculateInterest")) {
result = commandsSourceWritePlatformService
.logCommandSource(builder.withNoJsonBody().currentAccountInterestCalculation(accountId).build());
} else if (is(commandParam, "postInterest")) {
result = commandsSourceWritePlatformService.logCommandSource(builder.currentAccountInterestPosting(accountId).build());
} else if (is(commandParam, "applyAnnualFees")) {
result = commandsSourceWritePlatformService.logCommandSource(builder.currentAccountApplyAnnualFees(accountId).build());
} else if (is(commandParam, "close")) { } else if (is(commandParam, "close")) {
result = commandsSourceWritePlatformService.logCommandSource(builder.closeCurrentAccountApplication(accountId).build()); result = currentAccountWritePlatformService.closeAccount(accountId, command);
} else if (is(commandParam, "assignSavingsOfficer")) {
result = commandsSourceWritePlatformService.logCommandSource(builder.assignCurrentAccountOfficer(accountId).build());
} else if (is(commandParam, "unassignSavingsOfficer")) {
result = commandsSourceWritePlatformService.logCommandSource(builder.unassignCurrentAccountOfficer(accountId).build());
} else if (is(commandParam, SavingsApiConstants.COMMAND_BLOCK_DEBIT)) {
result = commandsSourceWritePlatformService.logCommandSource(builder.blockDebitsFromCurrentAccount(accountId).build());
} else if (is(commandParam, SavingsApiConstants.COMMAND_UNBLOCK_DEBIT)) {
result = commandsSourceWritePlatformService.logCommandSource(builder.unblockDebitsFromCurrentAccount(accountId).build());
} else if (is(commandParam, SavingsApiConstants.COMMAND_BLOCK_CREDIT)) {
result = commandsSourceWritePlatformService.logCommandSource(builder.blockCreditsToCurrentAccount(accountId).build());
} else if (is(commandParam, SavingsApiConstants.COMMAND_UNBLOCK_CREDIT)) {
result = commandsSourceWritePlatformService.logCommandSource(builder.unblockCreditsToCurrentAccount(accountId).build());
} else if (is(commandParam, SavingsApiConstants.COMMAND_BLOCK_ACCOUNT)) {
result = commandsSourceWritePlatformService.logCommandSource(builder.blockCurrentAccount(accountId).build());
} else if (is(commandParam, SavingsApiConstants.COMMAND_UNBLOCK_ACCOUNT)) {
result = commandsSourceWritePlatformService.logCommandSource(builder.unblockCurrentAccount(accountId).build());
} }
if (result == null) { if (result == null) {
throw new UnrecognizedQueryParamException("command", commandParam, throw new UnrecognizedQueryParamException("command", commandParam,
new Object[] { "reject", "withdrawnByApplicant", "approve", "undoapproval", "activate", "calculateInterest", new Object[] { "reject", "withdrawnByApplicant", "approve", "undoapproval", "activate", "close" });
"postInterest", "close", "assignSavingsOfficer", "unassignSavingsOfficer",
SavingsApiConstants.COMMAND_BLOCK_DEBIT, SavingsApiConstants.COMMAND_UNBLOCK_DEBIT,
SavingsApiConstants.COMMAND_BLOCK_CREDIT, SavingsApiConstants.COMMAND_UNBLOCK_CREDIT,
SavingsApiConstants.COMMAND_BLOCK_ACCOUNT, SavingsApiConstants.COMMAND_UNBLOCK_ACCOUNT });
} }
return toApiJsonSerializer.serialize(result); return toApiJsonSerializer.serialize(result);
@@ -251,8 +203,7 @@ public class CurrentAccountsApiResource {
@Operation(summary = "Delete a current account application") @Operation(summary = "Delete a current account application")
@ApiResponse(responseCode = "200", description = "OK") @ApiResponse(responseCode = "200", description = "OK")
public String delete(@PathParam("accountId") final Long accountId) { public String delete(@PathParam("accountId") final Long accountId) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteCurrentAccount(accountId).build(); final CommandProcessingResult result = currentAccountWritePlatformService.deleteApplication(accountId);
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
return toApiJsonSerializer.serialize(result); return toApiJsonSerializer.serialize(result);
} }
@@ -260,42 +211,4 @@ public class CurrentAccountsApiResource {
return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue); return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
} }
private String populateTemplateAndAssociations(final Long accountId, final SavingsAccountData savingsAccount,
final boolean staffInSelectedOfficeOnly, final String chargeStatus, final UriInfo uriInfo) {
Collection<SavingsAccountTransactionData> transactions = null;
Collection<SavingsAccountChargeData> charges = null;
final Set<String> associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters());
if (!associationParameters.isEmpty()) {
if (associationParameters.contains("all")) {
associationParameters.addAll(Arrays.asList(SavingsApiConstants.transactions, SavingsApiConstants.charges));
}
if (associationParameters.contains(SavingsApiConstants.transactions)) {
final Collection<SavingsAccountTransactionData> currentTransactions = savingsAccountReadPlatformService
.retrieveAllTransactions(accountId, DepositAccountType.CURRENT_DEPOSIT);
if (!CollectionUtils.isEmpty(currentTransactions)) {
transactions = currentTransactions;
}
}
if (associationParameters.contains(SavingsApiConstants.charges)) {
final Collection<SavingsAccountChargeData> currentCharges = savingsAccountChargeReadPlatformService
.retrieveSavingsAccountCharges(accountId, chargeStatus);
if (!CollectionUtils.isEmpty(currentCharges)) {
charges = currentCharges;
}
}
}
SavingsAccountData templateData = null;
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
if (settings.isTemplate()) {
templateData = savingsAccountTemplateReadPlatformService.retrieveTemplate(savingsAccount.getClientId(),
savingsAccount.getGroupId(), savingsAccount.getSavingsProductId(), staffInSelectedOfficeOnly);
}
return toApiJsonSerializer.serialize(settings,
SavingsAccountData.withTemplateOptions(savingsAccount, templateData, transactions, charges),
SavingsApiSetConstants.SAVINGS_ACCOUNT_RESPONSE_DATA_PARAMETERS);
}
} }
@@ -36,6 +36,7 @@ import jakarta.ws.rs.core.UriInfo;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.fineract.accounting.common.AccountingDropdownReadPlatformService; import org.apache.fineract.accounting.common.AccountingDropdownReadPlatformService;
@@ -49,6 +50,8 @@ import org.apache.fineract.commands.domain.CommandWrapper;
import org.apache.fineract.commands.service.CommandWrapperBuilder; import org.apache.fineract.commands.service.CommandWrapperBuilder;
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService; import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper; import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.data.EnumOptionData;
@@ -62,16 +65,18 @@ import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService;
import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData; import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadService; import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadService;
import org.apache.fineract.portfolio.savings.DepositsApiConstants; import org.apache.fineract.portfolio.savings.DepositsApiConstants;
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType; import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType; import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType; import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType; import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
import org.apache.fineract.portfolio.savings.data.SavingsProductData; import org.apache.fineract.portfolio.savings.data.SavingsProductData;
import org.apache.fineract.portfolio.currentproduct.service.CurrentProductReadPlatformService;
import org.apache.fineract.portfolio.savings.service.SavingsDropdownReadPlatformService; import org.apache.fineract.portfolio.savings.service.SavingsDropdownReadPlatformService;
import org.apache.fineract.portfolio.savings.service.SavingsEnumerations; import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
import org.apache.fineract.portfolio.savings.service.SavingsProductReadPlatformService;
import org.apache.fineract.portfolio.tax.data.TaxGroupData; import org.apache.fineract.portfolio.tax.data.TaxGroupData;
import org.apache.fineract.portfolio.tax.service.TaxReadPlatformService; import org.apache.fineract.portfolio.tax.service.TaxReadPlatformService;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
@@ -81,7 +86,7 @@ import org.springframework.util.CollectionUtils;
@RequiredArgsConstructor @RequiredArgsConstructor
public class CurrentProductsApiResource { public class CurrentProductsApiResource {
private final SavingsProductReadPlatformService savingProductReadPlatformService; private final CurrentProductReadPlatformService savingProductReadPlatformService;
private final SavingsDropdownReadPlatformService dropdownReadPlatformService; private final SavingsDropdownReadPlatformService dropdownReadPlatformService;
private final CurrencyReadPlatformService currencyReadPlatformService; private final CurrencyReadPlatformService currencyReadPlatformService;
private final PlatformSecurityContext context; private final PlatformSecurityContext context;
@@ -94,6 +99,24 @@ public class CurrentProductsApiResource {
private final PaymentTypeReadService paymentTypeReadPlatformService; private final PaymentTypeReadService paymentTypeReadPlatformService;
private final TaxReadPlatformService taxReadPlatformService; private final TaxReadPlatformService taxReadPlatformService;
private final ConfigurationDomainService configurationDomainService; private final ConfigurationDomainService configurationDomainService;
private final CodeValueReadPlatformService codeValueReadPlatformService;
private final JdbcTemplate jdbcTemplate;
private String generateNextProductCode(final String tableName, final String prefix) {
try {
String maxCode = jdbcTemplate.queryForObject(
"SELECT MAX(product_code) FROM " + tableName + " WHERE product_code LIKE ?",
String.class, prefix + "%");
if (maxCode == null || maxCode.isBlank()) {
return prefix + "001";
}
String numStr = maxCode.substring(prefix.length());
int num = Integer.parseInt(numStr);
return prefix + String.format("%03d", num + 1);
} catch (Exception e) {
return prefix + "001";
}
}
@POST @POST
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@@ -142,7 +165,7 @@ public class CurrentProductsApiResource {
context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.CURRENT_PRODUCT_RESOURCE_NAME); context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.CURRENT_PRODUCT_RESOURCE_NAME);
SavingsProductData savingProductData = savingProductReadPlatformService.retrieveOne(productId); SavingsProductData savingProductData = savingProductReadPlatformService.retrieveOne(productId);
final Collection<ChargeData> charges = chargeReadPlatformService.retrieveSavingsProductCharges(productId); final Collection<ChargeData> charges = chargeReadPlatformService.retrieveCurrentProductCharges(productId);
savingProductData = SavingsProductData.withCharges(savingProductData, charges); savingProductData = SavingsProductData.withCharges(savingProductData, charges);
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
@@ -181,6 +204,21 @@ public class CurrentProductsApiResource {
return toApiJsonSerializer.serialize(settings, savingProduct, SavingsApiSetConstants.SAVINGS_PRODUCT_RESPONSE_DATA_PARAMETERS); return toApiJsonSerializer.serialize(settings, savingProduct, SavingsApiSetConstants.SAVINGS_PRODUCT_RESPONSE_DATA_PARAMETERS);
} }
private Collection<EnumOptionData> retrievePeriodFrequencyOptions() {
return this.codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.PERIOD_FREQUENCY).stream()
.map(codeValue -> new EnumOptionData(codeValue.getId(),
"periodFrequencyType." + codeValue.getName().toLowerCase(Locale.ROOT).replace(' ', '_'), codeValue.getName()))
.toList();
}
private Collection<EnumOptionData> retrieveInterestCalculationTypeOption() {
return this.codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.INTEREST_CALCULATION_TYPE_OPTION).stream()
.map(codeValue -> new EnumOptionData(codeValue.getId(),
"interestCalculationTypeOption." + codeValue.getName().toLowerCase(Locale.ROOT).replace(' ', '_'),
codeValue.getName()))
.toList();
}
@DELETE @DELETE
@Path("{productId}") @Path("{productId}")
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@@ -221,11 +259,13 @@ public class CurrentProductsApiResource {
.retrieveInterestPostingPeriodTypeOptions(); .retrieveInterestPostingPeriodTypeOptions();
final Collection<EnumOptionData> interestCalculationTypeOptions = dropdownReadPlatformService final Collection<EnumOptionData> interestCalculationTypeOptions = dropdownReadPlatformService
.retrieveInterestCalculationTypeOptions(); .retrieveInterestCalculationTypeOptions();
final Collection<EnumOptionData> interestCalculationTypeOption = retrieveInterestCalculationTypeOption();
final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = dropdownReadPlatformService final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = dropdownReadPlatformService
.retrieveInterestCalculationDaysInYearTypeOptions(); .retrieveInterestCalculationDaysInYearTypeOptions();
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = dropdownReadPlatformService final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = dropdownReadPlatformService
.retrieveLockinPeriodFrequencyTypeOptions(); .retrieveLockinPeriodFrequencyTypeOptions();
final Collection<EnumOptionData> withdrawalFeeTypeOptions = dropdownReadPlatformService.retrievewithdrawalFeeTypeOptions(); final Collection<EnumOptionData> withdrawalFeeTypeOptions = dropdownReadPlatformService.retrievewithdrawalFeeTypeOptions();
final Collection<EnumOptionData> periodFrequencyOptions = retrievePeriodFrequencyOptions();
final Collection<PaymentTypeData> paymentTypeOptions = paymentTypeReadPlatformService.retrieveAllPaymentTypes(); final Collection<PaymentTypeData> paymentTypeOptions = paymentTypeReadPlatformService.retrieveAllPaymentTypes();
@@ -244,19 +284,29 @@ public class CurrentProductsApiResource {
penaltyOptions = CollectionUtils.isEmpty(penaltyOptions) ? null : penaltyOptions; penaltyOptions = CollectionUtils.isEmpty(penaltyOptions) ? null : penaltyOptions;
final Collection<TaxGroupData> taxGroupOptions = taxReadPlatformService.retrieveTaxGroupsForLookUp(); final Collection<TaxGroupData> taxGroupOptions = taxReadPlatformService.retrieveTaxGroupsForLookUp();
final List<CodeValueData> yesNoOptions = new ArrayList<>(
codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.YES_NO));
final List<CodeValueData> accountTypeOptions = new ArrayList<>(
codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.CURRENT_PRODUCT_ACCOUNT_TYPE));
final List<CodeValueData> customerSegmentOptions = new ArrayList<>(
codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.CURRENT_PRODUCT_CUSTOMER_SEGMENT));
SavingsProductData savingsProductToReturn = null; SavingsProductData savingsProductToReturn = null;
if (savingsProduct != null) { if (savingsProduct != null) {
savingsProductToReturn = SavingsProductData.withTemplate(savingsProduct, currencyOptions, interestCompoundingPeriodTypeOptions, savingsProductToReturn = SavingsProductData.withTemplate(savingsProduct, currencyOptions, interestCompoundingPeriodTypeOptions,
interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationTypeOption,
lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, interestCalculationDaysInYearTypeOptions,
accountingMappingOptions, chargeOptions, penaltyOptions, taxGroupOptions, accountMappingForPayment); lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, periodFrequencyOptions, paymentTypeOptions,
accountingRuleOptions, accountingMappingOptions, chargeOptions, penaltyOptions, taxGroupOptions, yesNoOptions,
accountTypeOptions, customerSegmentOptions, accountMappingForPayment);
} else { } else {
savingsProductToReturn = SavingsProductData.template(currency, interestCompoundingPeriodType, interestPostingPeriodType, savingsProductToReturn = SavingsProductData.template(currency, interestCompoundingPeriodType, interestPostingPeriodType,
interestCalculationType, interestCalculationDaysInYearType, accountingRule, currencyOptions, interestCalculationType, interestCalculationDaysInYearType, accountingRule, currencyOptions,
interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, interestCalculationTypeOption, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions,
paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions, penaltyOptions, taxGroupOptions, periodFrequencyOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions,
accountMappingForPayment); penaltyOptions, taxGroupOptions, yesNoOptions, accountTypeOptions, customerSegmentOptions, accountMappingForPayment);
savingsProductToReturn.setProductCode(generateNextProductCode("m_current_product", "CA-IND-"));
} }
return savingsProductToReturn; return savingsProductToReturn;
@@ -0,0 +1,53 @@
/**
* 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
*/
package org.apache.fineract.portfolio.savings.api;
import java.math.BigDecimal;
import java.util.List;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* Request payload for saving a new RD interest rate version.
* JSON structure:
* {
* "versionLabel": "Rate Version 2025-Q2",
* "remarks": "Revised rates",
* "slabs": [
* { "srNo": 1, "description": "15 Days to 29 Days", "periodTypeEnum": 1,
* "fromPeriod": 15, "toPeriod": 29, "annualInterestRate": 3.00 },
* ...
* ]
* }
*/
@Getter
@Setter
@NoArgsConstructor
public class RdInterestRateSaveRequest {
private String versionLabel;
private String remarks;
private List<SlabRequest> slabs;
@Getter
@Setter
@NoArgsConstructor
public static class SlabRequest {
private Integer srNo;
private String description;
private Integer periodTypeEnum;
private Integer fromPeriod;
private Integer toPeriod;
private BigDecimal annualInterestRate;
}
}
@@ -41,6 +41,7 @@ import jakarta.ws.rs.core.UriInfo;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.fineract.accounting.common.AccountingDropdownReadPlatformService; import org.apache.fineract.accounting.common.AccountingDropdownReadPlatformService;
@@ -68,12 +69,21 @@ import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartDat
import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartReadPlatformService; import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartReadPlatformService;
import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData; import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadService; import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadService;
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
import org.springframework.jdbc.core.JdbcTemplate;
import org.apache.fineract.portfolio.savings.DepositAccountType; import org.apache.fineract.portfolio.savings.DepositAccountType;
import org.apache.fineract.portfolio.savings.DepositsApiConstants; import org.apache.fineract.portfolio.savings.DepositsApiConstants;
import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType; import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType; import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType; import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType; import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializer;
import java.time.LocalDate;
import org.apache.fineract.portfolio.savings.data.RecurringDepositProductData; import org.apache.fineract.portfolio.savings.data.RecurringDepositProductData;
import org.apache.fineract.portfolio.savings.service.DepositProductReadPlatformService; import org.apache.fineract.portfolio.savings.service.DepositProductReadPlatformService;
import org.apache.fineract.portfolio.savings.service.DepositsDropdownReadPlatformService; import org.apache.fineract.portfolio.savings.service.DepositsDropdownReadPlatformService;
@@ -110,6 +120,12 @@ public class RecurringDepositProductsApiResource {
private final DropdownReadPlatformService dropdownReadPlatformService; private final DropdownReadPlatformService dropdownReadPlatformService;
private final PaymentTypeReadService paymentTypeReadPlatformService; private final PaymentTypeReadService paymentTypeReadPlatformService;
private final TaxReadPlatformService taxReadPlatformService; private final TaxReadPlatformService taxReadPlatformService;
private final CodeValueReadPlatformService codeValueReadPlatformService;
private final JdbcTemplate jdbcTemplate;
private final Gson gson = new GsonBuilder()
.registerTypeAdapter(LocalDate.class,
(JsonSerializer<LocalDate>) (src, typeOfSrc, ctx) -> new JsonPrimitive(src.toString()))
.create();
@POST @POST
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@@ -294,6 +310,15 @@ public class RecurringDepositProductsApiResource {
final InterestRateChartData chartTemplate = this.interestRateChartReadPlatformService.template(); final InterestRateChartData chartTemplate = this.interestRateChartReadPlatformService.template();
final Collection<TaxGroupData> taxGroupOptions = this.taxReadPlatformService.retrieveTaxGroupsForLookUp(); final Collection<TaxGroupData> taxGroupOptions = this.taxReadPlatformService.retrieveTaxGroupsForLookUp();
final Collection<EnumOptionData> interestCalculationTypeOption = retrieveInterestCalculationTypeOption();
final Collection<EnumOptionData> periodFrequencyOptions = retrievePeriodFrequencyOptions();
final List<CodeValueData> yesNoOptions = new ArrayList<>(
this.codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.YES_NO));
final List<CodeValueData> accountTypeOptions = new ArrayList<>(
this.codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.RD_PRODUCT_ACCOUNT_TYPE));
final List<CodeValueData> customerSegmentOptions = new ArrayList<>(
this.codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.RD_PRODUCT_CUSTOMER_SEGMENT));
RecurringDepositProductData recurringDepositProductToReturn = null; RecurringDepositProductData recurringDepositProductToReturn = null;
if (savingsProduct != null) { if (savingsProduct != null) {
recurringDepositProductToReturn = RecurringDepositProductData.withTemplate(savingsProduct, currencyOptions, recurringDepositProductToReturn = RecurringDepositProductData.withTemplate(savingsProduct, currencyOptions,
@@ -308,11 +333,51 @@ public class RecurringDepositProductsApiResource {
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions,
paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions, penaltyOptions, chartTemplate, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions, penaltyOptions, chartTemplate,
preClosurePenalInterestOnTypeOptions, periodFrequencyTypeOptions, taxGroupOptions); preClosurePenalInterestOnTypeOptions, periodFrequencyTypeOptions, taxGroupOptions);
recurringDepositProductToReturn.setProductCode(generateNextProductCode("m_savings_product", "RD-IND-"));
} }
recurringDepositProductToReturn.setInterestCalculationTypeOption(interestCalculationTypeOption);
recurringDepositProductToReturn.setPeriodFrequencyOptions(periodFrequencyOptions);
recurringDepositProductToReturn.setYesNoOptions(yesNoOptions);
recurringDepositProductToReturn.setAccountTypeOptions(accountTypeOptions);
recurringDepositProductToReturn.setCustomerSegmentOptions(customerSegmentOptions);
return recurringDepositProductToReturn; return recurringDepositProductToReturn;
} }
private Collection<EnumOptionData> retrievePeriodFrequencyOptions() {
return this.codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.PERIOD_FREQUENCY).stream()
.map(codeValue -> new EnumOptionData(codeValue.getId(),
"periodFrequencyType." + codeValue.getName().toLowerCase(Locale.ROOT).replace(' ', '_'), codeValue.getName()))
.toList();
}
private Collection<EnumOptionData> retrieveInterestCalculationTypeOption() {
return this.codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.INTEREST_CALCULATION_TYPE_OPTION).stream()
.map(codeValue -> new EnumOptionData(codeValue.getId(),
"interestCalculationTypeOption." + codeValue.getName().toLowerCase(Locale.ROOT).replace(' ', '_'),
codeValue.getName()))
.toList();
}
private String generateNextProductCode(final String tableName, final String prefix) {
try {
String maxCode = jdbcTemplate.queryForObject(
"SELECT MAX(product_code) FROM " + tableName + " WHERE product_code LIKE ? AND deposit_type_enum = 300",
String.class, prefix + "%");
if (maxCode == null || maxCode.isBlank()) {
return prefix + "001";
}
String numStr = maxCode.substring(prefix.length());
int num = Integer.parseInt(numStr);
return prefix + String.format("%03d", num + 1);
} catch (Exception e) {
return prefix + "001";
}
}
// ── RD Interest Rate Chart endpoints ────────────────────────────────────
@DELETE @DELETE
@Path("{productId}") @Path("{productId}")
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@@ -0,0 +1,84 @@
/**
* 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
*/
package org.apache.fineract.portfolio.savings.api;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializer;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import java.time.LocalDate;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.savings.DepositsApiConstants;
import org.apache.fineract.portfolio.savings.data.RdInterestRateEffectiveData;
import org.apache.fineract.portfolio.savings.service.RdBaseInterestRateReadPlatformService;
import org.springframework.stereotype.Component;
@Path("/v1/recurringdepositproducts/baseinterestrates")
@Component
@RequiredArgsConstructor
public class RecurringDepositProductsBaseInterestRatesApiResource {
private final PlatformSecurityContext context;
private final RdBaseInterestRateReadPlatformService rdBaseInterestRateReadPlatformService;
private final Gson gson = new GsonBuilder()
.registerTypeAdapter(LocalDate.class,
(JsonSerializer<LocalDate>) (src, typeOfSrc, ctx) -> new JsonPrimitive(src.toString()))
.create();
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Get all active RD base interest rate charts", description = "Returns the current CBS base interest-rate version with slabs.")
@ApiResponses({ @ApiResponse(responseCode = "200", description = "OK") })
public String retrieveAllActiveInterestRates() {
this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.RECURRING_DEPOSIT_PRODUCT_RESOURCE_NAME);
final List<RdInterestRateEffectiveData> data = this.rdBaseInterestRateReadPlatformService.retrieveAllActiveInterestRates();
return gson.toJson(data);
}
@GET
@Path("history")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Get all RD base interest rate charts", description = "Returns the full CBS base chart history.")
@ApiResponses({ @ApiResponse(responseCode = "200", description = "OK") })
public String retrieveHistory() {
this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.RECURRING_DEPOSIT_PRODUCT_RESOURCE_NAME);
final List<RdInterestRateEffectiveData> data = this.rdBaseInterestRateReadPlatformService.retrieveAllInterestRates();
return gson.toJson(data);
}
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Save new RD base interest rate version", description = "Creates a new CBS base chart version and deactivates the previous active version.")
@ApiResponses({ @ApiResponse(responseCode = "200", description = "OK") })
public String saveInterestRates(@Parameter(hidden = true) final String apiRequestBodyAsJson) {
this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.RECURRING_DEPOSIT_PRODUCT_RESOURCE_NAME);
final RdInterestRateSaveRequest request = gson.fromJson(apiRequestBodyAsJson, RdInterestRateSaveRequest.class);
final Long createdById = this.context.authenticatedUser().getId();
final RdInterestRateEffectiveData saved = this.rdBaseInterestRateReadPlatformService.saveInterestRates(request, createdById);
return gson.toJson(saved);
}
}
@@ -40,6 +40,7 @@ import jakarta.ws.rs.core.UriInfo;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.fineract.accounting.common.AccountingDropdownReadPlatformService; import org.apache.fineract.accounting.common.AccountingDropdownReadPlatformService;
@@ -56,6 +57,8 @@ import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDoma
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper; import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
@@ -76,6 +79,7 @@ import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
import org.apache.fineract.portfolio.savings.service.SavingsProductReadPlatformService; import org.apache.fineract.portfolio.savings.service.SavingsProductReadPlatformService;
import org.apache.fineract.portfolio.tax.data.TaxGroupData; import org.apache.fineract.portfolio.tax.data.TaxGroupData;
import org.apache.fineract.portfolio.tax.service.TaxReadPlatformService; import org.apache.fineract.portfolio.tax.service.TaxReadPlatformService;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
@@ -91,6 +95,7 @@ public class SavingsProductsApiResource {
private final CurrencyReadPlatformService currencyReadPlatformService; private final CurrencyReadPlatformService currencyReadPlatformService;
private final PlatformSecurityContext context; private final PlatformSecurityContext context;
private final DefaultToApiJsonSerializer<SavingsProductData> toApiJsonSerializer; private final DefaultToApiJsonSerializer<SavingsProductData> toApiJsonSerializer;
private final CodeValueReadPlatformService codeValueReadPlatformService;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final ApiRequestParameterHelper apiRequestParameterHelper; private final ApiRequestParameterHelper apiRequestParameterHelper;
private final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService; private final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService;
@@ -99,6 +104,23 @@ public class SavingsProductsApiResource {
private final PaymentTypeReadService paymentTypeReadPlatformService; private final PaymentTypeReadService paymentTypeReadPlatformService;
private final TaxReadPlatformService taxReadPlatformService; private final TaxReadPlatformService taxReadPlatformService;
private final ConfigurationDomainService configurationDomainService; private final ConfigurationDomainService configurationDomainService;
private final JdbcTemplate jdbcTemplate;
private String generateNextProductCode(final String tableName, final String prefix) {
try {
String maxCode = jdbcTemplate.queryForObject(
"SELECT MAX(product_code) FROM " + tableName + " WHERE product_code LIKE ?",
String.class, prefix + "%");
if (maxCode == null || maxCode.isBlank()) {
return prefix + "001";
}
String numStr = maxCode.substring(prefix.length());
int num = Integer.parseInt(numStr);
return prefix + String.format("%03d", num + 1);
} catch (Exception e) {
return prefix + "001";
}
}
@POST @POST
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@@ -242,6 +264,7 @@ public class SavingsProductsApiResource {
final Collection<EnumOptionData> interestCalculationTypeOptions = this.dropdownReadPlatformService final Collection<EnumOptionData> interestCalculationTypeOptions = this.dropdownReadPlatformService
.retrieveInterestCalculationTypeOptions(); .retrieveInterestCalculationTypeOptions();
final Collection<EnumOptionData> interestCalculationTypeOption = retrieveInterestCalculationTypeOption();
final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = this.dropdownReadPlatformService final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = this.dropdownReadPlatformService
.retrieveInterestCalculationDaysInYearTypeOptions(); .retrieveInterestCalculationDaysInYearTypeOptions();
@@ -250,6 +273,7 @@ public class SavingsProductsApiResource {
.retrieveLockinPeriodFrequencyTypeOptions(); .retrieveLockinPeriodFrequencyTypeOptions();
final Collection<EnumOptionData> withdrawalFeeTypeOptions = this.dropdownReadPlatformService.retrievewithdrawalFeeTypeOptions(); final Collection<EnumOptionData> withdrawalFeeTypeOptions = this.dropdownReadPlatformService.retrievewithdrawalFeeTypeOptions();
final Collection<EnumOptionData> periodFrequencyOptions = retrievePeriodFrequencyOptions();
final Collection<PaymentTypeData> paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes(); final Collection<PaymentTypeData> paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
@@ -268,24 +292,50 @@ public class SavingsProductsApiResource {
Collection<ChargeData> penaltyOptions = this.chargeReadPlatformService.retrieveSavingsApplicablePenalties(); Collection<ChargeData> penaltyOptions = this.chargeReadPlatformService.retrieveSavingsApplicablePenalties();
penaltyOptions = CollectionUtils.isEmpty(penaltyOptions) ? null : penaltyOptions; penaltyOptions = CollectionUtils.isEmpty(penaltyOptions) ? null : penaltyOptions;
final Collection<TaxGroupData> taxGroupOptions = this.taxReadPlatformService.retrieveTaxGroupsForLookUp(); final Collection<TaxGroupData> taxGroupOptions = this.taxReadPlatformService.retrieveTaxGroupsForLookUp();
final List<CodeValueData> yesNoOptions = new ArrayList<>(
this.codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.YES_NO));
final List<CodeValueData> accountTypeOptions = new ArrayList<>(
this.codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.SAVINGS_PRODUCT_ACCOUNT_TYPE));
final List<CodeValueData> customerSegmentOptions = new ArrayList<>(
this.codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.SAVINGS_PRODUCT_CUSTOMER_SEGMENT));
SavingsProductData savingsProductToReturn = null; SavingsProductData savingsProductToReturn = null;
if (savingsProduct != null) { if (savingsProduct != null) {
savingsProductToReturn = SavingsProductData.withTemplate(savingsProduct, currencyOptions, interestCompoundingPeriodTypeOptions, savingsProductToReturn = SavingsProductData.withTemplate(savingsProduct, currencyOptions, interestCompoundingPeriodTypeOptions,
interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationTypeOption,
lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, interestCalculationDaysInYearTypeOptions,
accountingMappingOptions, chargeOptions, penaltyOptions, taxGroupOptions, accountMappingForPayment); lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, periodFrequencyOptions, paymentTypeOptions,
accountingRuleOptions, accountingMappingOptions, chargeOptions, penaltyOptions, taxGroupOptions, yesNoOptions,
accountTypeOptions, customerSegmentOptions,
accountMappingForPayment);
} else { } else {
savingsProductToReturn = SavingsProductData.template(currency, interestCompoundingPeriodType, interestPostingPeriodType, savingsProductToReturn = SavingsProductData.template(currency, interestCompoundingPeriodType, interestPostingPeriodType,
interestCalculationType, interestCalculationDaysInYearType, accountingRule, currencyOptions, interestCalculationType, interestCalculationDaysInYearType, accountingRule, currencyOptions,
interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, interestCalculationTypeOption, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions,
paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions, penaltyOptions, taxGroupOptions, periodFrequencyOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions,
penaltyOptions, taxGroupOptions, yesNoOptions, accountTypeOptions, customerSegmentOptions,
accountMappingForPayment); accountMappingForPayment);
savingsProductToReturn.setProductCode(generateNextProductCode("m_savings_product", "SB-IND-"));
} }
return savingsProductToReturn; return savingsProductToReturn;
} }
private Collection<EnumOptionData> retrievePeriodFrequencyOptions() {
return this.codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.PERIOD_FREQUENCY).stream()
.map(codeValue -> new EnumOptionData(codeValue.getId(),
"periodFrequencyType." + codeValue.getName().toLowerCase(Locale.ROOT).replace(' ', '_'), codeValue.getName()))
.toList();
}
private Collection<EnumOptionData> retrieveInterestCalculationTypeOption() {
return this.codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.INTEREST_CALCULATION_TYPE_OPTION).stream()
.map(codeValue -> new EnumOptionData(codeValue.getId(),
"interestCalculationTypeOption." + codeValue.getName().toLowerCase(Locale.ROOT).replace(' ', '_'),
codeValue.getName()))
.toList();
}
@DELETE @DELETE
@Path("{productId}") @Path("{productId}")
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.savings.api;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
/** /**
@@ -82,6 +83,7 @@ final class SavingsProductsApiResourceSwagger {
public Boolean withHoldTax; public Boolean withHoldTax;
@Schema(example = "false") @Schema(example = "false")
public Boolean isDormancyTrackingActive; public Boolean isDormancyTrackingActive;
public List<Map<String, Object>> otherGlMappings;
} }
@Schema(description = "PostSavingsProductsResponse") @Schema(description = "PostSavingsProductsResponse")
@@ -128,6 +130,25 @@ final class SavingsProductsApiResourceSwagger {
public PutSavingsChanges changes; public PutSavingsChanges changes;
} }
@Schema(description = "GetSavingsProductsProductClassificationOptions")
static final class GetSavingsProductsProductClassificationOptions {
private GetSavingsProductsProductClassificationOptions() {}
@Schema(example = "1")
public Long id;
@Schema(example = "Savings Account Minor")
public String name;
@Schema(example = "1")
public Integer position;
@Schema(example = "Savings Account Minor")
public String description;
@Schema(example = "true")
public Boolean active;
@Schema(example = "false")
public Boolean mandatory;
}
@Schema(description = "GetSavingsProductsResponse") @Schema(description = "GetSavingsProductsResponse")
public static final class GetSavingsProductsResponse { public static final class GetSavingsProductsResponse {
@@ -377,6 +398,7 @@ final class SavingsProductsApiResourceSwagger {
public Set<GetSavingsProductsPenaltyToIncomeAccountMappings> penaltyToIncomeAccountMappings; public Set<GetSavingsProductsPenaltyToIncomeAccountMappings> penaltyToIncomeAccountMappings;
@Schema(example = "[]") @Schema(example = "[]")
public List<Long> charges; public List<Long> charges;
public List<Map<String, Object>> otherGlMappings;
} }
@Schema(description = "GetSavingsProductsTemplateResponse") @Schema(description = "GetSavingsProductsTemplateResponse")
@@ -408,6 +430,30 @@ final class SavingsProductsApiResourceSwagger {
public String value; public String value;
} }
static final class GetSavingsProductsPeriodFrequencyOptions {
private GetSavingsProductsPeriodFrequencyOptions() {}
@Schema(example = "1")
public Integer id;
@Schema(example = "periodFrequencyType.monthly")
public String code;
@Schema(example = "Monthly")
public String value;
}
static final class GetSavingsProductsInterestCalculationTypeOption {
private GetSavingsProductsInterestCalculationTypeOption() {}
@Schema(example = "1")
public Integer id;
@Schema(example = "interestCalculationTypeOption.compounding")
public String code;
@Schema(example = "Compounding")
public String value;
}
static final class GetSavingsProductsWithdrawalFeeTypeOptions { static final class GetSavingsProductsWithdrawalFeeTypeOptions {
private GetSavingsProductsWithdrawalFeeTypeOptions() {} private GetSavingsProductsWithdrawalFeeTypeOptions() {}
@@ -677,13 +723,18 @@ final class SavingsProductsApiResourceSwagger {
public Set<GetSavingsProductsResponse.GetSavingsProductsInterestCompoundingPeriodType> interestCompoundingPeriodTypeOptions; public Set<GetSavingsProductsResponse.GetSavingsProductsInterestCompoundingPeriodType> interestCompoundingPeriodTypeOptions;
public Set<GetSavingsProductsResponse.GetSavingsProductsInterestPostingPeriodType> interestPostingPeriodTypeOptions; public Set<GetSavingsProductsResponse.GetSavingsProductsInterestPostingPeriodType> interestPostingPeriodTypeOptions;
public Set<GetSavingsProductsResponse.GetSavingsProductsInterestCalculationType> interestCalculationTypeOptions; public Set<GetSavingsProductsResponse.GetSavingsProductsInterestCalculationType> interestCalculationTypeOptions;
public Set<GetSavingsProductsInterestCalculationTypeOption> interestCalculationTypeOption;
public Set<GetSavingsProductsResponse.GetSavingsProductsInterestCalculationDaysInYearType> interestCalculationDaysInYearTypeOptions; public Set<GetSavingsProductsResponse.GetSavingsProductsInterestCalculationDaysInYearType> interestCalculationDaysInYearTypeOptions;
public Set<GetSavingsProductsLockinPeriodFrequencyTypeOptions> lockinPeriodFrequencyTypeOptions; public Set<GetSavingsProductsLockinPeriodFrequencyTypeOptions> lockinPeriodFrequencyTypeOptions;
public Set<GetSavingsProductsWithdrawalFeeTypeOptions> withdrawalFeeTypeOptions; public Set<GetSavingsProductsWithdrawalFeeTypeOptions> withdrawalFeeTypeOptions;
public Set<GetSavingsProductsPeriodFrequencyOptions> periodFrequencyOptions;
public Set<GetSavingsProductsPaymentTypeOptions> paymentTypeOptions; public Set<GetSavingsProductsPaymentTypeOptions> paymentTypeOptions;
public Set<GetSavingsProductsTemplateAccountingRule> accountingRuleOptions; public Set<GetSavingsProductsTemplateAccountingRule> accountingRuleOptions;
public GetSavingsProductsAccountingMappingOptions accountingMappingOptions; public GetSavingsProductsAccountingMappingOptions accountingMappingOptions;
public Set<GetSavingsProductsChargeOptions> chargeOptions; public Set<GetSavingsProductsChargeOptions> chargeOptions;
public Set<GetSavingsProductsProductClassificationOptions> yesNoOptions;
public Set<GetSavingsProductsProductClassificationOptions> accountTypeOptions;
public Set<GetSavingsProductsProductClassificationOptions> customerSegmentOptions;
public GetSavingsProductsResponse.GetSavingsCurrency accountMapping; public GetSavingsProductsResponse.GetSavingsCurrency accountMapping;
} }
@@ -170,13 +170,21 @@ public class DepositProductDataValidator {
validatePreClosureDetailForCreate(element, baseDataValidator); validatePreClosureDetailForCreate(element, baseDataValidator);
validateDepositTermDeatilForCreate(element, baseDataValidator); // When custom rdSlabs are provided, minDepositTerm / charts / depositAmount are not required
final boolean hasRdSlabs = this.fromApiJsonHelper.parameterExists("rdSlabs", element)
&& this.fromApiJsonHelper.extractJsonArrayNamed("rdSlabs", element) != null
&& this.fromApiJsonHelper.extractJsonArrayNamed("rdSlabs", element).size() > 0;
if (!hasRdSlabs) {
validateDepositTermDeatilForCreate(element, baseDataValidator);
}
validateRecurringDetailForCreate(element, baseDataValidator); validateRecurringDetailForCreate(element, baseDataValidator);
validateChartsData(element, baseDataValidator); if (!hasRdSlabs) {
validateChartsData(element, baseDataValidator);
validateDepositAmountForCreate(element, baseDataValidator); validateDepositAmountForCreate(element, baseDataValidator);
}
throwExceptionIfValidationWarningsExist(dataValidationErrors); throwExceptionIfValidationWarningsExist(dataValidationErrors);
} }
@@ -198,13 +206,21 @@ public class DepositProductDataValidator {
validatePreClosureDetailForUpdate(element, baseDataValidator); validatePreClosureDetailForUpdate(element, baseDataValidator);
validateDepositTermDetailForUpdate(element, baseDataValidator); final boolean hasRdSlabsForUpdate = this.fromApiJsonHelper.parameterExists("rdSlabs", element)
&& this.fromApiJsonHelper.extractJsonArrayNamed("rdSlabs", element) != null
&& this.fromApiJsonHelper.extractJsonArrayNamed("rdSlabs", element).size() > 0;
if (!hasRdSlabsForUpdate) {
validateDepositTermDetailForUpdate(element, baseDataValidator);
}
validateRecurringDepositUpdate(element, baseDataValidator); validateRecurringDepositUpdate(element, baseDataValidator);
validateChartsData(element, baseDataValidator); // RD products now use the CBS base chart as a read-only template, so
// product updates no longer require a charts payload.
validateDepositAmountForUpdate(element, baseDataValidator); if (!hasRdSlabsForUpdate) {
validateDepositAmountForUpdate(element, baseDataValidator);
}
throwExceptionIfValidationWarningsExist(dataValidationErrors); throwExceptionIfValidationWarningsExist(dataValidationErrors);
} }
@@ -0,0 +1,31 @@
/**
* 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
*/
package org.apache.fineract.portfolio.savings.data;
import java.time.LocalDate;
import java.util.List;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class RdInterestRateEffectiveData {
private Long id;
private Long productId;
private String versionLabel;
private LocalDate effectiveFromDate;
private LocalDate effectiveToDate;
private Boolean isActive;
private String remarks;
private List<RdInterestRateSlabData> slabs;
}
@@ -0,0 +1,30 @@
/**
* 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
*/
package org.apache.fineract.portfolio.savings.data;
import java.math.BigDecimal;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class RdInterestRateSlabData {
private Long id;
private Integer srNo;
private String description;
private Integer periodTypeEnum;
private String periodTypeLabel;
private Integer fromPeriod;
private Integer toPeriod;
private BigDecimal annualInterestRate;
}
@@ -0,0 +1,70 @@
/**
* 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
*/
package org.apache.fineract.portfolio.savings.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.time.LocalDate;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Entity
@Table(name = "m_rd_base_interest_rate_effective")
@Getter
@NoArgsConstructor
public class RdBaseInterestRateEffective {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "version_label")
private String versionLabel;
@Column(name = "effective_from_date")
private LocalDate effectiveFromDate;
@Column(name = "effective_to_date")
private LocalDate effectiveToDate;
@Column(name = "is_active")
private Boolean isActive;
@Column(name = "remarks")
private String remarks;
@Column(name = "created_by_id")
private Long createdById;
@Column(name = "created_date")
private LocalDate createdDate;
public static RdBaseInterestRateEffective create(String versionLabel, LocalDate effectiveFromDate, String remarks, Long createdById) {
RdBaseInterestRateEffective e = new RdBaseInterestRateEffective();
e.versionLabel = versionLabel;
e.effectiveFromDate = effectiveFromDate;
e.isActive = true;
e.remarks = remarks;
e.createdById = createdById;
e.createdDate = LocalDate.now();
return e;
}
public void deactivate(LocalDate toDate) {
this.isActive = false;
this.effectiveToDate = toDate;
}
}
@@ -0,0 +1,22 @@
/**
* 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
*/
package org.apache.fineract.portfolio.savings.domain;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
public interface RdBaseInterestRateEffectiveRepository extends JpaRepository<RdBaseInterestRateEffective, Long> {
List<RdBaseInterestRateEffective> findByIsActiveTrueOrderByEffectiveFromDateDesc();
List<RdBaseInterestRateEffective> findByOrderByEffectiveFromDateDesc();
}
@@ -0,0 +1,67 @@
/**
* 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
*/
package org.apache.fineract.portfolio.savings.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.math.BigDecimal;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Entity
@Table(name = "m_rd_base_interest_rate_slab_history")
@Getter
@NoArgsConstructor
public class RdBaseInterestRateSlabHistory {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "effective_id")
private Long effectiveId;
@Column(name = "sr_no")
private Integer srNo;
@Column(name = "description")
private String description;
@Column(name = "period_type_enum")
private Integer periodTypeEnum;
@Column(name = "from_period")
private Integer fromPeriod;
@Column(name = "to_period")
private Integer toPeriod;
@Column(name = "annual_interest_rate")
private BigDecimal annualInterestRate;
public static RdBaseInterestRateSlabHistory create(Long effectiveId, Integer srNo,
String description, Integer periodTypeEnum, Integer fromPeriod, Integer toPeriod, BigDecimal annualInterestRate) {
RdBaseInterestRateSlabHistory s = new RdBaseInterestRateSlabHistory();
s.effectiveId = effectiveId;
s.srNo = srNo;
s.description = description;
s.periodTypeEnum = periodTypeEnum != null ? periodTypeEnum : 1;
s.fromPeriod = fromPeriod;
s.toPeriod = toPeriod;
s.annualInterestRate = annualInterestRate;
return s;
}
}
@@ -0,0 +1,20 @@
/**
* 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
*/
package org.apache.fineract.portfolio.savings.domain;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
public interface RdBaseInterestRateSlabHistoryRepository extends JpaRepository<RdBaseInterestRateSlabHistory, Long> {
List<RdBaseInterestRateSlabHistory> findByEffectiveIdOrderBySrNo(Long effectiveId);
}
@@ -0,0 +1,80 @@
/**
* 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
*/
package org.apache.fineract.portfolio.savings.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.time.LocalDate;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* Maps to: m_rd_interest_rate_effective
* Stores one "version" of the bank's RD interest rate chart for a product.
* When a new version is saved the old one is deactivated (is_active=0) but kept for history.
*/
@Entity
@Table(name = "m_rd_interest_rate_effective")
@Getter
@NoArgsConstructor
public class RdInterestRateEffective {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "product_id")
private Long productId;
@Column(name = "version_label")
private String versionLabel;
@Column(name = "effective_from_date")
private LocalDate effectiveFromDate;
@Column(name = "effective_to_date")
private LocalDate effectiveToDate;
@Column(name = "is_active")
private Boolean isActive;
@Column(name = "remarks")
private String remarks;
@Column(name = "created_by_id")
private Long createdById;
@Column(name = "created_date")
private LocalDate createdDate;
public static RdInterestRateEffective create(Long productId, String versionLabel,
LocalDate effectiveFromDate, String remarks, Long createdById) {
RdInterestRateEffective e = new RdInterestRateEffective();
e.productId = productId;
e.versionLabel = versionLabel;
e.effectiveFromDate = effectiveFromDate;
e.isActive = true;
e.remarks = remarks;
e.createdById = createdById;
e.createdDate = LocalDate.now();
return e;
}
public void deactivate(LocalDate toDate) {
this.isActive = false;
this.effectiveToDate = toDate;
}
}
@@ -0,0 +1,24 @@
/**
* 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
*/
package org.apache.fineract.portfolio.savings.domain;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
public interface RdInterestRateEffectiveRepository extends JpaRepository<RdInterestRateEffective, Long> {
List<RdInterestRateEffective> findByProductIdOrderByEffectiveFromDateDesc(Long productId);
List<RdInterestRateEffective> findByProductIdAndIsActiveTrueOrderByEffectiveFromDateDesc(Long productId);
List<RdInterestRateEffective> findByIsActiveTrueOrderByEffectiveFromDateDesc();
}
@@ -0,0 +1,72 @@
/**
* 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
*/
package org.apache.fineract.portfolio.savings.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.math.BigDecimal;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* Maps to: m_rd_interest_rate_slab_history
* One row per slab per version. All historical slabs are preserved.
*/
@Entity
@Table(name = "m_rd_interest_rate_slab_history")
@Getter
@NoArgsConstructor
public class RdInterestRateSlabHistory {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "effective_id")
private Long effectiveId;
@Column(name = "sr_no")
private Integer srNo;
@Column(name = "description")
private String description;
@Column(name = "period_type_enum")
private Integer periodTypeEnum;
@Column(name = "from_period")
private Integer fromPeriod;
@Column(name = "to_period")
private Integer toPeriod;
@Column(name = "annual_interest_rate")
private BigDecimal annualInterestRate;
public static RdInterestRateSlabHistory create(Long effectiveId, Integer srNo,
String description, Integer periodTypeEnum,
Integer fromPeriod, Integer toPeriod, BigDecimal annualInterestRate) {
RdInterestRateSlabHistory s = new RdInterestRateSlabHistory();
s.effectiveId = effectiveId;
s.srNo = srNo;
s.description = description;
s.periodTypeEnum = periodTypeEnum != null ? periodTypeEnum : 1;
s.fromPeriod = fromPeriod;
s.toPeriod = toPeriod;
s.annualInterestRate = annualInterestRate;
return s;
}
}
@@ -0,0 +1,20 @@
/**
* 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
*/
package org.apache.fineract.portfolio.savings.domain;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
public interface RdInterestRateSlabHistoryRepository extends JpaRepository<RdInterestRateSlabHistory, Long> {
List<RdInterestRateSlabHistory> findByEffectiveIdOrderBySrNo(Long effectiveId);
}

Some files were not shown because too many files have changed in this diff Show More