Files
kmobile/lib/features/yojna/screens/apy_register_screen.dart

661 lines
27 KiB
Dart

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:kmobile/api/services/yojna_service.dart';
import 'package:kmobile/di/injection.dart';
import 'package:kmobile/l10n/app_localizations.dart';
class APYRegisterScreen extends StatefulWidget {
final Map<String, dynamic>? initialData;
const APYRegisterScreen({super.key, this.initialData});
@override
State<APYRegisterScreen> createState() => _APYRegisterScreenState();
}
class _APYRegisterScreenState extends State<APYRegisterScreen> {
final _formKey = GlobalKey<FormState>();
// Helper to format initial date string (DDMMYYYY -> DD/MM/YYYY)
String _formatInitialDate(dynamic date) {
if (date == null) return '';
String dateStr = date.toString();
if (dateStr.length == 8) {
return "${dateStr.substring(0, 2)}/${dateStr.substring(2, 4)}/${dateStr.substring(4)}";
}
return dateStr;
}
// Controllers initialized strictly from initialData where available, otherwise empty.
late final _titleController = TextEditingController(
text: widget.initialData?['customertitle']?.toString());
late final _firstNameController = TextEditingController(
text: widget.initialData?['customerfirstname']?.toString());
late final _middleNameController = TextEditingController(
text: widget.initialData?['customermiddlename']?.toString());
late final _lastNameController = TextEditingController(
text: widget.initialData?['customerlastname']?.toString());
late final _customerNoController = TextEditingController(
text: widget.initialData?['customerno']?.toString());
late final _accountNoController = TextEditingController(
text: widget.initialData?['accountno']?.toString());
late final _balanceController = TextEditingController(
text: widget.initialData?['availablebalance']?.toString());
late final _dobController = TextEditingController(
text: _formatInitialDate(widget.initialData?['customerdob']));
late final _genderController = TextEditingController(
text: widget.initialData?['gender']?.toString());
late final _aadhaarController = TextEditingController(
text: widget.initialData?['aadharno']?.toString());
late final _ageOfJoiningController = TextEditingController(
text: widget.initialData?['ageofjoining']?.toString());
late final _emailController = TextEditingController(
text: widget.initialData?['emailid']?.toString());
late final _modeOfCollectionController = TextEditingController(
text: widget.initialData?['modeofcollection']?.toString());
// Fields that must be filled by the user (no defaults)
late final _marriedController = TextEditingController();
late final _mobileController = TextEditingController();
late final _pincodeController = TextEditingController();
late final _pensionAmountController = TextEditingController();
late final _spouseNameController = TextEditingController();
late final _incomeTaxPayerController = TextEditingController();
late final _otherSchemeController = TextEditingController();
late final _collectionChannelController = TextEditingController();
late final _contributionTypeController = TextEditingController();
late final _debitDateController = TextEditingController();
late final _nomineeNameController = TextEditingController();
late final _nomineeRelationController = TextEditingController();
late final _nomineeMinorController = TextEditingController();
late final _nomineeDobController = TextEditingController();
late final _guardianNameController = TextEditingController();
late final _fatcaController = TextEditingController();
late final _birthCountryController = TextEditingController();
late final _citizenshipCountryController = TextEditingController();
late final _taxResidenceCountryController = TextEditingController();
late final _usPersonController = TextEditingController();
late final _secondNomineeNameController = TextEditingController();
late final _secondNomineeMinorController = TextEditingController();
late final _secondNomineeRelationController = TextEditingController();
late final _fatcaCountController = TextEditingController();
late final _docCitizenshipFlagController = TextEditingController();
late final _reasonNoEvidenceController = TextEditingController();
late final _docNameEvidenceController = TextEditingController();
final Map<String, String> _yesNoOptions = {
'Y': 'Yes',
'N': 'No',
};
final Map<String, String> _titleOptions = {
'01': 'Mr.',
'02': 'Mrs.',
'03': 'Miss',
};
final Map<String, String> _genderOptions = {
'M': 'Male',
'F': 'Female',
'O': 'Other',
};
final Map<String, String> _pensionOptions = {
'1000': '₹1000',
'2000': '₹2000',
'3000': '₹3000',
'4000': '₹4000',
'5000': '₹5000',
};
final Map<String, String> _collectionChannelOptions = {
'1': 'Bank',
};
Map<String, String> _getPeriodicityOptions(AppLocalizations l10n) => {
'C': l10n.monthly,
'Q': l10n.quarterly,
'H': l10n.halfYearly,
};
// Contribution Calculation Map
final Map<String, Map<String, String>> _contributionRates = {
'C': {
'1000': '90',
'2000': '178',
'3000': '268',
'4000': '356',
'5000': '446',
},
'Q': {
'1000': '268',
'2000': '530',
'3000': '1061',
'4000': '356', // Following prompt's example exactly
'5000': '1329',
},
'H': {
'1000': '531',
'2000': '1050',
'3000': '1582',
'4000': '2101',
'5000': '2632',
},
};
String get _calculatedContribution {
final periodicity = _contributionTypeController.text;
final amount = _pensionAmountController.text;
return _contributionRates[periodicity]?[amount] ?? '0';
}
@override
void dispose() {
_titleController.dispose();
_firstNameController.dispose();
_middleNameController.dispose();
_lastNameController.dispose();
_customerNoController.dispose();
_accountNoController.dispose();
_balanceController.dispose();
_dobController.dispose();
_genderController.dispose();
_marriedController.dispose();
_mobileController.dispose();
_emailController.dispose();
_aadhaarController.dispose();
_pincodeController.dispose();
_pensionAmountController.dispose();
_ageOfJoiningController.dispose();
_spouseNameController.dispose();
_incomeTaxPayerController.dispose();
_otherSchemeController.dispose();
_collectionChannelController.dispose();
_contributionTypeController.dispose();
_debitDateController.dispose();
_nomineeNameController.dispose();
_nomineeRelationController.dispose();
_nomineeMinorController.dispose();
_nomineeDobController.dispose();
_guardianNameController.dispose();
_fatcaController.dispose();
_birthCountryController.dispose();
_citizenshipCountryController.dispose();
_taxResidenceCountryController.dispose();
_usPersonController.dispose();
_secondNomineeNameController.dispose();
_secondNomineeMinorController.dispose();
_secondNomineeRelationController.dispose();
_fatcaCountController.dispose();
_docCitizenshipFlagController.dispose();
_reasonNoEvidenceController.dispose();
_docNameEvidenceController.dispose();
_modeOfCollectionController.dispose();
super.dispose();
}
Future<void> _selectDate(BuildContext context, TextEditingController controller) async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(1900),
lastDate: DateTime(2100),
);
if (picked != null) {
setState(() {
// Display format: DD/MM/YYYY
controller.text = DateFormat('dd/MM/yyyy').format(picked);
});
}
}
void _handleRegister() async {
if (_formKey.currentState!.validate()) {
final age = int.tryParse(_ageOfJoiningController.text) ?? 0;
if (age < 18 || age > 40) {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => AlertDialog(
title: const Text('Age Restriction'),
content: const Text(
'Age of joining must be between 18 and 40 years to register for APY.'),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context); // Close dialog
Navigator.popUntil(context, (route) => route.isFirst);
},
child: const Text('OK'),
),
],
),
);
return;
}
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => const Center(child: CircularProgressIndicator()),
);
try {
final yojnaService = getIt<YojnaService>();
final response = await yojnaService.registerAPY(
accountno: _accountNoController.text,
customerfirstname: _firstNameController.text,
customermiddlename: _middleNameController.text,
customerlastname: _lastNameController.text,
availablebalance: _balanceController.text,
// Remove slashes for API (DD/MM/YYYY -> DDMMYYYY)
customerdob: _dobController.text.replaceAll('/', ''),
emailid: _emailController.text,
gender: _genderController.text,
married: _marriedController.text,
nomineename: _nomineeNameController.text,
relationwithsubscriber: _nomineeRelationController.text,
mobilenumber: _mobileController.text,
nomineeminor: _nomineeMinorController.text,
customerno: _customerNoController.text,
beneficaryofothersociatysecurityschemes: _otherSchemeController.text,
whetherincometaxpayer: _incomeTaxPayerController.text,
customertitle: _titleController.text,
aadharno: _aadhaarController.text,
nameofspouse: _spouseNameController.text,
ageofjoining: _ageOfJoiningController.text,
pensionamtoptedfor: _pensionAmountController.text,
montlycontributioncalculate: _calculatedContribution,
collectionchannel: _collectionChannelController.text,
// Remove slashes for API
subsequentContributionDebitDate: _debitDateController.text.replaceAll('/', ''),
secondnomineeminor: _secondNomineeMinorController.text,
secondnomineename: _secondNomineeNameController.text,
secondrelationshipwithsubscriber: _secondNomineeRelationController.text,
pincode: _pincodeController.text,
fatcacrsapplicable: _fatcaController.text,
countryofbirth: _birthCountryController.text,
countryofcitizenship: _citizenshipCountryController.text,
countryofresidencefortaxpurpose: _taxResidenceCountryController.text,
uspersonflag: _usPersonController.text,
fatcadeclarationcount: _fatcaCountController.text,
documentevidencingcitizenshipflag: _docCitizenshipFlagController.text,
reasonfornoevidence: _reasonNoEvidenceController.text,
nameofdocumentforcitizenshipevidence: _docNameEvidenceController.text,
modeofcollection: _modeOfCollectionController.text,
contributionType: _contributionTypeController.text,
);
if (!mounted) return;
Navigator.pop(context); // Close loading dialog
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Registration Result'),
content: SingleChildScrollView(
child: Text(response.toString()),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
),
);
} catch (e) {
if (!mounted) return;
Navigator.pop(context); // Close loading dialog
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Error'),
content: Text('Failed to register: $e'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
),
);
}
}
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return Scaffold(
appBar: AppBar(
title: Text(l10n.apyRegistration),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Tile 1: Customer Details
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(l10n.personaldetails,
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
_buildDropdownField(
_titleController, l10n.customerTitle, _titleOptions),
_buildTextField(
_firstNameController, l10n.customerFirstName),
_buildTextField(
_middleNameController, l10n.customerMiddleName),
_buildTextField(
_lastNameController, l10n.customerLastName),
_buildTextField(_customerNoController, l10n.customerNo),
_buildTextField(_accountNoController, l10n.accountNumber,
keyboardType: TextInputType.number),
_buildTextField(_balanceController, l10n.availableBalance,
keyboardType: TextInputType.number),
_buildTextField(_dobController, l10n.customerDobFormat,
mandatory: true, onTap: () => _selectDate(context, _dobController)),
_buildDropdownField(
_genderController, l10n.gender, _genderOptions),
_buildTextField(_mobileController, l10n.mobileNumber,
keyboardType: TextInputType.phone),
_buildTextField(_emailController, l10n.emailId,
keyboardType: TextInputType.emailAddress),
_buildTextField(_aadhaarController, l10n.aadhaarNo,
keyboardType: TextInputType.number),
_buildTextField(_pincodeController, l10n.pincode,
keyboardType: TextInputType.number),
_buildDropdownField(
_marriedController, l10n.marriedYesNo, _yesNoOptions,
mandatory: true),
],
),
),
),
const SizedBox(height: 16),
// Tile 2: Nominee Details
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(l10n.nomineeDetails,
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
Text("Nominee 1", style: TextStyle(fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.primary)),
const SizedBox(height: 8),
_buildTextField(_nomineeNameController, l10n.nomineeName,
mandatory: true),
_buildTextField(_nomineeRelationController, l10n.relationWithSubscriber,
mandatory: true),
_buildDropdownField(_nomineeMinorController,
l10n.nomineeMinor, _yesNoOptions, mandatory: true,
onChanged: (val) {
setState(() {
_nomineeMinorController.text = val ?? '';
});
}),
if (_nomineeMinorController.text == 'Y') ...[
_buildTextField(_nomineeDobController, l10n.nomineeDob,
mandatory: true, onTap: () => _selectDate(context, _nomineeDobController)),
_buildTextField(
_guardianNameController, l10n.guardianName,
mandatory: true),
],
const Divider(height: 32),
Text("Nominee 2", style: TextStyle(fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.primary)),
const SizedBox(height: 8),
_buildTextField(_secondNomineeNameController, l10n.secondNomineeName),
_buildTextField(_secondNomineeRelationController, l10n.secondNomineeRelationship),
_buildDropdownField(_secondNomineeMinorController,
l10n.secondNomineeMinor, _yesNoOptions,
onChanged: (val) {
setState(() {
_secondNomineeMinorController.text = val ?? '';
});
}),
],
),
),
),
const SizedBox(height: 16),
// Tile 3: Scheme & APY Details
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(l10n.schemeDetails,
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
_buildDropdownField(_otherSchemeController,
l10n.isBeneficiaryOtherScheme, _yesNoOptions,
mandatory: true),
_buildDropdownField(_incomeTaxPayerController,
l10n.isIncomeTaxPayer, _yesNoOptions,
mandatory: true),
_buildTextField(_spouseNameController, l10n.nameOfSpouse,
mandatory: true),
_buildTextField(_ageOfJoiningController, l10n.ageOfJoining,
keyboardType: TextInputType.number, mandatory: true),
_buildDropdownField(_collectionChannelController,
l10n.collectionChannel, _collectionChannelOptions,
mandatory: true),
_buildTextField(_modeOfCollectionController, l10n.modeOfCollection),
],
),
),
),
const SizedBox(height: 16),
// Tile 4: FATCA / CRS Details (KYC Details)
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(l10n.kycdetails,
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
_buildDropdownField(_fatcaController,
l10n.fatcaApplicable, _yesNoOptions),
_buildTextField(
_birthCountryController, l10n.countryOfBirth),
_buildTextField(_citizenshipCountryController,
l10n.countryOfCitizenship),
_buildTextField(_taxResidenceCountryController,
l10n.countryOfTaxResidence),
_buildDropdownField(_usPersonController,
l10n.usPersonFlag, _yesNoOptions),
_buildTextField(_fatcaCountController, "FATCA Declaration Count"),
_buildTextField(_docCitizenshipFlagController, "Doc Evidencing Citizenship Flag"),
_buildTextField(_reasonNoEvidenceController, "Reason for No Evidence"),
_buildTextField(_docNameEvidenceController, "Doc Name for Citizenship Evidence"),
],
),
),
),
const SizedBox(height: 16),
// Tile 5: Pension & Contribution Settings
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(l10n.contributionAmount,
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
_buildDropdownField(_pensionAmountController,
l10n.pensionAmount, _pensionOptions, mandatory: true,
onChanged: (val) {
setState(() {
_pensionAmountController.text = val ?? '';
});
}),
_buildDropdownField(_contributionTypeController,
l10n.periodicity, _getPeriodicityOptions(l10n),
mandatory: true, onChanged: (val) {
setState(() {
_contributionTypeController.text = val ?? '';
});
}),
_buildTextField(
_debitDateController, l10n.subsequentDebitDate,
mandatory: true, onTap: () => _selectDate(context, _debitDateController)),
],
),
),
),
const SizedBox(height: 16),
// Tile 6: Contribution Amount Summary
Card(
elevation: 2,
color: Theme.of(context).colorScheme.secondaryContainer,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Text(
'${l10n.contributionAmount} (${_getPeriodicityOptions(l10n)[_contributionTypeController.text]})',
style: TextStyle(
fontSize: 16,
color: Theme.of(context)
.colorScheme
.onSecondaryContainer)),
const SizedBox(height: 8),
Text('$_calculatedContribution',
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.colorScheme
.onSecondaryContainer)),
],
),
),
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: _handleRegister,
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: Text(
l10n.register,
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}) {
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
? 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,
onTap: 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;
},
),
);
}
}