Skip to content

Banking & Receivables Reconciliation

Chapter scope: the "Banking" and "Reconciliation" nav areas under Sales & Receivables. This is the money-in engine: how money collected in the field (route sales), at POS (cash sales), and from debtors (credit customers) gets matched to bank statements, verified, approved, posted to the GL, and how exceptions (unknown/unmatched/short/suspended) are handled.

Out of scope (owned by other chapters): how the sale/invoice/cash sale is created (Order Taking, POS/Cash Sales), customer credit standing & bad debts (Customer Management), accounts payable / money-out and bank-file generation (Procurement/Finance), and the deep GL chart-of-accounts/journals (Finance & Accounting). This chapter owns the reconciliation and banking-approval side and documents the handoff point where money-in transactions post into the GL.


1. Purpose (plain language)

Every day, Bizwiz distributors take in money three ways:

  1. Route sales — salesmen sell to shops on their routes; customers pay by M-Pesa, bank transfer (Equity/KCB/Coop), Vooma, Eazzy, cheque, or cash that is later banked.
  2. Cash sales at the counter (POS) — walk-in sales, with cash dropped and banked, plus digital channel payments.
  3. Debtor payments — credit customers pay down invoices later.

The problem this module solves: the money the ERP thinks it received has to be reconciled against what the bank actually received. Salesmen key in a receipt ("customer paid 5,000 via M-Pesa, ref ABC123"); separately, the bank statement shows a deposit ("5,000, ref ABC123"). Reconciliation matches the two, an approver signs off, and only then is the receipt posted to the General Ledger as real money in the bank account.

The workflow, at a glance:

  • Upload the bank statement (per channel).
  • Match each statement line to a system receipt (auto-match on reference + amount + date, or manual allocation).
  • Verify the matched pairs.
  • Approve the verified batch — this is the point where GL journal entries are written (debit bank / credit debtors-control or cash-control).
  • Close the day's banking.

Anything that doesn't match cleanly is routed to an exception queue:

  • Unknown Bankings — money hit the bank but no system receipt claims it. Someone must "claim" it and an approver resolves the claim.
  • Unmatched Debtors — a receipt exists but isn't attached to a customer/invoice yet.
  • Short Banking / Excess — the amount banked is less (or more) than expected.
  • Suspended → Expunged / Restored — a receipt is pulled out of the workflow (wrong customer, duplicate, fraud), then either permanently voided (expunge) or re-created with corrected details (restore).

The two "Banking" dashboards (Route Sales Banking, Cash Sales Banking) are the operational front-ends where verify/approve/close happens; the "Reconciliation" menu holds the statement-level tooling (verifications, matching records, approvals, statements, logs, debtor tools).


2. Users & roles (permissions)

Access is gated by the sales-and-receivables___view top-level permission plus fine-grained reconciliation___* and sibling permissions. Users with role_id == config('app.allowed_role') (super-role) bypass the checks. Menu gating is in resources/views/admin/includes/sidebar_includes/sales_and_receivables.blade.php:709-898.

Permission strings are checked in code as can('<action>', 'reconciliation') (module reconciliation), e.g. can('see-overview', 'reconciliation'). In the sidebar they render as reconciliation___<action>.

Banking area

Menu item Route name Permission Controller
Route Sales Banking route-banking-approval.overview reconciliation___see-overview-route BankingApprovalController@showRouteOverviewPage (app/Http/Controllers/BankingApprovalController.php:49)
Cash Sales Banking pos-banking.daily-overview reconciliation___see-overview-pos PosBankingController@showDailyOverviewPage (app/Http/Controllers/PosBankingController.php:91)
GL Reconciliation gl-recon.overview reconciliation___gl-reconciliation GlReconciliationController@showOverviewPage (app/Http/Controllers/GlReconciliationController.php:22)
Unknown Bankings unknown-bankings.index reconciliation___unknown-bankings UnknownBankingsController@index (app/Http/Controllers/Admin/UnknownBankingsController.php:39)
Banking Utility banking-utility.index reconciliation___banking-utility BankingUtilityController@index (app/Http/Controllers/Admin/BankingUtilityController.php:45)

The Banking parent menu itself requires reconciliation___see-overview (sales_and_receivables.blade.php:709).

Reconciliation area

Menu item Route name Permission
Payment Verifications payment-reconciliation.verification reconciliation___verification
Matching Records payment-reconciliation.matching-records matching-records___view
Payment Approvals payment-reconciliation.approval reconciliation___approval
Suspended / Expunged / Restored Transactions suspended-transactions.{index,expunged,restored} reconciliation___suspend
Debtor Transactions debtor-trans reconciliation___view-debtor-trans (multi-branch adds view-branches-debtor-trans)
Debtor Transfers debtor-transfers.index reconciliation___transfer-debtor-record
Unmatched Debtors unmatched-debtors.index unmatched-debtors___view (matching action: match-unmatched-debtors)
Bank Statements (Listing / Channel Statements) bank-statements, bank-statements.channel-statements reconciliation___bank-statement-upload
Approve Uploads manual-upload-list reconciliation___view-manual-upload
Bank Posting Logs bank-posting-logs reconciliation___bank_post_log
Bank Error Logs bank-error-logs reconciliation___bank-error-logs
Transaction History transaction-history reconciliation___transaction-history
View Invoice Payments view-invoice-payments.index reconciliation___view-invoice-payments

The Reconciliation parent menu requires reconciliation___view (sales_and_receivables.blade.php:754).

Action-level permissions (used inside controllers)

  • reconciliation___post-sweeps-to-gl — post bank "sweep" transfers to GL (BankingApprovalController@postSweepsToGL).
  • reconciliation___claim-unknown — claim an unknown banking (UnknownBankingsController@claim).
  • reconciliation___resolve-unknown-claims — approve/reject unknown-banking claims.
  • reconciliation___view-all-branches — see all branches instead of only the user's own (POS banking, banking utility, debtor trans).
  • reconciliation___allocate-old-payments, reconciliation___allocate-payments-from-main-accounts, reconciliation___unverify-cash-sales — POS allocation/undo controls.
  • reconciliation___edit-debtor-reference, reconciliation___approve-debtor-edit-request — debtor reference editing / approval.

Note: the sidebar wires a few Route-Approval links to reconciliation___* permissions (e.g. sales_and_receivables.blade.php:464,471,477). This looks like copy-paste reuse of permission strings rather than a real dependency — see Open Questions.


3. Processes (start → finish)

3.1 The core reconciliation state machine

The central ledger is a verification range (payment_verifications) with two sides:

  • System side (payment_verification_systems) — the ERP's own receipts (from wa_debtor_trans, the debtor-transaction table).
  • Bank side (payment_verification_banks) — the uploaded bank-statement lines.

The status enum driving both sides is App\Enums\Status\PaymentVerification (app/Enums/Status/PaymentVerification.php): Verifying, Processing, Partially Verified, Verified, Pending, Approved, Partially Approved, Discard, Duplicate, Same Reference.

The receipt itself (wa_debtor_trans) carries its own lifecycle columns: verification_status (pending → verified → approved, plus manual upload), bank_statement_id (link to the matched bank line), posting_date (set once posted to GL), and flags is_settled, reconciled, manual_upload_status.

stateDiagram-v2
    [*] --> Unmatched: receipt created (wa_debtor_trans)\nOR statement line uploaded (payment_verification_banks)
    Unmatched --> Matched: auto-match on reference+amount+date\nOR manual allocation
    Matched --> Verified: verify (verification_status=verified,\nbank line status=Verified)
    Verified --> Approved: approve batch\n(verification_status=approved)
    Approved --> Posted: GL journal written\n(posting_date set, gl_posting_logs row)
    Posted --> [*]

    Unmatched --> Suspended: pull out of workflow\n(wrong cust / dup / fraud)
    Matched --> Suspended
    Verified --> Suspended
    Suspended --> Expunged: permanent void\n(GL/bank/tender rows deleted)
    Suspended --> Restored: re-create with edited\ncustomer/reference/amount
    Restored --> Unmatched: new wa_debtor_trans re-enters flow
    Expunged --> [*]

    Unmatched --> UnknownBanking: bank line with no system receipt
    UnknownBanking --> Claimed: user claims (claim-unknown)
    Claimed --> ResolvedUnknown: approver resolves (resolve-unknown-claims)\nis_unknown cleared → available for matching
    Claimed --> UnknownBanking: rejected → back to pending
    UnknownBanking --> PostedAsUnknown: postUnknowns → dr Unknown GL / cr Bank

3.2 Route Sales Banking approval

Controller: app/Http/Controllers/BankingApprovalController.php. A day's banking for a branch is tracked by a banking_approvals row with sales_type = 2 (route). The controller fetches/creates it via getBankingRecord (:109) and progresses it through stages (verifyBanking :932, approveBanking :989, closeBanking :965).

flowchart TD
    A[Overview dashboard\nshowRouteOverviewPage] --> B[Daily records\naggregate sales, receipts,\ndebtor trans per channel]
    B --> C[Channel breakdown\ngetMpesa/getEazzy/getEbMain/\ngetVooma/getKcbMain/getCoop...]
    C --> D{Match receipts\nto bank lines}
    D -->|matched| E[verifyBanking\nstage=verified]
    D -->|no receipt| F[postUnknowns\nmark bank line is_unknown]
    D -->|unverified junk| G[suspendUnverified\n→ SuspendedTransaction + FraudJournal]
    E --> H[approveBanking\nstage=approved + WRITE GL]
    H --> I[closeBanking\nstage=closed]
    H --> J[wa_gl_trans + wa_banktran inserted]

approveBanking (:989) is the GL handoff for route sales. For each wa_debtor_trans being approved (filtered verification_status != 'approved' and document_no not like 'BDT-%', :1006):

  • Sets verification_status = 'approved'.
  • Writes a credit to Debtors Control for -abs(amount).
  • If the document is an RCT receipt: writes a debit to the bank channel account for +abs(amount).
  • Otherwise (non-receipt): writes a debit to a Fraud account (55001-007) for +abs(amount).
  • Bulk-inserts into wa_gl_trans (:1092) and wa_banktran (:1093).

suspendUnverified (:853) handles unverified route junk: creates a SuspendedTransaction (status expunged), deletes the tender entry, writes a FraudJournal, creates a replacement wa_debtor_trans on the fraud-journals channel, then deletes the original.

postSweepsToGL (:2751, permission post-sweeps-to-gl): a "sweep" is a bank-to-bank transfer between accounts. Posts a credit to the source bank nominal and a debit to the sweep destination (sweep_channel / paymentMethod->sweep_account_id), marks the sweep Verified, resolves any linked UnknownBanking to resolved, and writes a BankStatementConsumer for traceability.

postUnknowns (:5746): marks bank lines as unknown-in-GL — debits an Unknown/undebited-transactions GL account and credits the bank; sets payment_verification_banks.is_unknown = true, is_posted_in_gl = 1, and creates an unknown_bankings row.

Per-channel record getters (all read payment_verification_banks scoped to route customers): getEazzy :1102, getEbMain :1276, getVooma :1448, getKcbMain :1623, getCoop :1797, getCoopMain :1969, getMpesa :2142. Equity/KCB/Coop each split into a channel account (account_type=1, use_in_pos=0) and a "main" account (account_type=2).

3.3 Cash Sales Banking (POS daily overview)

Controller: app/Http/Controllers/PosBankingController.php. Uses a banking_approvals row with sales_type = 1 (POS) and a four-stage lifecycle:

flowchart TD
    A[Daily overview\nshowDailyOverviewPage] --> B[getDailyRecords\ncash sales, returns, CDM drops,\npayment breakdowns]
    B --> C[runVerification\nmatch POS payments ↔ bank lines\n+ auto-verify CDM drops]
    C --> D[completeVerification\nstage=2 verified]
    D --> E[approveAndCloseBanking\nstage=3 approved + WRITE GL]
    E --> F[closeBanking\nstage=4 closed]
    C -.->|manual| G[allocateCdmDeposit / allocateCbDeposit\n+ GlPostingService]
    C -.->|short/excess| H[short_bankings_comments\nallocate / markAsFraud]
    C -.->|no receipt| I[getUnknown → postUnknowns]

runVerification (:1850): matches each unverified wa_pos_cash_sales_payments to a PaymentVerificationBank (primary: amount + reference + Pending + optional channel; fallback: amount + substring reference). Sets the payment verified=true, bank_statement_id; sets the bank line Verified; writes a BankStatementConsumer. Also auto-verifies CDM drops (banked_drop_transactions) by matching bank references.

approveAndCloseBanking (:6188) is the GL handoff for cash sales. For each verified payment it resolves a Cash Control account (default 54008-000, or branch-specific when SEPARATE_GL_ACCOUNTS_PER_BRANCH is set), then writes: credit Cash Control -abs(amount), and either debit bank (RCT receipt) or debit Fraud (55001-007) (non-receipt), +abs(amount). Bulk-inserts wa_gl_trans + wa_banktran.

CDM (Cash Deposit Machine) deposits: getCdms :2034, getDrops :2231, allocateCdmDeposit :2253 (validates the statement, creates a BankedDropTransaction, finds/creates a tender entry, then posts GL via GlPostingService: credit Cash / debit Bank).

Short banking & excess: getShortBankingRecords :4596 lists short_bankings_comments (amount > 0 = "short", < 0 = "excess"); allocateShortBanking :4557 resolves a short against a bank statement line; markShortBankingAsFraud :7944 resolves it as fraud and writes a FraudJournal.

3.4 Statement upload → matching → verification → approval → posting (Reconciliation menu)

Controller: app/Http/Controllers/Admin/Finance/PaymentReconciliationController.php; routes in routes/web.php:5679-5700.

flowchart TD
    A[verification_create\nnew payment_verifications range] --> B[verification_upload\nparse Excel/CSV bank statement\n→ payment_verification_banks Pending]
    B --> C[verification_process\nmatch bank lines ↔ wa_debtor_trans\non reference+amount+date +/- channel]
    C --> D[Verified\nbank line=Verified, debtor=verified\n+ BankStatementConsumer]
    D --> E[approval / approval_store\nverification_status=approved]
    E --> F[GL posting job\nApproveReconciliationPayments\n+ gl_posting_logs]
    C -.->|no match| G[manual allocation\nManualUploadController]
    C -.->|discard| H[verification_discard]
    C -.->|suspend| I[verification_suspend]
  • verification_upload (:531) parses the statement file (columns: trans_date, channel, reference, amount, narration) into payment_verification_banks (Pending), and can create tender entries.
  • verification_process (:42) joins bank lines to wa_debtor_trans on reference + amount + date range, using ROW_NUMBER() to force 1:1 matches. The setting MATCH_CHANNEL_ON_PAYMENT_VERIFICATION (:70) controls whether the payment method/channel must also match.
  • matchingRecordsView (:949) is the "Matching Records" screen — shows which statement lines matched which receipts.
  • approval / approval_store (:1347) sets verification_status = approved, queues the GL posting job, and writes gl_posting_logs.
  • bank_post_logs (:1697, route bank-posting-logs) shows GL postings grouped by transaction_no.

3.5 Manual uploads → approval (Approve Uploads)

Controller: app/Http/Controllers/Admin/ManualUploadController.php; routes routes/web.php:5746-5753.

When auto-matching fails, an operator manually allocates a bank statement line to an invoice: manual_upload_transaction (:46) creates a wa_debtor_trans with verification_status='manual upload', a WaTenderEntry, invoice allocation, and a BankStatementConsumer; the bank line becomes Verified. These land in Approve Uploads (manual_upload_list :671, RCT docs with manual_upload_status=1), where manual_update_status (:828) or manual_bulk_approve (:842) promotes them to verified (recording manual_upload_approved_by), after which they can be approved to approved in the normal flow.

3.6 Unknown / Unmatched handling

  • Unknown Bankings (UnknownBankingsController): a bank line flagged is_unknown=1 with no matching receipt. Lifecycle pending → claimed → resolved (or back to pending on reject):
  • claim (:532, permission claim-unknown) sets claimed, records requested_by/at + comment, and SMS-notifies resolvers.
  • approveClaim (:579, permission resolve-unknown-claims) sets resolved, clears payment_verification_banks.is_unknown, SMS-notifies the claimer.
  • rejectClaim (:630) returns to pending with a rejection reason.
  • Bulk variants: bulkClaim :688, bulkApproveClaim :759, bulkRejectClaim :838.
  • Unmatched Debtors (app/Http/Controllers/Admin/Finance/UnmatchedDebtorsController.php): wa_debtor_trans with wa_sales_invoice_id IS NULL AND wa_route_customer_id IS NULL and document_no NOT LIKE 'RCOP%' (:100). match (:165) attaches the receipt to a chosen route customer + invoice after validating the invoice belongs to that customer.

3.7 Suspended → Expunged / Restored

Controller: app/Http/Controllers/Admin/SuspendedTransactionController.php; routes routes/web.php:5764-5773.

  • Suspend (store :120): checks the receipt isn't already Approved, creates a suspended_transactions row (status suspended) with the original data + reason, cleans up allocations (invoice_payments_allocations, rcop_allocations, bank_statement_consumers), resets the matched bank line to Pending, and deletes the wa_debtor_trans.
  • Expunge (expunge :268): sets status expunged; deletes the wa_gl_trans, wa_bank_trans, and wa_tender_entry rows for that document — permanent void, audit trail only.
  • Restore (restore :305): sets status restored with edited customer/reference/amount; creates a new wa_debtor_trans and WaTenderEntry with the corrected values, re-mapping the payment method by channel (Eazzy=7, Equity=10, Vooma=8, KCB=9, M-Pesa/Cheque=3, :351).

transaction-history (TransactionHistoryController@fetch) ties this together: search a document_no to see the original wa_debtor_trans plus every suspend/expunge/restore action against it.

3.8 GL Reconciliation (bank vs GL control)

GlReconciliationController@getRecords (:83) compares, per channel: bank debits/credits (sum of payment_verification_banks.amount by type) against system debits/credits (sum of wa_banktrans.amount for the bank GL account), starting from a per-channel opening balance (bank_opening_balances, editable via getOpeningBalances/updateOpeningBalances), and surfaces a variance for each. The view (resources/views/gl_recon/overview.blade.php) shows Opening Balance, System Debits/Credits, and Debit/Credit Variance columns.


4. Tables touched & key data

Migrations live in database/migrations/. Key tables (with the columns that drive the state machine in bold):

Verification ledger - payment_verifications (2024_05_13_121816_...) — a reconciliation range: created_by, start_date, end_date, branch_id, channel,status (PaymentVerification enum). - payment_verification_banks (2024_05_14_104950_...) — bank-statement lines: reference, amount, bank_date, channel, payment_method_id,status(Pending→Verified→Approved),matched_debtors_id(→ wa_debtor_trans = matched),is_unknown, is_posted_in_gl, gl_tran_id, sweep_channel, narration, uploaded_by. - payment_verification_systems (2024_05_14_104941_...) — system side: payment_verification_id, debtor_id,status, verified_by, verified_date, approved_by, approved_date, amount, document_no, reference.

Receipts / debtor transactions - wa_debtor_trans (base 2023_09_08_134414_..., many later ALTERs) — the money-in record. State columns: verification_status (pending/verified/approved/manual upload, added 2024_06_05_114311_...), bank_statement_id (matched bank line, added 2024_06_08_085121_...), posting_date (GL posted, added 2024_10_20_134340_...), is_settled, reconciled (2024_04_13_130858_...), manual_upload_status, manual_upload_approved_by, verification_record_id, channel, bank_ref, wa_sales_invoice_id, wa_route_customer_id, wa_customer_id, document_no (RCT%, RCOP%, VAT%, BDT%). - debtors_record_change_requests — debtor transfer requests: wa_debtor_trans_id, from_wa_customer_id, to_wa_customer_id, requested_by, approved_by,status(Pending/Approved/Rejected). - debtor_trans_edit_requests — pending debtor edits (gated by EDIT_DEBTORS_TRANSACTION_REQUIRES_APPROVAL).

Exceptions - suspended_transactions (2024_04_16_105156_...) — wa_customer_id, edited_wa_customer_id, suspended_by, resolved_by, document_no, reference, edited_reference, amount, edited_amount, trans_date, route, reason,status(defaultsuspended→expunged/restored),channel, branch, verification_record_id, manual_upload_status. -unknown_bankings(2025_06_09_154602_...) —payment_verification_bank_id, user_id, **status** (default pending → claimed/resolved), requested_by/at, approved_by/at, gl_account_code. -short_bankings_comments(2024_11_27_121744_...) —created_by, comment, amount (>0 short, <0 excess), **status** (Pending/Resolved), sales_date, branch_id, type, resolved_by/at, resolve_remarks, bank_statement_id, attachment, parent_comment_id. Linking tables:resolved_short_bank_statements,resolved_short_excess_links`.

Banking approval control - banking_approvals (2024_09_28_094551_... + ALTERs) — one row per branch per day per sales type. Tracks the stage lifecycle. Confirmed columns include sales_date, payment_date, closed, closed_by; the controllers also read/write sales_type (1=POS, 2=route), verified/verified_by/verified_at, approved/approved_by/approved_at, closed_at, and a stage field (see Open Questions — these live in the later ALTER migrations, confirmed via controller usage rather than a single migration read).

CDM / cash banking - banked_drop_transactions (2024_10_02_143114_...) — CDM deposits: cash_drop_transaction_id, bank_reference, cash_drop_reference, amount, banked_at,verified. - banked_cash_transactions (2024_10_02_143231_...) — chief-cashier cash banking. - cash_banking_reference_requests (2025_01_15_110834_...). - chief_cashier_declarations, cash_drop_transactions — upstream POS cash sources (owned by POS chapter; read here).

Bank statement plumbing & logs - bank_statement_consumers (2025_09_16_143218_...) — polymorphic junction linking a statement line to whatever consumed it (statement_id, consumer_id, consumer_type, amount, narration, user_id). Consumer types seen: WaGlTran, WaDebtorTran, WaPosCashSalesPayments, RegisterCheque, PaymentVerificationSystem, ShortBankingComment, BankedDropTransaction, CashBankingReferenceRequest, WithholdingPaymentVoucher, PaymentVoucherCheque. - bank_statement_bank_errors (2024_09_07_163127_...) — flagged statement errors: payment_verification_bank_id, reason,status, created_by, restored_by, restored_date. - bank_statement_edit_requests (2025_02_04_114713_...), bank_statement_mispost_histories (2024_09_05_134314_...). - bank_opening_balances (2024_10_18_093431_...) — per-account opening balances for GL recon.

GL bridge - gl_posting_logs (2024_06_27_114939_...) — audit of postings: created_by, transaction_no, document_no, amount, wa_banktrans_id, wa_debtor_trans_id, payment_verification_id. - wa_gl_trans, wa_banktran — the GL/bank ledger tables written on approval (owned by Finance & Accounting; this module inserts money-in entries).

⚠️ Naming caution: wallet_trans / WalletTran and wallet_matrix / WalletMatrix are NOT the reconciliation ledger. wallet_trans (app/WalletTran.php, migration 2024_03_20_153827_...) is an employee-wallet table (employee_id, transaction_type = withdrawal/deposit/incentive, route_id, shift_id), and wallet_matrix holds salesman/driver commission-rate parameters. They belong to salesman incentives, not banking reconciliation. The real money-in ledger is the payment_verification_* + wa_debtor_trans family above.


5. Interactions with other modules (esp. GL posting handoff)

  • Order Taking / POS / Cash Sales (upstream): receipts (wa_debtor_trans), POS cash sales (wa_pos_cash_sales*), cash drops and chief-cashier declarations are created there; this module consumes and reconciles them.
  • Customer Management: debtor balances, unmatched-debtor matching to route customers/invoices, and debtor transfers reference wa_customers / wa_route_customers. Bad debts (BDT-% documents) are explicitly excluded from route-banking GL approval (BankingApprovalController@approveBanking :1006).
  • General Ledger / Finance & Accounting (the handoff): this is the boundary this chapter owns up to. Money-in posts to the GL at four points:
  • Route approval (approveBanking): credit Debtors Control, debit Bank (or Fraud) → wa_gl_trans + wa_banktran.
  • POS approval (approveAndCloseBanking): credit Cash Control (54008-000/branch), debit Bank (or Fraud).
  • Reconciliation approval (PaymentReconciliationController@approval_store): queues ApproveReconciliationPayments, writes gl_posting_logs.
  • Sweeps / Unknowns / CDM (postSweepsToGL, postUnknowns, allocateCdmDeposit via GlPostingService).

Downstream, the Finance book's GL reconciliation (App\Http\Controllers\Admin\GlReconciliationController, routes/modules/general_ledger.php:19-52, incl. ManualGLPostingController and gl-utility for pending-GL sales-payments/bank-statements/resolved-short-banking) closes out the chart-of-accounts side. The banking-side "GL Reconciliation" screen in this chapter (App\Http\Controllers\GlReconciliationController, gl-recon.overview) is a different, lighter tool that only reconciles bank-statement totals vs wa_banktrans per channel — don't confuse the two. - SMS / Alerts: unknown-banking claim/resolve steps send SMS via SmsService (SmsScenarios::UNKNOWN_BANKING_CLAIM_REQUEST*). - Number series: posting steps mint document numbers via NumberSeriesGeneratorService.


6. Alternatives & variants

Per-channel differences

Channels: M-Pesa, cheque, Eazzy, Vooma, KCB, Coop, CDM (plus Equity "EB").

  • Bank channels split into "channel" vs "main" accounts. Equity, KCB, and Coop each have a route/POS channel account (account_type=1, use_in_pos=0) and a "main" account (account_type=2) — hence separate getters getEazzy/getEbMain, getVooma/getKcbMain, getCoop/getCoopMain.
  • Payment-method IDs on restore (SuspendedTransactionController@restore :351): Eazzy=7, Equity=10, Vooma=8, KCB=9, and M-Pesa/Cheque/others=3.
  • Channel matching is optional in auto-verification, toggled by MATCH_CHANNEL_ON_PAYMENT_VERIFICATION. When off, matches key only on reference + amount + date.
  • CDM is a distinct flow: bank deposits at a Cash Deposit Machine are matched to physical cash drops (banked_drop_transactions ↔ cash_drop_transactions) rather than to a digital reference, and post credit-Cash/debit-Bank via GlPostingService.
  • Cheque payments interact with cheque management (register/deposit/bounce), outside this chapter; here they appear as a channel and as getCheques in POS banking.

Manual vs automated

  • Automated: verification_process / runVerification auto-match on reference/amount/date.
  • Manual: ManualUploadController allocation + the Approve Uploads queue for anything that couldn't auto-match; BankingUtilityController for corrective re-assignment (see below).
  • Statement ingestion is upload-based (Excel/CSV via verification_upload and BankStatementUploadController), not (from what was traced) a live bank API feed — see Open Questions.

Banking Utility (corrective / admin)

BankingUtilityController is the "fix it" toolkit (permission banking-utility): re-date/re-assign CDM records (updateCdmRecord), cash-banking records (updateCbRecord), and manual allocations (updateManualAllocationRecord); delete CDM / manual-allocation records; resolve duplicate POS sales (updateDuplicateSaleRecord, which re-numbers and re-books via PerformPostSaleActions); edit/delete bank-statement entries and add statement opening balances.

Flavor / tenant differences

Behaviour is toggled by Setting/company-preference flags, so it varies per client ("flavor"): MATCH_CHANNEL_ON_PAYMENT_VERIFICATION, SEPARATE_GL_ACCOUNTS_PER_BRANCH, BLOCK_COUNTER_SALES_WITH_EXCESS_UNBANKED_CASH, EDIT_DEBTORS_TRANSACTION_REQUIRES_APPROVAL, REQUIRE_APPROVAL_FOR_ROUTE_CUSTOMER_DEACTIVATION, unknown-bankings GL account preferences (unknown_bankings_gl_account / _2). Hard-coded GL codes seen: cash control 54008-000, fraud 55001-007.

Legacy

  • A commented-out "Manual Uploads" menu (sales_and_receivables.blade.php:803-808) pointing at maintain-customers.real_recon.index suggests a superseded reconciliation entry point.
  • routes/modules/sales_and_receivables.php.bak exists — an older copy of the routes.
  • A separate BankReconciliationController (routes web.php:5674-5677, bank-reconciliation.*) appears older/parallel to the payment-reconciliation.* flow.

7. Open questions to confirm

  1. banking_approvals exact schema. The stage lifecycle (verified/approved/closed + stage 1–4, sales_type 1=POS/2=route) is proven by controller reads/writes (BankingApprovalController / PosBankingController), but only sales_date, payment_date, closed, closed_by were confirmed in a single migration read; the rest are spread across ALTER migrations (2024_11_28_233225, 2025_06_18_161524, 2025_06_30_214256). Confirm the full column list and whether stage numbers differ between route (max 3) and POS (max 4).
  2. is_settled vs verification_status vs reconciled. wa_debtor_trans has three overlapping "state" flags. Confirm which is authoritative for "matched" at each step (the state machine here treats verification_status + bank_statement_id as primary; is_settled/reconciled may be legacy or invoice-allocation flags).
  3. Suspended status values. Migration default is suspended; expunge/restore set expunged/restored, but BankingApprovalController@suspendUnverified writes status expunged directly at creation. Confirm the complete set of valid statuses and whether "resolved" is also used.
  4. Unknown-banking claimed status. The create migration comments only pending/approved, but the controller clearly uses pending → claimed → resolved. Confirm the DB actually stores claimed/resolved (vs approved) and that reject truly reverts to pending.
  5. Automated bank feeds? All statement ingestion traced is manual upload (Excel/CSV). Confirm whether any client uses an automated/API bank feed for M-Pesa/Equity/KCB, or whether ProcessUploadedBankStatements / SyncUnknownBankingJob implies a scheduled sync.
  6. Two GL-reconciliation controllers. Confirm the intended division of labour between banking-side App\Http\Controllers\GlReconciliationController (this chapter) and Finance-side App\Http\Controllers\Admin\GlReconciliationController (Finance book), and whether both are live per flavor.
  7. Route-Approval sidebar permissions. sales_and_receivables.blade.php:464/471/477 gate Route-Approval links on reconciliation___* permissions — confirm this is intentional or a copy-paste artifact.
  8. ManualUploadController permission on write. manual_upload_transaction had no explicit can() check in the trace; confirm whether authorization is enforced at the route/middleware level.
  9. wa_debtor_tran_recons table — its role (a flagged/batch reconciliation-tracking side table) was inferred from schema only; confirm where it's written and read.

8. Source references

Nav / IA - resources/views/admin/includes/sidebar_includes/sales_and_receivables.blade.php:709-898 (Banking + Reconciliation menus & permissions)

Routes - routes/modules/sales_and_receivables.php:147-517 (banking prefix: route/pos/gl-recon/utility/unknown-bankings; reconciliation overview; transaction-history; view-invoice-payments; debtor transfers; unmatched debtors) - routes/web.php:5679-5773 (payment-reconciliation verification/matching/approval; bank-post-log; debtor-trans; bank-statements; manual-upload; suspended-transactions) - routes/modules/general_ledger.php:19-52,177-180 (Finance-side GL reconciliation & bank-recon utility — boundary)

Controllers - app/Http/Controllers/BankingApprovalController.php (Route Sales Banking; approveBanking:989, suspendUnverified:853, postSweepsToGL:2751, postUnknowns:5746, channel getters:1102-2316) - app/Http/Controllers/PosBankingController.php (Cash Sales Banking; runVerification:1850, approveAndCloseBanking:6188, closeBanking:6337, CDM:2034-2421, short banking:4557-7944) - app/Http/Controllers/GlReconciliationController.php (banking-side GL recon; getRecords:83, opening balances:38-81) - app/Http/Controllers/Admin/BankingUtilityController.php (corrective utility) - app/Http/Controllers/Admin/UnknownBankingsController.php (claim/approve; claim:532, approveClaim:579, rejectClaim:630, bulk:688-906) - app/Http/Controllers/Admin/Finance/PaymentReconciliationController.php (verification_process:42, verification_upload:531, matchingRecordsView:949, approval:1347, bank_post_logs:1697) - app/Http/Controllers/Admin/SuspendedTransactionController.php (store:120, expunge:268, restore:305) - app/Http/Controllers/Admin/DebtorRecordChangeRequestController.php (debtor transfers) - app/Http/Controllers/Admin/Finance/DebtorTransController.php (debtor-trans listing & edit requests) - app/Http/Controllers/Admin/Finance/UnmatchedDebtorsController.php (unmatched:100, match:165) - app/Http/Controllers/Admin/ManualUploadController.php (manual_upload_transaction:46, manual_upload_list:671) - app/Http/Controllers/Admin/BankStatementUploadController.php (statement upload, channel statements, bank_error_logs) - app/Http/Controllers/Admin/TransactionHistoryController.php, app/Http/Controllers/Admin/ViewInvoicePaymentsController.php

Models / enums - app/Enums/Status/PaymentVerification.php (status enum) - app/Models/PaymentVerification.php, PaymentVerificationBank.php, PaymentVerificationSystem.php - app/Models/SuspendedTransaction.php, app/Models/UnknownBanking.php, app/Models/ShortBankingComment.php, app/Models/BankStatementConsumer.php, app/Models/BankStatementBankError.php - app/Model/WaDebtorTran.php - (Not this module: app/WalletTran.php, app/WalletMatrix.php — employee wallet/commission, see §4 caution)

Migrations (database/migrations/) - 2024_05_13_121816_create_payment_verifications_table.php, 2024_05_14_104950_create_payment_verification_banks_table.php, 2024_05_14_104941_create_payment_verification_systems_table.php - 2023_09_08_134414_create_wa_debtor_trans_table.php + ALTERs (2024_04_13_130858, 2024_06_05_114311, 2024_06_08_085121, 2024_10_20_134340, …) - 2024_04_16_105156_create_suspended_transactions_table.php (+ 2024_06_14_123444, 2024_08_14_091629) - 2025_06_09_154602_create_unknown_bankings_table.php (+ 2026_05_04_*, 2026_05_04_130000 gl_account_code) - 2024_09_28_094551_create_banking_approvals_table.php (+ ALTERs 2024_11_28, 2025_06_18, 2025_06_30, index 2026_01_17) - 2024_11_27_121744_create_short_bankings_comments_table.php (+ resolve columns 2025_08_01, 2025_08_06, 2025_08_07); 2025_08_08_164816_create_resolved_short_bank_statements_table.php - 2024_10_02_143114_create_banked_drop_transactions_table.php, 2024_10_02_143231_create_banked_cash_transactions_table.php, 2025_01_15_110834_create_cash_banking_reference_requests_table.php - 2025_09_16_143218_create_bank_statement_consumers_table.php, 2024_09_07_163127_create_bank_statement_bank_errors_table.php, 2025_02_04_114713_create_bank_statement_edit_requests_table.php, 2024_09_05_134314_create_bank_statement_mispost_histories_table.php - 2024_10_18_093431_create_bank_opening_balances_table.php, 2024_06_27_114939_create_gl_posting_logs_table.php

Views - resources/views/banking_approval/ (route_overview, pos_daily_overview, route_banking_show, short_bankings_comments, utility, PDFs) - resources/views/reconciliation/payments/overview.blade.php (recon dashboard: Debtors Balance, Sales vs Receipts, Reconciliation Issues, Reconciliation Resolutions) - resources/views/gl_recon/overview.blade.php (opening balance / system debits-credits / variance)