first commit
This commit is contained in:
271
lib/Screens/cash and bank/cheques/cheques_deposit_screen.dart
Normal file
271
lib/Screens/cash and bank/cheques/cheques_deposit_screen.dart
Normal file
@@ -0,0 +1,271 @@
|
||||
// File: transfer_cheque_deposit_screen.dart
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:iconly/iconly.dart';
|
||||
import 'package:mobile_pos/Screens/cash%20and%20bank/cheques/repo/cheque_repository.dart';
|
||||
|
||||
// --- Local Imports ---
|
||||
import 'package:mobile_pos/currency.dart';
|
||||
import 'package:mobile_pos/generated/l10n.dart' as l;
|
||||
// Data Layer Imports
|
||||
import '../bank%20account/model/bank_account_list_model.dart';
|
||||
import '../bank%20account/provider/bank_account_provider.dart';
|
||||
import 'model/cheques_list_model.dart';
|
||||
|
||||
// NOTE: Add a static Cash option to the dropdown list
|
||||
final BankData _cashOption = BankData(name: 'Cash', id: 0);
|
||||
|
||||
class TransferChequeDepositScreen extends ConsumerStatefulWidget {
|
||||
final ChequeTransactionData cheque;
|
||||
|
||||
const TransferChequeDepositScreen({super.key, required this.cheque});
|
||||
|
||||
@override
|
||||
ConsumerState<TransferChequeDepositScreen> createState() => _TransferChequeDepositScreenState();
|
||||
}
|
||||
|
||||
class _TransferChequeDepositScreenState extends ConsumerState<TransferChequeDepositScreen> {
|
||||
final GlobalKey<FormState> _key = GlobalKey();
|
||||
final descriptionController = TextEditingController();
|
||||
final dateController = TextEditingController();
|
||||
|
||||
// Changed to dynamic to hold either BankData or _cashOption
|
||||
BankData? _depositDestination;
|
||||
DateTime? _selectedDate;
|
||||
|
||||
final DateFormat _displayFormat = DateFormat('dd/MM/yyyy');
|
||||
final DateFormat _apiFormat = DateFormat('yyyy-MM-dd');
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_selectedDate = DateTime.now();
|
||||
dateController.text = _displayFormat.format(_selectedDate!);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
descriptionController.dispose();
|
||||
dateController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _selectDate(BuildContext context) async {
|
||||
final DateTime? picked = await showDatePicker(
|
||||
initialDate: _selectedDate ?? DateTime.now(),
|
||||
firstDate: DateTime(2000),
|
||||
lastDate: DateTime(2101),
|
||||
context: context,
|
||||
);
|
||||
if (picked != null) {
|
||||
setState(() {
|
||||
_selectedDate = picked;
|
||||
dateController.text = _displayFormat.format(picked);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _submit() async {
|
||||
if (!_key.currentState!.validate()) return;
|
||||
if (_depositDestination == null) {
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(const SnackBar(content: Text('Please select a deposit destination (Bank or Cash).')));
|
||||
return;
|
||||
}
|
||||
|
||||
final repo = ChequeRepository();
|
||||
|
||||
// Determine the value to send to the repository: Bank ID or 'cash' string
|
||||
dynamic paymentDestination;
|
||||
if (_depositDestination!.id == 0) {
|
||||
// Using 0 for Cash option
|
||||
paymentDestination = 'cash';
|
||||
} else {
|
||||
paymentDestination = _depositDestination!.id; // Bank ID
|
||||
}
|
||||
|
||||
await repo.depositCheque(
|
||||
ref: ref,
|
||||
context: context,
|
||||
chequeTransactionId: widget.cheque.id!,
|
||||
paymentDestination: paymentDestination,
|
||||
transferDate: _apiFormat.format(_selectedDate!),
|
||||
description: descriptionController.text.trim(),
|
||||
);
|
||||
}
|
||||
|
||||
void _resetForm() {
|
||||
setState(() {
|
||||
_depositDestination = null;
|
||||
descriptionController.clear();
|
||||
_selectedDate = DateTime.now();
|
||||
dateController.text = _displayFormat.format(_selectedDate!);
|
||||
});
|
||||
// Reset form validation & states
|
||||
_key.currentState?.reset();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final _lang = l.S.of(context);
|
||||
final cheque = widget.cheque;
|
||||
final banksAsync = ref.watch(bankListProvider);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
appBar: AppBar(
|
||||
title: Text(_lang.transferCheque),
|
||||
automaticallyImplyLeading: false,
|
||||
actions: [
|
||||
IconButton(onPressed: () => Navigator.pop(context), icon: const Icon(Icons.close)),
|
||||
],
|
||||
),
|
||||
body: banksAsync.when(
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (err, stack) => Center(child: Text('Error loading banks: $err')),
|
||||
data: (bankModel) {
|
||||
// Combine Bank List with the static Cash option
|
||||
final banks = [
|
||||
_cashOption, // Cash option first
|
||||
...(bankModel.data ?? []),
|
||||
];
|
||||
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Form(
|
||||
key: _key,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// --- Cheque Details ---
|
||||
_buildDetailRow(theme, _lang.receivedFrom, cheque.user?.name ?? 'N/A'),
|
||||
_buildDetailRow(theme, _lang.chequeAmount, '$currency${cheque.amount?.toStringAsFixed(2) ?? '0.00'}'),
|
||||
_buildDetailRow(theme, _lang.chequeNumber, cheque.meta?.chequeNumber ?? 'N/A'),
|
||||
_buildDetailRow(theme, _lang.chequeDate, _formatDate(cheque.date)),
|
||||
_buildDetailRow(theme, _lang.referenceNo, cheque.invoiceNo ?? 'N/A'),
|
||||
const Divider(height: 30),
|
||||
|
||||
DropdownButtonFormField<BankData>(
|
||||
value: _depositDestination, // use value instead of initialValue
|
||||
decoration: InputDecoration(
|
||||
hintText: _lang.selectBankToCash,
|
||||
labelText: _lang.depositTo,
|
||||
),
|
||||
validator: (value) => value == null ? _lang.selectDepositDestination : null,
|
||||
items: banks.map((destination) {
|
||||
return DropdownMenuItem<BankData>(
|
||||
value: destination,
|
||||
child: Text(destination.name ?? 'Unknown'),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (BankData? newValue) {
|
||||
setState(() {
|
||||
_depositDestination = newValue;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// --- Transfer Date Input ---
|
||||
_buildDateInput(context),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// --- Description Input ---
|
||||
_buildDescriptionInput(),
|
||||
const SizedBox(height: 40),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
bottomNavigationBar: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: _resetForm,
|
||||
style: OutlinedButton.styleFrom(
|
||||
minimumSize: const Size(double.infinity, 50),
|
||||
side: const BorderSide(color: Colors.red),
|
||||
foregroundColor: Colors.red,
|
||||
),
|
||||
child: Text(_lang.resets),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: _submit,
|
||||
style: ElevatedButton.styleFrom(
|
||||
minimumSize: const Size(double.infinity, 50),
|
||||
backgroundColor: const Color(0xFFB71C1C),
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
child: Text(_lang.send),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDetailRow(ThemeData theme, String label, String value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 120,
|
||||
child: Text(label, style: theme.textTheme.bodyMedium),
|
||||
),
|
||||
Text(': ', style: theme.textTheme.bodyMedium),
|
||||
Expanded(
|
||||
child: Text(value, style: theme.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDateInput(BuildContext context) {
|
||||
return TextFormField(
|
||||
readOnly: true,
|
||||
controller: dateController,
|
||||
decoration: InputDecoration(
|
||||
labelText: l.S.of(context).transferDate,
|
||||
suffixIcon: IconButton(
|
||||
icon: const Icon(IconlyLight.calendar, size: 22),
|
||||
onPressed: () => _selectDate(context),
|
||||
),
|
||||
),
|
||||
validator: (value) => value!.isEmpty ? l.S.of(context).dateIsRequired : null,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDescriptionInput() {
|
||||
return TextFormField(
|
||||
controller: descriptionController,
|
||||
maxLines: 3,
|
||||
decoration: InputDecoration(
|
||||
labelText: l.S.of(context).description,
|
||||
hintText: l.S.of(context).enterDescription,
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 12.0, vertical: 10.0),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _formatDate(String? date) {
|
||||
if (date == null) return 'N/A';
|
||||
try {
|
||||
return DateFormat('dd MMM, yyyy').format(DateTime.parse(date));
|
||||
} catch (_) {
|
||||
return date;
|
||||
}
|
||||
}
|
||||
}
|
||||
401
lib/Screens/cash and bank/cheques/cheques_list_screen.dart
Normal file
401
lib/Screens/cash and bank/cheques/cheques_list_screen.dart
Normal file
@@ -0,0 +1,401 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:mobile_pos/Screens/cash%20and%20bank/cheques/repo/cheque_repository.dart';
|
||||
|
||||
// --- Local Imports ---
|
||||
import 'package:mobile_pos/constant.dart';
|
||||
import 'package:mobile_pos/core/theme/_app_colors.dart';
|
||||
import 'package:mobile_pos/currency.dart';
|
||||
import 'package:nb_utils/nb_utils.dart';
|
||||
import 'package:mobile_pos/generated/l10n.dart' as l;
|
||||
|
||||
import '../widgets/cheques_filter_search.dart';
|
||||
import 'cheques_deposit_screen.dart';
|
||||
import 'model/cheques_list_model.dart';
|
||||
|
||||
class ChequesListScreen extends ConsumerStatefulWidget {
|
||||
const ChequesListScreen({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<ChequesListScreen> createState() => _ChequesListScreenState();
|
||||
}
|
||||
|
||||
class _ChequesListScreenState extends ConsumerState<ChequesListScreen> {
|
||||
String _currentSearchQuery = '';
|
||||
DateTime? _currentFromDate;
|
||||
DateTime? _currentToDate;
|
||||
|
||||
final DateFormat _apiFormat = DateFormat('yyyy-MM-dd');
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final now = DateTime.now();
|
||||
_currentFromDate = DateTime(now.year, 1, 1);
|
||||
_currentToDate = DateTime(now.year, now.month, now.day, 23, 59, 59);
|
||||
}
|
||||
|
||||
String _formatDate(String? date) {
|
||||
if (date == null) return 'N/A';
|
||||
try {
|
||||
return DateFormat('dd MMM, yyyy').format(DateTime.parse(date));
|
||||
} catch (_) {
|
||||
return date;
|
||||
}
|
||||
}
|
||||
|
||||
void _navigateToDepositScreen(ChequeTransactionData cheque) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => TransferChequeDepositScreen(cheque: cheque),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
//------------ Re Open dialog -----------------------------------
|
||||
void _showOpenDialog(ChequeTransactionData cheque) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
final _theme = Theme.of(context);
|
||||
return Dialog(
|
||||
backgroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadiusGeometry.circular(8),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Icon(Icons.close),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: Text(
|
||||
l.S.of(context).doYouWantToRellyReOpenThisCheque,
|
||||
textAlign: TextAlign.center,
|
||||
style: _theme.textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
style: OutlinedButton.styleFrom(
|
||||
minimumSize: const Size(double.infinity, 40),
|
||||
side: const BorderSide(color: Colors.red),
|
||||
foregroundColor: Colors.red,
|
||||
),
|
||||
child: Text(l.S.of(context).cancel),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: () async {
|
||||
// --- IMPLEMENTATION HERE ---
|
||||
if (cheque.id != null) {
|
||||
final repo = ChequeRepository();
|
||||
await repo.reOpenCheque(
|
||||
ref: ref,
|
||||
context: context,
|
||||
chequeTransactionId: cheque.id!,
|
||||
);
|
||||
}
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
minimumSize: const Size(double.infinity, 40),
|
||||
backgroundColor: const Color(0xFFB71C1C),
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
child: Text(l.S.of(context).okay),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// --- LOCAL FILTERING FUNCTION ---
|
||||
List<ChequeTransactionData> _filterTransactionsLocally(List<ChequeTransactionData> transactions) {
|
||||
Iterable<ChequeTransactionData> dateFiltered = transactions.where((t) {
|
||||
if (_currentFromDate == null && _currentToDate == null) return true;
|
||||
if (t.date == null) return false;
|
||||
|
||||
try {
|
||||
final transactionDate = DateTime.parse(t.date!);
|
||||
final start = _currentFromDate;
|
||||
final end = _currentToDate;
|
||||
|
||||
bool afterStart = start == null || transactionDate.isAfter(start) || transactionDate.isAtSameMomentAs(start);
|
||||
bool beforeEnd = end == null || transactionDate.isBefore(end) || transactionDate.isAtSameMomentAs(end);
|
||||
|
||||
return afterStart && beforeEnd;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
final query = _currentSearchQuery.toLowerCase();
|
||||
if (query.isEmpty) {
|
||||
return dateFiltered.toList();
|
||||
}
|
||||
|
||||
return dateFiltered.where((c) {
|
||||
return (c.user?.name ?? '').toLowerCase().contains(query) ||
|
||||
(c.meta?.chequeNumber ?? '').contains(query) ||
|
||||
(c.amount?.toString() ?? '').contains(query) ||
|
||||
(c.invoiceNo ?? '').toLowerCase().contains(query);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
// --- Filter Callback Handler ---
|
||||
void _handleFilterChange(CashFilterState filterState) {
|
||||
setState(() {
|
||||
_currentSearchQuery = filterState.searchQuery;
|
||||
_currentFromDate = filterState.fromDate;
|
||||
_currentToDate = filterState.toDate;
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildChequeListTile(ThemeData theme, ChequeTransactionData cheque) {
|
||||
final status = cheque.type ?? 'N/A';
|
||||
final isDepositable = status == 'pending';
|
||||
return ListTile(
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
cheque.user?.name ?? 'n/a',
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
isDepositable ? _navigateToDepositScreen(cheque) : _showOpenDialog(cheque);
|
||||
},
|
||||
style: ButtonStyle(
|
||||
visualDensity: VisualDensity(vertical: -4),
|
||||
padding: WidgetStatePropertyAll(
|
||||
EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
),
|
||||
),
|
||||
shape: WidgetStatePropertyAll(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius: BorderRadiusGeometry.circular(4),
|
||||
),
|
||||
),
|
||||
foregroundColor: WidgetStatePropertyAll(
|
||||
isDepositable
|
||||
? DAppColors.kWarning.withValues(
|
||||
alpha: 0.5,
|
||||
)
|
||||
: kSuccessColor.withValues(
|
||||
alpha: 0.5,
|
||||
),
|
||||
),
|
||||
backgroundColor: WidgetStatePropertyAll(isDepositable
|
||||
? kSuccessColor.withValues(
|
||||
alpha: 0.1,
|
||||
)
|
||||
: DAppColors.kWarning.withValues(
|
||||
alpha: 0.1,
|
||||
)),
|
||||
),
|
||||
child: Text(
|
||||
isDepositable ? l.S.of(context).deposit : l.S.of(context).reOpen,
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
color: isDepositable ? kSuccessColor : DAppColors.kWarning,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
subtitle: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
DateFormat('dd MMM, yyyy').format(
|
||||
DateTime.parse(cheque.date ?? 'n/a'),
|
||||
),
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: kGrey6,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'$currency${cheque.amount?.toStringAsFixed(2) ?? '0.00'}',
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 6),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
text: '${l.S.of(context).type}: ',
|
||||
style: TextStyle(color: kGreyTextColor),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: cheque.platform.capitalizeFirstLetter(),
|
||||
style: TextStyle(
|
||||
color: kTitleColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: kTitleColor,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
isDepositable
|
||||
? l.S.of(context).open
|
||||
: 'Deposit to ${cheque.paymentType == null ? l.S.of(context).cash : cheque.paymentType?.name ?? 'n/a'}',
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final _lang = l.S.of(context);
|
||||
final theme = Theme.of(context);
|
||||
final chequesAsync = ref.watch(chequeListProvider);
|
||||
|
||||
return DefaultTabController(
|
||||
length: 3,
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
appBar: AppBar(
|
||||
title: Text(_lang.chequeList),
|
||||
centerTitle: true,
|
||||
toolbarHeight: 100,
|
||||
bottom: PreferredSize(
|
||||
preferredSize: Size.fromHeight(10),
|
||||
child: Column(
|
||||
children: [
|
||||
Divider(
|
||||
color: kLineColor,
|
||||
height: 1,
|
||||
),
|
||||
TabBar(
|
||||
dividerColor: kLineColor,
|
||||
dividerHeight: 0.1,
|
||||
indicatorSize: TabBarIndicatorSize.tab,
|
||||
labelStyle: theme.textTheme.titleMedium?.copyWith(
|
||||
color: kMainColor,
|
||||
),
|
||||
unselectedLabelStyle: theme.textTheme.titleMedium?.copyWith(
|
||||
color: kGreyTextColor,
|
||||
),
|
||||
tabs: [
|
||||
Tab(
|
||||
text: _lang.all,
|
||||
),
|
||||
Tab(
|
||||
text: _lang.open,
|
||||
),
|
||||
Tab(
|
||||
text: _lang.closed,
|
||||
),
|
||||
],
|
||||
),
|
||||
Divider(
|
||||
color: kLineColor,
|
||||
height: 1,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () => ref.refresh(chequeListProvider.future),
|
||||
child: chequesAsync.when(
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (err, stack) => Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: Text(
|
||||
'Error loading cheques: ${err.toString()}',
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
data: (model) {
|
||||
final allCheques = model.data ?? [];
|
||||
final filteredCheques = _filterTransactionsLocally(allCheques);
|
||||
|
||||
return TabBarView(
|
||||
children: [
|
||||
_buildChequeList(theme, filteredCheques),
|
||||
_buildChequeList(
|
||||
theme,
|
||||
filteredCheques.where((c) => (c.type ?? '').toLowerCase() == 'pending').toList(),
|
||||
),
|
||||
_buildChequeList(
|
||||
theme,
|
||||
filteredCheques.where((c) => (c.type ?? '').toLowerCase() == 'deposit').toList(),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildChequeList(ThemeData theme, List<ChequeTransactionData> cheques) {
|
||||
if (cheques.isEmpty) {
|
||||
return Center(child: Text(l.S.of(context).noChequeFound));
|
||||
}
|
||||
|
||||
return ListView.separated(
|
||||
itemCount: cheques.length,
|
||||
separatorBuilder: (_, __) => const Divider(height: 1, color: kBackgroundColor),
|
||||
itemBuilder: (_, index) {
|
||||
final cheque = cheques[index];
|
||||
return _buildChequeListTile(theme, cheque);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import 'package:mobile_pos/Screens/cash%20and%20bank/bank%20account/model/bank_account_list_model.dart';
|
||||
|
||||
import '../../../../model/business_info_model.dart';
|
||||
|
||||
class ChequeTransactionModel {
|
||||
final String? message;
|
||||
final List<ChequeTransactionData>? data;
|
||||
|
||||
ChequeTransactionModel({this.message, this.data});
|
||||
|
||||
factory ChequeTransactionModel.fromJson(Map<String, dynamic> json) {
|
||||
return ChequeTransactionModel(
|
||||
message: json['message'],
|
||||
data: (json['data'] as List<dynamic>?)?.map((e) => ChequeTransactionData.fromJson(e as Map<String, dynamic>)).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ChequeTransactionData {
|
||||
final int? id;
|
||||
final String? platform;
|
||||
final String? transactionType;
|
||||
final String? type; // 'credit'
|
||||
final num? amount;
|
||||
final String? date;
|
||||
final num? referenceId;
|
||||
final String? invoiceNo;
|
||||
final String? image;
|
||||
final String? note;
|
||||
final ChequeMeta? meta;
|
||||
final User? user; // Received From
|
||||
BankData? paymentType;
|
||||
|
||||
ChequeTransactionData({
|
||||
this.id,
|
||||
this.platform,
|
||||
this.transactionType,
|
||||
this.type,
|
||||
this.amount,
|
||||
this.date,
|
||||
this.referenceId,
|
||||
this.invoiceNo,
|
||||
this.image,
|
||||
this.note,
|
||||
this.meta,
|
||||
this.user,
|
||||
this.paymentType,
|
||||
});
|
||||
|
||||
factory ChequeTransactionData.fromJson(Map<String, dynamic> json) {
|
||||
return ChequeTransactionData(
|
||||
id: json['id'],
|
||||
platform: json['platform'],
|
||||
transactionType: json['transaction_type'],
|
||||
type: json['type'],
|
||||
amount: json['amount'],
|
||||
date: json['date'],
|
||||
referenceId: json['reference_id'],
|
||||
invoiceNo: json['invoice_no'],
|
||||
image: json['image'],
|
||||
note: json['note'],
|
||||
meta: json['meta'] != null ? ChequeMeta.fromJson(json['meta']) : null,
|
||||
user: json['user'] != null ? User.fromJson(json['user']) : null,
|
||||
paymentType: json['payment_type'] != null ? BankData.fromJson(json['payment_type']) : null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ChequeMeta {
|
||||
final String? chequeNumber;
|
||||
final String? status; // 'open'
|
||||
|
||||
ChequeMeta({this.chequeNumber, this.status});
|
||||
|
||||
factory ChequeMeta.fromJson(Map<String, dynamic> json) {
|
||||
return ChequeMeta(
|
||||
chequeNumber: json['cheque_number'],
|
||||
status: json['status'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// User model is assumed to be shared from bank_transfer_history_model.dart
|
||||
141
lib/Screens/cash and bank/cheques/repo/cheque_repository.dart
Normal file
141
lib/Screens/cash and bank/cheques/repo/cheque_repository.dart
Normal file
@@ -0,0 +1,141 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../../../Const/api_config.dart';
|
||||
import '../../../../http_client/custome_http_client.dart';
|
||||
import '../../../../http_client/customer_http_client_get.dart';
|
||||
import '../../bank account/provider/bank_account_provider.dart';
|
||||
import '../model/cheques_list_model.dart';
|
||||
|
||||
final chequeListProvider = FutureProvider.autoDispose<ChequeTransactionModel>((ref) async {
|
||||
final repo = ChequeRepository();
|
||||
return repo.fetchChequeList(filter: 'Current Year');
|
||||
});
|
||||
|
||||
class ChequeRepository {
|
||||
static const String _endpoint = '/cheques';
|
||||
|
||||
// --- 1. FETCH LIST ---
|
||||
Future<ChequeTransactionModel> fetchChequeList({
|
||||
required String? filter,
|
||||
}) async {
|
||||
final uri = Uri.parse('${APIConfig.url}$_endpoint');
|
||||
|
||||
try {
|
||||
CustomHttpClientGet customHttpClientGet = CustomHttpClientGet(client: http.Client());
|
||||
final response = await customHttpClientGet.get(
|
||||
url: uri,
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return ChequeTransactionModel.fromJson(jsonDecode(response.body));
|
||||
} else {
|
||||
throw Exception('Failed to load cheques: ${response.statusCode}');
|
||||
}
|
||||
} catch (e) {
|
||||
throw Exception('Network Error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// --- 2. DEPOSIT Cheque (POST /api/v1/cheques) ---
|
||||
Future<void> depositCheque({
|
||||
required WidgetRef ref,
|
||||
required BuildContext context,
|
||||
required num chequeTransactionId,
|
||||
required dynamic paymentDestination,
|
||||
required String transferDate,
|
||||
required String description,
|
||||
}) async {
|
||||
final uri = Uri.parse('${APIConfig.url}$_endpoint');
|
||||
|
||||
final Map<String, dynamic> fields = {
|
||||
'transaction_id': chequeTransactionId.toString(),
|
||||
'payment_type': paymentDestination.toString(),
|
||||
'date': transferDate,
|
||||
'note': description,
|
||||
};
|
||||
|
||||
CustomHttpClient customHttpClient = CustomHttpClient(client: http.Client(), context: context, ref: ref);
|
||||
|
||||
try {
|
||||
EasyLoading.show(status: 'Depositing Cheque...');
|
||||
|
||||
var response = await customHttpClient.post(
|
||||
url: uri,
|
||||
body: fields,
|
||||
permission: 'cheque_deposit_permit',
|
||||
);
|
||||
|
||||
final parsedData = jsonDecode(response.body);
|
||||
EasyLoading.dismiss();
|
||||
|
||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||
ref.invalidate(chequeListProvider);
|
||||
ref.invalidate(bankListProvider);
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(parsedData['message'] ?? 'Cheque Deposited Successfully!')),
|
||||
);
|
||||
Navigator.pop(context);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Deposit Failed: ${parsedData['message'] ?? 'Unknown error'}')),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
EasyLoading.dismiss();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('An error occurred: $error')),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// --- 3. RE-OPEN Cheque (POST /api/v1/cheque-reopen/{transaction_id}) ---
|
||||
Future<void> reOpenCheque({
|
||||
required WidgetRef ref,
|
||||
required BuildContext context,
|
||||
required num chequeTransactionId,
|
||||
}) async {
|
||||
// API Call: POST /cheque-reopen/{id}
|
||||
final uri = Uri.parse('${APIConfig.url}/cheque-reopen/$chequeTransactionId');
|
||||
|
||||
CustomHttpClient customHttpClient = CustomHttpClient(client: http.Client(), context: context, ref: ref);
|
||||
|
||||
try {
|
||||
EasyLoading.show(status: 'Re-opening Cheque...');
|
||||
|
||||
// Sending Empty body as the ID is in the URL
|
||||
var response = await customHttpClient.post(
|
||||
url: uri,
|
||||
body: {},
|
||||
);
|
||||
|
||||
final parsedData = jsonDecode(response.body);
|
||||
EasyLoading.dismiss();
|
||||
|
||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||
// Success: Refresh Lists and Close Dialog
|
||||
ref.invalidate(chequeListProvider);
|
||||
ref.invalidate(bankListProvider);
|
||||
|
||||
Navigator.pop(context); // Close the confirmation dialog
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(parsedData['message'] ?? 'Cheque Re-opened Successfully!')),
|
||||
);
|
||||
} else {
|
||||
// API Error
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Failed: ${parsedData['message'] ?? 'Unknown error'}')),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
EasyLoading.dismiss();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('An error occurred: $error')),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user