Replaces the one-click Close action on the savings account page (removed from the Danger Zone menu) with a maker-checker flow, since closing an account pays real cash out of a teller's drawer. Clerk: an Action button on Transaction Entry, shown once an ACTIVE savings account is loaded. Ticking "Close this account" previews the payout -- balance plus interest calculated to today by the same code the closure itself runs -- and Save drops that figure into the slip's AMOUNT. The clerk then fills TOK. NO. on the slip and presses Save, which raises the request. Token and narration deliberately live on the slip, not in the popup, so the closure is entered like any other transaction. Manager: a Closure Requests tab in the Approval Center, reviewed in a popup dialog (matching Disbursement Requests) rather than a separate route. Cashier: the closure token is recognised at the counter alongside withdrawal and dividend tokens, auto-filling the rounded cash figure, locking it, and closing the account once the denomination is counted. Save-path fixes, all of which silently blocked a valid closure: - amount came from the denomination total whenever clerkMode was false -- which it is for any route other than the Clerk Dashboard wrapper -- so the calculated payout was replaced by zero - the balance check rejected the payout because it legitimately exceeds today's withdrawable amount: it includes interest close() posts before withdrawing. Min-balance, withdrawal-facility and clerk-side denomination checks do not apply to a closure either - on the cashier side the account is already CLOSED, so accountDetails is a stub with no balance and the same check reported "Withdrawable 0.00" - the closure branch had to precede the plain clerk-withdrawal branch: a closure is a full-balance withdrawal, so that branch would have swallowed it and the account would never have closed - onSave's one silent return now says what is missing, instead of leaving Save looking like a broken button Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
297 lines
12 KiB
TypeScript
297 lines
12 KiB
TypeScript
/**
|
|
* Copyright since 2025 Mifos Initiative
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
import { NgModule } from '@angular/core';
|
|
import { Routes, RouterModule } from '@angular/router';
|
|
|
|
import { Route } from '../core/route/route.service';
|
|
import { CashierOnlyGuard } from '../core/authentication/cashier-only.guard';
|
|
import { CashExchangeEntryComponent } from './cash-exchange-entry/cash-exchange-entry.component';
|
|
import { CurrencyExchangeDetailComponent } from './currency-exchange-detail/currency-exchange-detail.component';
|
|
import { CashTransferComponent } from './cash-transfer/cash-transfer.component';
|
|
import { CashSettlementComponent } from './cash-settlement/cash-settlement.component';
|
|
import { InterCashierTransferComponent } from './inter-cashier-transfer/inter-cashier-transfer.component';
|
|
import { CashierPositionComponent } from './cashier-position/cashier-position.component';
|
|
import { ReconciliationComponent } from './reconciliation/reconciliation.component';
|
|
import { CashHistoryComponent } from './cash-history/cash-history.component';
|
|
import { DayBookComponent } from './day-book/day-book.component';
|
|
import { VoucherExceptionsComponent } from './voucher-exceptions/voucher-exceptions.component';
|
|
import { VoucherViewComponent } from './voucher-view/voucher-view.component';
|
|
import { VoucherReportComponent } from './voucher-report/voucher-report.component';
|
|
import { BranchDayComponent } from './branch-day/branch-day.component';
|
|
import { VaultPositionComponent } from './vault-position/vault-position.component';
|
|
import { AccountRegisterComponent } from './account-register/account-register.component';
|
|
import { ClosingCashReportComponent } from './closing-cash-report/closing-cash-report.component';
|
|
import { ExcessTransactionReportComponent } from './excess-transaction-report/excess-transaction-report.component';
|
|
import { ShareCollectionComponent } from './share-collection/share-collection.component';
|
|
|
|
const routes: Routes = [
|
|
Route.withShell([
|
|
{
|
|
path: 'clerk',
|
|
data: { title: 'Clerk', breadcrumb: 'Clerk' },
|
|
children: [
|
|
{
|
|
path: 'dashboard',
|
|
loadComponent: () =>
|
|
import('../clerk/clerk-dashboard/clerk-dashboard.component').then((m) => m.ClerkDashboardComponent),
|
|
data: { title: 'Clerk Dashboard', breadcrumb: 'Clerk Dashboard' }
|
|
},
|
|
{
|
|
path: 'withdrawal-request',
|
|
loadComponent: () =>
|
|
import('../clerk/withdrawal-request/withdrawal-request.component').then(
|
|
(m) => m.WithdrawalRequestComponent
|
|
),
|
|
data: { title: 'New Withdrawal Request', breadcrumb: 'New Withdrawal Request', mode: 'create' }
|
|
},
|
|
{
|
|
path: 'withdrawal-request/:id',
|
|
loadComponent: () =>
|
|
import('../clerk/withdrawal-edit/withdrawal-edit.component').then((m) => m.WithdrawalEditComponent),
|
|
data: { title: 'Edit Withdrawal Request', breadcrumb: 'Edit Withdrawal Request' }
|
|
},
|
|
{
|
|
path: 'withdrawal-requests',
|
|
loadComponent: () => import('../clerk/my-requests/my-requests.component').then((m) => m.MyRequestsComponent),
|
|
data: { title: 'My Withdrawal Requests', breadcrumb: 'My Withdrawal Requests' }
|
|
},
|
|
{
|
|
path: 'my-disbursement-requests',
|
|
loadComponent: () =>
|
|
import('../clerk/my-disbursement-requests/my-disbursement-requests.component').then(
|
|
(m) => m.MyDisbursementRequestsComponent
|
|
),
|
|
data: { title: 'My Disbursement Requests', breadcrumb: 'My Disbursement Requests' }
|
|
},
|
|
{
|
|
path: 'share-withdrawal-request',
|
|
loadComponent: () =>
|
|
import('../clerk/share-withdrawal-request/share-withdrawal-request.component').then(
|
|
(m) => m.ShareWithdrawalRequestComponent
|
|
),
|
|
data: { title: 'New Share Withdrawal Request', breadcrumb: 'New Share Withdrawal Request', mode: 'create' }
|
|
},
|
|
{
|
|
path: 'share-withdrawal-requests',
|
|
loadComponent: () =>
|
|
import('../clerk/my-share-withdrawal-requests/my-share-withdrawal-requests.component').then(
|
|
(m) => m.MyShareWithdrawalRequestsComponent
|
|
),
|
|
data: { title: 'My Share Requests', breadcrumb: 'My Share Requests' }
|
|
},
|
|
{
|
|
path: 'closure-requests',
|
|
loadComponent: () =>
|
|
import('../clerk/my-closure-requests/my-closure-requests.component').then(
|
|
(m) => m.MyClosureRequestsComponent
|
|
),
|
|
data: { title: 'My Closure Requests', breadcrumb: 'My Closure Requests' }
|
|
},
|
|
{
|
|
path: 'account-facilities',
|
|
loadComponent: () =>
|
|
import('../clerk/account-facilities/account-facilities.component').then(
|
|
(m) => m.AccountFacilitiesComponent
|
|
),
|
|
data: { title: 'Account Facilities', breadcrumb: 'Account Facilities' }
|
|
},
|
|
{
|
|
path: 'account-facilities/cheque-book',
|
|
loadComponent: () =>
|
|
import('../clerk/cheque-book/cheque-book.component').then((m) => m.ClerkChequeBookComponent),
|
|
data: { title: 'Cheque Book', breadcrumb: 'Cheque Book' }
|
|
},
|
|
{
|
|
path: 'account-facilities/cheque-book/:id',
|
|
loadComponent: () =>
|
|
import('../clerk/cheque-book/cheque-book-detail.component').then((m) => m.ClerkChequeBookDetailComponent),
|
|
data: { title: 'Cheque Book Detail', breadcrumb: 'Cheque Book Detail' }
|
|
},
|
|
{
|
|
path: 'photo-signature',
|
|
loadComponent: () =>
|
|
import('../clerk/photo-signature/photo-signature.component').then((m) => m.PhotoSignatureComponent),
|
|
data: { title: 'Photo & Signature', breadcrumb: 'Photo & Signature' }
|
|
},
|
|
{
|
|
path: 'standing-instructions',
|
|
loadComponent: () =>
|
|
import('../clerk/standing-instructions/standing-instructions.component').then(
|
|
(m) => m.StandingInstructionsComponent
|
|
),
|
|
data: { title: 'Standing Instructions', breadcrumb: 'Standing Instructions' }
|
|
},
|
|
{
|
|
path: 'today-activity',
|
|
loadComponent: () =>
|
|
import('../clerk/today-activity/today-activity.component').then((m) => m.TodayActivityComponent),
|
|
data: { title: "Today's Activity", breadcrumb: "Today's Activity" }
|
|
},
|
|
{
|
|
path: 'dividend-dashboard',
|
|
loadComponent: () =>
|
|
import('../clerk/dividend-dashboard/dividend-dashboard.component').then(
|
|
(m) => m.DividendDashboardComponent
|
|
),
|
|
data: { title: 'Dividend Dashboard', breadcrumb: 'Dividend Dashboard' }
|
|
}
|
|
]
|
|
},
|
|
{
|
|
// Branch-level operations, kept out of the cashier tree on purpose: opening and closing the
|
|
// day governs the whole branch, and is not something a cashier does.
|
|
path: 'branch-ops',
|
|
data: { title: 'Branch Operations', breadcrumb: 'Branch Operations' },
|
|
children: [
|
|
{
|
|
path: 'day-register',
|
|
component: BranchDayComponent,
|
|
data: { title: 'Day Open / Day Close', breadcrumb: 'Day Register' }
|
|
},
|
|
{
|
|
// Same screen, narrowed to the days that were never closed. A separate route rather than
|
|
// a second link to the register, so choosing it visibly does something.
|
|
path: 'missed-close',
|
|
component: BranchDayComponent,
|
|
data: { title: 'Missed Close Register', breadcrumb: 'Missed Close', missedOnly: true }
|
|
},
|
|
{
|
|
// Lives here alongside the day register because both answer "what did this branch look
|
|
// like on that date"; it is linked from the Reports menu, where people go to find it.
|
|
path: 'account-register',
|
|
component: AccountRegisterComponent,
|
|
data: { title: 'Account Register', breadcrumb: 'Account Register' }
|
|
},
|
|
{
|
|
// Same branch-level scope as the day register: this is the vault's own stock, not any
|
|
// one cashier's float, so it sits with the other branch-wide screens.
|
|
path: 'vault-position',
|
|
component: VaultPositionComponent,
|
|
data: { title: 'Vault Position', breadcrumb: 'Vault Position' }
|
|
}
|
|
]
|
|
},
|
|
{
|
|
path: 'cashier',
|
|
data: { title: 'Cashier', breadcrumb: 'Cashier' },
|
|
children: [
|
|
{
|
|
path: 'cash-exchange',
|
|
component: CashExchangeEntryComponent,
|
|
data: { title: 'Cash Exchange Entry', breadcrumb: 'Cash Exchange Entry' }
|
|
},
|
|
{
|
|
path: 'exchange-detail',
|
|
component: CurrencyExchangeDetailComponent,
|
|
data: { title: 'Currency Exchange Detail', breadcrumb: 'Currency Exchange Detail' }
|
|
},
|
|
{
|
|
path: 'cash-transfer',
|
|
component: CashTransferComponent,
|
|
data: { title: 'Cash Transfer', breadcrumb: 'Cash Transfer' }
|
|
},
|
|
{
|
|
path: 'cash-settlement',
|
|
component: CashSettlementComponent,
|
|
data: { title: 'Cash Settlement', breadcrumb: 'Cash Settlement' }
|
|
},
|
|
{
|
|
path: 'inter-cashier',
|
|
component: InterCashierTransferComponent,
|
|
data: { title: 'Inter-Cashier Transfer', breadcrumb: 'Inter-Cashier Transfer' }
|
|
},
|
|
{
|
|
path: 'cashier-position',
|
|
component: CashierPositionComponent,
|
|
data: { title: 'Cashier Position', breadcrumb: 'Cashier Position' }
|
|
},
|
|
{
|
|
path: 'reconciliation',
|
|
component: ReconciliationComponent,
|
|
data: { title: 'Reconciliation', breadcrumb: 'Reconciliation' }
|
|
},
|
|
{
|
|
path: 'cash-history',
|
|
component: CashHistoryComponent,
|
|
data: { title: 'Cash History', breadcrumb: 'Cash History' }
|
|
},
|
|
{
|
|
path: 'day-book',
|
|
component: DayBookComponent,
|
|
data: { title: 'Day Book', breadcrumb: 'Day Book' }
|
|
},
|
|
{
|
|
path: 'voucher-exceptions',
|
|
component: VoucherExceptionsComponent,
|
|
data: { title: 'Voucher Exceptions', breadcrumb: 'Voucher Exceptions' }
|
|
},
|
|
{
|
|
path: 'voucher',
|
|
component: VoucherViewComponent,
|
|
data: { title: 'Voucher', breadcrumb: 'Voucher' }
|
|
},
|
|
{
|
|
path: 'voucher-report',
|
|
component: VoucherReportComponent,
|
|
data: { title: 'Voucher Report', breadcrumb: 'Voucher Report' }
|
|
},
|
|
{
|
|
path: 'closing-report',
|
|
component: ClosingCashReportComponent,
|
|
data: { title: 'Closing Cash Report', breadcrumb: 'Closing Cash Report' }
|
|
},
|
|
{
|
|
path: 'excess-report',
|
|
component: ExcessTransactionReportComponent,
|
|
data: { title: 'Excess Transaction Report', breadcrumb: 'Excess Transaction Report' }
|
|
},
|
|
{
|
|
path: 'share-collection',
|
|
component: ShareCollectionComponent,
|
|
data: { title: 'Share Collection', breadcrumb: 'Share Collection' }
|
|
},
|
|
{
|
|
path: 'transaction-entry',
|
|
canActivate: [CashierOnlyGuard],
|
|
loadComponent: () =>
|
|
import('./transaction-entry/transaction-entry.component').then((m) => m.TransactionEntryComponent),
|
|
data: {
|
|
title: 'Cashier Transaction Entry',
|
|
breadcrumb: 'Transaction Entry',
|
|
hideShellLayout: true
|
|
}
|
|
},
|
|
{
|
|
path: 'my-deposit-requests',
|
|
loadComponent: () =>
|
|
import('./my-deposit-requests/my-deposit-requests.component').then((m) => m.MyDepositRequestsComponent),
|
|
data: { title: 'My Deposit Requests', breadcrumb: 'My Deposit Requests' }
|
|
},
|
|
{
|
|
path: 'cash-clearing-transfer-summary',
|
|
loadComponent: () =>
|
|
import('./cash-clearing-transfer-summary/cash-clearing-transfer-summary.component').then(
|
|
(m) => m.CashClearingTransferSummaryComponent
|
|
),
|
|
data: {
|
|
title: 'Cash / Clearing / Transfer - Summary Report',
|
|
breadcrumb: 'Cash / Clearing / Transfer - Summary Report'
|
|
}
|
|
}
|
|
]
|
|
}
|
|
])
|
|
];
|
|
|
|
@NgModule({
|
|
imports: [RouterModule.forChild(routes)],
|
|
exports: [RouterModule]
|
|
})
|
|
export class CashierRoutingModule {}
|