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

715 lines
26 KiB
Dart

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();
Map<String, String> get _productOptions {
final l10n = AppLocalizations.of(context);
return {
'20': l10n.termDepositOption,
'25': l10n.fixedDepositOption,
'28': l10n.recurringDepositOption,
};
}
Map<String, String> get _typeOptions {
final l10n = AppLocalizations.of(context);
return {
'01': l10n.memberOption,
'02': l10n.nonMemberOption,
'03': l10n.staffOption,
'04': l10n.govtOption,
'05': l10n.schoolOption,
'06': l10n.panchayatOption,
'07': l10n.trustsOption,
'08': l10n.municipalCouncilOption,
'09': l10n.nroOption,
'10': l10n.bankOption,
'11': l10n.noFrillOption,
'12': l10n.specialSchemesOption,
'13': l10n.minorOption,
};
}
Map<String, String> get _customerCategoryOptions {
final l10n = AppLocalizations.of(context);
return {
'1': l10n.publicIndividualOption,
'2': l10n.societiesOption,
'3': l10n.seniorCitizenOption,
'5': l10n.governmentOption,
'6': l10n.localBodiesOption,
'7': l10n.otherOption,
};
}
Map<String, String> get _termLocationOptions {
final l10n = AppLocalizations.of(context);
return {
'10': l10n.sbCaOption,
'13': l10n.days46_90Option,
'14': l10n.days91_180Option,
'15': l10n.days180_1YearOption,
'16': l10n.year1_18MonthsOption,
'20': l10n.months18_2YearsOption,
'17': l10n.years2_3Option,
'18': l10n.years3_10Option,
'22': l10n.above10YearsOption,
'24': l10n.devKanyaYojnaOption,
'25': l10n.hazarDainLakhOption,
};
}
Map<String, String> get _currencyOptions {
final l10n = AppLocalizations.of(context);
return {
'1': l10n.currencyINR,
};
}
Map<String, String> get _acctSgmtCodeOptions {
final l10n = AppLocalizations.of(context);
return {
'0706': l10n.publicIndividualOption,
'5002': l10n.societiesOption,
'5005': l10n.governmentOption,
'5050': l10n.glOthersOption,
};
}
Map<String, String> get _interestPaymentMethodOptions {
final l10n = AppLocalizations.of(context);
return {
'R': l10n.reInvestOption,
};
}
Map<String, String> get _termBasisOptions {
final l10n = AppLocalizations.of(context);
return {
'D': l10n.daysOption,
'M': l10n.monthsOption,
};
}
Map<String, String> get _interestFrequencyOptions {
final l10n = AppLocalizations.of(context);
return {
'M': l10n.onMaturityOption,
'1M': l10n.monthlyOption,
'3M': l10n.quarterlyOption,
'1A': l10n.anniversaryMonthlyOption,
'3A': l10n.anniversaryQuarterlyOption,
};
}
Map<String, String> get _rdInstlFreqOptions {
final l10n = AppLocalizations.of(context);
return {
'M': l10n.monthlyOption,
};
}
@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) {
final l10n = AppLocalizations.of(context);
if (response['error'] == 'INCORRECT_TPIN' || response['message'] == 'INCORRECT_TPIN') {
_showSimpleDialog(l10n.invalidTpin, l10n.incorrectTpinMessage);
} else if (response['status'] == 'FAILED') {
_showFailureDialog(response['message']?.toString());
} else if (response.containsKey('accountno')) {
_showResponseDialog(response);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l10n.unexpectedResponse)),
);
}
}
} else {
if (mounted) {
final l10n = AppLocalizations.of(context);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l10n.failedToCreateDeposit)),
);
}
}
} on DioException catch (e) {
if (mounted) {
final l10n = AppLocalizations.of(context);
try {
final errorBody = e.response?.data;
if (errorBody is Map && (errorBody['error'] == 'INCORRECT_TPIN' || errorBody['message'] == 'INCORRECT_TPIN')) {
_showSimpleDialog(l10n.invalidTpin, l10n.incorrectTpinMessage);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("${l10n.error}: ${e.message}")),
);
}
} catch (_) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l10n.internalServerError)),
);
}
}
} catch (e) {
if (mounted) {
final l10n = AppLocalizations.of(context);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("${l10n.error}: $e")),
);
}
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
Future<void> _showSimpleDialog(String title, String message) async {
final l10n = AppLocalizations.of(context);
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: Text(l10n.ok),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
void _showFailureDialog(String? apiMessage) {
final l10n = AppLocalizations.of(context);
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Row(
children: [
Icon(Icons.error_outline, color: Theme.of(context).colorScheme.error),
const SizedBox(width: 8),
Text(l10n.creationFailed),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.accountCreationFailed,
style: const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
if (apiMessage != null) ...[
Text(l10n.reason(apiMessage)),
const SizedBox(height: 12),
],
Text(
l10n.creationFailedDescription,
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(l10n.ok),
),
],
),
);
}
void _showResponseDialog(Map<String, dynamic> response) {
final l10n = AppLocalizations.of(context);
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: Text(l10n.depositCreatedSuccessfully),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("${l10n.accountNumberLabel}: $accountNo"),
const SizedBox(height: 8),
Text("${l10n.accountTypeLabel}: $accountTypeText"),
const SizedBox(height: 8),
Text(l10n.amountValue(amount)),
],
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).popUntil((route) => route.isFirst);
},
child: Text(l10n.ok),
),
],
),
);
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return Scaffold(
appBar: AppBar(
title: Text(l10n.createDeposit),
),
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: [
Text(l10n.customerInformation,
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
_buildTextField(_fromAcctNoController, l10n.fromAccountNo,
mandatory: true, readOnly: true),
_buildTextField(_cifNoController, l10n.cifNo),
_buildTextField(_idNoController, l10n.idNo),
_buildTextField(_customerNameController, l10n.customerName),
_buildTextField(_nationalityController, l10n.nationality),
_buildTextField(_addressLine1Controller, l10n.addressLine1),
_buildTextField(_addressLine2Controller, l10n.addressLine2),
_buildTextField(_pincodeController, l10n.pincodeLabel,
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: [
Text(l10n.depositDetails,
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
_buildDropdownField(
_productController, l10n.product, _productOptions,
mandatory: true, onChanged: (val) {
setState(() {
_productController.text = val ?? '';
});
}),
_buildDropdownField(_typeController, l10n.type, _typeOptions,
mandatory: true),
_buildDropdownField(_customerCategoryController,
l10n.customerCategory, _customerCategoryOptions,
mandatory: true),
_buildDropdownField(_termLocationController,
l10n.termLocation, _termLocationOptions,
mandatory: true),
_buildDropdownField(
_currencyController, l10n.currency, _currencyOptions,
mandatory: true),
_buildDropdownField(_acctSgmtCodeController,
l10n.accountSegmentCode, _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: [
Text(l10n.interestTermSettings,
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
_buildDropdownField(_interestPaymentMethodController,
l10n.interestPaymentMethod, _interestPaymentMethodOptions,
mandatory: true),
_buildTextField(_taxFileNumberIndicatorController,
l10n.taxFileNumberIndicator),
_buildTextField(_termLengthController, l10n.termLength),
_buildDropdownField(
_termBasisController, l10n.termBasis, _termBasisOptions,
mandatory: true),
_buildTextField(_termValueDepositedController,
l10n.termValueDeposited,
keyboardType: TextInputType.number,
mandatory: _productController.text != '28'),
_buildDropdownField(_interestFrequencyController,
l10n.interestFrequency, _interestFrequencyOptions),
_buildTextField(_termDaysController, l10n.termDays,
keyboardType: TextInputType.number),
_buildTextField(_termMonthsController, l10n.termMonths,
keyboardType: TextInputType.number),
_buildTextField(_termYearsController, l10n.termYears,
keyboardType: TextInputType.number, mandatory: true),
_buildTextField(
_nominationRequiredController, l10n.nominationRequired),
_buildTextField(
_printNomineeNameController, l10n.printNomineeName),
],
),
),
),
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: [
Text(l10n.recurringDepositSettings,
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
_buildTextField(_rdInstallmentValueController,
l10n.rdInstallmentValue,
keyboardType: TextInputType.number),
_buildTextField(_monthlyRDInstallmentDueDayController,
l10n.monthlyRDInstallmentDueDay,
keyboardType: TextInputType.number),
_buildDropdownField(_rdInstlFreqController,
l10n.rdInstallmentFrequency, _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,
),
)
: Text(
l10n.proceedButton,
style: const 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}) {
final l10n = AppLocalizations.of(context);
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 l10n.fieldRequired;
}
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}) {
final l10n = AppLocalizations.of(context);
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 l10n.fieldRequired;
}
return null;
},
),
);
}
}