feat(cashier): add GL denomination tracking, live GL balance, and enhanced closing report
- Add CashierGlDenominationApiResource: new POST /v1/cashier/gl-denomination endpoint to record denomination breakdown for GL journal entries in m_savings_txn_denomination - Fix GL account real-time balance in GLAccountReadPlatformServiceImpl using live SUM query - Add Liquibase migrations 0318/0319/0320: make savings_account_id and txn_id nullable, add payment_type column to m_savings_txn_denomination - Add validCheckSum to 0317b changeset to resolve checksum mismatch on existing installs - Enhance GL entries closing report SQL with date, time, teller, payment type, txn source Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
53b6a94bf8
commit
19639c9e02
+13
-4
@@ -200,12 +200,21 @@ public class GLAccountReadPlatformServiceImpl implements GLAccountReadPlatformSe
|
|||||||
final StringBuilder sql = new StringBuilder();
|
final StringBuilder sql = new StringBuilder();
|
||||||
sql.append("select ").append(rm.schema());
|
sql.append("select ").append(rm.schema());
|
||||||
sql.append(" where gl.id = ?");
|
sql.append(" where gl.id = ?");
|
||||||
if (associationParametersData.isRunningBalanceRequired()) {
|
|
||||||
sql.append(" and gl_j.is_running_balance_calculated = true ")
|
final GLAccountData glAccount = this.jdbcTemplate.queryForObject(sql.toString(), rm, new Object[] { glAccountId });
|
||||||
.append(" ORDER BY gl_j.entry_date DESC,gl_j.id DESC LIMIT 1");
|
|
||||||
|
// Live balance: sum all non-reversed journal entries so cashier sees real-time balance
|
||||||
|
// without waiting for the "Update Accounting Running Balances" scheduled job.
|
||||||
|
if (associationParametersData != null && associationParametersData.isRunningBalanceRequired() && glAccount != null) {
|
||||||
|
final String liveBalanceSql = "SELECT COALESCE(ABS(SUM(CASE WHEN type_enum = 2 THEN amount ELSE -amount END)), 0) "
|
||||||
|
+ "FROM acc_gl_journal_entry "
|
||||||
|
+ "WHERE account_id = ? AND (reversed = false OR reversed IS NULL)";
|
||||||
|
final java.math.BigDecimal liveBalance = this.jdbcTemplate.queryForObject(liveBalanceSql,
|
||||||
|
java.math.BigDecimal.class, glAccountId);
|
||||||
|
glAccount.setOrganizationRunningBalance(liveBalance != null ? liveBalance.longValue() : 0L);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.jdbcTemplate.queryForObject(sql.toString(), rm, new Object[] { glAccountId });
|
return glAccount;
|
||||||
} catch (final EmptyResultDataAccessException e) {
|
} catch (final EmptyResultDataAccessException e) {
|
||||||
throw new GLAccountNotFoundException(glAccountId, e);
|
throw new GLAccountNotFoundException(glAccountId, e);
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-4
@@ -584,25 +584,35 @@ public class CashReportApiResource {
|
|||||||
params = new Object[] { txnDate };
|
params = new Object[] { txnDate };
|
||||||
}
|
}
|
||||||
|
|
||||||
// One row per transaction — account no, account holder name, dr/cr amount
|
// One row per transaction — account no, account holder name, dr/cr amount, date, teller, payment type
|
||||||
final String sql = "SELECT "
|
final String sql = "SELECT "
|
||||||
+ " std.txn_id AS voucher_no, "
|
+ " std.txn_id AS voucher_no, "
|
||||||
+ " std.account_no AS ac_no, "
|
+ " std.account_no AS ac_no, "
|
||||||
+ " COALESCE(mc.display_name, '') AS ac_name, "
|
+ " COALESCE(mc.display_name, std.cif_no, '') AS ac_name, "
|
||||||
|
+ " MAX(std.txn_date) AS txn_date, "
|
||||||
|
+ " TO_CHAR(MAX(std.created_at), 'HH24:MI:SS') AS txn_time, "
|
||||||
|
+ " MAX(std.officer_username) AS teller_name, "
|
||||||
|
+ " MAX(std.payment_type) AS payment_type, "
|
||||||
|
+ " CASE WHEN MAX(std.client_id) IS NULL THEN 'GL' ELSE 'SAVINGS' END AS txn_source, "
|
||||||
+ " SUM(CASE WHEN std.txn_type = 'WITHDRAWAL' THEN std.total_amount ELSE 0 END) AS dr_amount, "
|
+ " SUM(CASE WHEN std.txn_type = 'WITHDRAWAL' THEN std.total_amount ELSE 0 END) AS dr_amount, "
|
||||||
+ " SUM(CASE WHEN std.txn_type = 'DEPOSIT' THEN std.total_amount ELSE 0 END) AS cr_amount "
|
+ " SUM(CASE WHEN std.txn_type = 'DEPOSIT' THEN std.total_amount ELSE 0 END) AS cr_amount "
|
||||||
+ "FROM m_savings_txn_denomination std "
|
+ "FROM m_savings_txn_denomination std "
|
||||||
+ "LEFT JOIN m_client mc ON mc.id = std.client_id "
|
+ "LEFT JOIN m_client mc ON mc.id = std.client_id "
|
||||||
+ "WHERE std.txn_date = CAST(? AS DATE) "
|
+ "WHERE std.txn_date = CAST(? AS DATE) "
|
||||||
+ officerFilter
|
+ officerFilter
|
||||||
+ " GROUP BY std.txn_id, std.account_no, mc.display_name "
|
+ " GROUP BY std.txn_id, std.account_no, mc.display_name, std.cif_no "
|
||||||
+ "ORDER BY std.txn_id";
|
+ "ORDER BY std.txn_id NULLS LAST, MAX(std.created_at)";
|
||||||
|
|
||||||
List<Map<String, Object>> rows = jdbcTemplate.query(sql, (rs, rowNum) -> {
|
List<Map<String, Object>> rows = jdbcTemplate.query(sql, (rs, rowNum) -> {
|
||||||
Map<String, Object> row = new LinkedHashMap<>();
|
Map<String, Object> row = new LinkedHashMap<>();
|
||||||
row.put("voucherNo", rs.getLong("voucher_no"));
|
row.put("voucherNo", rs.getLong("voucher_no"));
|
||||||
row.put("acNo", rs.getString("ac_no"));
|
row.put("acNo", rs.getString("ac_no"));
|
||||||
row.put("acName", rs.getString("ac_name"));
|
row.put("acName", rs.getString("ac_name"));
|
||||||
|
row.put("txnDate", rs.getString("txn_date"));
|
||||||
|
row.put("txnTime", rs.getString("txn_time"));
|
||||||
|
row.put("tellerName", rs.getString("teller_name"));
|
||||||
|
row.put("paymentType", rs.getString("payment_type"));
|
||||||
|
row.put("txnSource", rs.getString("txn_source"));
|
||||||
row.put("dr", rs.getBigDecimal("dr_amount"));
|
row.put("dr", rs.getBigDecimal("dr_amount"));
|
||||||
row.put("cr", rs.getBigDecimal("cr_amount"));
|
row.put("cr", rs.getBigDecimal("cr_amount"));
|
||||||
return row;
|
return row;
|
||||||
|
|||||||
+159
@@ -0,0 +1,159 @@
|
|||||||
|
/**
|
||||||
|
* 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.cashtransfer.api;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.ws.rs.Consumes;
|
||||||
|
import jakarta.ws.rs.POST;
|
||||||
|
import jakarta.ws.rs.Path;
|
||||||
|
import jakarta.ws.rs.Produces;
|
||||||
|
import jakarta.ws.rs.core.MediaType;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||||
|
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.Component;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records cashier denomination breakdown for GL account transactions (journal entries).
|
||||||
|
* Since journal entries API does not accept denominationBreakdown, this endpoint
|
||||||
|
* saves denomination data to m_savings_txn_denomination so the cashier position
|
||||||
|
* calculation includes GL cash movements.
|
||||||
|
*
|
||||||
|
* POST /cashier/gl-denomination
|
||||||
|
* Body: { txnId, accountNo, txnType ("DEPOSIT"/"WITHDRAWAL"), txnDate (yyyy-MM-dd),
|
||||||
|
* denominationBreakdown: [{ denominationId, denominationValue, count }] }
|
||||||
|
*/
|
||||||
|
@Path("/v1/cashier/gl-denomination")
|
||||||
|
@Component
|
||||||
|
@Tag(name = "Cashier GL Denomination")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class CashierGlDenominationApiResource {
|
||||||
|
|
||||||
|
private final PlatformSecurityContext context;
|
||||||
|
private final SavingsTxnDenominationRepository savingsTxnDenominationRepository;
|
||||||
|
private final FromJsonHelper fromJsonHelper;
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
|
@Transactional
|
||||||
|
public String recordGlDenomination(final String body) {
|
||||||
|
final JsonElement root = fromJsonHelper.parse(body);
|
||||||
|
|
||||||
|
// txnId may be a hex string (journal entry) or numeric (savings txn)
|
||||||
|
// Hex IDs (e.g. "a1fd0749a8a9") are parsed as base-16 Long so each GL
|
||||||
|
// transaction gets a unique txn_id — required for grouping in closing report
|
||||||
|
final String txnIdRaw = fromJsonHelper.extractStringNamed("txnId", root);
|
||||||
|
Long txnId = null;
|
||||||
|
if (txnIdRaw != null && !txnIdRaw.isBlank()) {
|
||||||
|
try {
|
||||||
|
txnId = Long.valueOf(txnIdRaw);
|
||||||
|
} catch (final NumberFormatException ignored) {
|
||||||
|
try {
|
||||||
|
final String hex = txnIdRaw.length() > 15 ? txnIdRaw.substring(txnIdRaw.length() - 15) : txnIdRaw;
|
||||||
|
txnId = Long.parseUnsignedLong(hex, 16);
|
||||||
|
} catch (final NumberFormatException ignored2) {
|
||||||
|
txnId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final String accountNo = fromJsonHelper.extractStringNamed("accountNo", root);
|
||||||
|
final String accountName = fromJsonHelper.extractStringNamed("accountName", root);
|
||||||
|
final String txnType = fromJsonHelper.extractStringNamed("txnType", root);
|
||||||
|
final String paymentTypeName = fromJsonHelper.extractStringNamed("paymentTypeName", root);
|
||||||
|
final String txnDateStr = fromJsonHelper.extractStringNamed("txnDate", root);
|
||||||
|
final LocalDate txnDate = (txnDateStr != null && !txnDateStr.isBlank())
|
||||||
|
? LocalDate.parse(txnDateStr)
|
||||||
|
: LocalDate.now();
|
||||||
|
|
||||||
|
if (!fromJsonHelper.parameterExists("denominationBreakdown", root)) {
|
||||||
|
return "{\"result\":\"skipped\"}";
|
||||||
|
}
|
||||||
|
|
||||||
|
final JsonArray breakdown = fromJsonHelper.extractJsonArrayNamed("denominationBreakdown", root);
|
||||||
|
if (breakdown == null || breakdown.size() == 0) {
|
||||||
|
return "{\"result\":\"skipped\"}";
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {}
|
||||||
|
|
||||||
|
final String resolvedTxnType = (txnType != null && !txnType.isBlank())
|
||||||
|
? txnType.toUpperCase()
|
||||||
|
: "DEPOSIT";
|
||||||
|
|
||||||
|
for (final JsonElement entry : breakdown) {
|
||||||
|
final JsonObject obj = entry.getAsJsonObject();
|
||||||
|
if (!obj.has("denominationId") || obj.get("denominationId").isJsonNull()
|
||||||
|
|| !obj.has("count")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final int count = obj.get("count").getAsInt();
|
||||||
|
if (count <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final long denominationId = obj.get("denominationId").getAsLong();
|
||||||
|
int denominationValue = 0;
|
||||||
|
if (obj.has("denominationValue") && !obj.get("denominationValue").isJsonNull()) {
|
||||||
|
denominationValue = obj.get("denominationValue").getAsInt();
|
||||||
|
}
|
||||||
|
final BigDecimal lineTotal = BigDecimal.valueOf((long) denominationValue * count);
|
||||||
|
|
||||||
|
final SavingsTxnDenomination row = SavingsTxnDenomination.builder()
|
||||||
|
.txnId(txnId)
|
||||||
|
.savingsAccountId(null) // GL transaction — no savings account
|
||||||
|
.accountNo(accountNo)
|
||||||
|
.clientId(null)
|
||||||
|
.cifNo(accountName) // Store GL account name for closing report display
|
||||||
|
.denominationId(denominationId)
|
||||||
|
.denominationValue(denominationValue)
|
||||||
|
.count(count)
|
||||||
|
.totalAmount(lineTotal)
|
||||||
|
.txnDate(txnDate)
|
||||||
|
.createdAt(LocalDateTime.now())
|
||||||
|
.txnType(resolvedTxnType)
|
||||||
|
.officerId(officerId)
|
||||||
|
.officerUsername(officerUsername)
|
||||||
|
.paymentType(paymentTypeName)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
this.savingsTxnDenominationRepository.save(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{\"result\":\"success\"}";
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
-2
@@ -46,10 +46,10 @@ public class SavingsTxnDenomination {
|
|||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
@Column(name = "txn_id", nullable = false)
|
@Column(name = "txn_id")
|
||||||
private Long txnId;
|
private Long txnId;
|
||||||
|
|
||||||
@Column(name = "savings_account_id", nullable = false)
|
@Column(name = "savings_account_id")
|
||||||
private Long savingsAccountId;
|
private Long savingsAccountId;
|
||||||
|
|
||||||
@Column(name = "account_no", length = 50)
|
@Column(name = "account_no", length = 50)
|
||||||
@@ -90,4 +90,8 @@ public class SavingsTxnDenomination {
|
|||||||
/** Username of the teller / officer who performed the transaction. */
|
/** Username of the teller / officer who performed the transaction. */
|
||||||
@Column(name = "officer_username", length = 100)
|
@Column(name = "officer_username", length = 100)
|
||||||
private String officerUsername;
|
private String officerUsername;
|
||||||
|
|
||||||
|
/** Payment mode name (Cash, Cheque, NEFT, etc.) — nullable for legacy rows. */
|
||||||
|
@Column(name = "payment_type", length = 100)
|
||||||
|
private String paymentType;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -345,4 +345,7 @@
|
|||||||
<include file="parts/0315_grant_cashier_transaction_entry_permissions.xml" relativeToChangelogFile="true"/>
|
<include file="parts/0315_grant_cashier_transaction_entry_permissions.xml" relativeToChangelogFile="true"/>
|
||||||
<include file="parts/0316_create_clerk_role_and_permissions.xml" relativeToChangelogFile="true"/>
|
<include file="parts/0316_create_clerk_role_and_permissions.xml" relativeToChangelogFile="true"/>
|
||||||
<include file="parts/0317_grant_cashier_gl_loan_share_read_permissions.xml" relativeToChangelogFile="true"/>
|
<include file="parts/0317_grant_cashier_gl_loan_share_read_permissions.xml" relativeToChangelogFile="true"/>
|
||||||
|
<include file="parts/0318_allow_nullable_savings_account_denomination.xml" relativeToChangelogFile="true"/>
|
||||||
|
<include file="parts/0319_allow_nullable_txn_id_denomination.xml" relativeToChangelogFile="true"/>
|
||||||
|
<include file="parts/0320_add_payment_type_to_denomination.xml" relativeToChangelogFile="true"/>
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|||||||
+38
@@ -46,4 +46,42 @@
|
|||||||
</rollback>
|
</rollback>
|
||||||
</changeSet>
|
</changeSet>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Grant journal entry and financial activity permissions to CASHIER role
|
||||||
|
for GL transactions posted via the Cashier Transaction Entry screen.
|
||||||
|
- CREATE_JOURNALENTRY : Post manual journal entries (Dr/Cr GL accounts)
|
||||||
|
- READ_JOURNALENTRY : Read journal entries for confirmation/history
|
||||||
|
- READ_FINANCIALACTIVITYACCOUNT: Read financial activity → GL mappings (cash vault lookup)
|
||||||
|
-->
|
||||||
|
<changeSet id="0317b_grant_cashier_journal_entry_permissions" author="fineract">
|
||||||
|
<validCheckSum>9:b3a623604e792772bc89005772540108</validCheckSum>
|
||||||
|
<sql>
|
||||||
|
INSERT INTO m_role_permission (role_id, permission_id)
|
||||||
|
SELECT r.id, p.id
|
||||||
|
FROM m_role r
|
||||||
|
JOIN m_permission p ON p.code IN (
|
||||||
|
'CREATE_JOURNALENTRY',
|
||||||
|
'READ_JOURNALENTRY',
|
||||||
|
'READ_FINANCIALACTIVITYACCOUNT'
|
||||||
|
)
|
||||||
|
WHERE r.name = 'CASHIER'
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM m_role_permission rp
|
||||||
|
WHERE rp.role_id = r.id AND rp.permission_id = p.id
|
||||||
|
);
|
||||||
|
</sql>
|
||||||
|
<rollback>
|
||||||
|
DELETE FROM m_role_permission
|
||||||
|
WHERE role_id = (SELECT id FROM m_role WHERE name = 'CASHIER')
|
||||||
|
AND permission_id IN (
|
||||||
|
SELECT id FROM m_permission
|
||||||
|
WHERE code IN (
|
||||||
|
'CREATE_JOURNALENTRY',
|
||||||
|
'READ_JOURNALENTRY',
|
||||||
|
'READ_FINANCIALACTIVITYACCOUNT'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
</rollback>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
for licensing details.
|
||||||
|
-->
|
||||||
|
<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">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Allow savings_account_id to be NULL in m_savings_txn_denomination so that
|
||||||
|
GL account transactions (journal entries) can also record denomination
|
||||||
|
breakdown without a savings account reference.
|
||||||
|
-->
|
||||||
|
<changeSet id="0318_allow_nullable_savings_account_denomination" author="fineract">
|
||||||
|
<dropNotNullConstraint tableName="m_savings_txn_denomination"
|
||||||
|
columnName="savings_account_id"
|
||||||
|
columnDataType="BIGINT"/>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
</databaseChangeLog>
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
for licensing details.
|
||||||
|
-->
|
||||||
|
<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">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Allow txn_id to be NULL in m_savings_txn_denomination so that
|
||||||
|
GL account transactions (journal entries with hex IDs) can also
|
||||||
|
record denomination breakdown without a numeric transaction ID.
|
||||||
|
-->
|
||||||
|
<changeSet id="0319_allow_nullable_txn_id_denomination" author="fineract">
|
||||||
|
<dropNotNullConstraint tableName="m_savings_txn_denomination"
|
||||||
|
columnName="txn_id"
|
||||||
|
columnDataType="BIGINT"/>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
</databaseChangeLog>
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
for licensing details.
|
||||||
|
-->
|
||||||
|
<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="0320_add_payment_type_to_denomination" author="fineract">
|
||||||
|
<addColumn tableName="m_savings_txn_denomination">
|
||||||
|
<column name="payment_type" type="VARCHAR(100)">
|
||||||
|
<constraints nullable="true"/>
|
||||||
|
</column>
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
</databaseChangeLog>
|
||||||
Reference in New Issue
Block a user