feat: denomination save+validation for all deposit types + cash exchange backend
- DenominationBreakdownWriter helper: validates breakdown total == amount and persists per-denomination rows; wired into current, FD and RD deposit flows (savings already had inline save) so all 4 deposit types now record the cash denomination breakdown. - CIF on denomination rows now sourced from the client (getCifNumber) instead of the account external id. - Allow denominationBreakdown param in the FD/RD transaction validator. - Cash Exchange backend: m_cash_exchange + m_cash_exchange_detail tables, entities, repositories and JAX-RS API with received==given validation; registered the 0013 migration in changelog-tenant. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
ee915e395a
commit
9a30ede278
+199
@@ -0,0 +1,199 @@
|
||||
package org.apache.fineract.portfolio.cashexchange.api;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.cashexchange.domain.CashExchange;
|
||||
import org.apache.fineract.portfolio.cashexchange.domain.CashExchangeDetail;
|
||||
import org.apache.fineract.portfolio.cashexchange.domain.CashExchangeDetailRepository;
|
||||
import org.apache.fineract.portfolio.cashexchange.domain.CashExchangeRepository;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Path("/v1/cashexchange")
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public class CashExchangeApiResource {
|
||||
|
||||
private final CashExchangeRepository repository;
|
||||
private final CashExchangeDetailRepository detailRepository;
|
||||
private final PlatformSecurityContext context;
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
@POST
|
||||
public String create(String body) {
|
||||
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
|
||||
|
||||
final Long accountId = json.has("accountId") && !json.get("accountId").isJsonNull() ? json.get("accountId").getAsLong() : null;
|
||||
final String accountNo = json.has("accountNo") && !json.get("accountNo").isJsonNull() ? json.get("accountNo").getAsString()
|
||||
: null;
|
||||
final Long clientId = json.has("clientId") && !json.get("clientId").isJsonNull() ? json.get("clientId").getAsLong() : null;
|
||||
final LocalDate exchangeDate = json.has("exchangeDate") && !json.get("exchangeDate").isJsonNull()
|
||||
? LocalDate.parse(json.get("exchangeDate").getAsString())
|
||||
: null;
|
||||
final String note = json.has("note") && !json.get("note").isJsonNull() ? json.get("note").getAsString() : null;
|
||||
|
||||
final JsonArray received = json.has("received") && json.get("received").isJsonArray() ? json.getAsJsonArray("received")
|
||||
: new JsonArray();
|
||||
final JsonArray given = json.has("given") && json.get("given").isJsonArray() ? json.getAsJsonArray("given") : new JsonArray();
|
||||
|
||||
final BigDecimal receivedTotal = sumTotal(received);
|
||||
final BigDecimal givenTotal = sumTotal(given);
|
||||
|
||||
if (!hasPositiveCount(received) || !hasPositiveCount(given)) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashexchange.empty",
|
||||
"Both received and given denominations are required.");
|
||||
}
|
||||
if (receivedTotal.compareTo(givenTotal) != 0) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashexchange.mismatch",
|
||||
"Received total (" + receivedTotal + ") must equal given total (" + givenTotal + ").");
|
||||
}
|
||||
if (receivedTotal.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashexchange.empty",
|
||||
"Both received and given denominations are required.");
|
||||
}
|
||||
|
||||
CashExchange header = new CashExchange();
|
||||
header.setAccountId(accountId);
|
||||
header.setAccountNo(accountNo);
|
||||
header.setClientId(clientId);
|
||||
header.setExchangeAmount(receivedTotal);
|
||||
header.setExchangeDate(exchangeDate);
|
||||
header.setNote(note);
|
||||
header.setCreatedAt(LocalDateTime.now());
|
||||
try {
|
||||
AppUser user = this.context.authenticatedUser();
|
||||
if (user != null) {
|
||||
header.setPerformedBy(user.getId());
|
||||
header.setPerformedByUsername(user.getUsername());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// resolving the current user must never block recording the exchange
|
||||
}
|
||||
CashExchange saved = repository.save(header);
|
||||
|
||||
saveDetails(saved.getId(), "RECEIVED", received);
|
||||
saveDetails(saved.getId(), "GIVEN", given);
|
||||
|
||||
JsonObject result = new JsonObject();
|
||||
result.addProperty("resourceId", saved.getId());
|
||||
return gson.toJson(result);
|
||||
}
|
||||
|
||||
@GET
|
||||
public String getAll(@QueryParam("accountId") Long accountId) {
|
||||
List<CashExchange> list = accountId != null ? repository.findByAccountIdOrderByCreatedAtDesc(accountId)
|
||||
: repository.findAllByOrderByCreatedAtDesc();
|
||||
return gson.toJson(list.stream().map(this::toMap).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{id}")
|
||||
public String getOne(@PathParam("id") Long id) {
|
||||
CashExchange header = repository.findById(id)
|
||||
.orElseThrow(() -> new RuntimeException("Cash exchange not found: " + id));
|
||||
Map<String, Object> map = toMap(header);
|
||||
List<Map<String, Object>> details = detailRepository.findByExchangeId(id).stream().map(this::detailToMap)
|
||||
.collect(Collectors.toList());
|
||||
map.put("details", details);
|
||||
return gson.toJson(map);
|
||||
}
|
||||
|
||||
private BigDecimal sumTotal(JsonArray items) {
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
for (JsonElement el : items) {
|
||||
JsonObject item = el.getAsJsonObject();
|
||||
int value = item.has("denominationValue") && !item.get("denominationValue").isJsonNull()
|
||||
? item.get("denominationValue").getAsInt()
|
||||
: 0;
|
||||
int count = item.has("count") && !item.get("count").isJsonNull() ? item.get("count").getAsInt() : 0;
|
||||
if (count > 0) {
|
||||
total = total.add(BigDecimal.valueOf((long) value * count));
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
private boolean hasPositiveCount(JsonArray items) {
|
||||
for (JsonElement el : items) {
|
||||
JsonObject item = el.getAsJsonObject();
|
||||
int count = item.has("count") && !item.get("count").isJsonNull() ? item.get("count").getAsInt() : 0;
|
||||
if (count > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void saveDetails(Long exchangeId, String direction, JsonArray items) {
|
||||
for (JsonElement el : items) {
|
||||
JsonObject item = el.getAsJsonObject();
|
||||
int count = item.has("count") && !item.get("count").isJsonNull() ? item.get("count").getAsInt() : 0;
|
||||
if (count <= 0) {
|
||||
continue;
|
||||
}
|
||||
int value = item.has("denominationValue") && !item.get("denominationValue").isJsonNull()
|
||||
? item.get("denominationValue").getAsInt()
|
||||
: 0;
|
||||
Long denominationId = item.has("denominationId") && !item.get("denominationId").isJsonNull()
|
||||
? item.get("denominationId").getAsLong()
|
||||
: null;
|
||||
CashExchangeDetail detail = new CashExchangeDetail();
|
||||
detail.setExchangeId(exchangeId);
|
||||
detail.setDirection(direction);
|
||||
detail.setDenominationId(denominationId);
|
||||
detail.setDenominationValue(value);
|
||||
detail.setCount(count);
|
||||
detail.setTotalAmount(BigDecimal.valueOf((long) value * count));
|
||||
detailRepository.save(detail);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> toMap(CashExchange e) {
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
map.put("id", e.getId());
|
||||
map.put("accountId", e.getAccountId());
|
||||
map.put("accountNo", e.getAccountNo());
|
||||
map.put("clientId", e.getClientId());
|
||||
map.put("exchangeAmount", e.getExchangeAmount());
|
||||
map.put("exchangeDate", e.getExchangeDate() != null ? e.getExchangeDate().toString() : null);
|
||||
map.put("note", e.getNote());
|
||||
map.put("performedBy", e.getPerformedBy());
|
||||
map.put("performedByUsername", e.getPerformedByUsername());
|
||||
map.put("createdAt", e.getCreatedAt() != null ? e.getCreatedAt().toString() : null);
|
||||
return map;
|
||||
}
|
||||
|
||||
private Map<String, Object> detailToMap(CashExchangeDetail d) {
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
map.put("id", d.getId());
|
||||
map.put("direction", d.getDirection());
|
||||
map.put("denominationId", d.getDenominationId());
|
||||
map.put("denominationValue", d.getDenominationValue());
|
||||
map.put("count", d.getCount());
|
||||
map.put("totalAmount", d.getTotalAmount());
|
||||
return map;
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.cashexchange.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 java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_cash_exchange")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class CashExchange {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "account_id")
|
||||
private Long accountId;
|
||||
|
||||
@Column(name = "account_no", length = 50)
|
||||
private String accountNo;
|
||||
|
||||
@Column(name = "client_id")
|
||||
private Long clientId;
|
||||
|
||||
@Column(name = "exchange_amount", nullable = false)
|
||||
private BigDecimal exchangeAmount;
|
||||
|
||||
@Column(name = "exchange_date")
|
||||
private LocalDate exchangeDate;
|
||||
|
||||
@Column(name = "note", length = 500)
|
||||
private String note;
|
||||
|
||||
@Column(name = "performed_by")
|
||||
private Long performedBy;
|
||||
|
||||
@Column(name = "performed_by_username", length = 100)
|
||||
private String performedByUsername;
|
||||
|
||||
@Column(name = "created_at")
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.cashexchange.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;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_cash_exchange_detail")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class CashExchangeDetail {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "exchange_id", nullable = false)
|
||||
private Long exchangeId;
|
||||
|
||||
@Column(name = "direction", nullable = false, length = 10)
|
||||
private String direction;
|
||||
|
||||
@Column(name = "denomination_id")
|
||||
private Long denominationId;
|
||||
|
||||
@Column(name = "denomination_value", nullable = false)
|
||||
private Integer denominationValue;
|
||||
|
||||
@Column(name = "count", nullable = false)
|
||||
private Integer count;
|
||||
|
||||
@Column(name = "total_amount", nullable = false)
|
||||
private BigDecimal totalAmount;
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package org.apache.fineract.portfolio.cashexchange.domain;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface CashExchangeDetailRepository extends JpaRepository<CashExchangeDetail, Long> {
|
||||
List<CashExchangeDetail> findByExchangeId(Long exchangeId);
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package org.apache.fineract.portfolio.cashexchange.domain;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface CashExchangeRepository extends JpaRepository<CashExchange, Long> {
|
||||
List<CashExchange> findAllByOrderByCreatedAtDesc();
|
||||
List<CashExchange> findByAccountIdOrderByCreatedAtDesc(Long accountId);
|
||||
}
|
||||
+12
@@ -78,4 +78,16 @@ public class SavingsTxnDenomination {
|
||||
|
||||
@Column(name = "created_at")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
/** "DEPOSIT" or "WITHDRAWAL" — identifies the direction of the cash flow. */
|
||||
@Column(name = "txn_type", nullable = false, length = 20)
|
||||
private String txnType;
|
||||
|
||||
/** ID of the logged-in teller / officer who performed the transaction. */
|
||||
@Column(name = "officer_id")
|
||||
private Long officerId;
|
||||
|
||||
/** Username of the teller / officer who performed the transaction. */
|
||||
@Column(name = "officer_username", length = 100)
|
||||
private String officerUsername;
|
||||
}
|
||||
|
||||
+232
@@ -0,0 +1,232 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.denominationmaster.service;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.denominationmaster.domain.SavingsTxnDenomination;
|
||||
import org.apache.fineract.portfolio.denominationmaster.domain.SavingsTxnDenominationRepository;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Validates and persists the per-transaction cash denomination breakdown sent by the UI
|
||||
* on both deposits and withdrawals.
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DenominationBreakdownWriter {
|
||||
|
||||
private final SavingsTxnDenominationRepository repository;
|
||||
private final PlatformSecurityContext context;
|
||||
|
||||
// ── Public API ────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Full validation of the denomination breakdown:
|
||||
* <ol>
|
||||
* <li>Each item must have a non-null {@code denominationId}.</li>
|
||||
* <li>Each item's {@code count} must be > 0.</li>
|
||||
* <li>Each item's {@code denominationValue} (or {@code denomination}) must be > 0.</li>
|
||||
* <li>The grand total (sum of value × count) must equal {@code expectedAmount}.</li>
|
||||
* </ol>
|
||||
* No-op when no breakdown is provided.
|
||||
*
|
||||
* @param command the JSON command containing the denominationBreakdown array
|
||||
* @param expectedAmount the transaction amount that the breakdown must sum to
|
||||
* @param txnType "DEPOSIT" or "WITHDRAWAL" — used in error messages
|
||||
*/
|
||||
public void validateBreakdown(final JsonCommand command, final BigDecimal expectedAmount, final String txnType) {
|
||||
if (command == null || !command.parameterExists("denominationBreakdown")) {
|
||||
return;
|
||||
}
|
||||
final JsonArray breakdown = command.arrayOfParameterNamed("denominationBreakdown");
|
||||
if (breakdown == null || breakdown.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<ApiParameterError> errors = new ArrayList<>();
|
||||
final DataValidatorBuilder validator = new DataValidatorBuilder(errors).resource("denominationBreakdown");
|
||||
BigDecimal computedTotal = BigDecimal.ZERO;
|
||||
final String label = (txnType != null && !txnType.isBlank()) ? txnType.toUpperCase() : "TRANSACTION";
|
||||
|
||||
for (int i = 0; i < breakdown.size(); i++) {
|
||||
final JsonObject obj = breakdown.get(i).getAsJsonObject();
|
||||
final String itemPrefix = "denominationBreakdown[" + i + "]";
|
||||
|
||||
// 1. denominationId is required
|
||||
if (!obj.has("denominationId") || obj.get("denominationId").isJsonNull()) {
|
||||
validator.reset().parameter(itemPrefix + ".denominationId")
|
||||
.value(null)
|
||||
.failWithCode("required", "denominationId is required for each denomination item.");
|
||||
continue; // cannot compute further without ID
|
||||
}
|
||||
final long denominationId = obj.get("denominationId").getAsLong();
|
||||
if (denominationId <= 0) {
|
||||
validator.reset().parameter(itemPrefix + ".denominationId")
|
||||
.value(denominationId)
|
||||
.failWithCode("invalid", "denominationId must be a positive number.");
|
||||
}
|
||||
|
||||
// 2. count is required and must be > 0
|
||||
if (!obj.has("count") || obj.get("count").isJsonNull()) {
|
||||
validator.reset().parameter(itemPrefix + ".count")
|
||||
.value(null)
|
||||
.failWithCode("required", "count is required for each denomination item.");
|
||||
continue;
|
||||
}
|
||||
final int count = obj.get("count").getAsInt();
|
||||
if (count <= 0) {
|
||||
// count = 0 is silently skipped (UI may send zero for unselected rows)
|
||||
continue;
|
||||
}
|
||||
|
||||
// 3. denominationValue (or denomination) must be present and > 0
|
||||
int denominationValue = 0;
|
||||
if (obj.has("denominationValue") && !obj.get("denominationValue").isJsonNull()) {
|
||||
denominationValue = obj.get("denominationValue").getAsInt();
|
||||
} else if (obj.has("denomination") && !obj.get("denomination").isJsonNull()) {
|
||||
denominationValue = obj.get("denomination").getAsInt();
|
||||
}
|
||||
if (denominationValue <= 0) {
|
||||
validator.reset().parameter(itemPrefix + ".denominationValue")
|
||||
.value(denominationValue)
|
||||
.failWithCode("invalid",
|
||||
"denominationValue must be greater than zero for denomination item " + i + ".");
|
||||
continue;
|
||||
}
|
||||
|
||||
// 4. Accumulate total: value × count
|
||||
computedTotal = computedTotal.add(BigDecimal.valueOf((long) denominationValue * count));
|
||||
}
|
||||
|
||||
// Throw item-level validation errors first
|
||||
if (!errors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
|
||||
"Denomination breakdown validation errors exist.", errors);
|
||||
}
|
||||
|
||||
// 5. Grand total must match the transaction amount
|
||||
if (expectedAmount != null && computedTotal.compareTo(expectedAmount) != 0) {
|
||||
throw new PlatformDataIntegrityException(
|
||||
"error.msg.denomination.total.mismatch",
|
||||
label + " denomination breakdown total (" + computedTotal
|
||||
+ ") does not match the transaction amount (" + expectedAmount
|
||||
+ "). Please re-check the note/coin counts.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience wrapper kept for backward-compatibility with any callers that do not
|
||||
* yet pass a {@code txnType}. Delegates to {@link #validateBreakdown}.
|
||||
*/
|
||||
public void validateTotal(final JsonCommand command, final BigDecimal expectedAmount) {
|
||||
validateBreakdown(command, expectedAmount, "TRANSACTION");
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist each valid denomination row for the given transaction.
|
||||
* <p>
|
||||
* Items with {@code count <= 0} are silently skipped (UI sends zero for
|
||||
* unselected rows). Items missing {@code denominationId} are also skipped
|
||||
* (they should have already been rejected by {@link #validateBreakdown}).
|
||||
* <p>
|
||||
* The logged-in officer (teller) is automatically resolved from the
|
||||
* security context; if resolution fails (e.g. batch job) the officer fields
|
||||
* are left {@code null}.
|
||||
*
|
||||
* @param command the JSON command containing the denominationBreakdown array
|
||||
* @param txnId ID of the saved savings / current / RD / FD transaction
|
||||
* @param savingsAccountId savings / current / RD / FD account ID
|
||||
* @param accountNo account number string
|
||||
* @param clientId client ID (may be null for group accounts)
|
||||
* @param cifNo CIF number (may be null)
|
||||
* @param txnDate transaction date
|
||||
* @param txnType "DEPOSIT" or "WITHDRAWAL"
|
||||
*/
|
||||
public void save(final JsonCommand command, final Long txnId, final Long savingsAccountId, final String accountNo,
|
||||
final Long clientId, final String cifNo, final LocalDate txnDate, final String txnType) {
|
||||
|
||||
if (command == null || txnId == null || !command.parameterExists("denominationBreakdown")) {
|
||||
return;
|
||||
}
|
||||
final JsonArray breakdown = command.arrayOfParameterNamed("denominationBreakdown");
|
||||
if (breakdown == null || breakdown.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Resolve the officer / teller from the security context
|
||||
Long officerId = null;
|
||||
String officerUsername = null;
|
||||
try {
|
||||
final AppUser user = this.context.authenticatedUser();
|
||||
if (user != null) {
|
||||
officerId = user.getId();
|
||||
officerUsername = user.getUsername();
|
||||
}
|
||||
} catch (final Exception ignored) {
|
||||
// If user cannot be resolved (e.g. batch / async job), leave officer fields null
|
||||
}
|
||||
|
||||
final String resolvedTxnType = (txnType != null && !txnType.isBlank()) ? txnType.toUpperCase() : "DEPOSIT";
|
||||
|
||||
for (final JsonElement entry : breakdown) {
|
||||
final JsonObject obj = entry.getAsJsonObject();
|
||||
|
||||
// denominationId is mandatory (NOT NULL in DB)
|
||||
if (!obj.has("denominationId") || obj.get("denominationId").isJsonNull() || !obj.has("count")) {
|
||||
continue;
|
||||
}
|
||||
final int count = obj.get("count").getAsInt();
|
||||
if (count <= 0) {
|
||||
continue; // zero-count rows sent by UI are silently ignored
|
||||
}
|
||||
|
||||
final long denominationId = obj.get("denominationId").getAsLong();
|
||||
|
||||
// Resolve denomination face value
|
||||
int denominationValue = 0;
|
||||
if (obj.has("denominationValue") && !obj.get("denominationValue").isJsonNull()) {
|
||||
denominationValue = obj.get("denominationValue").getAsInt();
|
||||
} else if (obj.has("denomination") && !obj.get("denomination").isJsonNull()) {
|
||||
denominationValue = obj.get("denomination").getAsInt();
|
||||
}
|
||||
|
||||
final BigDecimal lineTotal = BigDecimal.valueOf((long) denominationValue * count);
|
||||
|
||||
final SavingsTxnDenomination row = SavingsTxnDenomination.builder()
|
||||
.txnId(txnId)
|
||||
.savingsAccountId(savingsAccountId)
|
||||
.accountNo(accountNo)
|
||||
.clientId(clientId)
|
||||
.cifNo(cifNo)
|
||||
.denominationId(denominationId)
|
||||
.denominationValue(denominationValue)
|
||||
.count(count)
|
||||
.totalAmount(lineTotal)
|
||||
.txnDate(txnDate)
|
||||
.createdAt(LocalDateTime.now())
|
||||
.txnType(resolvedTxnType)
|
||||
.officerId(officerId)
|
||||
.officerUsername(officerUsername)
|
||||
.build();
|
||||
|
||||
this.repository.save(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
+257
-257
@@ -1,257 +1,257 @@
|
||||
/**
|
||||
* 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.savings.data;
|
||||
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.activatedOnDateParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.bankNumberParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.checkNumberParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.closedOnDateParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.onAccountClosureIdParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.paymentTypeIdParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.receiptNumberParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.routingCodeParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.toSavingsAccountIdParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transactionAccountNumberParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transactionAmountParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transactionDateParamName;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import java.lang.reflect.Type;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
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.exception.InvalidJsonException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.apache.fineract.portfolio.savings.DepositAccountOnClosureType;
|
||||
import org.apache.fineract.portfolio.savings.DepositAccountType;
|
||||
import org.apache.fineract.portfolio.savings.DepositsApiConstants;
|
||||
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class DepositAccountTransactionDataValidator {
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
|
||||
private static final Set<String> DEPOSIT_ACCOUNT_TRANSACTION_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(
|
||||
DepositsApiConstants.localeParamName, DepositsApiConstants.dateFormatParamName, transactionDateParamName,
|
||||
transactionAmountParamName, paymentTypeIdParamName, transactionAccountNumberParamName, checkNumberParamName,
|
||||
routingCodeParamName, receiptNumberParamName, bankNumberParamName, DepositsApiConstants.amountParamName,
|
||||
DepositsApiConstants.accountIdParamName, DepositsApiConstants.dateParamName, DepositsApiConstants.submittedOnDateParamName,
|
||||
DepositsApiConstants.lienTransaction, DepositsApiConstants.isManualTransaction, DepositsApiConstants.chargesPaidByData,
|
||||
DepositsApiConstants.accountNoParamName, DepositsApiConstants.noteParamName));
|
||||
|
||||
private static final Set<String> DEPOSIT_ACCOUNT_RECOMMENDED_DEPOSIT_AMOUNT_UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(
|
||||
Arrays.asList(DepositsApiConstants.localeParamName, DepositsApiConstants.dateFormatParamName,
|
||||
DepositsApiConstants.mandatoryRecommendedDepositAmountParamName, DepositsApiConstants.effectiveDateParamName));
|
||||
|
||||
private static final Set<String> DEPOSIT_ACCOUNT_CLOSE_REQUEST_DATA_PARAMETERS = new HashSet<>(
|
||||
Arrays.asList(DepositsApiConstants.localeParamName, DepositsApiConstants.dateFormatParamName, closedOnDateParamName,
|
||||
DepositsApiConstants.noteParamName, onAccountClosureIdParamName, paymentTypeIdParamName,
|
||||
transactionAccountNumberParamName, checkNumberParamName, routingCodeParamName, receiptNumberParamName,
|
||||
bankNumberParamName, DepositsApiConstants.transferDescriptionParamName, toSavingsAccountIdParamName));
|
||||
|
||||
private static final Set<String> DEPOSIT_ACCOUNT_PRE_MATURE_CALCULATION_REQUEST_DATA_PARAMETERS = new HashSet<>(
|
||||
Arrays.asList(DepositsApiConstants.localeParamName, DepositsApiConstants.dateFormatParamName, closedOnDateParamName));
|
||||
|
||||
@Autowired
|
||||
public DepositAccountTransactionDataValidator(final FromJsonHelper fromApiJsonHelper) {
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
}
|
||||
|
||||
public void validate(final JsonCommand command, DepositAccountType depositAccountType) {
|
||||
|
||||
final String json = command.json();
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, DEPOSIT_ACCOUNT_TRANSACTION_REQUEST_DATA_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(depositAccountType.resourceName());
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(transactionDateParamName, element);
|
||||
baseDataValidator.reset().parameter(transactionDateParamName).value(transactionDate).notNull();
|
||||
|
||||
final BigDecimal transactionAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(transactionAmountParamName, element);
|
||||
baseDataValidator.reset().parameter(transactionAmountParamName).value(transactionAmount).notNull().positiveAmount();
|
||||
|
||||
// Validate all string payment detail fields for max length
|
||||
final Integer paymentTypeId = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(paymentTypeIdParamName, element);
|
||||
baseDataValidator.reset().parameter(paymentTypeIdParamName).value(paymentTypeId).ignoreIfNull().integerGreaterThanZero();
|
||||
final Set<String> paymentDetailParameters = new HashSet<>(Arrays.asList(transactionAccountNumberParamName, checkNumberParamName,
|
||||
routingCodeParamName, receiptNumberParamName, bankNumberParamName));
|
||||
for (final String paymentDetailParameterName : paymentDetailParameters) {
|
||||
final String paymentDetailParameterValue = this.fromApiJsonHelper.extractStringNamed(paymentDetailParameterName, element);
|
||||
baseDataValidator.reset().parameter(paymentDetailParameterName).value(paymentDetailParameterValue).ignoreIfNull()
|
||||
.notExceedingLengthOf(50);
|
||||
}
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
public void validateDepositAmountUpdate(final JsonCommand command) {
|
||||
final String json = command.json();
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
|
||||
DEPOSIT_ACCOUNT_RECOMMENDED_DEPOSIT_AMOUNT_UPDATE_REQUEST_DATA_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME);
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final LocalDate effectiveDate = this.fromApiJsonHelper.extractLocalDateNamed(DepositsApiConstants.effectiveDateParamName, element);
|
||||
baseDataValidator.reset().parameter(DepositsApiConstants.effectiveDateParamName).value(effectiveDate).notNull();
|
||||
|
||||
final BigDecimal mandatoryRecommendedDepositAmount = this.fromApiJsonHelper
|
||||
.extractBigDecimalWithLocaleNamed(DepositsApiConstants.mandatoryRecommendedDepositAmountParamName, element);
|
||||
baseDataValidator.reset().parameter(DepositsApiConstants.mandatoryRecommendedDepositAmountParamName)
|
||||
.value(mandatoryRecommendedDepositAmount).notNull().positiveAmount();
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
public void validateActivation(final JsonCommand command) {
|
||||
final String json = command.json();
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
|
||||
SavingsAccountConstant.SAVINGS_ACCOUNT_ACTIVATION_REQUEST_DATA_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME);
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final LocalDate activationDate = this.fromApiJsonHelper.extractLocalDateNamed(activatedOnDateParamName, element);
|
||||
baseDataValidator.reset().parameter(activatedOnDateParamName).value(activationDate).notNull();
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
public void validatePreMatureAmountCalculation(final String json, final DepositAccountType depositAccountType) {
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
|
||||
DEPOSIT_ACCOUNT_PRE_MATURE_CALCULATION_REQUEST_DATA_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(depositAccountType.resourceName());
|
||||
|
||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||
|
||||
final LocalDate closeDate = this.fromApiJsonHelper.extractLocalDateNamed(closedOnDateParamName, element);
|
||||
baseDataValidator.reset().parameter(closedOnDateParamName).value(closeDate).notNull();
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
public void validateClosing(final JsonCommand command, DepositAccountType depositAccountType, final boolean isPreMatureClose) {
|
||||
final String json = command.json();
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, DEPOSIT_ACCOUNT_CLOSE_REQUEST_DATA_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(depositAccountType.resourceName());
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final LocalDate activationDate = this.fromApiJsonHelper.extractLocalDateNamed(closedOnDateParamName, element);
|
||||
baseDataValidator.reset().parameter(closedOnDateParamName).value(activationDate).notNull();
|
||||
|
||||
final Integer onAccountClosureId = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(onAccountClosureIdParamName, element);
|
||||
baseDataValidator.reset().parameter(onAccountClosureIdParamName).value(onAccountClosureId).notBlank()
|
||||
.isOneOfTheseValues(DepositAccountOnClosureType.integerValues());
|
||||
|
||||
if (onAccountClosureId != null) {
|
||||
final DepositAccountOnClosureType accountOnClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId);
|
||||
if (accountOnClosureType.isTransferToSavings()) {
|
||||
final Long toSavingsAccountId = this.fromApiJsonHelper.extractLongNamed(toSavingsAccountIdParamName, element);
|
||||
baseDataValidator.reset().parameter(toSavingsAccountIdParamName).value(toSavingsAccountId)
|
||||
.cantBeBlankWhenParameterProvidedIs(onAccountClosureIdParamName,
|
||||
DepositAccountOnClosureType.fromInt(onAccountClosureId).getCode());
|
||||
} else if (accountOnClosureType.isReinvest() && isPreMatureClose) {
|
||||
baseDataValidator.reset().parameter(onAccountClosureIdParamName).value(onAccountClosureId)
|
||||
.failWithCode("reinvest.not.allowed", "Re-Invest is not supported for account pre mature close");
|
||||
}
|
||||
}
|
||||
|
||||
// Validate all string payment detail fields for max length
|
||||
final Integer paymentTypeId = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(paymentTypeIdParamName, element);
|
||||
baseDataValidator.reset().parameter(paymentTypeIdParamName).value(paymentTypeId).ignoreIfNull().integerGreaterThanZero();
|
||||
final Set<String> paymentDetailParameters = new HashSet<>(Arrays.asList(transactionAccountNumberParamName, checkNumberParamName,
|
||||
routingCodeParamName, receiptNumberParamName, bankNumberParamName));
|
||||
for (final String paymentDetailParameterName : paymentDetailParameters) {
|
||||
final String paymentDetailParameterValue = this.fromApiJsonHelper.extractStringNamed(paymentDetailParameterName, element);
|
||||
baseDataValidator.reset().parameter(paymentDetailParameterName).value(paymentDetailParameterValue).ignoreIfNull()
|
||||
.notExceedingLengthOf(50);
|
||||
}
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
//
|
||||
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
|
||||
dataValidationErrors);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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.savings.data;
|
||||
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.activatedOnDateParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.bankNumberParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.checkNumberParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.closedOnDateParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.onAccountClosureIdParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.paymentTypeIdParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.receiptNumberParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.routingCodeParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.toSavingsAccountIdParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transactionAccountNumberParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transactionAmountParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transactionDateParamName;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import java.lang.reflect.Type;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
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.exception.InvalidJsonException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.apache.fineract.portfolio.savings.DepositAccountOnClosureType;
|
||||
import org.apache.fineract.portfolio.savings.DepositAccountType;
|
||||
import org.apache.fineract.portfolio.savings.DepositsApiConstants;
|
||||
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class DepositAccountTransactionDataValidator {
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
|
||||
private static final Set<String> DEPOSIT_ACCOUNT_TRANSACTION_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(
|
||||
DepositsApiConstants.localeParamName, DepositsApiConstants.dateFormatParamName, transactionDateParamName,
|
||||
transactionAmountParamName, paymentTypeIdParamName, transactionAccountNumberParamName, checkNumberParamName,
|
||||
routingCodeParamName, receiptNumberParamName, bankNumberParamName, DepositsApiConstants.amountParamName,
|
||||
DepositsApiConstants.accountIdParamName, DepositsApiConstants.dateParamName, DepositsApiConstants.submittedOnDateParamName,
|
||||
DepositsApiConstants.lienTransaction, DepositsApiConstants.isManualTransaction, DepositsApiConstants.chargesPaidByData,
|
||||
DepositsApiConstants.accountNoParamName, DepositsApiConstants.noteParamName, "denominationBreakdown"));
|
||||
|
||||
private static final Set<String> DEPOSIT_ACCOUNT_RECOMMENDED_DEPOSIT_AMOUNT_UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(
|
||||
Arrays.asList(DepositsApiConstants.localeParamName, DepositsApiConstants.dateFormatParamName,
|
||||
DepositsApiConstants.mandatoryRecommendedDepositAmountParamName, DepositsApiConstants.effectiveDateParamName));
|
||||
|
||||
private static final Set<String> DEPOSIT_ACCOUNT_CLOSE_REQUEST_DATA_PARAMETERS = new HashSet<>(
|
||||
Arrays.asList(DepositsApiConstants.localeParamName, DepositsApiConstants.dateFormatParamName, closedOnDateParamName,
|
||||
DepositsApiConstants.noteParamName, onAccountClosureIdParamName, paymentTypeIdParamName,
|
||||
transactionAccountNumberParamName, checkNumberParamName, routingCodeParamName, receiptNumberParamName,
|
||||
bankNumberParamName, DepositsApiConstants.transferDescriptionParamName, toSavingsAccountIdParamName));
|
||||
|
||||
private static final Set<String> DEPOSIT_ACCOUNT_PRE_MATURE_CALCULATION_REQUEST_DATA_PARAMETERS = new HashSet<>(
|
||||
Arrays.asList(DepositsApiConstants.localeParamName, DepositsApiConstants.dateFormatParamName, closedOnDateParamName));
|
||||
|
||||
@Autowired
|
||||
public DepositAccountTransactionDataValidator(final FromJsonHelper fromApiJsonHelper) {
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
}
|
||||
|
||||
public void validate(final JsonCommand command, DepositAccountType depositAccountType) {
|
||||
|
||||
final String json = command.json();
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, DEPOSIT_ACCOUNT_TRANSACTION_REQUEST_DATA_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(depositAccountType.resourceName());
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(transactionDateParamName, element);
|
||||
baseDataValidator.reset().parameter(transactionDateParamName).value(transactionDate).notNull();
|
||||
|
||||
final BigDecimal transactionAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(transactionAmountParamName, element);
|
||||
baseDataValidator.reset().parameter(transactionAmountParamName).value(transactionAmount).notNull().positiveAmount();
|
||||
|
||||
// Validate all string payment detail fields for max length
|
||||
final Integer paymentTypeId = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(paymentTypeIdParamName, element);
|
||||
baseDataValidator.reset().parameter(paymentTypeIdParamName).value(paymentTypeId).ignoreIfNull().integerGreaterThanZero();
|
||||
final Set<String> paymentDetailParameters = new HashSet<>(Arrays.asList(transactionAccountNumberParamName, checkNumberParamName,
|
||||
routingCodeParamName, receiptNumberParamName, bankNumberParamName));
|
||||
for (final String paymentDetailParameterName : paymentDetailParameters) {
|
||||
final String paymentDetailParameterValue = this.fromApiJsonHelper.extractStringNamed(paymentDetailParameterName, element);
|
||||
baseDataValidator.reset().parameter(paymentDetailParameterName).value(paymentDetailParameterValue).ignoreIfNull()
|
||||
.notExceedingLengthOf(50);
|
||||
}
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
public void validateDepositAmountUpdate(final JsonCommand command) {
|
||||
final String json = command.json();
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
|
||||
DEPOSIT_ACCOUNT_RECOMMENDED_DEPOSIT_AMOUNT_UPDATE_REQUEST_DATA_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME);
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final LocalDate effectiveDate = this.fromApiJsonHelper.extractLocalDateNamed(DepositsApiConstants.effectiveDateParamName, element);
|
||||
baseDataValidator.reset().parameter(DepositsApiConstants.effectiveDateParamName).value(effectiveDate).notNull();
|
||||
|
||||
final BigDecimal mandatoryRecommendedDepositAmount = this.fromApiJsonHelper
|
||||
.extractBigDecimalWithLocaleNamed(DepositsApiConstants.mandatoryRecommendedDepositAmountParamName, element);
|
||||
baseDataValidator.reset().parameter(DepositsApiConstants.mandatoryRecommendedDepositAmountParamName)
|
||||
.value(mandatoryRecommendedDepositAmount).notNull().positiveAmount();
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
public void validateActivation(final JsonCommand command) {
|
||||
final String json = command.json();
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
|
||||
SavingsAccountConstant.SAVINGS_ACCOUNT_ACTIVATION_REQUEST_DATA_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME);
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final LocalDate activationDate = this.fromApiJsonHelper.extractLocalDateNamed(activatedOnDateParamName, element);
|
||||
baseDataValidator.reset().parameter(activatedOnDateParamName).value(activationDate).notNull();
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
public void validatePreMatureAmountCalculation(final String json, final DepositAccountType depositAccountType) {
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
|
||||
DEPOSIT_ACCOUNT_PRE_MATURE_CALCULATION_REQUEST_DATA_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(depositAccountType.resourceName());
|
||||
|
||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||
|
||||
final LocalDate closeDate = this.fromApiJsonHelper.extractLocalDateNamed(closedOnDateParamName, element);
|
||||
baseDataValidator.reset().parameter(closedOnDateParamName).value(closeDate).notNull();
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
public void validateClosing(final JsonCommand command, DepositAccountType depositAccountType, final boolean isPreMatureClose) {
|
||||
final String json = command.json();
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, DEPOSIT_ACCOUNT_CLOSE_REQUEST_DATA_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(depositAccountType.resourceName());
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final LocalDate activationDate = this.fromApiJsonHelper.extractLocalDateNamed(closedOnDateParamName, element);
|
||||
baseDataValidator.reset().parameter(closedOnDateParamName).value(activationDate).notNull();
|
||||
|
||||
final Integer onAccountClosureId = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(onAccountClosureIdParamName, element);
|
||||
baseDataValidator.reset().parameter(onAccountClosureIdParamName).value(onAccountClosureId).notBlank()
|
||||
.isOneOfTheseValues(DepositAccountOnClosureType.integerValues());
|
||||
|
||||
if (onAccountClosureId != null) {
|
||||
final DepositAccountOnClosureType accountOnClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId);
|
||||
if (accountOnClosureType.isTransferToSavings()) {
|
||||
final Long toSavingsAccountId = this.fromApiJsonHelper.extractLongNamed(toSavingsAccountIdParamName, element);
|
||||
baseDataValidator.reset().parameter(toSavingsAccountIdParamName).value(toSavingsAccountId)
|
||||
.cantBeBlankWhenParameterProvidedIs(onAccountClosureIdParamName,
|
||||
DepositAccountOnClosureType.fromInt(onAccountClosureId).getCode());
|
||||
} else if (accountOnClosureType.isReinvest() && isPreMatureClose) {
|
||||
baseDataValidator.reset().parameter(onAccountClosureIdParamName).value(onAccountClosureId)
|
||||
.failWithCode("reinvest.not.allowed", "Re-Invest is not supported for account pre mature close");
|
||||
}
|
||||
}
|
||||
|
||||
// Validate all string payment detail fields for max length
|
||||
final Integer paymentTypeId = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(paymentTypeIdParamName, element);
|
||||
baseDataValidator.reset().parameter(paymentTypeIdParamName).value(paymentTypeId).ignoreIfNull().integerGreaterThanZero();
|
||||
final Set<String> paymentDetailParameters = new HashSet<>(Arrays.asList(transactionAccountNumberParamName, checkNumberParamName,
|
||||
routingCodeParamName, receiptNumberParamName, bankNumberParamName));
|
||||
for (final String paymentDetailParameterName : paymentDetailParameters) {
|
||||
final String paymentDetailParameterValue = this.fromApiJsonHelper.extractStringNamed(paymentDetailParameterName, element);
|
||||
baseDataValidator.reset().parameter(paymentDetailParameterName).value(paymentDetailParameterValue).ignoreIfNull()
|
||||
.notExceedingLengthOf(50);
|
||||
}
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
//
|
||||
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
|
||||
dataValidationErrors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+15
@@ -43,6 +43,7 @@ import org.apache.fineract.portfolio.currentaccount.exception.CurrentAccountNotF
|
||||
import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountTransactionWritePlatformService;
|
||||
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProduct;
|
||||
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductRepository;
|
||||
import org.apache.fineract.portfolio.denominationmaster.service.DenominationBreakdownWriter;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -60,6 +61,7 @@ public class CurrentAccountTransactionWritePlatformServiceImpl implements Curren
|
||||
private final CurrentProductRepository currentProductRepository;
|
||||
private final ProductToGLAccountMappingRepository productToGLAccountMappingRepository;
|
||||
private final JournalEntryRepository journalEntryRepository;
|
||||
private final DenominationBreakdownWriter denominationBreakdownWriter;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
@@ -74,6 +76,7 @@ public class CurrentAccountTransactionWritePlatformServiceImpl implements Curren
|
||||
|
||||
final BigDecimal amount = command.bigDecimalValueOfParameterNamed("transactionAmount");
|
||||
validatePositiveAmount(amount, accountId, "deposit");
|
||||
this.denominationBreakdownWriter.validateBreakdown(command, amount, "DEPOSIT");
|
||||
|
||||
final String refNo = command.stringValueOfParameterNamed("accountNumber");
|
||||
|
||||
@@ -85,6 +88,11 @@ public class CurrentAccountTransactionWritePlatformServiceImpl implements Curren
|
||||
|
||||
currentAccountTransactionRepository.saveAndFlush(txn);
|
||||
|
||||
this.denominationBreakdownWriter.save(command, txn.getId(), account.getId(), account.getAccountNo(),
|
||||
account.getClient() != null ? account.getClient().getId() : null,
|
||||
account.getClient() != null ? account.getClient().getCifNumber() : null, txnDate,
|
||||
"DEPOSIT");
|
||||
|
||||
account.setAccountBalanceDerived(newBalance);
|
||||
account.setTotalDepositsDerived(safeBalance(account.getTotalDepositsDerived()).add(amount));
|
||||
currentAccountRepository.save(account);
|
||||
@@ -135,6 +143,13 @@ public class CurrentAccountTransactionWritePlatformServiceImpl implements Curren
|
||||
account.setTotalWithdrawalsDerived(safeBalance(account.getTotalWithdrawalsDerived()).add(amount));
|
||||
currentAccountRepository.save(account);
|
||||
|
||||
// Save denomination breakdown for WITHDRAWAL if provided
|
||||
this.denominationBreakdownWriter.validateBreakdown(command, amount, "WITHDRAWAL");
|
||||
this.denominationBreakdownWriter.save(command, txn.getId(), account.getId(), account.getAccountNo(),
|
||||
account.getClient() != null ? account.getClient().getId() : null,
|
||||
account.getClient() != null ? account.getClient().getCifNumber() : null, txnDate,
|
||||
"WITHDRAWAL");
|
||||
|
||||
// Withdrawal GL: DR SAVINGS_CONTROL (liability down), CR SAVINGS_REFERENCE (cash out)
|
||||
postTransactionGl(account, txn, JournalEntryType.CREDIT, JournalEntryType.DEBIT, "Current Account Withdrawal");
|
||||
|
||||
|
||||
+1419
-1401
File diff suppressed because it is too large
Load Diff
+13
-36
@@ -88,8 +88,7 @@ import org.apache.fineract.portfolio.account.service.AccountAssociationsReadPlat
|
||||
import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService;
|
||||
import org.apache.fineract.portfolio.charge.domain.Charge;
|
||||
import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.denominationmaster.domain.SavingsTxnDenomination;
|
||||
import org.apache.fineract.portfolio.denominationmaster.domain.SavingsTxnDenominationRepository;
|
||||
import org.apache.fineract.portfolio.denominationmaster.service.DenominationBreakdownWriter;
|
||||
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
|
||||
import org.apache.fineract.portfolio.client.domain.Client;
|
||||
import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
|
||||
@@ -170,7 +169,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
|
||||
private final SavingsAccountInterestPostingService savingsAccountInterestPostingService;
|
||||
private final ErrorHandler errorHandler;
|
||||
private final FacilityChargeAutoApplyService facilityChargeAutoApplyService;
|
||||
private final SavingsTxnDenominationRepository savingsTxnDenominationRepository;
|
||||
private final DenominationBreakdownWriter denominationBreakdownWriter;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
@@ -329,6 +328,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
|
||||
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
|
||||
|
||||
this.savingsAccountTransactionDataValidator.validateTransactionWithPivotDate(transactionDate, account);
|
||||
this.denominationBreakdownWriter.validateBreakdown(command, transactionAmount, "DEPOSIT");
|
||||
|
||||
final Map<String, Object> changes = new LinkedHashMap<>();
|
||||
final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
|
||||
@@ -337,38 +337,10 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
|
||||
final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(account, fmt, transactionDate,
|
||||
transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction, backdatedTxnsAllowedTill);
|
||||
|
||||
// Save denomination breakdown if provided
|
||||
if (command.parameterExists("denominationBreakdown")) {
|
||||
final JsonArray denominationBreakdown = command.arrayOfParameterNamed("denominationBreakdown");
|
||||
if (denominationBreakdown != null && denominationBreakdown.size() > 0) {
|
||||
final Long clientId = account.clientId();
|
||||
final String accountNo = account.getAccountNumber();
|
||||
final String cifNo = account.getExternalId() != null ? account.getExternalId().getValue() : null;
|
||||
for (JsonElement entry : denominationBreakdown) {
|
||||
final com.google.gson.JsonObject obj = entry.getAsJsonObject();
|
||||
if (!obj.has("denominationId") || !obj.has("count")) continue;
|
||||
final long denominationId = obj.get("denominationId").getAsLong();
|
||||
final int count = obj.get("count").getAsInt();
|
||||
if (count <= 0) continue;
|
||||
final int denominationValue = obj.has("denominationValue") ? obj.get("denominationValue").getAsInt() : 0;
|
||||
final BigDecimal total = BigDecimal.valueOf((long) denominationValue * count);
|
||||
final SavingsTxnDenomination row = SavingsTxnDenomination.builder()
|
||||
.txnId(deposit.getId())
|
||||
.savingsAccountId(account.getId())
|
||||
.accountNo(accountNo)
|
||||
.clientId(clientId)
|
||||
.cifNo(cifNo)
|
||||
.denominationId(denominationId)
|
||||
.denominationValue(denominationValue)
|
||||
.count(count)
|
||||
.totalAmount(total)
|
||||
.txnDate(transactionDate)
|
||||
.createdAt(LocalDateTime.now())
|
||||
.build();
|
||||
this.savingsTxnDenominationRepository.save(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Save denomination breakdown for DEPOSIT via shared writer
|
||||
this.denominationBreakdownWriter.save(command, deposit.getId(), account.getId(), account.getAccountNumber(),
|
||||
account.clientId(), account.getClient() != null ? account.getClient().getCifNumber() : null,
|
||||
transactionDate, "DEPOSIT");
|
||||
|
||||
if (isGsim && (deposit.getId() != null)) {
|
||||
|
||||
@@ -450,9 +422,14 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
|
||||
BigDecimal currentBalance = gsim.getParentDeposit().subtract(transactionAmount);
|
||||
gsim.setParentDeposit(currentBalance);
|
||||
gsimRepository.save(gsim);
|
||||
|
||||
}
|
||||
|
||||
// Validate and save denomination breakdown for WITHDRAWAL via shared writer
|
||||
this.denominationBreakdownWriter.validateBreakdown(command, transactionAmount, "WITHDRAWAL");
|
||||
this.denominationBreakdownWriter.save(command, withdrawal.getId(), account.getId(), account.getAccountNumber(),
|
||||
account.clientId(), account.getClient() != null ? account.getClient().getCifNumber() : null,
|
||||
transactionDate, "WITHDRAWAL");
|
||||
|
||||
final String noteText = command.stringValueOfParameterNamed("note");
|
||||
if (StringUtils.isNotBlank(noteText)) {
|
||||
final Note note = Note.savingsTransactionNote(account, withdrawal, noteText);
|
||||
|
||||
+7
-4
@@ -108,6 +108,7 @@ import org.apache.fineract.portfolio.savings.service.DepositAccountPreMatureCalc
|
||||
import org.apache.fineract.portfolio.savings.service.DepositAccountPreMatureCalculationPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.savings.service.DepositAccountReadPlatformService;
|
||||
import org.apache.fineract.portfolio.savings.service.DepositAccountReadPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.denominationmaster.service.DenominationBreakdownWriter;
|
||||
import org.apache.fineract.portfolio.savings.service.DepositAccountWritePlatformService;
|
||||
import org.apache.fineract.portfolio.savings.service.DepositAccountWritePlatformServiceJpaRepositoryImpl;
|
||||
import org.apache.fineract.portfolio.savings.service.DepositApplicationProcessWritePlatformService;
|
||||
@@ -268,7 +269,8 @@ public class SavingsConfiguration {
|
||||
DepositAccountReadPlatformService depositAccountReadPlatformService, CalendarInstanceRepository calendarInstanceRepository,
|
||||
ConfigurationDomainService configurationDomainService, HolidayRepositoryWrapper holidayRepository,
|
||||
WorkingDaysRepositoryWrapper workingDaysRepository,
|
||||
DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository
|
||||
DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository,
|
||||
DenominationBreakdownWriter denominationBreakdownWriter
|
||||
|
||||
) {
|
||||
return new DepositAccountWritePlatformServiceJpaRepositoryImpl(context, savingAccountRepositoryWrapper,
|
||||
@@ -277,7 +279,8 @@ public class SavingsConfiguration {
|
||||
journalEntryWritePlatformService, depositAccountDomainService, noteRepository, accountTransfersReadPlatformService,
|
||||
chargeRepository, savingsAccountChargeRepository, accountAssociationsReadPlatformService,
|
||||
accountTransfersWritePlatformService, depositAccountReadPlatformService, calendarInstanceRepository,
|
||||
configurationDomainService, holidayRepository, workingDaysRepository, depositAccountOnHoldTransactionRepository);
|
||||
configurationDomainService, holidayRepository, workingDaysRepository, depositAccountOnHoldTransactionRepository,
|
||||
denominationBreakdownWriter);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -426,7 +429,7 @@ public class SavingsConfiguration {
|
||||
StandingInstructionRepository standingInstructionRepository, BusinessEventNotifierService businessEventNotifierService,
|
||||
GSIMRepositoy gsimRepository, SavingsAccountInterestPostingService savingsAccountInterestPostingService,
|
||||
ErrorHandler errorHandler, FacilityChargeAutoApplyService facilityChargeAutoApplyService,
|
||||
SavingsTxnDenominationRepository savingsTxnDenominationRepository) {
|
||||
DenominationBreakdownWriter denominationBreakdownWriter) {
|
||||
return new SavingsAccountWritePlatformServiceJpaRepositoryImpl(context, fromApiJsonDeserializer, savingAccountRepositoryWrapper,
|
||||
staffRepository, savingsAccountTransactionRepository, savingAccountAssembler, savingsAccountTransactionDataValidator,
|
||||
savingsAccountChargeDataValidator, paymentDetailWritePlatformService, journalEntryWritePlatformService,
|
||||
@@ -434,7 +437,7 @@ public class SavingsConfiguration {
|
||||
chargeRepository, savingsAccountChargeRepository, holidayRepository, workingDaysRepository, configurationDomainService,
|
||||
depositAccountOnHoldTransactionRepository, entityDatatableChecksWritePlatformService, appuserRepository,
|
||||
standingInstructionRepository, businessEventNotifierService, gsimRepository, savingsAccountInterestPostingService,
|
||||
errorHandler, facilityChargeAutoApplyService, savingsTxnDenominationRepository);
|
||||
errorHandler, facilityChargeAutoApplyService, denominationBreakdownWriter);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -319,4 +319,5 @@
|
||||
<include file="parts/0300_rename_fd_base_interest_rate_tables_to_cbs.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0301_add_current_account_to_transfer_details.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0012_denomination_master.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0013_cash_exchange.xml" relativeToChangelogFile="true"/>
|
||||
</databaseChangeLog>
|
||||
|
||||
+24
@@ -177,4 +177,28 @@
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
|
||||
<!-- =====================================================================
|
||||
Migration: Add txn_type, officer_id, officer_username to
|
||||
m_savings_txn_denomination.
|
||||
txn_type distinguishes DEPOSIT from WITHDRAWAL.
|
||||
officer_id / officer_username record who performed the transaction.
|
||||
===================================================================== -->
|
||||
<changeSet id="savings_txn_denomination_add_txn_type_officer_001" author="CBS">
|
||||
<addColumn tableName="m_savings_txn_denomination">
|
||||
<column name="txn_type" type="VARCHAR(20)" defaultValue="DEPOSIT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="officer_id" type="BIGINT"/>
|
||||
<column name="officer_username" type="VARCHAR(100)"/>
|
||||
</addColumn>
|
||||
<createIndex tableName="m_savings_txn_denomination"
|
||||
indexName="idx_savings_txn_denom_txntype">
|
||||
<column name="txn_type"/>
|
||||
</createIndex>
|
||||
<createIndex tableName="m_savings_txn_denomination"
|
||||
indexName="idx_savings_txn_denom_officer">
|
||||
<column name="officer_id"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||
|
||||
<changeSet id="cash_exchange_table_001" author="CBS">
|
||||
<createTable tableName="m_cash_exchange">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="account_id" type="BIGINT"/>
|
||||
<column name="account_no" type="VARCHAR(50)"/>
|
||||
<column name="client_id" type="BIGINT"/>
|
||||
<column name="exchange_amount" type="DECIMAL(19,6)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="exchange_date" type="DATE"/>
|
||||
<column name="note" type="VARCHAR(500)"/>
|
||||
<column name="performed_by" type="BIGINT"/>
|
||||
<column name="performed_by_username" type="VARCHAR(100)"/>
|
||||
<column name="created_at" type="TIMESTAMP" defaultValueComputed="CURRENT_TIMESTAMP"/>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="cash_exchange_detail_table_001" author="CBS">
|
||||
<createTable tableName="m_cash_exchange_detail">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="exchange_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="direction" type="VARCHAR(10)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="denomination_id" type="BIGINT"/>
|
||||
<column name="denomination_value" type="INT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="count" type="INT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="total_amount" type="DECIMAL(19,6)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createIndex tableName="m_cash_exchange_detail" indexName="idx_cash_exchange_detail_eid">
|
||||
<column name="exchange_id"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
Reference in New Issue
Block a user