Files
kmobile/lib/features/account_opening/screens/create_deposit_screen.dart
2026-03-23 16:41:05 +05:30

676 lines
25 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:kmobile/api/services/deposit_service.dart';
import 'package:kmobile/data/models/user.dart';
import 'package:kmobile/di/injection.dart';
import 'package:kmobile/features/fund_transfer/screens/transaction_pin_screen.dart';
import 'package:kmobile/l10n/app_localizations.dart';
class CreateDepositScreen extends StatefulWidget {
final User selectedAccount;
final Map<String, dynamic>? initialData;
const CreateDepositScreen({
super.key,
required this.selectedAccount,
this.initialData,
});
@override
State<CreateDepositScreen> createState() => _CreateDepositScreenState();
}
class _CreateDepositScreenState extends State<CreateDepositScreen> {
final _formKey = GlobalKey<FormState>();
bool _isLoading = false;
// Controllers
late final _fromAcctNoController =
TextEditingController(text: widget.selectedAccount.accountNo.toString());
late final _cifNoController = TextEditingController(
text: widget.initialData?['cifNo']?.toString());
late final _idNoController = TextEditingController(
text: widget.initialData?['idno']?.toString());
late final _customerNameController = TextEditingController(
text: widget.initialData?['customername']?.toString());
late final _nationalityController = TextEditingController(
text: widget.initialData?['nationality']?.toString());
late final _addressLine1Controller = TextEditingController(
text: widget.initialData?['addressline1']?.toString());
late final _addressLine2Controller = TextEditingController(
text: widget.initialData?['addressline2']?.toString());
late final _pincodeController = TextEditingController(
text: widget.initialData?['pincode']?.toString());
late final _productController = TextEditingController();
late final _typeController = TextEditingController();
late final _customerCategoryController = TextEditingController();
late final _termLocationController = TextEditingController();
late final _currencyController = TextEditingController();
late final _acctSgmtCodeController = TextEditingController();
late final _interestPaymentMethodController = TextEditingController();
late final _taxFileNumberIndicatorController = TextEditingController();
late final _termLengthController = TextEditingController();
late final _termBasisController = TextEditingController();
late final _termValueDepositedController = TextEditingController();
late final _interestFrequencyController = TextEditingController();
late final _termDaysController = TextEditingController();
late final _termMonthsController = TextEditingController();
late final _termYearsController = TextEditingController();
late final _nominationRequiredController = TextEditingController();
late final _printNomineeNameController = TextEditingController();
// RD specific controllers
late final _rdInstallmentValueController = TextEditingController();
late final _monthlyRDInstallmentDueDayController = TextEditingController();
late final _rdInstlFreqController = TextEditingController();
final Map<String, String> _productOptions = {
'20': 'Term Deposit',
'25': 'Fixed Deposit',
'28': 'Recurring Deposit',
};
final Map<String, String> _typeOptions = {
'01': 'Member',
'02': 'Non-Member',
'03': 'Staff',
'04': 'Govt',
'05': 'School',
'06': 'Panchayat',
'07': 'Trusts',
'08': 'Municipal Council',
'09': 'NRO',
'10': 'Bank',
'11': 'No Frill',
'12': 'Special Schemes',
'13': 'Minor',
};
final Map<String, String> _customerCategoryOptions = {
'1': 'Public Individual',
'2': 'Societies',
'3': 'Senior Citizen',
'5': 'Government',
'6': 'Local Bodies',
'7': 'Other',
};
final Map<String, String> _termLocationOptions = {
'10': 'SB/CA',
'13': '4690 Days',
'14': '91180 Days',
'15': '180 Days1 Year',
'16': '1 Year<18 Months',
'20': '18 Months<2 Years',
'17': '23 Years',
'18': '310 Years',
'22': 'Above 10 Years',
'24': 'Dev Kanya Yojna 9+1',
'25': 'Hazar Dain Lakh Ley Jayain',
};
final Map<String, String> _currencyOptions = {
'1': 'INR',
};
final Map<String, String> _acctSgmtCodeOptions = {
'0706': 'Public Individual',
'5002': 'Societies',
'5005': 'Government',
'5050': 'GL Others',
};
final Map<String, String> _interestPaymentMethodOptions = {
'R': 'Re-invest',
};
final Map<String, String> _termBasisOptions = {
'D': 'Days',
'M': 'Months',
};
final Map<String, String> _interestFrequencyOptions = {
'M': 'On Maturity / Roll Over',
'1M': 'Monthly',
'3M': 'Quarterly',
'1A': 'Anniversary Monthly',
'3A': 'Anniversary Quarterly',
};
final Map<String, String> _rdInstlFreqOptions = {
'M': 'Monthly',
};
@override
void dispose() {
_fromAcctNoController.dispose();
_cifNoController.dispose();
_idNoController.dispose();
_customerNameController.dispose();
_nationalityController.dispose();
_addressLine1Controller.dispose();
_addressLine2Controller.dispose();
_pincodeController.dispose();
_productController.dispose();
_typeController.dispose();
_customerCategoryController.dispose();
_termLocationController.dispose();
_currencyController.dispose();
_acctSgmtCodeController.dispose();
_interestPaymentMethodController.dispose();
_taxFileNumberIndicatorController.dispose();
_termLengthController.dispose();
_termBasisController.dispose();
_termValueDepositedController.dispose();
_interestFrequencyController.dispose();
_termDaysController.dispose();
_termMonthsController.dispose();
_termYearsController.dispose();
_nominationRequiredController.dispose();
_printNomineeNameController.dispose();
_rdInstallmentValueController.dispose();
_monthlyRDInstallmentDueDayController.dispose();
_rdInstlFreqController.dispose();
super.dispose();
}
void _handleCreate() {
if (_formKey.currentState!.validate()) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TransactionPinScreen(
onPinCompleted: (ctx, pin) async {
Navigator.pop(context);
_executeCreation(pin);
},
),
),
);
}
}
void _executeCreation(String pin) async {
setState(() {
_isLoading = true;
});
try {
final product = _productController.text;
dynamic response;
if (product == '20' || product == '25') {
response = await getIt<DepositService>().createFdTd(
fromacctno: _fromAcctNoController.text,
cifNo: _cifNoController.text,
idno: _idNoController.text,
customername: _customerNameController.text,
nationality: _nationalityController.text,
addressline1: _addressLine1Controller.text,
addressline2: _addressLine2Controller.text,
pincode: _pincodeController.text,
product: _productController.text,
type: _typeController.text,
customercategory: _customerCategoryController.text,
termlocation: _termLocationController.text,
currency: _currencyController.text,
acctsgmtcode: _acctSgmtCodeController.text,
interestpaymentmethod: _interestPaymentMethodController.text,
taxfilenumberindicator: _taxFileNumberIndicatorController.text,
termlenght: _termLengthController.text,
termbasis: _termBasisController.text,
termvaluedeposited: _termValueDepositedController.text,
interestfrequency: _interestFrequencyController.text,
termdays: _termDaysController.text,
termmonths: _termMonthsController.text,
termyears: _termYearsController.text,
nominationRequired: _nominationRequiredController.text,
printNomineename: _printNomineeNameController.text,
tpin: pin,
);
} else if (product == '28') {
response = await getIt<DepositService>().createRd(
fromacctno: _fromAcctNoController.text,
cifNo: _cifNoController.text,
idno: _idNoController.text,
customername: _customerNameController.text,
nationality: _nationalityController.text,
addressline1: _addressLine1Controller.text,
addressline2: _addressLine2Controller.text,
pincode: _pincodeController.text,
product: _productController.text,
type: _typeController.text,
customercategory: _customerCategoryController.text,
termlocation: _termLocationController.text,
currency: _currencyController.text,
acctsgmtcode: _acctSgmtCodeController.text,
interestpaymentmethod: _interestPaymentMethodController.text,
taxfilenumberindicator: _taxFileNumberIndicatorController.text,
termlenght: _termLengthController.text,
termbasis: _termBasisController.text,
termvaluedeposited: _termValueDepositedController.text,
interestfrequency: _interestFrequencyController.text,
termdays: _termDaysController.text,
termmonths: _termMonthsController.text,
termyears: _termYearsController.text,
nominationRequired: _nominationRequiredController.text,
printNomineename: _printNomineeNameController.text,
rdinstallmentvalue: _rdInstallmentValueController.text,
monthlyRDInstallmentdueday: _monthlyRDInstallmentDueDayController.text,
rdInstlFreq: _rdInstlFreqController.text,
tpin: pin,
);
}
if (response != null && response is Map<String, dynamic>) {
if (mounted) {
if (response['error'] == 'INCORRECT_TPIN' || response['message'] == 'INCORRECT_TPIN') {
_showSimpleDialog('Invalid TPIN', 'The TPIN you entered is incorrect. Please try again.');
} else if (response['status'] == 'FAILED') {
_showFailureDialog(response['message']?.toString());
} else if (response.containsKey('accountno')) {
_showResponseDialog(response);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Unexpected response from server")),
);
}
}
} else {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Failed to create deposit")),
);
}
}
} on DioException catch (e) {
if (mounted) {
try {
final errorBody = e.response?.data;
if (errorBody is Map && (errorBody['error'] == 'INCORRECT_TPIN' || errorBody['message'] == 'INCORRECT_TPIN')) {
_showSimpleDialog('Invalid TPIN', 'The TPIN you entered is incorrect. Please try again.');
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Error: ${e.message}")),
);
}
} catch (_) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Internal Server Error")),
);
}
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Error: $e")),
);
}
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
Future<void> _showSimpleDialog(String title, String message) async {
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
title: Text(title),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text(message),
],
),
),
actions: <Widget>[
TextButton(
child: const Text("OK"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
void _showFailureDialog(String? apiMessage) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Row(
children: [
Icon(Icons.error_outline, color: Theme.of(context).colorScheme.error),
const SizedBox(width: 8),
const Text("Creation Failed"),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Account creation could not be completed at this time.",
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
if (apiMessage != null) ...[
Text("Reason: $apiMessage"),
const SizedBox(height: 12),
],
const Text(
"Please ensure that the provided details (term length, basis, interest frequency, etc.) are correct and compatible with the selected product. If the issue persists, please contact the branch.",
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text("OK"),
),
],
),
);
}
void _showResponseDialog(Map<String, dynamic> response) {
final accountNo = response['accountno']?.toString() ?? 'N/A';
final accountTypeRaw = response['accounttype']?.toString() ?? '';
final accountTypeText = _productOptions[accountTypeRaw] ?? accountTypeRaw;
final amount = response['amount']?.toString() ?? '0.0';
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => AlertDialog(
title: const Text("Deposit Created Successfully"),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Account No: $accountNo"),
const SizedBox(height: 8),
Text("Account Type: $accountTypeText"),
const SizedBox(height: 8),
Text("Amount: $amount"),
],
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).popUntil((route) => route.isFirst);
},
child: const Text("OK"),
),
],
),
);
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return Scaffold(
appBar: AppBar(
title: const Text("Create Deposit"),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Tile 1: Customer Info
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("Customer Information",
style: TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
_buildTextField(_fromAcctNoController, "From Account No",
mandatory: true, readOnly: true),
_buildTextField(_cifNoController, "CIF No"),
_buildTextField(_idNoController, "ID No"),
_buildTextField(_customerNameController, "Customer Name"),
_buildTextField(_nationalityController, "Nationality"),
_buildTextField(_addressLine1Controller, "Address Line 1"),
_buildTextField(_addressLine2Controller, "Address Line 2"),
_buildTextField(_pincodeController, "Pincode",
keyboardType: TextInputType.number),
],
),
),
),
const SizedBox(height: 16),
// Tile 2: Deposit Details
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("Deposit Details",
style: TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
_buildDropdownField(
_productController, "Product", _productOptions,
mandatory: true, onChanged: (val) {
setState(() {
_productController.text = val ?? '';
});
}),
_buildDropdownField(_typeController, "Type", _typeOptions,
mandatory: true),
_buildDropdownField(_customerCategoryController,
"Customer Category", _customerCategoryOptions,
mandatory: true),
_buildDropdownField(_termLocationController,
"Term Location", _termLocationOptions,
mandatory: true),
_buildDropdownField(
_currencyController, "Currency", _currencyOptions,
mandatory: true),
_buildDropdownField(_acctSgmtCodeController,
"Account Segment Code", _acctSgmtCodeOptions,
mandatory: true),
],
),
),
),
const SizedBox(height: 16),
// Tile 3: Interest & Term Settings
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("Interest & Term Settings",
style: TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
_buildDropdownField(_interestPaymentMethodController,
"Interest Payment Method", _interestPaymentMethodOptions,
mandatory: true),
_buildTextField(_taxFileNumberIndicatorController,
"Tax File Number Indicator"),
_buildTextField(_termLengthController, "Term Length"),
_buildDropdownField(
_termBasisController, "Term Basis", _termBasisOptions,
mandatory: true),
_buildTextField(_termValueDepositedController,
"Term Value Deposited",
keyboardType: TextInputType.number,
mandatory: _productController.text != '28'),
_buildDropdownField(_interestFrequencyController,
"Interest Frequency", _interestFrequencyOptions),
_buildTextField(_termDaysController, "Term Days",
keyboardType: TextInputType.number),
_buildTextField(_termMonthsController, "Term Months",
keyboardType: TextInputType.number),
_buildTextField(_termYearsController, "Term Years",
keyboardType: TextInputType.number, mandatory: true),
_buildTextField(
_nominationRequiredController, "Nomination Required"),
_buildTextField(
_printNomineeNameController, "Print Nominee Name"),
],
),
),
),
const SizedBox(height: 16),
// Tile 4: RD Specific Fields (Conditional)
if (_productController.text == '28')
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("Recurring Deposit Settings",
style: TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
_buildTextField(_rdInstallmentValueController,
"RD Installment Value",
keyboardType: TextInputType.number),
_buildTextField(_monthlyRDInstallmentDueDayController,
"Monthly RD Installment Due Day",
keyboardType: TextInputType.number),
_buildDropdownField(_rdInstlFreqController,
"RD Installment Frequency", _rdInstlFreqOptions),
],
),
),
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: _isLoading ? null : _handleCreate,
style: ElevatedButton.styleFrom(
backgroundColor:
Theme.of(context).colorScheme.primaryContainer,
foregroundColor:
Theme.of(context).colorScheme.onPrimaryContainer,
padding: const EdgeInsets.symmetric(vertical: 16),
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: _isLoading
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
),
)
: const Text(
"Proceed",
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
),
const SizedBox(height: 32),
],
),
),
),
);
}
Widget _buildDropdownField(TextEditingController controller, String label,
Map<String, String> options,
{bool readOnly = false,
bool mandatory = false,
void Function(String?)? onChanged}) {
String? currentValue =
options.containsKey(controller.text) ? controller.text : null;
return Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: DropdownButtonFormField<String>(
value: currentValue,
onChanged: readOnly || _isLoading
? null
: (newValue) {
if (newValue != null) {
setState(() {
controller.text = newValue;
});
}
if (onChanged != null) {
onChanged(newValue);
}
},
decoration: InputDecoration(
labelText: mandatory ? '$label *' : label,
border: const OutlineInputBorder(),
contentPadding:
const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
),
validator: (value) {
if (mandatory && (value == null || value.isEmpty)) {
return 'This field is required';
}
return null;
},
items: options.entries.map((entry) {
return DropdownMenuItem<String>(
value: entry.key,
child: Text(entry.value),
);
}).toList(),
),
);
}
Widget _buildTextField(TextEditingController controller, String label,
{TextInputType keyboardType = TextInputType.text,
bool readOnly = false,
bool mandatory = false,
VoidCallback? onTap}) {
return Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: TextFormField(
controller: controller,
readOnly: readOnly || onTap != null || _isLoading,
onTap: _isLoading ? null : onTap,
decoration: InputDecoration(
labelText: mandatory ? '$label *' : label,
border: const OutlineInputBorder(),
suffixIcon: onTap != null ? const Icon(Icons.calendar_today) : null,
contentPadding:
const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
),
keyboardType: keyboardType,
validator: (value) {
if (mandatory && (value == null || value.isEmpty)) {
return 'This field is required';
}
return null;
},
),
);
}
}