Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package org.apache.fineract.portfolio.overdraftproduct.accounting;

import jakarta.transaction.Transactional;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.accounting.glaccount.domain.GLAccount;
import org.apache.fineract.portfolio.overdraftproduct.domain.OverdraftProduct;
import org.apache.fineract.portfolio.overdraftproduct.domain.OverdraftProductAccounting;
import org.apache.fineract.portfolio.overdraftproduct.repository.OverdraftProductRepository;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
@Transactional
public class OverdraftAccountingMapper {

private final OverdraftProductRepository productRepository;

@Transactional(Transactional.TxType.SUPPORTS)
public Optional<GLAccount> receivableAccount(Long productId) {
return getAccounting(productId).map(OverdraftProductAccounting::getReceivableAccount);
}

@Transactional(Transactional.TxType.SUPPORTS)
public Optional<GLAccount> interestIncomeAccount(Long productId) {
return getAccounting(productId).map(OverdraftProductAccounting::getInterestIncomeAccount);
}

@Transactional(Transactional.TxType.SUPPORTS)
public Optional<GLAccount> feeIncomeAccount(Long productId) {
return getAccounting(productId).map(OverdraftProductAccounting::getFeeIncomeAccount);
}

@Transactional(Transactional.TxType.SUPPORTS)
public Optional<GLAccount> suspenseAccount(Long productId) {
return getAccounting(productId).map(OverdraftProductAccounting::getSuspenseAccount);
}

@Transactional(Transactional.TxType.SUPPORTS)
public Optional<GLAccount> writeOffAccount(Long productId) {
return getAccounting(productId).map(OverdraftProductAccounting::getWriteOffAccount);
}

@Transactional(Transactional.TxType.SUPPORTS)
public Optional<GLAccount> penaltyIncomeAccount(Long productId) {
return getAccounting(productId).map(OverdraftProductAccounting::getPenaltyIncomeAccount);
}

private Optional<OverdraftProductAccounting> getAccounting(Long productId) {
if (productId == null) {
return Optional.empty();
}
return productRepository.findById(productId).map(OverdraftProduct::getAccounting);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package org.apache.fineract.portfolio.overdraftproduct.api;

import jakarta.validation.Valid;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.portfolio.overdraftproduct.domain.OverdraftProductStatus;
import org.apache.fineract.portfolio.overdraftproduct.serialization.OverdraftFeeCommand;
import org.apache.fineract.portfolio.overdraftproduct.serialization.OverdraftProductAccountingCommand;
import org.apache.fineract.portfolio.overdraftproduct.serialization.OverdraftProductCommand;
import org.apache.fineract.portfolio.overdraftproduct.serialization.OverdraftProductData;
import org.apache.fineract.portfolio.overdraftproduct.serialization.OverdraftProductLifecycleCommand;
import org.apache.fineract.portfolio.overdraftproduct.serialization.OverdraftProductValidationResult;
import org.apache.fineract.portfolio.overdraftproduct.serialization.OverdraftUsageRuleCommand;
import org.apache.fineract.portfolio.overdraftproduct.service.OverdraftProductReadPlatformService;
import org.apache.fineract.portfolio.overdraftproduct.service.OverdraftProductWritePlatformService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@RequestMapping("/overdraft-products")
@Validated
public class OverdraftProductApiResource {

private final OverdraftProductWritePlatformService writeService;
private final OverdraftProductReadPlatformService readService;

@PostMapping
@PreAuthorize("hasAuthority('OVERDRAFT_PRODUCT_ADMIN')")
public ResponseEntity<OverdraftProductData> create(@Valid @RequestBody OverdraftProductCommand command) {
OverdraftProductData data = writeService.createProduct(command);
return new ResponseEntity<>(data, HttpStatus.CREATED);
}

@PutMapping("/{id}")
@PreAuthorize("hasAuthority('OVERDRAFT_PRODUCT_ADMIN')")
public ResponseEntity<OverdraftProductData> update(@PathVariable Long id,
@Valid @RequestBody OverdraftProductCommand command) {
return ResponseEntity.ok(writeService.updateProduct(id, command));
}

@PostMapping("/{id}:activate")
@PreAuthorize("hasAuthority('OVERDRAFT_PRODUCT_ADMIN')")
public ResponseEntity<OverdraftProductData> activate(@PathVariable Long id,
@RequestBody(required = false) OverdraftProductLifecycleCommand command) {
return ResponseEntity.ok(writeService.activateProduct(id, command));
}

@PostMapping("/{id}:retire")
@PreAuthorize("hasAuthority('OVERDRAFT_PRODUCT_ADMIN')")
public ResponseEntity<OverdraftProductData> retire(@PathVariable Long id,
@RequestBody(required = false) OverdraftProductLifecycleCommand command) {
return ResponseEntity.ok(writeService.retireProduct(id, command));
}

@GetMapping
@PreAuthorize("hasAnyAuthority('OVERDRAFT_PRODUCT_ADMIN','OVERDRAFT_PRODUCT_VIEW')")
public ResponseEntity<List<OverdraftProductData>> retrieveAll(@RequestParam(value = "status", required = false) String status,
@RequestParam(value = "currency", required = false) String currency) {
Optional<OverdraftProductStatus> statusFilter = Optional.ofNullable(status).map(OverdraftProductStatus::fromValue);
Optional<String> currencyFilter = Optional.ofNullable(currency);
return ResponseEntity.ok(readService.retrieveAll(statusFilter, currencyFilter));
}

@GetMapping("/{id}")
@PreAuthorize("hasAnyAuthority('OVERDRAFT_PRODUCT_ADMIN','OVERDRAFT_PRODUCT_VIEW')")
public ResponseEntity<OverdraftProductData> retrieveOne(@PathVariable Long id) {
return ResponseEntity.ok(readService.retrieveOne(id));
}

@GetMapping("/{id}/validate")
@PreAuthorize("hasAnyAuthority('OVERDRAFT_PRODUCT_ADMIN','OVERDRAFT_PRODUCT_VIEW')")
public ResponseEntity<OverdraftProductValidationResult> validate(@PathVariable Long id) {
return ResponseEntity.ok(readService.validate(id));
}

@PostMapping("/{id}/fees")
@PreAuthorize("hasAuthority('OVERDRAFT_PRODUCT_ADMIN')")
public ResponseEntity<OverdraftProductData> updateFees(@PathVariable Long id,
@Valid @RequestBody List<OverdraftFeeCommand> fees) {
return ResponseEntity.ok(writeService.updateFees(id, fees));
}

@PostMapping("/{id}/usage-rules")
@PreAuthorize("hasAuthority('OVERDRAFT_PRODUCT_ADMIN')")
public ResponseEntity<OverdraftProductData> updateUsageRules(@PathVariable Long id,
@Valid @RequestBody List<OverdraftUsageRuleCommand> rules) {
return ResponseEntity.ok(writeService.updateUsageRules(id, rules));
}

@PostMapping("/{id}/accounting")
@PreAuthorize("hasAnyAuthority('OVERDRAFT_PRODUCT_ADMIN','FINANCE_ADMIN')")
public ResponseEntity<OverdraftProductData> updateAccounting(@PathVariable Long id,
@Valid @RequestBody OverdraftProductAccountingCommand command) {
return ResponseEntity.ok(writeService.updateAccounting(id, command));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.apache.fineract.portfolio.overdraftproduct.domain;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
import java.util.Locale;

public enum OverdraftDayCountBasis {
ACTUAL_ACTUAL,
ACTUAL_360,
ACTUAL_365,
THIRTY_360;

@JsonCreator
public static OverdraftDayCountBasis fromValue(String value) {
if (value == null) {
return null;
}
return Arrays.stream(values()).filter(basis -> basis.name().equalsIgnoreCase(value)).findFirst()
.orElseThrow(() -> new IllegalArgumentException("Unknown overdraft day count basis: " + value));
}

@JsonValue
public String toValue() {
return name().toLowerCase(Locale.ROOT);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.apache.fineract.portfolio.overdraftproduct.domain;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
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_overdraft_fee")
@Getter
@Setter
public class OverdraftFee extends AbstractPersistableCustom<Long> {

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "overdraft_product_id", nullable = false)
private OverdraftProduct product;

@Enumerated(EnumType.STRING)
@Column(name = "fee_type", nullable = false, length = 50)
private OverdraftFeeType feeType;

@Enumerated(EnumType.STRING)
@Column(name = "calculation_type", nullable = false, length = 50)
private OverdraftFeeCalculationType calculationType;

@Column(name = "amount", precision = 19, scale = 6)
private BigDecimal amount;

@Column(name = "percentage", precision = 10, scale = 6)
private BigDecimal percentage;

@Column(name = "tier_definition", length = 1000)
private String tierDefinition;

@Column(name = "trigger_condition", length = 255)
private String triggerCondition;

@Column(name = "is_active")
private boolean active = true;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.apache.fineract.portfolio.overdraftproduct.domain;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
import java.util.Locale;

public enum OverdraftFeeCalculationType {
FLAT,
PERCENTAGE,
TIERED;

@JsonCreator
public static OverdraftFeeCalculationType fromValue(String value) {
if (value == null) {
return null;
}
return Arrays.stream(values()).filter(type -> type.name().equalsIgnoreCase(value)).findFirst()
.orElseThrow(() -> new IllegalArgumentException("Unknown overdraft fee calculation type: " + value));
}

@JsonValue
public String toValue() {
return name().toLowerCase(Locale.ROOT);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.apache.fineract.portfolio.overdraftproduct.domain;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
import java.util.Locale;

public enum OverdraftFeeType {
SETUP,
USAGE,
LATE,
RENEWAL;

@JsonCreator
public static OverdraftFeeType fromValue(String value) {
if (value == null) {
return null;
}
return Arrays.stream(values()).filter(type -> type.name().equalsIgnoreCase(value)).findFirst()
.orElseThrow(() -> new IllegalArgumentException("Unknown overdraft fee type: " + value));
}

@JsonValue
public String toValue() {
return name().toLowerCase(Locale.ROOT);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.apache.fineract.portfolio.overdraftproduct.domain;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
import java.util.Locale;

public enum OverdraftInterestMethod {
DAILY_BALANCE,
MINIMUM_BALANCE,
AVERAGE_DAILY_BALANCE;

@JsonCreator
public static OverdraftInterestMethod fromValue(String value) {
if (value == null) {
return null;
}
return Arrays.stream(values()).filter(method -> method.name().equalsIgnoreCase(value)).findFirst()
.orElseThrow(() -> new IllegalArgumentException("Unknown overdraft interest method: " + value));
}

@JsonValue
public String toValue() {
return name().toLowerCase(Locale.ROOT);
}
}
Loading
Loading