Skip to content

Commit fad0d9e

Browse files
committed
feat: add overdraft product configuration domain and API
1 parent 5ea7c36 commit fad0d9e

File tree

43 files changed

+2270
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+2270
-0
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package org.apache.fineract.portfolio.overdraftproduct.accounting;
2+
3+
import jakarta.transaction.Transactional;
4+
import java.util.Optional;
5+
import lombok.RequiredArgsConstructor;
6+
import org.apache.fineract.accounting.glaccount.domain.GLAccount;
7+
import org.apache.fineract.portfolio.overdraftproduct.domain.OverdraftProduct;
8+
import org.apache.fineract.portfolio.overdraftproduct.domain.OverdraftProductAccounting;
9+
import org.apache.fineract.portfolio.overdraftproduct.repository.OverdraftProductRepository;
10+
import org.springframework.stereotype.Component;
11+
12+
@Component
13+
@RequiredArgsConstructor
14+
@Transactional
15+
public class OverdraftAccountingMapper {
16+
17+
private final OverdraftProductRepository productRepository;
18+
19+
@Transactional(Transactional.TxType.SUPPORTS)
20+
public Optional<GLAccount> receivableAccount(Long productId) {
21+
return getAccounting(productId).map(OverdraftProductAccounting::getReceivableAccount);
22+
}
23+
24+
@Transactional(Transactional.TxType.SUPPORTS)
25+
public Optional<GLAccount> interestIncomeAccount(Long productId) {
26+
return getAccounting(productId).map(OverdraftProductAccounting::getInterestIncomeAccount);
27+
}
28+
29+
@Transactional(Transactional.TxType.SUPPORTS)
30+
public Optional<GLAccount> feeIncomeAccount(Long productId) {
31+
return getAccounting(productId).map(OverdraftProductAccounting::getFeeIncomeAccount);
32+
}
33+
34+
@Transactional(Transactional.TxType.SUPPORTS)
35+
public Optional<GLAccount> suspenseAccount(Long productId) {
36+
return getAccounting(productId).map(OverdraftProductAccounting::getSuspenseAccount);
37+
}
38+
39+
@Transactional(Transactional.TxType.SUPPORTS)
40+
public Optional<GLAccount> writeOffAccount(Long productId) {
41+
return getAccounting(productId).map(OverdraftProductAccounting::getWriteOffAccount);
42+
}
43+
44+
@Transactional(Transactional.TxType.SUPPORTS)
45+
public Optional<GLAccount> penaltyIncomeAccount(Long productId) {
46+
return getAccounting(productId).map(OverdraftProductAccounting::getPenaltyIncomeAccount);
47+
}
48+
49+
private Optional<OverdraftProductAccounting> getAccounting(Long productId) {
50+
if (productId == null) {
51+
return Optional.empty();
52+
}
53+
return productRepository.findById(productId).map(OverdraftProduct::getAccounting);
54+
}
55+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package org.apache.fineract.portfolio.overdraftproduct.api;
2+
3+
import jakarta.validation.Valid;
4+
import java.util.List;
5+
import java.util.Optional;
6+
import lombok.RequiredArgsConstructor;
7+
import org.apache.fineract.portfolio.overdraftproduct.domain.OverdraftProductStatus;
8+
import org.apache.fineract.portfolio.overdraftproduct.serialization.OverdraftFeeCommand;
9+
import org.apache.fineract.portfolio.overdraftproduct.serialization.OverdraftProductAccountingCommand;
10+
import org.apache.fineract.portfolio.overdraftproduct.serialization.OverdraftProductCommand;
11+
import org.apache.fineract.portfolio.overdraftproduct.serialization.OverdraftProductData;
12+
import org.apache.fineract.portfolio.overdraftproduct.serialization.OverdraftProductLifecycleCommand;
13+
import org.apache.fineract.portfolio.overdraftproduct.serialization.OverdraftProductValidationResult;
14+
import org.apache.fineract.portfolio.overdraftproduct.serialization.OverdraftUsageRuleCommand;
15+
import org.apache.fineract.portfolio.overdraftproduct.service.OverdraftProductReadPlatformService;
16+
import org.apache.fineract.portfolio.overdraftproduct.service.OverdraftProductWritePlatformService;
17+
import org.springframework.http.HttpStatus;
18+
import org.springframework.http.ResponseEntity;
19+
import org.springframework.security.access.prepost.PreAuthorize;
20+
import org.springframework.validation.annotation.Validated;
21+
import org.springframework.web.bind.annotation.GetMapping;
22+
import org.springframework.web.bind.annotation.PathVariable;
23+
import org.springframework.web.bind.annotation.PostMapping;
24+
import org.springframework.web.bind.annotation.PutMapping;
25+
import org.springframework.web.bind.annotation.RequestBody;
26+
import org.springframework.web.bind.annotation.RequestMapping;
27+
import org.springframework.web.bind.annotation.RequestParam;
28+
import org.springframework.web.bind.annotation.RestController;
29+
30+
@RestController
31+
@RequiredArgsConstructor
32+
@RequestMapping("/overdraft-products")
33+
@Validated
34+
public class OverdraftProductApiResource {
35+
36+
private final OverdraftProductWritePlatformService writeService;
37+
private final OverdraftProductReadPlatformService readService;
38+
39+
@PostMapping
40+
@PreAuthorize("hasAuthority('OVERDRAFT_PRODUCT_ADMIN')")
41+
public ResponseEntity<OverdraftProductData> create(@Valid @RequestBody OverdraftProductCommand command) {
42+
OverdraftProductData data = writeService.createProduct(command);
43+
return new ResponseEntity<>(data, HttpStatus.CREATED);
44+
}
45+
46+
@PutMapping("/{id}")
47+
@PreAuthorize("hasAuthority('OVERDRAFT_PRODUCT_ADMIN')")
48+
public ResponseEntity<OverdraftProductData> update(@PathVariable Long id,
49+
@Valid @RequestBody OverdraftProductCommand command) {
50+
return ResponseEntity.ok(writeService.updateProduct(id, command));
51+
}
52+
53+
@PostMapping("/{id}:activate")
54+
@PreAuthorize("hasAuthority('OVERDRAFT_PRODUCT_ADMIN')")
55+
public ResponseEntity<OverdraftProductData> activate(@PathVariable Long id,
56+
@RequestBody(required = false) OverdraftProductLifecycleCommand command) {
57+
return ResponseEntity.ok(writeService.activateProduct(id, command));
58+
}
59+
60+
@PostMapping("/{id}:retire")
61+
@PreAuthorize("hasAuthority('OVERDRAFT_PRODUCT_ADMIN')")
62+
public ResponseEntity<OverdraftProductData> retire(@PathVariable Long id,
63+
@RequestBody(required = false) OverdraftProductLifecycleCommand command) {
64+
return ResponseEntity.ok(writeService.retireProduct(id, command));
65+
}
66+
67+
@GetMapping
68+
@PreAuthorize("hasAnyAuthority('OVERDRAFT_PRODUCT_ADMIN','OVERDRAFT_PRODUCT_VIEW')")
69+
public ResponseEntity<List<OverdraftProductData>> retrieveAll(@RequestParam(value = "status", required = false) String status,
70+
@RequestParam(value = "currency", required = false) String currency) {
71+
Optional<OverdraftProductStatus> statusFilter = Optional.ofNullable(status).map(OverdraftProductStatus::fromValue);
72+
Optional<String> currencyFilter = Optional.ofNullable(currency);
73+
return ResponseEntity.ok(readService.retrieveAll(statusFilter, currencyFilter));
74+
}
75+
76+
@GetMapping("/{id}")
77+
@PreAuthorize("hasAnyAuthority('OVERDRAFT_PRODUCT_ADMIN','OVERDRAFT_PRODUCT_VIEW')")
78+
public ResponseEntity<OverdraftProductData> retrieveOne(@PathVariable Long id) {
79+
return ResponseEntity.ok(readService.retrieveOne(id));
80+
}
81+
82+
@GetMapping("/{id}/validate")
83+
@PreAuthorize("hasAnyAuthority('OVERDRAFT_PRODUCT_ADMIN','OVERDRAFT_PRODUCT_VIEW')")
84+
public ResponseEntity<OverdraftProductValidationResult> validate(@PathVariable Long id) {
85+
return ResponseEntity.ok(readService.validate(id));
86+
}
87+
88+
@PostMapping("/{id}/fees")
89+
@PreAuthorize("hasAuthority('OVERDRAFT_PRODUCT_ADMIN')")
90+
public ResponseEntity<OverdraftProductData> updateFees(@PathVariable Long id,
91+
@Valid @RequestBody List<OverdraftFeeCommand> fees) {
92+
return ResponseEntity.ok(writeService.updateFees(id, fees));
93+
}
94+
95+
@PostMapping("/{id}/usage-rules")
96+
@PreAuthorize("hasAuthority('OVERDRAFT_PRODUCT_ADMIN')")
97+
public ResponseEntity<OverdraftProductData> updateUsageRules(@PathVariable Long id,
98+
@Valid @RequestBody List<OverdraftUsageRuleCommand> rules) {
99+
return ResponseEntity.ok(writeService.updateUsageRules(id, rules));
100+
}
101+
102+
@PostMapping("/{id}/accounting")
103+
@PreAuthorize("hasAnyAuthority('OVERDRAFT_PRODUCT_ADMIN','FINANCE_ADMIN')")
104+
public ResponseEntity<OverdraftProductData> updateAccounting(@PathVariable Long id,
105+
@Valid @RequestBody OverdraftProductAccountingCommand command) {
106+
return ResponseEntity.ok(writeService.updateAccounting(id, command));
107+
}
108+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package org.apache.fineract.portfolio.overdraftproduct.domain;
2+
3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonValue;
5+
import java.util.Arrays;
6+
import java.util.Locale;
7+
8+
public enum OverdraftDayCountBasis {
9+
ACTUAL_ACTUAL,
10+
ACTUAL_360,
11+
ACTUAL_365,
12+
THIRTY_360;
13+
14+
@JsonCreator
15+
public static OverdraftDayCountBasis fromValue(String value) {
16+
if (value == null) {
17+
return null;
18+
}
19+
return Arrays.stream(values()).filter(basis -> basis.name().equalsIgnoreCase(value)).findFirst()
20+
.orElseThrow(() -> new IllegalArgumentException("Unknown overdraft day count basis: " + value));
21+
}
22+
23+
@JsonValue
24+
public String toValue() {
25+
return name().toLowerCase(Locale.ROOT);
26+
}
27+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package org.apache.fineract.portfolio.overdraftproduct.domain;
2+
3+
import jakarta.persistence.Column;
4+
import jakarta.persistence.Entity;
5+
import jakarta.persistence.EnumType;
6+
import jakarta.persistence.Enumerated;
7+
import jakarta.persistence.FetchType;
8+
import jakarta.persistence.JoinColumn;
9+
import jakarta.persistence.ManyToOne;
10+
import jakarta.persistence.Table;
11+
import java.math.BigDecimal;
12+
import lombok.Getter;
13+
import lombok.Setter;
14+
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
15+
16+
@Entity
17+
@Table(name = "m_overdraft_fee")
18+
@Getter
19+
@Setter
20+
public class OverdraftFee extends AbstractPersistableCustom<Long> {
21+
22+
@ManyToOne(fetch = FetchType.LAZY)
23+
@JoinColumn(name = "overdraft_product_id", nullable = false)
24+
private OverdraftProduct product;
25+
26+
@Enumerated(EnumType.STRING)
27+
@Column(name = "fee_type", nullable = false, length = 50)
28+
private OverdraftFeeType feeType;
29+
30+
@Enumerated(EnumType.STRING)
31+
@Column(name = "calculation_type", nullable = false, length = 50)
32+
private OverdraftFeeCalculationType calculationType;
33+
34+
@Column(name = "amount", precision = 19, scale = 6)
35+
private BigDecimal amount;
36+
37+
@Column(name = "percentage", precision = 10, scale = 6)
38+
private BigDecimal percentage;
39+
40+
@Column(name = "tier_definition", length = 1000)
41+
private String tierDefinition;
42+
43+
@Column(name = "trigger_condition", length = 255)
44+
private String triggerCondition;
45+
46+
@Column(name = "is_active")
47+
private boolean active = true;
48+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.apache.fineract.portfolio.overdraftproduct.domain;
2+
3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonValue;
5+
import java.util.Arrays;
6+
import java.util.Locale;
7+
8+
public enum OverdraftFeeCalculationType {
9+
FLAT,
10+
PERCENTAGE,
11+
TIERED;
12+
13+
@JsonCreator
14+
public static OverdraftFeeCalculationType fromValue(String value) {
15+
if (value == null) {
16+
return null;
17+
}
18+
return Arrays.stream(values()).filter(type -> type.name().equalsIgnoreCase(value)).findFirst()
19+
.orElseThrow(() -> new IllegalArgumentException("Unknown overdraft fee calculation type: " + value));
20+
}
21+
22+
@JsonValue
23+
public String toValue() {
24+
return name().toLowerCase(Locale.ROOT);
25+
}
26+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package org.apache.fineract.portfolio.overdraftproduct.domain;
2+
3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonValue;
5+
import java.util.Arrays;
6+
import java.util.Locale;
7+
8+
public enum OverdraftFeeType {
9+
SETUP,
10+
USAGE,
11+
LATE,
12+
RENEWAL;
13+
14+
@JsonCreator
15+
public static OverdraftFeeType fromValue(String value) {
16+
if (value == null) {
17+
return null;
18+
}
19+
return Arrays.stream(values()).filter(type -> type.name().equalsIgnoreCase(value)).findFirst()
20+
.orElseThrow(() -> new IllegalArgumentException("Unknown overdraft fee type: " + value));
21+
}
22+
23+
@JsonValue
24+
public String toValue() {
25+
return name().toLowerCase(Locale.ROOT);
26+
}
27+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.apache.fineract.portfolio.overdraftproduct.domain;
2+
3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonValue;
5+
import java.util.Arrays;
6+
import java.util.Locale;
7+
8+
public enum OverdraftInterestMethod {
9+
DAILY_BALANCE,
10+
MINIMUM_BALANCE,
11+
AVERAGE_DAILY_BALANCE;
12+
13+
@JsonCreator
14+
public static OverdraftInterestMethod fromValue(String value) {
15+
if (value == null) {
16+
return null;
17+
}
18+
return Arrays.stream(values()).filter(method -> method.name().equalsIgnoreCase(value)).findFirst()
19+
.orElseThrow(() -> new IllegalArgumentException("Unknown overdraft interest method: " + value));
20+
}
21+
22+
@JsonValue
23+
public String toValue() {
24+
return name().toLowerCase(Locale.ROOT);
25+
}
26+
}

0 commit comments

Comments
 (0)