chequemanagement #1

This commit is contained in:
2025-12-24 18:06:12 +05:30
parent 72a2c56392
commit 07d5ea8fbe
7 changed files with 565 additions and 67 deletions

View File

@@ -105,7 +105,7 @@ class BranchService {
if (response.statusCode == 200) { if (response.statusCode == 200) {
return Branch.listFromJson(response.data); return Branch.listFromJson(response.data);
} else { } else {
throw Exception("Failed to fetch beneficiaries"); throw Exception("Failed to fetch");
} }
} catch (e) { } catch (e) {
return []; return [];

View File

@@ -0,0 +1,96 @@
import 'package:dio/dio.dart';
class Cheque {
final String? type;
final String? InstrType;
final String? Date;
final String? branchCode;
final String? fromCheque;
final String? toCheque;
final String? Chequescount;
final String? ChequeNumber;
final String? transactionCode;
final String? amount;
final String? status;
final String? stopIssueDate;
final String? StopExpiryDate;
Cheque({
this.type,
this.InstrType,
this.Date,
this.branchCode,
this.fromCheque,
this.toCheque,
this.Chequescount,
this.ChequeNumber,
this.transactionCode,
this.amount,
this.status,
this.stopIssueDate,
this.StopExpiryDate,
});
factory Cheque.fromJson(Map<String, dynamic> json) {
return Cheque(
type: json['type'] ?? '',
InstrType: json['InstrType'] ?? '',
Date: json['Date'] ?? '',
branchCode: json['branchCode'] ?? '',
fromCheque: json['fromCheque'] ?? '',
toCheque: json['toCheque'] ?? '',
Chequescount: json['Chequescount'] ?? '',
ChequeNumber: json['ChequeNumber'] ?? '',
transactionCode: json['transactionCode'] ?? '',
amount: json['amount'] ?? '',
status: json['status'] ?? '',
stopIssueDate: json['stopIssueDate'] ?? '',
StopExpiryDate: json['StopExpiryDate'] ?? '',
);
}
static List<Cheque> listFromJson(List<dynamic> jsonList) {
final chequeList =
jsonList.map((cheque) => Cheque.fromJson(cheque)).toList();
return chequeList;
}
}
class ChequeService {
final Dio _dio;
ChequeService(this._dio);
Future<List<Cheque>> ChequeEnquiry(
{required String accountNumber,
required String instrType,
}) async {
try {
final response = await _dio.get(
"/api/cheque",
queryParameters: {
'accountNumber': accountNumber,
'instrumentType': instrType,
},
options: Options(
headers: {
"Content-Type": "application/json",
},
),
);
if (response.statusCode == 200) {
if (response.data is Map<String, dynamic> && response.data.containsKey('records')) {
final records = response.data['records'];
if (records is List) {
return Cheque.listFromJson(records);
}
}
throw Exception("Unexpected API response format: 'records' list not found or malformed");
} else {
throw Exception("Failed to fetch");
}
} catch (e) {
print('Error in ChequeEnquiry: $e');
throw e;
}
}
}

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:kmobile/api/services/branch_service.dart'; import 'package:kmobile/api/services/branch_service.dart';
import 'package:kmobile/api/services/cheque_service.dart';
import 'package:kmobile/api/services/limit_service.dart'; import 'package:kmobile/api/services/limit_service.dart';
import 'package:kmobile/api/services/rtgs_service.dart'; import 'package:kmobile/api/services/rtgs_service.dart';
import 'package:kmobile/api/services/neft_service.dart'; import 'package:kmobile/api/services/neft_service.dart';
@@ -56,6 +57,7 @@ Future<void> setupDependencies() async {
getIt.registerSingleton<RtgsService>(RtgsService(getIt<Dio>())); getIt.registerSingleton<RtgsService>(RtgsService(getIt<Dio>()));
getIt.registerSingleton<ImpsService>(ImpsService(getIt<Dio>())); getIt.registerSingleton<ImpsService>(ImpsService(getIt<Dio>()));
getIt.registerSingleton<BranchService>(BranchService(getIt<Dio>())); getIt.registerSingleton<BranchService>(BranchService(getIt<Dio>()));
getIt.registerSingleton<ChequeService>(ChequeService(getIt<Dio>()));
getIt.registerLazySingleton<ChangePasswordService>( getIt.registerLazySingleton<ChangePasswordService>(
() => ChangePasswordService(getIt<Dio>()), () => ChangePasswordService(getIt<Dio>()),
); );
@@ -74,9 +76,9 @@ Dio _createDioClient() {
final dio = Dio( final dio = Dio(
BaseOptions( BaseOptions(
baseUrl: baseUrl:
//'http://lb-test-mobile-banking-app-192209417.ap-south-1.elb.amazonaws.com:8080', //test 'http://lb-test-mobile-banking-app-192209417.ap-south-1.elb.amazonaws.com', //test
//'http://lb-kccb-mobile-banking-app-848675342.ap-south-1.elb.amazonaws.com', //prod //'http://lb-kccb-mobile-banking-app-848675342.ap-south-1.elb.amazonaws.com', //prod
'https://kccbmbnk.net', //prod small //'https://kccbmbnk.net', //prod small
connectTimeout: const Duration(seconds: 60), connectTimeout: const Duration(seconds: 60),
receiveTimeout: const Duration(seconds: 60), receiveTimeout: const Duration(seconds: 60),
headers: { headers: {

View File

@@ -1,16 +1,25 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:kmobile/features/enquiry/screens/enquiry_screen.dart'; import 'package:kmobile/data/models/user.dart';
import 'package:kmobile/features/cheque/screens/cheque_status_screen.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart';
import '../../../l10n/app_localizations.dart'; import '../../../l10n/app_localizations.dart';
class ChequeManagementScreen extends StatefulWidget { class ChequeManagementScreen extends StatefulWidget {
const ChequeManagementScreen({super.key}); final List<User> users;
final int selectedIndex;
const ChequeManagementScreen({
super.key,
required this.users,
required this.selectedIndex,});
@override @override
State<ChequeManagementScreen> createState() => _ChequeManagementScreen(); State<ChequeManagementScreen> createState() => _ChequeManagementScreen();
} }
class _ChequeManagementScreen extends State<ChequeManagementScreen> { class _ChequeManagementScreen extends State<ChequeManagementScreen> {
List<User> get users => widget.users;
int get selectedAccountIndex => widget.selectedIndex;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@@ -22,52 +31,55 @@ class _ChequeManagementScreen extends State<ChequeManagementScreen> {
), ),
body: Stack( body: Stack(
children: [ children: [
ListView( Padding(
children: [ padding: const EdgeInsets.symmetric(vertical: 16.0),
const SizedBox(height: 15), child: Column(
ChequeManagementTile( crossAxisAlignment: CrossAxisAlignment.stretch,
icon: Symbols.add, children: [
label: AppLocalizations.of(context).requestChequeBook, Expanded(
onTap: () {}, child: ChequeManagementCardTile(
), icon: Symbols.add,
Divider(height: 1, color: Theme.of(context).dividerColor), label: AppLocalizations.of(context).requestChequeBook,
ChequeManagementTile( subtitle: "Apply for a new cheque book",
icon: Symbols.data_alert, onTap: () {},
label: AppLocalizations.of(context).enquiry, ),
onTap: () { ),
Navigator.push( Expanded(
context, child: ChequeManagementCardTile(
MaterialPageRoute( icon: Symbols.data_alert,
builder: (context) => const EnquiryScreen()), label: "Cheque History",
); subtitle: "View the status of your issued cheques",
}, onTap: () {},
), ),
Divider(height: 1, color: Theme.of(context).dividerColor), ),
ChequeManagementTile( Expanded(
icon: Symbols.approval_delegation, child: ChequeManagementCardTile(
label: AppLocalizations.of(context).chequeDeposit, icon: Symbols.front_hand,
onTap: () {}, label: AppLocalizations.of(context).stopCheque,
), subtitle: "Stop payment for a cheque",
Divider(height: 1, color: Theme.of(context).dividerColor), onTap: () {},
ChequeManagementTile( ),
icon: Symbols.front_hand, ),
label: AppLocalizations.of(context).stopCheque, Expanded(
onTap: () {}, child: ChequeManagementCardTile(
), icon: Symbols.payments,
Divider(height: 1, color: Theme.of(context).dividerColor), label: "Track Cheque Status",
ChequeManagementTile( subtitle: "Check the current status of your cheque",
icon: Symbols.cancel_presentation, onTap: () {
label: AppLocalizations.of(context).revokeStop, Navigator.push(
onTap: () {}, context,
), MaterialPageRoute(
Divider(height: 1, color: Theme.of(context).dividerColor), builder: (context) => ChequeStatusScreen(
ChequeManagementTile( users: users,
icon: Symbols.payments, selectedIndex: selectedAccountIndex,
label: AppLocalizations.of(context).positivePay, ),
onTap: () {}, ),
), );
Divider(height: 1, color: Theme.of(context).dividerColor), },
], ),
),
],
),
), ),
IgnorePointer( IgnorePointer(
child: Center( child: Center(
@@ -89,25 +101,78 @@ class _ChequeManagementScreen extends State<ChequeManagementScreen> {
} }
} }
class ChequeManagementTile extends StatelessWidget { class ChequeManagementCardTile extends StatelessWidget {
final IconData icon; final IconData icon;
final String label; final String label;
final String? subtitle;
final VoidCallback onTap; final VoidCallback onTap;
final bool disable;
const ChequeManagementTile({ const ChequeManagementCardTile({
super.key, super.key,
required this.icon, required this.icon,
required this.label, required this.label,
this.subtitle,
required this.onTap, required this.onTap,
this.disable = false,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListTile( final theme = Theme.of(context);
leading: Icon(icon), return Card(
title: Text(label), margin: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
trailing: const Icon(Symbols.arrow_right, size: 20), shape: RoundedRectangleBorder(
onTap: onTap, borderRadius: BorderRadius.circular(12.0),
),
elevation: 4, // Add some elevation for better visual separation
child: InkWell(
onTap:
disable ? null : onTap, // Disable InkWell if the tile is disabled
borderRadius: BorderRadius.circular(12.0),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 24.0, horizontal: 16.0),
child: Center(
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
icon,
size: 48, // Make icon larger
color:
disable ? theme.disabledColor : theme.colorScheme.primary,
),
const SizedBox(height: 12),
Text(
label,
textAlign: TextAlign.center,
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
color: disable
? theme.disabledColor
: theme.colorScheme.onSurface,
),
),
if (subtitle != null)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
subtitle!,
textAlign: TextAlign.center,
style: theme.textTheme.bodyMedium?.copyWith(
color: disable
? theme.disabledColor
: theme.colorScheme.onSurfaceVariant,
),
),
),
],
),
),
),
),
),
); );
} }
} }

View File

@@ -0,0 +1,333 @@
import 'package:flutter/material.dart';
import 'package:kmobile/api/services/cheque_service.dart';
import 'package:kmobile/data/models/user.dart';
import 'package:kmobile/di/injection.dart';
class ChequeStatusScreen extends StatefulWidget {
final List<User> users;
final int selectedIndex;
const ChequeStatusScreen({
super.key,
required this.users,
required this.selectedIndex,
});
@override
State<ChequeStatusScreen> createState() => _ChequeStatusScreenState();
}
class _ChequeStatusScreenState extends State<ChequeStatusScreen> {
User? _selectedAccount;
final TextEditingController _searchController = TextEditingController();
var service = getIt<ChequeService>();
bool _isLoading = true;
List<Cheque> _allCheques = [];
Map<String, List<Cheque>> _groupedCheques = {};
@override
void initState() {
super.initState();
final List<User> _filteredUsers = widget.users
.where((user) => ['SA', 'SB', 'CA', 'CC'].contains(user.accountType))
.toList();
if (widget.users.isNotEmpty && widget.selectedIndex < widget.users.length) {
if (_filteredUsers.isNotEmpty) {
if (_filteredUsers.contains(widget.users[widget.selectedIndex])) {
_selectedAccount = widget.users[widget.selectedIndex];
} else {
_selectedAccount = _filteredUsers.first;
}
} else {
_selectedAccount = widget.users[widget.selectedIndex];
}
} else {
if (_filteredUsers.isNotEmpty) {
_selectedAccount = _filteredUsers.first;
}
}
_loadCheques();
_searchController.addListener(() {
_filterCheques(_searchController.text);
});
}
Future<void> _loadCheques() async {
if (_selectedAccount == null) {
setState(() {
_isLoading = false;
_groupedCheques = {};
});
return;
}
setState(() {
_isLoading = true;
});
String instrType;
switch (_selectedAccount!.accountType) {
case 'SA':
case 'SB':
instrType = '10';
break;
case 'CA':
instrType = '11';
break;
case 'CC':
instrType = '13';
break;
default:
instrType = '10';
}
try {
final data = await service.ChequeEnquiry(
accountNumber: _selectedAccount!.accountNo!, instrType: instrType);
_allCheques = data;
_groupedCheques.clear();
for (var cheque in _allCheques) {
if (cheque.type != null) {
if (!_groupedCheques.containsKey(cheque.type)) {
_groupedCheques[cheque.type!] = [];
}
_groupedCheques[cheque.type!]!.add(cheque);
}
}
setState(() {
_isLoading = false;
});
} catch (e) {
setState(() {
_isLoading = false;
_groupedCheques = {};
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Failed to fetch cheque status: ${e.toString()}'),
),
);
}
}
void _filterCheques(String query) {
_groupedCheques.clear();
List<Cheque> filteredCheques;
if (query.isEmpty) {
filteredCheques = _allCheques;
} else {
filteredCheques = _allCheques.where((cheque) {
final lowerQuery = query.toLowerCase();
return (cheque.ChequeNumber?.toLowerCase().contains(lowerQuery) ?? false) ||
(cheque.amount?.toLowerCase().contains(lowerQuery) ?? false) ||
(cheque.status?.toLowerCase().contains(lowerQuery) ?? false) ||
(cheque.fromCheque?.toLowerCase().contains(lowerQuery) ?? false) ||
(cheque.toCheque?.toLowerCase().contains(lowerQuery) ?? false);
}).toList();
}
for (var cheque in filteredCheques) {
if (cheque.type != null) {
if (!_groupedCheques.containsKey(cheque.type)) {
_groupedCheques[cheque.type!] = [];
}
_groupedCheques[cheque.type!]!.add(cheque);
}
}
setState(() {});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Track Cheque Status'),
centerTitle: false,
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Card(
elevation: 4,
margin: const EdgeInsets.symmetric(vertical: 8.0),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Account Number",
style:
TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
),
if (_selectedAccount != null)
DropdownButtonFormField<User>(
value: _selectedAccount,
onChanged: (User? newUser) {
if (newUser != null) {
setState(() {
_selectedAccount = newUser;
_loadCheques();
});
}
},
items: widget.users
.where((user) =>
['SA', 'SB', 'CA', 'CC'].contains(user.accountType))
.map((user) {
return DropdownMenuItem<User>(
value: user,
child: Text(user.accountNo.toString()),
);
}).toList(),
)
else
const Text('No accounts found'),
],
),
),
),
const SizedBox(height: 20),
TextField(
controller: _searchController,
decoration: const InputDecoration(
labelText: 'Search...',
prefixIcon: Icon(Icons.search),
),
),
const SizedBox(height: 20),
Expanded(
child: _isLoading
? const Center(child: CircularProgressIndicator())
: _groupedCheques.isEmpty
? const Center(child: Text('No cheque status found.'))
: ListView(
children: _groupedCheques.entries.map((entry) {
return ExpansionTile(
title: Text(entry.key, style: TextStyle(fontWeight: FontWeight.bold)),
children: entry.value
.map((cheque) => ChequeStatusTile(cheque: cheque))
.toList(),
);
}).toList(),
),
),
],
),
),
);
}
}
class ChequeStatusTile extends StatelessWidget {
final Cheque cheque;
const ChequeStatusTile({
super.key,
required this.cheque,
});
@override
Widget build(BuildContext context) {
switch (cheque.type) {
case 'CI':
return _buildCiTile(context);
case 'PR':
return _buildPrTile(context);
case 'ST':
return _buildStTile(context);
default:
return const SizedBox.shrink();
}
}
Widget _buildCiTile(BuildContext context) {
return Card(
margin: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildInfoRow('Branch Code:', cheque.branchCode),
_buildInfoRow('From Cheque:', cheque.fromCheque),
_buildInfoRow('To Cheque:', cheque.toCheque),
_buildInfoRow('Date:', cheque.Date),
_buildInfoRow('Cheques Count:', cheque.Chequescount),
_buildInfoRow('Instrument Type:', cheque.InstrType),
],
),
),
);
}
Widget _buildPrTile(BuildContext context) {
return Card(
margin: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildInfoRow('Branch Code:', cheque.branchCode),
_buildInfoRow('Cheque Number:', cheque.ChequeNumber),
_buildInfoRow('Date:', cheque.Date),
_buildInfoRow('Transaction Code:', cheque.transactionCode),
_buildInfoRow('Amount:', cheque.amount),
_buildInfoRow('Status:', cheque.status, statusColor: _getStatusColor(cheque.status ?? '')),
_buildInfoRow('Instrument Type:', cheque.InstrType),
],
),
),
);
}
Widget _buildStTile(BuildContext context) {
return Card(
margin: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildInfoRow('Branch Code:', cheque.branchCode),
_buildInfoRow('From Cheque:', cheque.fromCheque),
_buildInfoRow('To Cheque:', cheque.toCheque),
_buildInfoRow('Stop Issue Date:', cheque.stopIssueDate),
_buildInfoRow('Stop Expiry Date:', cheque.StopExpiryDate),
_buildInfoRow('Cheques Count:', cheque.Chequescount),
_buildInfoRow('Instrument Type:', cheque.InstrType),
],
),
),
);
}
Widget _buildInfoRow(String label, String? value, {Color? statusColor}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: const TextStyle(fontWeight: FontWeight.bold)),
Text(value ?? '', style: TextStyle(color: statusColor)),
],
),
);
}
Color _getStatusColor(String status) {
switch (status.toLowerCase()) {
case 'cashed':
return Colors.green;
case 'pending':
return Colors.orange;
case 'bounced':
return Colors.red;
default:
return Colors.grey;
}
}
}

View File

@@ -8,6 +8,7 @@ import 'package:kmobile/features/accounts/screens/account_statement_screen.dart'
import 'package:kmobile/features/accounts/screens/all_accounts_screen.dart'; import 'package:kmobile/features/accounts/screens/all_accounts_screen.dart';
import 'package:kmobile/features/auth/controllers/auth_cubit.dart'; import 'package:kmobile/features/auth/controllers/auth_cubit.dart';
import 'package:kmobile/features/auth/controllers/auth_state.dart'; import 'package:kmobile/features/auth/controllers/auth_state.dart';
import 'package:kmobile/features/cheque/screens/cheque_management_screen.dart';
import 'package:kmobile/features/customer_info/screens/customer_info_screen.dart'; import 'package:kmobile/features/customer_info/screens/customer_info_screen.dart';
import 'package:kmobile/features/beneficiaries/screens/manage_beneficiaries_screen.dart'; import 'package:kmobile/features/beneficiaries/screens/manage_beneficiaries_screen.dart';
import 'package:kmobile/features/enquiry/screens/enquiry_screen.dart'; import 'package:kmobile/features/enquiry/screens/enquiry_screen.dart';
@@ -443,7 +444,8 @@ class _DashboardScreenState extends State<DashboardScreen>
final accountType = currAccount.accountType?.toLowerCase(); final accountType = currAccount.accountType?.toLowerCase();
final isPaymentDisabled = accountType != 'sa' && final isPaymentDisabled = accountType != 'sa' &&
accountType != 'sb' && accountType != 'sb' &&
accountType != 'ca'; accountType != 'ca' &&
accountType != 'cc';
// firsttime load // firsttime load
if (!_txInitialized) { if (!_txInitialized) {
_txInitialized = true; _txInitialized = true;
@@ -643,20 +645,20 @@ class _DashboardScreenState extends State<DashboardScreen>
const EnquiryScreen())); const EnquiryScreen()));
}), }),
_buildQuickLink( _buildQuickLink(
Symbols.person, Symbols.checkbook,
AppLocalizations.of(context).profile, AppLocalizations.of(context).chequeManagement,
() { () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => ProfileScreen( builder: (context) => ChequeManagementScreen(
mobileNumber: mobileNumberToPass, users: users,
customerNo: customerNo, selectedIndex: selectedAccountIndex
customerName: customerName), ),
), ),
); );
}, },
disable: false, disable: isPaymentDisabled,
), ),
], ],
), ),