feat: Denomination Master backend - master, audit, soft delete, txn breakdown

- m_denomination_master CRUD API with duplicate (denomination+kind)
  validation, soft delete + restore, and a full audit trail
  (m_denomination_master_audit logs CREATE/UPDATE/ACTIVATE/DEACTIVATE/
  DELETE/RESTORE with snapshot, changes, user and timestamp)
- m_savings_txn_denomination: persist per-transaction cash denomination
  breakdown on savings deposit; allow denominationBreakdown request param
- Liquibase 0012_denomination_master registered in tenant changelog

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jaanu423
2026-05-27 11:16:52 +05:30
co-authored by Claude Opus 4.7
parent 7d4f409202
commit ee915e395a
12 changed files with 2765 additions and 2039 deletions
@@ -0,0 +1,232 @@
package org.apache.fineract.portfolio.denominationmaster.api;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
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.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
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.denominationmaster.domain.DenominationMaster;
import org.apache.fineract.portfolio.denominationmaster.domain.DenominationMasterAudit;
import org.apache.fineract.portfolio.denominationmaster.domain.DenominationMasterAuditRepository;
import org.apache.fineract.portfolio.denominationmaster.domain.DenominationMasterRepository;
import org.apache.fineract.useradministration.domain.AppUser;
import org.springframework.stereotype.Component;
@Path("/v1/denominationmaster")
@Component
@RequiredArgsConstructor
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public class DenominationMasterApiResource {
private final DenominationMasterRepository repository;
private final DenominationMasterAuditRepository auditRepository;
private final PlatformSecurityContext context;
private final Gson gson = new Gson();
@GET
public String getAll() {
List<DenominationMaster> list = repository.findByDeletedFalseOrderByKindDescDenominationDesc();
return gson.toJson(list.stream().map(this::toMap).collect(Collectors.toList()));
}
@GET
@Path("active")
public String getActive() {
List<DenominationMaster> list = repository.findByIsActiveTrueAndDeletedFalseOrderByKindDescDenominationDesc();
return gson.toJson(list.stream().map(this::toMap).collect(Collectors.toList()));
}
@GET
@Path("deleted")
public String getDeleted() {
List<DenominationMaster> list = repository.findByDeletedTrueOrderByKindDescDenominationDesc();
return gson.toJson(list.stream().map(this::toMap).collect(Collectors.toList()));
}
@GET
@Path("audit")
public String getAllAudit() {
List<DenominationMasterAudit> list = auditRepository.findAllByOrderByPerformedOnDesc();
return gson.toJson(list.stream().map(this::auditToMap).collect(Collectors.toList()));
}
@GET
@Path("{id}/audit")
public String getAuditForDenomination(@PathParam("id") Long id) {
List<DenominationMasterAudit> list = auditRepository.findByDenominationIdOrderByPerformedOnDesc(id);
return gson.toJson(list.stream().map(this::auditToMap).collect(Collectors.toList()));
}
@POST
public String create(String body) {
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
final int denomination = json.get("denomination").getAsInt();
final String kind = json.get("kind").getAsString();
if (repository.existsByDenominationAndKindAndDeletedFalse(denomination, kind)) {
throw new PlatformDataIntegrityException("error.msg.denomination.duplicate",
"A " + kind + " of denomination " + denomination + " already exists.", "denomination", denomination);
}
DenominationMaster dm = DenominationMaster.create(denomination, kind,
json.has("description") ? json.get("description").getAsString() : null,
json.has("isActive") ? json.get("isActive").getAsBoolean() : true,
json.has("introducedOn") ? LocalDate.parse(json.get("introducedOn").getAsString()) : null, null);
DenominationMaster saved = repository.save(dm);
writeAudit("CREATE", saved, null);
JsonObject result = new JsonObject();
result.addProperty("resourceId", saved.getId());
return gson.toJson(result);
}
@PUT
@Path("{id}")
public String update(@PathParam("id") Long id, @QueryParam("command") String command, String body) {
DenominationMaster dm = repository.findById(id).orElseThrow(() -> new RuntimeException("Denomination not found: " + id));
if ("activate".equals(command)) {
dm.setIsActive(true);
dm.setDiscontinuedOn(null);
repository.save(dm);
writeAudit("ACTIVATE", dm, null);
} else if ("deactivate".equals(command)) {
dm.setIsActive(false);
dm.setDiscontinuedOn(LocalDate.now());
repository.save(dm);
writeAudit("DEACTIVATE", dm, null);
} else if ("restore".equals(command)) {
if (repository.existsByDenominationAndKindAndDeletedFalseAndIdNot(dm.getDenomination(), dm.getKind(), id)) {
throw new PlatformDataIntegrityException("error.msg.denomination.duplicate.restore",
"Cannot restore: a " + dm.getKind() + " of denomination " + dm.getDenomination() + " already exists.",
"denomination", dm.getDenomination());
}
dm.setDeleted(false);
repository.save(dm);
writeAudit("RESTORE", dm, null);
} else {
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
final int effectiveDenomination = json.has("denomination") ? json.get("denomination").getAsInt() : dm.getDenomination();
final String effectiveKind = json.has("kind") ? json.get("kind").getAsString() : dm.getKind();
if (repository.existsByDenominationAndKindAndDeletedFalseAndIdNot(effectiveDenomination, effectiveKind, id)) {
throw new PlatformDataIntegrityException("error.msg.denomination.duplicate",
"A " + effectiveKind + " of denomination " + effectiveDenomination + " already exists.", "denomination",
effectiveDenomination);
}
List<String> changes = new ArrayList<>();
if (json.has("denomination")) {
int newVal = json.get("denomination").getAsInt();
if (!java.util.Objects.equals(dm.getDenomination(), newVal)) {
changes.add("denomination: " + dm.getDenomination() + " -> " + newVal);
}
dm.setDenomination(newVal);
}
if (json.has("kind")) {
String newVal = json.get("kind").getAsString();
if (!java.util.Objects.equals(dm.getKind(), newVal)) {
changes.add("kind: " + dm.getKind() + " -> " + newVal);
}
dm.setKind(newVal);
}
if (json.has("description")) {
String newVal = json.get("description").getAsString();
if (!java.util.Objects.equals(dm.getDescription(), newVal)) {
changes.add("description: " + dm.getDescription() + " -> " + newVal);
}
dm.setDescription(newVal);
}
if (json.has("isActive")) {
boolean newVal = json.get("isActive").getAsBoolean();
if (!java.util.Objects.equals(dm.getIsActive(), newVal)) {
changes.add("isActive: " + dm.getIsActive() + " -> " + newVal);
}
dm.setIsActive(newVal);
}
if (json.has("introducedOn")) {
LocalDate newVal = LocalDate.parse(json.get("introducedOn").getAsString());
if (!java.util.Objects.equals(dm.getIntroducedOn(), newVal)) {
changes.add("introducedOn: " + dm.getIntroducedOn() + " -> " + newVal);
}
dm.setIntroducedOn(newVal);
}
repository.save(dm);
writeAudit("UPDATE", dm, changes.isEmpty() ? null : String.join("; ", changes));
}
JsonObject result = new JsonObject();
result.addProperty("resourceId", id);
return gson.toJson(result);
}
@DELETE
@Path("{id}")
public String delete(@PathParam("id") Long id) {
DenominationMaster dm = repository.findById(id).orElseThrow(() -> new RuntimeException("Denomination not found: " + id));
// soft delete: flag the row instead of removing it, so it can be restored and history is preserved
dm.setDeleted(true);
dm.setIsActive(false);
repository.save(dm);
writeAudit("DELETE", dm, null);
JsonObject result = new JsonObject();
result.addProperty("resourceId", id);
return gson.toJson(result);
}
private void writeAudit(final String action, final DenominationMaster dm, final String changes) {
Long userId = null;
String username = null;
try {
AppUser user = this.context.authenticatedUser();
if (user != null) {
userId = user.getId();
username = user.getUsername();
}
} catch (Exception e) {
// audit should never block the main operation if the user cannot be resolved
}
auditRepository.save(DenominationMasterAudit.of(action, dm, changes, userId, username));
}
private java.util.Map<String, Object> toMap(DenominationMaster dm) {
java.util.Map<String, Object> map = new java.util.LinkedHashMap<>();
map.put("id", dm.getId());
map.put("denomination", dm.getDenomination());
map.put("kind", dm.getKind());
map.put("description", dm.getDescription());
map.put("isActive", dm.getIsActive());
map.put("introducedOn", dm.getIntroducedOn() != null ? dm.getIntroducedOn().toString() : null);
map.put("discontinuedOn", dm.getDiscontinuedOn() != null ? dm.getDiscontinuedOn().toString() : null);
map.put("deleted", dm.getDeleted());
return map;
}
private java.util.Map<String, Object> auditToMap(DenominationMasterAudit a) {
java.util.Map<String, Object> map = new java.util.LinkedHashMap<>();
map.put("id", a.getId());
map.put("denominationId", a.getDenominationId());
map.put("action", a.getAction());
map.put("denomination", a.getDenomination());
map.put("kind", a.getKind());
map.put("description", a.getDescription());
map.put("isActive", a.getIsActive());
map.put("introducedOn", a.getIntroducedOn() != null ? a.getIntroducedOn().toString() : null);
map.put("discontinuedOn", a.getDiscontinuedOn() != null ? a.getDiscontinuedOn().toString() : null);
map.put("changes", a.getChanges());
map.put("performedBy", a.getPerformedBy());
map.put("performedByUsername", a.getPerformedByUsername());
map.put("performedOn", a.getPerformedOn() != null ? a.getPerformedOn().toString() : null);
return map;
}
}
@@ -0,0 +1,62 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.
*/
package org.apache.fineract.portfolio.denominationmaster.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.time.LocalDate;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@Table(name = "m_denomination_master")
@Getter
@Setter
@NoArgsConstructor
public class DenominationMaster {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "denomination", nullable = false)
private Integer denomination;
@Column(name = "kind", nullable = false, length = 10)
private String kind;
@Column(name = "description", length = 100)
private String description;
@Column(name = "is_active", nullable = false)
private Boolean isActive = true;
@Column(name = "introduced_on")
private LocalDate introducedOn;
@Column(name = "discontinued_on")
private LocalDate discontinuedOn;
@Column(name = "deleted", nullable = false)
private Boolean deleted = false;
public static DenominationMaster create(Integer denomination, String kind, String description,
Boolean isActive, LocalDate introducedOn, LocalDate discontinuedOn) {
DenominationMaster dm = new DenominationMaster();
dm.denomination = denomination;
dm.kind = kind;
dm.description = description;
dm.isActive = isActive != null ? isActive : true;
dm.introducedOn = introducedOn;
dm.discontinuedOn = discontinuedOn;
dm.deleted = false;
return dm;
}
}
@@ -0,0 +1,83 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.
*/
package org.apache.fineract.portfolio.denominationmaster.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.time.LocalDate;
import java.time.LocalDateTime;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@Table(name = "m_denomination_master_audit")
@Getter
@Setter
@NoArgsConstructor
public class DenominationMasterAudit {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "denomination_id")
private Long denominationId;
@Column(name = "action", nullable = false, length = 20)
private String action;
@Column(name = "denomination")
private Integer denomination;
@Column(name = "kind", length = 10)
private String kind;
@Column(name = "description", length = 100)
private String description;
@Column(name = "is_active")
private Boolean isActive;
@Column(name = "introduced_on")
private LocalDate introducedOn;
@Column(name = "discontinued_on")
private LocalDate discontinuedOn;
@Column(name = "changes", length = 1000)
private String changes;
@Column(name = "performed_by")
private Long performedBy;
@Column(name = "performed_by_username", length = 100)
private String performedByUsername;
@Column(name = "performed_on", nullable = false)
private LocalDateTime performedOn;
public static DenominationMasterAudit of(final String action, final DenominationMaster dm, final String changes,
final Long performedBy, final String performedByUsername) {
final DenominationMasterAudit a = new DenominationMasterAudit();
a.action = action;
a.denominationId = dm.getId();
a.denomination = dm.getDenomination();
a.kind = dm.getKind();
a.description = dm.getDescription();
a.isActive = dm.getIsActive();
a.introducedOn = dm.getIntroducedOn();
a.discontinuedOn = dm.getDiscontinuedOn();
a.changes = changes;
a.performedBy = performedBy;
a.performedByUsername = performedByUsername;
a.performedOn = LocalDateTime.now();
return a;
}
}
@@ -0,0 +1,17 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.
*/
package org.apache.fineract.portfolio.denominationmaster.domain;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface DenominationMasterAuditRepository extends JpaRepository<DenominationMasterAudit, Long> {
List<DenominationMasterAudit> findByDenominationIdOrderByPerformedOnDesc(Long denominationId);
List<DenominationMasterAudit> findAllByOrderByPerformedOnDesc();
}
@@ -0,0 +1,15 @@
package org.apache.fineract.portfolio.denominationmaster.domain;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface DenominationMasterRepository extends JpaRepository<DenominationMaster, Long> {
List<DenominationMaster> findByDeletedFalseOrderByKindDescDenominationDesc();
List<DenominationMaster> findByIsActiveTrueAndDeletedFalseOrderByKindDescDenominationDesc();
List<DenominationMaster> findByDeletedTrueOrderByKindDescDenominationDesc();
boolean existsByDenominationAndKindAndDeletedFalse(Integer denomination, String kind);
boolean existsByDenominationAndKindAndDeletedFalseAndIdNot(Integer denomination, String kind, Long id);
}
@@ -0,0 +1,81 @@
/**
* 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.denominationmaster.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.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@Table(name = "m_savings_txn_denomination")
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SavingsTxnDenomination {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "txn_id", nullable = false)
private Long txnId;
@Column(name = "savings_account_id", nullable = false)
private Long savingsAccountId;
@Column(name = "account_no", length = 50)
private String accountNo;
@Column(name = "client_id")
private Long clientId;
@Column(name = "cif_no", length = 50)
private String cifNo;
@Column(name = "denomination_id", nullable = false)
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, precision = 19, scale = 6)
private BigDecimal totalAmount;
@Column(name = "txn_date")
private LocalDate txnDate;
@Column(name = "created_at")
private LocalDateTime createdAt;
}
@@ -0,0 +1,15 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.
*/
package org.apache.fineract.portfolio.denominationmaster.domain;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface SavingsTxnDenominationRepository extends JpaRepository<SavingsTxnDenomination, Long> {
List<SavingsTxnDenomination> findByTxnId(Long txnId);
List<SavingsTxnDenomination> findBySavingsAccountId(Long savingsAccountId);
}
@@ -98,6 +98,7 @@ import org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityConfig
import org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityLimit; import org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityLimit;
import org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityLimitRepository; import org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityLimitRepository;
import org.apache.fineract.portfolio.savings.domain.SavingsProductRepository; import org.apache.fineract.portfolio.savings.domain.SavingsProductRepository;
import org.apache.fineract.portfolio.denominationmaster.domain.SavingsTxnDenominationRepository;
import org.apache.fineract.portfolio.savings.service.FacilityChargeAutoApplyService; import org.apache.fineract.portfolio.savings.service.FacilityChargeAutoApplyService;
import org.apache.fineract.portfolio.savings.service.DepositAccountInterestRateChartReadPlatformService; import org.apache.fineract.portfolio.savings.service.DepositAccountInterestRateChartReadPlatformService;
import org.apache.fineract.portfolio.savings.service.DepositAccountInterestRateChartReadPlatformServiceImpl; import org.apache.fineract.portfolio.savings.service.DepositAccountInterestRateChartReadPlatformServiceImpl;
@@ -424,7 +425,8 @@ public class SavingsConfiguration {
EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService, AppUserRepositoryWrapper appuserRepository, EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService, AppUserRepositoryWrapper appuserRepository,
StandingInstructionRepository standingInstructionRepository, BusinessEventNotifierService businessEventNotifierService, StandingInstructionRepository standingInstructionRepository, BusinessEventNotifierService businessEventNotifierService,
GSIMRepositoy gsimRepository, SavingsAccountInterestPostingService savingsAccountInterestPostingService, GSIMRepositoy gsimRepository, SavingsAccountInterestPostingService savingsAccountInterestPostingService,
ErrorHandler errorHandler, FacilityChargeAutoApplyService facilityChargeAutoApplyService) { ErrorHandler errorHandler, FacilityChargeAutoApplyService facilityChargeAutoApplyService,
SavingsTxnDenominationRepository savingsTxnDenominationRepository) {
return new SavingsAccountWritePlatformServiceJpaRepositoryImpl(context, fromApiJsonDeserializer, savingAccountRepositoryWrapper, return new SavingsAccountWritePlatformServiceJpaRepositoryImpl(context, fromApiJsonDeserializer, savingAccountRepositoryWrapper,
staffRepository, savingsAccountTransactionRepository, savingAccountAssembler, savingsAccountTransactionDataValidator, staffRepository, savingsAccountTransactionRepository, savingAccountAssembler, savingsAccountTransactionDataValidator,
savingsAccountChargeDataValidator, paymentDetailWritePlatformService, journalEntryWritePlatformService, savingsAccountChargeDataValidator, paymentDetailWritePlatformService, journalEntryWritePlatformService,
@@ -432,7 +434,7 @@ public class SavingsConfiguration {
chargeRepository, savingsAccountChargeRepository, holidayRepository, workingDaysRepository, configurationDomainService, chargeRepository, savingsAccountChargeRepository, holidayRepository, workingDaysRepository, configurationDomainService,
depositAccountOnHoldTransactionRepository, entityDatatableChecksWritePlatformService, appuserRepository, depositAccountOnHoldTransactionRepository, entityDatatableChecksWritePlatformService, appuserRepository,
standingInstructionRepository, businessEventNotifierService, gsimRepository, savingsAccountInterestPostingService, standingInstructionRepository, businessEventNotifierService, gsimRepository, savingsAccountInterestPostingService,
errorHandler, facilityChargeAutoApplyService); errorHandler, facilityChargeAutoApplyService, savingsTxnDenominationRepository);
} }
@Bean @Bean
@@ -318,4 +318,5 @@
<include file="parts/0299_seed_indian_states.xml" relativeToChangelogFile="true"/> <include file="parts/0299_seed_indian_states.xml" relativeToChangelogFile="true"/>
<include file="parts/0300_rename_fd_base_interest_rate_tables_to_cbs.xml" relativeToChangelogFile="true"/> <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/0301_add_current_account_to_transfer_details.xml" relativeToChangelogFile="true"/>
<include file="parts/0012_denomination_master.xml" relativeToChangelogFile="true"/>
</databaseChangeLog> </databaseChangeLog>
@@ -0,0 +1,180 @@
<?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="denomination_master_table_001" author="CBS">
<createTable tableName="m_denomination_master">
<column name="id" type="BIGINT" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="denomination" type="INT">
<constraints nullable="false"/>
</column>
<column name="kind" type="VARCHAR(10)">
<constraints nullable="false"/>
</column>
<column name="description" type="VARCHAR(100)"/>
<column name="is_active" type="BOOLEAN" defaultValueBoolean="true">
<constraints nullable="false"/>
</column>
<column name="introduced_on" type="DATE"/>
<column name="discontinued_on" type="DATE"/>
</createTable>
</changeSet>
<changeSet id="denomination_master_initial_data_001" author="CBS">
<insert tableName="m_denomination_master">
<column name="denomination" value="2000"/>
<column name="kind" value="note"/>
<column name="description" value="2000 Rupee Note"/>
<column name="is_active" valueBoolean="false"/>
<column name="introduced_on" value="2016-11-08"/>
<column name="discontinued_on" value="2023-09-30"/>
</insert>
<insert tableName="m_denomination_master">
<column name="denomination" value="500"/>
<column name="kind" value="note"/>
<column name="description" value="500 Rupee Note"/>
<column name="is_active" valueBoolean="true"/>
<column name="introduced_on" value="2016-11-08"/>
</insert>
<insert tableName="m_denomination_master">
<column name="denomination" value="200"/>
<column name="kind" value="note"/>
<column name="description" value="200 Rupee Note"/>
<column name="is_active" valueBoolean="true"/>
<column name="introduced_on" value="2017-08-25"/>
</insert>
<insert tableName="m_denomination_master">
<column name="denomination" value="100"/>
<column name="kind" value="note"/>
<column name="description" value="100 Rupee Note"/>
<column name="is_active" valueBoolean="true"/>
<column name="introduced_on" value="2018-07-19"/>
</insert>
<insert tableName="m_denomination_master">
<column name="denomination" value="50"/>
<column name="kind" value="note"/>
<column name="description" value="50 Rupee Note"/>
<column name="is_active" valueBoolean="true"/>
<column name="introduced_on" value="2017-08-25"/>
</insert>
<insert tableName="m_denomination_master">
<column name="denomination" value="20"/>
<column name="kind" value="note"/>
<column name="description" value="20 Rupee Note"/>
<column name="is_active" valueBoolean="true"/>
<column name="introduced_on" value="2019-08-26"/>
</insert>
<insert tableName="m_denomination_master">
<column name="denomination" value="10"/>
<column name="kind" value="note"/>
<column name="description" value="10 Rupee Note"/>
<column name="is_active" valueBoolean="true"/>
<column name="introduced_on" value="2018-01-01"/>
</insert>
<insert tableName="m_denomination_master">
<column name="denomination" value="20"/>
<column name="kind" value="coin"/>
<column name="description" value="20 Rupee Coin"/>
<column name="is_active" valueBoolean="true"/>
<column name="introduced_on" value="2019-01-01"/>
</insert>
<insert tableName="m_denomination_master">
<column name="denomination" value="10"/>
<column name="kind" value="coin"/>
<column name="description" value="10 Rupee Coin"/>
<column name="is_active" valueBoolean="true"/>
<column name="introduced_on" value="2009-01-01"/>
</insert>
<insert tableName="m_denomination_master">
<column name="denomination" value="5"/>
<column name="kind" value="coin"/>
<column name="description" value="5 Rupee Coin"/>
<column name="is_active" valueBoolean="true"/>
<column name="introduced_on" value="2009-01-01"/>
</insert>
<insert tableName="m_denomination_master">
<column name="denomination" value="2"/>
<column name="kind" value="coin"/>
<column name="description" value="2 Rupee Coin"/>
<column name="is_active" valueBoolean="true"/>
<column name="introduced_on" value="2009-01-01"/>
</insert>
<insert tableName="m_denomination_master">
<column name="denomination" value="1"/>
<column name="kind" value="coin"/>
<column name="description" value="1 Rupee Coin"/>
<column name="is_active" valueBoolean="true"/>
<column name="introduced_on" value="2009-01-01"/>
</insert>
</changeSet>
<changeSet id="savings_txn_denomination_table_001" author="CBS">
<createTable tableName="m_savings_txn_denomination">
<column name="id" type="BIGINT" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="txn_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="savings_account_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="account_no" type="VARCHAR(50)"/>
<column name="client_id" type="BIGINT"/>
<column name="cif_no" type="VARCHAR(50)"/>
<column name="denomination_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="denomination_value" type="INT">
<constraints nullable="false"/>
</column>
<column name="count" type="INT" defaultValueNumeric="0">
<constraints nullable="false"/>
</column>
<column name="total_amount" type="DECIMAL(19,6)">
<constraints nullable="false"/>
</column>
<column name="txn_date" type="DATE"/>
<column name="created_at" type="TIMESTAMP" defaultValueComputed="CURRENT_TIMESTAMP"/>
</createTable>
</changeSet>
<changeSet id="denomination_master_audit_table_001" author="CBS">
<createTable tableName="m_denomination_master_audit">
<column name="id" type="BIGINT" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="denomination_id" type="BIGINT"/>
<column name="action" type="VARCHAR(20)">
<constraints nullable="false"/>
</column>
<column name="denomination" type="INT"/>
<column name="kind" type="VARCHAR(10)"/>
<column name="description" type="VARCHAR(100)"/>
<column name="is_active" type="BOOLEAN"/>
<column name="introduced_on" type="DATE"/>
<column name="discontinued_on" type="DATE"/>
<column name="changes" type="VARCHAR(1000)"/>
<column name="performed_by" type="BIGINT"/>
<column name="performed_by_username" type="VARCHAR(100)"/>
<column name="performed_on" type="TIMESTAMP" defaultValueComputed="CURRENT_TIMESTAMP">
<constraints nullable="false"/>
</column>
</createTable>
<createIndex tableName="m_denomination_master_audit" indexName="idx_denom_audit_denom_id">
<column name="denomination_id"/>
</createIndex>
</changeSet>
<changeSet id="denomination_master_soft_delete_001" author="CBS">
<addColumn tableName="m_denomination_master">
<column name="deleted" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
</addColumn>
</changeSet>
</databaseChangeLog>
@@ -1,82 +1,83 @@
/** /**
* Licensed to the Apache Software Foundation (ASF) under one * Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file * or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information * distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file * regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the * to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance * "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at * with the License. You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, * Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an * software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the * KIND, either express or implied. See the License for the
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
package org.apache.fineract.portfolio.savings.data; package org.apache.fineract.portfolio.savings.data;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import org.apache.fineract.portfolio.savings.SavingsApiConstants; import org.apache.fineract.portfolio.savings.SavingsApiConstants;
public class SavingsAccountConstant extends SavingsApiConstants { public class SavingsAccountConstant extends SavingsApiConstants {
/** /**
* These parameters will match the class level parameters of {@link SavingsProductData}. Where possible, we try to * These parameters will match the class level parameters of {@link SavingsProductData}. Where possible, we try to
* get response parameters to match those of request parameters. * get response parameters to match those of request parameters.
*/ */
protected static final Set<String> SAVINGS_ACCOUNT_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName, protected static final Set<String> SAVINGS_ACCOUNT_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
dateFormatParamName, monthDayFormatParamName, staffIdParamName, isGSIM, isParentAccount, accountNoParamName, dateFormatParamName, monthDayFormatParamName, staffIdParamName, isGSIM, isParentAccount, accountNoParamName,
externalIdParamName, clientIdParamName, groupIdParamName, productIdParamName, fieldOfficerIdParamName, submittedOnDateParamName, externalIdParamName, clientIdParamName, groupIdParamName, productIdParamName, fieldOfficerIdParamName, submittedOnDateParamName,
nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName, nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName,
interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName, interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName,
lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName,
// withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, // withdrawalFeeAmountParamName, withdrawalFeeTypeParamName,
withdrawalFeeForTransfersParamName, feeAmountParamName, feeOnMonthDayParamName, chargesParamName, allowOverdraftParamName, withdrawalFeeForTransfersParamName, feeAmountParamName, feeOnMonthDayParamName, chargesParamName, allowOverdraftParamName,
overdraftLimitParamName, minRequiredBalanceParamName, enforceMinRequiredBalanceParamName, lienAllowedParamName, overdraftLimitParamName, minRequiredBalanceParamName, enforceMinRequiredBalanceParamName, lienAllowedParamName,
maxAllowedLienLimitParamName, nominalAnnualInterestRateOverdraftParamName, minOverdraftForInterestCalculationParamName, maxAllowedLienLimitParamName, nominalAnnualInterestRateOverdraftParamName, minOverdraftForInterestCalculationParamName,
withHoldTaxParamName, datatables, gsimApplicationId, gsimLastApplication, withHoldTaxParamName, datatables, gsimApplicationId, gsimLastApplication,
// CBS Account fields // CBS Account fields
"accountType", "customerSegment", accountStatusParamName, modeOfOperationParamName, "accountType", "customerSegment", accountStatusParamName, modeOfOperationParamName,
specialCategoryParamName, weakerSectionParamName, specialCategoryParamName, weakerSectionParamName,
guardianNameParamName, guardianCifNumberParamName, guardianRelationshipParamName, guardianNameParamName, guardianCifNumberParamName, guardianRelationshipParamName,
guardianClientIdParamName, minorDateOfBirthParamName, guardianClientIdParamName, minorDateOfBirthParamName,
introducerIdParamName, introducerNameParamName, introducerCifNumberParamName, introducerIdParamName, introducerNameParamName, introducerCifNumberParamName,
nomineeParamName, "nominees", linkedAccountIdParamName, linkedAccountTypeParamName, linkedAccountNoParamName, nomineeParamName, "nominees", linkedAccountIdParamName, linkedAccountTypeParamName, linkedAccountNoParamName,
accountFacilityConfigParamName, jointHoldersParamName, accountFacilityConfigParamName, jointHoldersParamName,
balanceSwapOptionParamName, msmeParamName, autoDebitFacilityParamName)); balanceSwapOptionParamName, msmeParamName, autoDebitFacilityParamName));
/** /**
* These parameters will match the class level parameters of {@link SavingsAccountData}. Where possible, we try to * These parameters will match the class level parameters of {@link SavingsAccountData}. Where possible, we try to
* get response parameters to match those of request parameters. * get response parameters to match those of request parameters.
*/ */
protected static final Set<String> SAVINGS_ACCOUNT_TRANSACTION_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName, protected static final Set<String> SAVINGS_ACCOUNT_TRANSACTION_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
dateFormatParamName, transactionDateParamName, transactionAmountParamName, paymentTypeIdParamName, dateFormatParamName, transactionDateParamName, transactionAmountParamName, paymentTypeIdParamName,
transactionAccountNumberParamName, checkNumberParamName, routingCodeParamName, receiptNumberParamName, bankNumberParamName, transactionAccountNumberParamName, checkNumberParamName, routingCodeParamName, receiptNumberParamName, bankNumberParamName,
retailEntriesParamName, childAccountIdParamName, noteParamName, amountParamName, dateParamName, isManualTransaction, retailEntriesParamName, childAccountIdParamName, noteParamName, amountParamName, dateParamName, isManualTransaction,
lienTransaction, chargesPaidByData, submittedOnDateParamName, accountIdParamName, accountNoParamName)); lienTransaction, chargesPaidByData, submittedOnDateParamName, accountIdParamName, accountNoParamName,
"denominationBreakdown"));
protected static final Set<String> SAVINGS_ACCOUNT_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet<>(
Arrays.asList(idParamName, accountNoParamName)); protected static final Set<String> SAVINGS_ACCOUNT_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet<>(
Arrays.asList(idParamName, accountNoParamName));
protected static final Set<String> SAVINGS_ACCOUNT_ACTIVATION_REQUEST_DATA_PARAMETERS = new HashSet<>(
Arrays.asList(localeParamName, dateFormatParamName, activatedOnDateParamName, "openingBalance")); protected static final Set<String> SAVINGS_ACCOUNT_ACTIVATION_REQUEST_DATA_PARAMETERS = new HashSet<>(
Arrays.asList(localeParamName, dateFormatParamName, activatedOnDateParamName, "openingBalance"));
protected static final Set<String> SAVINGS_ACCOUNT_CLOSE_REQUEST_DATA_PARAMETERS = new HashSet<>(
Arrays.asList(localeParamName, dateFormatParamName, closedOnDateParamName, noteParamName, paymentTypeIdParamName, protected static final Set<String> SAVINGS_ACCOUNT_CLOSE_REQUEST_DATA_PARAMETERS = new HashSet<>(
withdrawBalanceParamName, transactionAccountNumberParamName, checkNumberParamName, routingCodeParamName, Arrays.asList(localeParamName, dateFormatParamName, closedOnDateParamName, noteParamName, paymentTypeIdParamName,
receiptNumberParamName, bankNumberParamName, postInterestValidationOnClosure)); withdrawBalanceParamName, transactionAccountNumberParamName, checkNumberParamName, routingCodeParamName,
receiptNumberParamName, bankNumberParamName, postInterestValidationOnClosure));
protected static final Set<String> SAVINGS_ACCOUNT_CHARGES_ADD_REQUEST_DATA_PARAMETERS = new HashSet<>(
Arrays.asList(chargeIdParamName, amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName, protected static final Set<String> SAVINGS_ACCOUNT_CHARGES_ADD_REQUEST_DATA_PARAMETERS = new HashSet<>(
feeOnMonthDayParamName, monthDayFormatParamName, feeIntervalParamName)); Arrays.asList(chargeIdParamName, amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName,
feeOnMonthDayParamName, monthDayFormatParamName, feeIntervalParamName));
protected static final Set<String> SAVINGS_ACCOUNT_CHARGES_PAY_CHARGE_REQUEST_DATA_PARAMETERS = new HashSet<>(
Arrays.asList(amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName, noteParamName)); protected static final Set<String> SAVINGS_ACCOUNT_CHARGES_PAY_CHARGE_REQUEST_DATA_PARAMETERS = new HashSet<>(
Arrays.asList(amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName, noteParamName));
}
}