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.SavingsProductFacilityLimitRepository;
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.DepositAccountInterestRateChartReadPlatformService;
import org.apache.fineract.portfolio.savings.service.DepositAccountInterestRateChartReadPlatformServiceImpl;
@@ -424,7 +425,8 @@ public class SavingsConfiguration {
EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService, AppUserRepositoryWrapper appuserRepository,
StandingInstructionRepository standingInstructionRepository, BusinessEventNotifierService businessEventNotifierService,
GSIMRepositoy gsimRepository, SavingsAccountInterestPostingService savingsAccountInterestPostingService,
ErrorHandler errorHandler, FacilityChargeAutoApplyService facilityChargeAutoApplyService) {
ErrorHandler errorHandler, FacilityChargeAutoApplyService facilityChargeAutoApplyService,
SavingsTxnDenominationRepository savingsTxnDenominationRepository) {
return new SavingsAccountWritePlatformServiceJpaRepositoryImpl(context, fromApiJsonDeserializer, savingAccountRepositoryWrapper,
staffRepository, savingsAccountTransactionRepository, savingAccountAssembler, savingsAccountTransactionDataValidator,
savingsAccountChargeDataValidator, paymentDetailWritePlatformService, journalEntryWritePlatformService,
@@ -432,7 +434,7 @@ public class SavingsConfiguration {
chargeRepository, savingsAccountChargeRepository, holidayRepository, workingDaysRepository, configurationDomainService,
depositAccountOnHoldTransactionRepository, entityDatatableChecksWritePlatformService, appuserRepository,
standingInstructionRepository, businessEventNotifierService, gsimRepository, savingsAccountInterestPostingService,
errorHandler, facilityChargeAutoApplyService);
errorHandler, facilityChargeAutoApplyService, savingsTxnDenominationRepository);
}
@Bean
@@ -318,4 +318,5 @@
<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/0301_add_current_account_to_transfer_details.xml" relativeToChangelogFile="true"/>
<include file="parts/0012_denomination_master.xml" relativeToChangelogFile="true"/>
</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
* 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 java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
public class SavingsAccountConstant extends SavingsApiConstants {
/**
* 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.
*/
protected static final Set<String> SAVINGS_ACCOUNT_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
dateFormatParamName, monthDayFormatParamName, staffIdParamName, isGSIM, isParentAccount, accountNoParamName,
externalIdParamName, clientIdParamName, groupIdParamName, productIdParamName, fieldOfficerIdParamName, submittedOnDateParamName,
nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName,
interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName,
lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName,
// withdrawalFeeAmountParamName, withdrawalFeeTypeParamName,
withdrawalFeeForTransfersParamName, feeAmountParamName, feeOnMonthDayParamName, chargesParamName, allowOverdraftParamName,
overdraftLimitParamName, minRequiredBalanceParamName, enforceMinRequiredBalanceParamName, lienAllowedParamName,
maxAllowedLienLimitParamName, nominalAnnualInterestRateOverdraftParamName, minOverdraftForInterestCalculationParamName,
withHoldTaxParamName, datatables, gsimApplicationId, gsimLastApplication,
// CBS Account fields
"accountType", "customerSegment", accountStatusParamName, modeOfOperationParamName,
specialCategoryParamName, weakerSectionParamName,
guardianNameParamName, guardianCifNumberParamName, guardianRelationshipParamName,
guardianClientIdParamName, minorDateOfBirthParamName,
introducerIdParamName, introducerNameParamName, introducerCifNumberParamName,
nomineeParamName, "nominees", linkedAccountIdParamName, linkedAccountTypeParamName, linkedAccountNoParamName,
accountFacilityConfigParamName, jointHoldersParamName,
balanceSwapOptionParamName, msmeParamName, autoDebitFacilityParamName));
/**
* 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.
*/
protected static final Set<String> SAVINGS_ACCOUNT_TRANSACTION_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
dateFormatParamName, transactionDateParamName, transactionAmountParamName, paymentTypeIdParamName,
transactionAccountNumberParamName, checkNumberParamName, routingCodeParamName, receiptNumberParamName, bankNumberParamName,
retailEntriesParamName, childAccountIdParamName, noteParamName, amountParamName, dateParamName, isManualTransaction,
lienTransaction, chargesPaidByData, submittedOnDateParamName, accountIdParamName, 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_CLOSE_REQUEST_DATA_PARAMETERS = new HashSet<>(
Arrays.asList(localeParamName, dateFormatParamName, closedOnDateParamName, noteParamName, paymentTypeIdParamName,
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,
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));
}
/**
* 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 java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
public class SavingsAccountConstant extends SavingsApiConstants {
/**
* 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.
*/
protected static final Set<String> SAVINGS_ACCOUNT_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
dateFormatParamName, monthDayFormatParamName, staffIdParamName, isGSIM, isParentAccount, accountNoParamName,
externalIdParamName, clientIdParamName, groupIdParamName, productIdParamName, fieldOfficerIdParamName, submittedOnDateParamName,
nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName,
interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName,
lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName,
// withdrawalFeeAmountParamName, withdrawalFeeTypeParamName,
withdrawalFeeForTransfersParamName, feeAmountParamName, feeOnMonthDayParamName, chargesParamName, allowOverdraftParamName,
overdraftLimitParamName, minRequiredBalanceParamName, enforceMinRequiredBalanceParamName, lienAllowedParamName,
maxAllowedLienLimitParamName, nominalAnnualInterestRateOverdraftParamName, minOverdraftForInterestCalculationParamName,
withHoldTaxParamName, datatables, gsimApplicationId, gsimLastApplication,
// CBS Account fields
"accountType", "customerSegment", accountStatusParamName, modeOfOperationParamName,
specialCategoryParamName, weakerSectionParamName,
guardianNameParamName, guardianCifNumberParamName, guardianRelationshipParamName,
guardianClientIdParamName, minorDateOfBirthParamName,
introducerIdParamName, introducerNameParamName, introducerCifNumberParamName,
nomineeParamName, "nominees", linkedAccountIdParamName, linkedAccountTypeParamName, linkedAccountNoParamName,
accountFacilityConfigParamName, jointHoldersParamName,
balanceSwapOptionParamName, msmeParamName, autoDebitFacilityParamName));
/**
* 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.
*/
protected static final Set<String> SAVINGS_ACCOUNT_TRANSACTION_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
dateFormatParamName, transactionDateParamName, transactionAmountParamName, paymentTypeIdParamName,
transactionAccountNumberParamName, checkNumberParamName, routingCodeParamName, receiptNumberParamName, bankNumberParamName,
retailEntriesParamName, childAccountIdParamName, noteParamName, amountParamName, dateParamName, isManualTransaction,
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_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,
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,
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));
}