feat(cashier+voucher): add cashier deposit maker-checker flow and central voucher system

- VoucherService: central voucher register with globally unique voucher numbers
- CashierDepositRequestApiResource: full submit/approve/reject/list flow
  - Cashier submits deposit request; balance credited only on manager approval
  - Self-approval blocked (maker cannot be checker)
  - Role guard: only manager/head-cashier/ALL_FUNCTIONS can approve/reject
  - Race condition guard: WHERE status='PENDING' on approve/reject UPDATE
  - Clean JSON error responses (no 500 stack traces) via apiError() helper
  - Denomination total validation at submit and approve
- ClerkWithdrawalRequestApiResource: refactored to use m_voucher
  - Voucher created at submit, linked to savings txn at approve
  - Same role guard and self-approval block as deposit flow
  - Disburse: denomination validation, null denoms for non-cash payments
- Migrations 0327-0330:
  - 0327: add voucher_no to m_clerk_withdrawal_request
  - 0328: add voucher_no to m_savings_account_transaction
  - 0329: create m_voucher central table (globally unique voucher_no)
  - 0330: create m_cashier_deposit_request table

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jaanu423
2026-06-12 10:53:47 +05:30
co-authored by Claude Sonnet 4.6
parent 8d5b4d1ce9
commit 33ea10689f
8 changed files with 1056 additions and 37 deletions
@@ -354,4 +354,8 @@
<include file="parts/0324_add_daily_token_counter.xml" relativeToChangelogFile="true"/>
<include file="parts/0325_add_disbursement_fields.xml" relativeToChangelogFile="true"/>
<include file="parts/0326_drop_daily_token_counter.xml" relativeToChangelogFile="true"/>
<include file="parts/0327_add_voucher_no.xml" relativeToChangelogFile="true"/>
<include file="parts/0328_add_voucher_no_to_savings_txn.xml" relativeToChangelogFile="true"/>
<include file="parts/0329_create_voucher.xml" relativeToChangelogFile="true"/>
<include file="parts/0330_create_cashier_deposit_request.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
@@ -0,0 +1,13 @@
<?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="0327_add_voucher_no" author="CBS">
<addColumn tableName="m_clerk_withdrawal_request">
<column name="voucher_no" type="VARCHAR(6)"/>
</addColumn>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,16 @@
<?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="0328_add_voucher_no_to_savings_txn" author="CBS">
<preConditions onFail="MARK_RAN">
<not><columnExists tableName="m_savings_account_transaction" columnName="voucher_no"/></not>
</preConditions>
<addColumn tableName="m_savings_account_transaction">
<column name="voucher_no" type="VARCHAR(6)"/>
</addColumn>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,45 @@
<?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="0329_create_voucher" author="CBS">
<preConditions onFail="MARK_RAN">
<not><tableExists tableName="m_voucher"/></not>
</preConditions>
<createTable tableName="m_voucher">
<column name="id" type="BIGINT" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="voucher_no" type="VARCHAR(6)">
<constraints unique="true" uniqueConstraintName="uq_voucher_no"/>
</column>
<column name="voucher_type" type="VARCHAR(20)">
<constraints nullable="false"/>
</column>
<column name="reference_type" type="VARCHAR(40)">
<constraints nullable="false"/>
</column>
<column name="reference_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="savings_txn_id" type="BIGINT"/>
<column name="amount" type="DECIMAL(19,6)"/>
<column name="created_by" type="BIGINT"/>
<column name="created_by_username" type="VARCHAR(100)"/>
<column name="created_at" type="TIMESTAMP"/>
</createTable>
<createIndex tableName="m_voucher" indexName="idx_voucher_reference">
<column name="reference_type"/>
<column name="reference_id"/>
</createIndex>
<createIndex tableName="m_voucher" indexName="idx_voucher_savings_txn">
<column name="savings_txn_id"/>
</createIndex>
<createIndex tableName="m_voucher" indexName="idx_voucher_created_at">
<column name="created_at"/>
</createIndex>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,61 @@
<?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="0330_create_cashier_deposit_request" author="CBS">
<preConditions onFail="MARK_RAN">
<not><tableExists tableName="m_cashier_deposit_request"/></not>
</preConditions>
<createTable tableName="m_cashier_deposit_request">
<column name="id" type="BIGINT" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="voucher_no" type="VARCHAR(6)"/>
<column name="account_no" type="VARCHAR(40)">
<constraints nullable="false"/>
</column>
<column name="account_type" type="VARCHAR(20)" defaultValue="SAVINGS"/>
<column name="savings_account_id" type="BIGINT"/>
<column name="client_id" type="BIGINT"/>
<column name="client_name" type="VARCHAR(200)"/>
<column name="amount" type="DECIMAL(19,6)">
<constraints nullable="false"/>
</column>
<column name="payment_type_id" type="BIGINT"/>
<column name="payment_type_name" type="VARCHAR(100)"/>
<column name="description" type="VARCHAR(500)"/>
<column name="cheque_no" type="VARCHAR(50)"/>
<column name="bank_name" type="VARCHAR(100)"/>
<column name="denomination_data" type="TEXT"/>
<column name="txn_date" type="DATE"/>
<column name="status" type="VARCHAR(20)" defaultValue="PENDING">
<constraints nullable="false"/>
</column>
<column name="cashier_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="cashier_name" type="VARCHAR(200)"/>
<column name="cashier_username" type="VARCHAR(100)"/>
<column name="manager_id" type="BIGINT"/>
<column name="manager_name" type="VARCHAR(200)"/>
<column name="manager_username" type="VARCHAR(100)"/>
<column name="rejection_reason" type="VARCHAR(1000)"/>
<column name="txn_id" type="VARCHAR(40)"/>
<column name="activity_log" type="TEXT"/>
<column name="submitted_at" type="TIMESTAMP"/>
<column name="actioned_at" type="TIMESTAMP"/>
</createTable>
<createIndex tableName="m_cashier_deposit_request" indexName="idx_cdr_status">
<column name="status"/>
</createIndex>
<createIndex tableName="m_cashier_deposit_request" indexName="idx_cdr_cashier">
<column name="cashier_id"/>
</createIndex>
<createIndex tableName="m_cashier_deposit_request" indexName="idx_cdr_submitted_at">
<column name="submitted_at"/>
</createIndex>
</changeSet>
</databaseChangeLog>