feat(savings,cashier): maker-checker cash closure with vouchers and rounding
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-1) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, build-core) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, cucumber) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-3) (push) Canceled after 0s
Fineract Docker Builds / build (false, docker-compose-postgresql.yml, postgresql) (push) Canceled after 0s
Fineract Docker Builds / build (true, docker-compose.yml, mariadb) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-5) (push) Canceled after 0s
Fineract Documentation build / build (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 1 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 10 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 2 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 5 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 6 of 10) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 3 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 4 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 7 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 8 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 9 of 10) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-4) (push) Canceled after 0s
Fineract Liquibase Only mode - PostgreSQL / test (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with ActiveMQ (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with Kafka (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-1) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, build-core) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, cucumber) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-3) (push) Canceled after 0s
Fineract Docker Builds / build (false, docker-compose-postgresql.yml, postgresql) (push) Canceled after 0s
Fineract Docker Builds / build (true, docker-compose.yml, mariadb) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-5) (push) Canceled after 0s
Fineract Documentation build / build (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 1 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 10 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 2 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 5 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 6 of 10) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 3 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 4 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 7 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 8 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 9 of 10) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-4) (push) Canceled after 0s
Fineract Liquibase Only mode - PostgreSQL / test (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with ActiveMQ (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with Kafka (push) Canceled after 0s
Closing a savings account paid real cash out of a teller's drawer in one click, with no maker-checker, no cash-position tracking, and no way to hand over a paisa balance (no paisa coins circulate, and the denomination writer demands an exact match). This adds a Clerk -> Manager -> Cashier flow for Cash closures, modelled on the existing Clerk Withdrawal Request. New ClerkClosureRequestApiResource: - submit: Cash only, token required, guards ACTIVE status and open requests - approve: runs Fineract's own close command (pro-rata interest and the real GL withdrawal post here, atomically), reads the posted transaction back, rounds the payout to whole rupees and posts the difference to a new Round Off Account. A nil-balance closure goes straight to CLOSED rather than parking forever in a cashier queue with no cash to hand over. - disburse: cashier records the denomination breakdown against the rounded figure and the account closes - preview: dry-runs the very same interest posting close() performs, inside a REQUIRES_NEW transaction that is rolled back, so the figure shown to the clerk matches the real payout to the paisa. Reading the stored accrual instead gave a days-stale number, and CALCULATEINTEREST is not equivalent: it uses MathContext(15) and no postInterestOnDate, where close() uses MathContext(10) and feeds the closing date in as a posting-period boundary, which changes how many per-period roundings happen. Vouchers: close() reaches handleWithdrawal directly rather than through the withdrawal command handler, so the handler's own raiseSavingsVoucher never ran and a closure paid cash out with nothing in the voucher register. One is now raised at disbursement -- when the cash actually crosses the counter, in the cashier's name -- with three lines that match the posted GL exactly: Dr Savings Control + Dr Round Off = Cr Cash. Cash direction: the rounding adjustment debited Cash where cash leaving the drawer is a credit, leaving the cash GL disagreeing with what was physically handed over. Both legs corrected. Dates: closure_date, closedOnDate, the preview and the rounding entry now all use the CBS business date, so preview and approval cannot land on different days across a day roll. Day Book: closures were absent entirely (it reads the request registers, not m_voucher). Added as a fourth register, restricted to CLOSED rows and dated on disbursed_at, since a closure only becomes a cash-book event at hand-over. Its terminal status CLOSED was also missing from the totals gate, so a completed closure moved real money out without appearing in the DR total. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+43
-6
@@ -94,6 +94,34 @@ public class CashReportApiResource {
|
|||||||
WHERE COALESCE(r.txn_date, CAST(r.submitted_at AS DATE)) = CAST(? AS DATE)
|
WHERE COALESCE(r.txn_date, CAST(r.submitted_at AS DATE)) = CAST(? AS DATE)
|
||||||
""", this::mapDayBookRow, txnDate));
|
""", this::mapDayBookRow, txnDate));
|
||||||
|
|
||||||
|
// Account closures — a full-balance payout, so the debit side. Its own register rather than
|
||||||
|
// the withdrawal one: a closure carries no clerk-entered amount (the figure is whatever
|
||||||
|
// close() computes at approval) and the cash handed over is the ROUNDED
|
||||||
|
// cashier_disbursed_amount, which is what a cash book has to agree with, not the
|
||||||
|
// paisa-exact closing_amount posted to the member's ledger.
|
||||||
|
//
|
||||||
|
// Only CLOSED rows, unlike the registers above: a closure becomes a cash-book event when the
|
||||||
|
// cashier actually hands the notes over, and not before. A PENDING or APPROVED closure has
|
||||||
|
// taken nothing out of the drawer yet, and a REJECTED one never will — listing those would
|
||||||
|
// put rows with no cash behind them (a rejected closure has no amount at all) into the
|
||||||
|
// teller's day book. And the day they belong to is the day the cash left, so this filters on
|
||||||
|
// disbursed_at, falling back to closure_date for a nil-balance closure that had no cashier
|
||||||
|
// step to reach.
|
||||||
|
rows.addAll(jdbcTemplate.query("""
|
||||||
|
SELECT r.voucher_no, 'DR' AS side, 'CLOSURE' AS kind, r.account_no,
|
||||||
|
'SAVINGS' AS account_type, r.client_name AS party,
|
||||||
|
COALESCE(r.cashier_disbursed_amount, r.closing_amount) AS amount,
|
||||||
|
r.payment_type_name, r.description, r.status,
|
||||||
|
COALESCE(r.disbursed_by, r.clerk_name) AS handled_by,
|
||||||
|
r.manager_name AS approved_by, r.tok_no AS txn_id,
|
||||||
|
r.submitted_at, r.actioned_at,
|
||||||
|
""" + verifyCols + """
|
||||||
|
FROM m_clerk_closure_request r
|
||||||
|
""" + verifyJoin + """
|
||||||
|
WHERE r.status = 'CLOSED'
|
||||||
|
AND COALESCE(CAST(r.disbursed_at AS DATE), r.closure_date) = CAST(? AS DATE)
|
||||||
|
""", this::mapDayBookRow, txnDate));
|
||||||
|
|
||||||
// Disbursement register: a collection pulls money into the GL (CR); a disbursement or a
|
// Disbursement register: a collection pulls money into the GL (CR); a disbursement or a
|
||||||
// bank transfer moves it out (DR) — matching how the voucher itself was numbered.
|
// bank transfer moves it out (DR) — matching how the voucher itself was numbered.
|
||||||
rows.addAll(jdbcTemplate.query("""
|
rows.addAll(jdbcTemplate.query("""
|
||||||
@@ -128,13 +156,15 @@ public class CashReportApiResource {
|
|||||||
filtered.add(row);
|
filtered.add(row);
|
||||||
|
|
||||||
// Only approved money is real money: a pending or rejected voucher must not inflate the
|
// Only approved money is real money: a pending or rejected voucher must not inflate the
|
||||||
// day's totals, even though it still belongs in the listing. The three source registers
|
// day's totals, even though it still belongs in the listing. The source registers don't
|
||||||
// don't share one terminal status string: deposits and disbursements land on APPROVED,
|
// share one terminal status string: deposits and disbursements land on APPROVED, a clerk
|
||||||
// but a clerk withdrawal's own finished state is WITHDRAWN -- missing it here meant a
|
// withdrawal's finished state is WITHDRAWN, and an account closure's is CLOSED -- missing
|
||||||
// completed withdrawal moved real money out yet never showed up in the DR total at all.
|
// any of them meant a completed transaction moved real money out yet never showed up in
|
||||||
|
// the DR total at all. A closure is one row that moves APPROVED -> CLOSED, so counting
|
||||||
|
// both states cannot double-count it; only 'CLOSED' is unique to that register.
|
||||||
final String rowStatus = (String) row.get("status");
|
final String rowStatus = (String) row.get("status");
|
||||||
if ("APPROVED".equalsIgnoreCase(rowStatus) || "COMPLETED".equalsIgnoreCase(rowStatus)
|
if ("APPROVED".equalsIgnoreCase(rowStatus) || "COMPLETED".equalsIgnoreCase(rowStatus)
|
||||||
|| "WITHDRAWN".equalsIgnoreCase(rowStatus)) {
|
|| "WITHDRAWN".equalsIgnoreCase(rowStatus) || "CLOSED".equalsIgnoreCase(rowStatus)) {
|
||||||
final BigDecimal amount = (BigDecimal) row.get("amount");
|
final BigDecimal amount = (BigDecimal) row.get("amount");
|
||||||
if (amount != null) {
|
if (amount != null) {
|
||||||
if ("CR".equals(row.get("side"))) {
|
if ("CR".equals(row.get("side"))) {
|
||||||
@@ -833,6 +863,12 @@ public class CashReportApiResource {
|
|||||||
+ " (cwr.txn_id = CAST(std.txn_id AS VARCHAR) "
|
+ " (cwr.txn_id = CAST(std.txn_id AS VARCHAR) "
|
||||||
+ " OR (cwr.txn_id ~ '^[0-9a-fA-F]{1,16}$' AND cwr.txn_id ~ '[a-fA-F]' "
|
+ " OR (cwr.txn_id ~ '^[0-9a-fA-F]{1,16}$' AND cwr.txn_id ~ '[a-fA-F]' "
|
||||||
+ " AND ('x' || lpad(cwr.txn_id, 16, '0'))::bit(64)::bigint = std.txn_id)) "
|
+ " AND ('x' || lpad(cwr.txn_id, 16, '0'))::bit(64)::bigint = std.txn_id)) "
|
||||||
|
// An account closure also writes its hand-over as a 'WITHDRAWAL' denomination row
|
||||||
|
// (the column records which way cash moved), but its request lives in its own
|
||||||
|
// register -- without this join the closure's real voucher number is never found and
|
||||||
|
// the report falls back to showing the bare savings-transaction id instead.
|
||||||
|
+ "LEFT JOIN m_clerk_closure_request ccr ON std.txn_type = 'WITHDRAWAL' AND "
|
||||||
|
+ " ccr.txn_id = CAST(std.txn_id AS VARCHAR) "
|
||||||
+ "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, std.cif_no ";
|
+ " GROUP BY std.txn_id, std.account_no, mc.display_name, std.cif_no ";
|
||||||
@@ -853,7 +889,8 @@ public class CashReportApiResource {
|
|||||||
|
|
||||||
// 2. Paged data query
|
// 2. Paged data query
|
||||||
final String dataSql = "SELECT "
|
final String dataSql = "SELECT "
|
||||||
+ " COALESCE(MAX(cdr.voucher_no), MAX(cwr.voucher_no), CAST(std.txn_id AS VARCHAR)) AS voucher_no, "
|
+ " COALESCE(MAX(cdr.voucher_no), MAX(cwr.voucher_no), MAX(ccr.voucher_no), "
|
||||||
|
+ " CAST(std.txn_id AS VARCHAR)) AS voucher_no, "
|
||||||
+ " std.account_no AS ac_no, "
|
+ " std.account_no AS ac_no, "
|
||||||
+ " COALESCE(mc.display_name, std.cif_no, MAX(gla.name), '') AS ac_name, "
|
+ " COALESCE(mc.display_name, std.cif_no, MAX(gla.name), '') AS ac_name, "
|
||||||
+ " MAX(std.txn_date) AS txn_date, "
|
+ " MAX(std.txn_date) AS txn_date, "
|
||||||
|
|||||||
+1039
File diff suppressed because it is too large
Load Diff
+11
-5
@@ -850,9 +850,9 @@ public class ClerkWithdrawalRequestApiResource {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensures the token number is unique for today's date among non-rejected requests, checking
|
* Ensures the token number is unique for today's date among non-rejected requests, checking
|
||||||
* both this table and {@code m_share_dividend_claim_request} -- a dividend cash claim's token
|
* this table plus {@code m_share_dividend_claim_request} and {@code m_clerk_closure_request} --
|
||||||
* is redeemed at the very same Transaction Entry field, so the two pools must never collide.
|
* all three are redeemed at the very same Transaction Entry token field, so none of the pools
|
||||||
* {@code excludeId} is set during resubmit to skip the record being edited.
|
* may ever collide. {@code excludeId} is set during resubmit to skip the record being edited.
|
||||||
*/
|
*/
|
||||||
private void validateTokenUniqueToday(final String tokNo, final Long excludeId) {
|
private void validateTokenUniqueToday(final String tokNo, final Long excludeId) {
|
||||||
final StringBuilder sql = new StringBuilder(
|
final StringBuilder sql = new StringBuilder(
|
||||||
@@ -867,13 +867,19 @@ public class ClerkWithdrawalRequestApiResource {
|
|||||||
final long ownCount = Optional.ofNullable(
|
final long ownCount = Optional.ofNullable(
|
||||||
jdbcTemplate.queryForObject(sql.toString(), Long.class, params.toArray())
|
jdbcTemplate.queryForObject(sql.toString(), Long.class, params.toArray())
|
||||||
).orElse(0L);
|
).orElse(0L);
|
||||||
final long otherCount = Optional.ofNullable(
|
final long dividendCount = Optional.ofNullable(
|
||||||
jdbcTemplate.queryForObject(
|
jdbcTemplate.queryForObject(
|
||||||
"SELECT COUNT(*) FROM m_share_dividend_claim_request "
|
"SELECT COUNT(*) FROM m_share_dividend_claim_request "
|
||||||
+ "WHERE tok_no = ? AND CAST(submitted_at AS DATE) = CURRENT_DATE AND status <> 'REJECTED'",
|
+ "WHERE tok_no = ? AND CAST(submitted_at AS DATE) = CURRENT_DATE AND status <> 'REJECTED'",
|
||||||
Long.class, tokNo)
|
Long.class, tokNo)
|
||||||
).orElse(0L);
|
).orElse(0L);
|
||||||
if (ownCount + otherCount > 0) {
|
final long closureCount = Optional.ofNullable(
|
||||||
|
jdbcTemplate.queryForObject(
|
||||||
|
"SELECT COUNT(*) FROM m_clerk_closure_request "
|
||||||
|
+ "WHERE tok_no = ? AND CAST(submitted_at AS DATE) = CURRENT_DATE AND status <> 'REJECTED'",
|
||||||
|
Long.class, tokNo)
|
||||||
|
).orElse(0L);
|
||||||
|
if (ownCount + dividendCount + closureCount > 0) {
|
||||||
throw apiError(400,
|
throw apiError(400,
|
||||||
"Token number '" + tokNo + "' is already used today. Please enter a different token number.");
|
"Token number '" + tokNo + "' is already used today. Please enter a different token number.");
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -63,6 +63,7 @@ public class VoucherService {
|
|||||||
public static final String TYPE_REPAYMENT = "REPAYMENT";
|
public static final String TYPE_REPAYMENT = "REPAYMENT";
|
||||||
public static final String TYPE_INTEREST_POSTING = "INTEREST_POSTING";
|
public static final String TYPE_INTEREST_POSTING = "INTEREST_POSTING";
|
||||||
public static final String REF_CLERK_WITHDRAWAL_REQUEST = "CLERK_WITHDRAWAL_REQUEST";
|
public static final String REF_CLERK_WITHDRAWAL_REQUEST = "CLERK_WITHDRAWAL_REQUEST";
|
||||||
|
public static final String REF_CLERK_CLOSURE_REQUEST = "CLERK_CLOSURE_REQUEST";
|
||||||
public static final String REF_CASHIER_DEPOSIT_REQUEST = "CASHIER_DEPOSIT_REQUEST";
|
public static final String REF_CASHIER_DEPOSIT_REQUEST = "CASHIER_DEPOSIT_REQUEST";
|
||||||
public static final String REF_CLERK_DISBURSEMENT_REQUEST = "CLERK_DISBURSEMENT_REQUEST";
|
public static final String REF_CLERK_DISBURSEMENT_REQUEST = "CLERK_DISBURSEMENT_REQUEST";
|
||||||
public static final String REF_BANK_TRANSFER_LOG = "BANK_TRANSFER_LOG";
|
public static final String REF_BANK_TRANSFER_LOG = "BANK_TRANSFER_LOG";
|
||||||
|
|||||||
@@ -51,4 +51,7 @@
|
|||||||
<include file="parts/0522_seed_opening_balance_contra_account.xml" relativeToChangelogFile="true"/>
|
<include file="parts/0522_seed_opening_balance_contra_account.xml" relativeToChangelogFile="true"/>
|
||||||
<include file="parts/0523_seed_basoda_bank_chart_of_accounts.xml" relativeToChangelogFile="true"/>
|
<include file="parts/0523_seed_basoda_bank_chart_of_accounts.xml" relativeToChangelogFile="true"/>
|
||||||
<include file="parts/0524_seed_basoda_bank_income_expense_accounts.xml" relativeToChangelogFile="true"/>
|
<include file="parts/0524_seed_basoda_bank_income_expense_accounts.xml" relativeToChangelogFile="true"/>
|
||||||
|
<include file="parts/0525_create_clerk_closure_request.xml" relativeToChangelogFile="true"/>
|
||||||
|
<include file="parts/0526_seed_round_off_account.xml" relativeToChangelogFile="true"/>
|
||||||
|
<include file="parts/0527_clerk_closure_request_voucher_no.xml" relativeToChangelogFile="true"/>
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|||||||
+75
@@ -0,0 +1,75 @@
|
|||||||
|
<?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">
|
||||||
|
|
||||||
|
<!-- Maker-checker savings account closure, mirroring m_clerk_withdrawal_request's shape: a
|
||||||
|
clerk submits a Cash closure request against an ACTIVE account (no amount: the real
|
||||||
|
payout, including pro-rata interest and TDS, is only known once a manager approves it and
|
||||||
|
Fineract's own close command runs). closing_amount is the exact, paisa-correct payout;
|
||||||
|
cashier_disbursed_amount is that figure rounded to the nearest whole rupee for physical
|
||||||
|
cash hand-over, with the difference absorbed by the Round Off Account. -->
|
||||||
|
<changeSet id="0525_create_clerk_closure_request" author="cbs-nitesh">
|
||||||
|
<preConditions onFail="MARK_RAN">
|
||||||
|
<not>
|
||||||
|
<tableExists tableName="m_clerk_closure_request"/>
|
||||||
|
</not>
|
||||||
|
</preConditions>
|
||||||
|
<createTable tableName="m_clerk_closure_request">
|
||||||
|
<column name="id" type="BIGSERIAL">
|
||||||
|
<constraints primaryKey="true" nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="savings_account_id" type="BIGINT">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="account_no" type="VARCHAR(50)">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="client_id" type="BIGINT"/>
|
||||||
|
<column name="client_name" type="VARCHAR(255)"/>
|
||||||
|
<column name="closure_date" type="DATE"/>
|
||||||
|
<column name="payment_type_id" type="INTEGER"/>
|
||||||
|
<column name="payment_type_name" type="VARCHAR(100)"/>
|
||||||
|
<column name="description" type="TEXT"/>
|
||||||
|
<column name="tok_no" type="VARCHAR(50)"/>
|
||||||
|
<column name="status" type="VARCHAR(20)" defaultValue="PENDING">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="clerk_id" type="BIGINT"/>
|
||||||
|
<column name="clerk_name" type="VARCHAR(255)"/>
|
||||||
|
<column name="clerk_username" type="VARCHAR(100)"/>
|
||||||
|
<column name="manager_id" type="BIGINT"/>
|
||||||
|
<column name="manager_name" type="VARCHAR(255)"/>
|
||||||
|
<column name="manager_username" type="VARCHAR(100)"/>
|
||||||
|
<column name="rejection_reason" type="TEXT"/>
|
||||||
|
<column name="txn_id" type="VARCHAR(100)"/>
|
||||||
|
<column name="closing_amount" type="DECIMAL(19,6)"/>
|
||||||
|
<column name="rounding_amount" type="DECIMAL(19,6)"/>
|
||||||
|
<column name="cashier_disbursed_amount" type="DECIMAL(19,6)"/>
|
||||||
|
<column name="cashier_id" type="BIGINT"/>
|
||||||
|
<column name="disbursed_by" type="VARCHAR(150)"/>
|
||||||
|
<column name="disbursed_at" type="TIMESTAMP WITHOUT TIME ZONE"/>
|
||||||
|
<column name="disbursement_denomination_data" type="TEXT"/>
|
||||||
|
<column name="activity_log" type="TEXT"/>
|
||||||
|
<column name="submitted_at" type="TIMESTAMP WITHOUT TIME ZONE" defaultValueComputed="CURRENT_TIMESTAMP">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="actioned_at" type="TIMESTAMP WITHOUT TIME ZONE"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<createIndex indexName="idx_ccr_status" tableName="m_clerk_closure_request">
|
||||||
|
<column name="status"/>
|
||||||
|
</createIndex>
|
||||||
|
<createIndex indexName="idx_ccr_savings_account_id" tableName="m_clerk_closure_request">
|
||||||
|
<column name="savings_account_id"/>
|
||||||
|
</createIndex>
|
||||||
|
<createIndex indexName="idx_ccr_clerk_id" tableName="m_clerk_closure_request">
|
||||||
|
<column name="clerk_id"/>
|
||||||
|
</createIndex>
|
||||||
|
<createIndex indexName="idx_ccr_submitted_at" tableName="m_clerk_closure_request">
|
||||||
|
<column name="submitted_at"/>
|
||||||
|
</createIndex>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
</databaseChangeLog>
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
<?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">
|
||||||
|
|
||||||
|
<!-- Cash closure of a savings account pays out a whole-rupee amount (no paisa coins are in
|
||||||
|
circulation), but the account's real, interest-inclusive balance is exact to the paisa.
|
||||||
|
The few paise of difference are absorbed here rather than on the customer's own ledger:
|
||||||
|
standard practice at every real bank. Looked up directly by gl_code in
|
||||||
|
ClerkClosureRequestApiResource, not via acc_gl_financial_activity_account (round-off isn't
|
||||||
|
one of Fineract's fixed financial-activity types). -->
|
||||||
|
<changeSet id="0526_seed_round_off_account" author="cbs-nitesh">
|
||||||
|
<preConditions onFail="MARK_RAN">
|
||||||
|
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM acc_gl_account WHERE gl_code = '501200'</sqlCheck>
|
||||||
|
</preConditions>
|
||||||
|
<sql>
|
||||||
|
INSERT INTO acc_gl_account
|
||||||
|
(name, gl_code, disabled, manual_journal_entries_allowed, account_usage, classification_enum)
|
||||||
|
VALUES ('Round Off Account', '501200', false, true, 1, 5);
|
||||||
|
</sql>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
</databaseChangeLog>
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
<?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">
|
||||||
|
|
||||||
|
<!-- A cash closure pays real money out of a teller's drawer, so it has to appear in the central
|
||||||
|
voucher register like every other counter transaction does. The voucher is raised at manager
|
||||||
|
approval (the amount is not known before that) and reattributed to the cashier who actually
|
||||||
|
hands the cash over: see ClerkClosureRequestApiResource#approve and #disburse. -->
|
||||||
|
<changeSet id="0527_clerk_closure_request_voucher_no" author="cbs-nitesh">
|
||||||
|
<preConditions onFail="MARK_RAN">
|
||||||
|
<not>
|
||||||
|
<columnExists tableName="m_clerk_closure_request" columnName="voucher_no"/>
|
||||||
|
</not>
|
||||||
|
</preConditions>
|
||||||
|
<addColumn tableName="m_clerk_closure_request">
|
||||||
|
<column name="voucher_no" type="VARCHAR(30)"/>
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
</databaseChangeLog>
|
||||||
Reference in New Issue
Block a user