import 'package:flutter/material.dart'; import 'package:hesabix_ui/l10n/app_localizations.dart'; import 'package:hesabix_ui/core/calendar_controller.dart'; import 'package:hesabix_ui/core/auth_store.dart'; import 'package:hesabix_ui/core/api_client.dart'; import 'package:hesabix_ui/models/document_model.dart'; import 'package:hesabix_ui/services/document_service.dart'; import 'package:hesabix_ui/widgets/data_table/data_table_widget.dart'; import 'package:hesabix_ui/widgets/data_table/data_table_config.dart'; import 'package:hesabix_ui/widgets/date_input_field.dart'; import 'package:hesabix_ui/utils/number_formatters.dart' show formatWithThousands; import 'package:hesabix_ui/widgets/document/document_details_dialog.dart'; import 'package:hesabix_ui/widgets/document/document_form_dialog.dart'; /// صفحه لیست اسناد حسابداری (عمومی و اتوماتیک) class DocumentsPage extends StatefulWidget { final int businessId; final CalendarController calendarController; final AuthStore authStore; final ApiClient apiClient; const DocumentsPage({ super.key, required this.businessId, required this.calendarController, required this.authStore, required this.apiClient, }); @override State createState() => _DocumentsPageState(); } class _DocumentsPageState extends State { late DocumentService _service; String? _selectedDocumentType; DateTime? _fromDate; DateTime? _toDate; final GlobalKey _tableKey = GlobalKey(); int _selectedCount = 0; // انواع اسناد final Map _documentTypes = { 'all': 'همه', 'manual': 'سند دستی', 'expense': 'هزینه', 'income': 'درآمد', 'receipt': 'دریافت', 'payment': 'پرداخت', 'transfer': 'انتقال', 'invoice': 'فاکتور', }; @override void initState() { super.initState(); _service = DocumentService(widget.apiClient); } /// تازه‌سازی داده‌های جدول void _refreshData() { final state = _tableKey.currentState; if (state != null) { try { (state as dynamic).refresh(); return; } catch (_) {} } if (mounted) setState(() {}); } @override Widget build(BuildContext context) { final t = AppLocalizations.of(context); return Scaffold( backgroundColor: Theme.of(context).colorScheme.surface, body: SafeArea( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // فیلترها _buildFilters(t), // جدول داده‌ها Expanded( child: Padding( padding: const EdgeInsets.all(8.0), child: DataTableWidget( key: _tableKey, config: _buildTableConfig(t), fromJson: (json) => DocumentModel.fromJson(json), calendarController: widget.calendarController, ), ), ), ], ), ), ); } /// ساخت فیلترها Widget _buildFilters(AppLocalizations t) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, border: Border( bottom: BorderSide( color: Theme.of(context).dividerColor, width: 1, ), ), ), child: Row( children: [ // فیلتر نوع سند SizedBox( width: 200, child: DropdownButtonFormField( initialValue: _selectedDocumentType, decoration: const InputDecoration( labelText: 'نوع سند', border: OutlineInputBorder(), contentPadding: EdgeInsets.symmetric( horizontal: 12, vertical: 8, ), isDense: true, ), items: _documentTypes.entries.map((entry) { return DropdownMenuItem( value: entry.key == 'all' ? null : entry.key, child: Text(entry.value), ); }).toList(), onChanged: (value) { setState(() { _selectedDocumentType = value; }); _refreshData(); }, ), ), const SizedBox(width: 8), // فیلتر از تاریخ Expanded( child: DateInputField( calendarController: widget.calendarController, onChanged: (date) { setState(() => _fromDate = date); _refreshData(); }, labelText: 'از تاریخ', hintText: 'انتخاب تاریخ شروع', ), ), const SizedBox(width: 8), // فیلتر تا تاریخ Expanded( child: DateInputField( calendarController: widget.calendarController, onChanged: (date) { setState(() => _toDate = date); _refreshData(); }, labelText: 'تا تاریخ', hintText: 'انتخاب تاریخ پایان', ), ), const SizedBox(width: 8), // دکمه پاک کردن فیلترها IconButton( onPressed: () { setState(() { _selectedDocumentType = null; _fromDate = null; _toDate = null; }); _refreshData(); }, icon: const Icon(Icons.clear), tooltip: 'پاک کردن فیلتر', ), const Spacer(), // دکمه افزودن سند جدید ElevatedButton.icon( onPressed: _createNewDocument, icon: const Icon(Icons.add), label: const Text('سند جدید'), style: ElevatedButton.styleFrom( backgroundColor: Theme.of(context).colorScheme.primary, foregroundColor: Theme.of(context).colorScheme.onPrimary, padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), ), ], ), ); } /// ایجاد سند جدید Future _createNewDocument() async { final result = await showDialog( context: context, barrierDismissible: false, builder: (context) => DocumentFormDialog( businessId: widget.businessId, calendarController: widget.calendarController, authStore: widget.authStore, apiClient: widget.apiClient, fiscalYearId: null, // TODO: از context یا state بگیریم currencyId: 1, // TODO: از تنظیمات بگیریم ), ); if (result == true) { _refreshData(); } } /// ساخت تنظیمات جدول DataTableConfig _buildTableConfig(AppLocalizations t) { return DataTableConfig( endpoint: '/businesses/${widget.businessId}/documents', title: 'اسناد حسابداری', excelEndpoint: '/businesses/${widget.businessId}/documents/export/excel', customHeaderActions: [ if (_selectedCount > 0) Tooltip( message: 'حذف انتخاب‌شده‌ها', child: FilledButton.icon( onPressed: _handleBulkDelete, style: FilledButton.styleFrom( backgroundColor: Theme.of(context).colorScheme.errorContainer, foregroundColor: Theme.of(context).colorScheme.onErrorContainer, ), icon: const Icon(Icons.delete_forever), label: Text('حذف ($_selectedCount)'), ), ), ], getExportParams: () => { 'business_id': widget.businessId, 'document_type': _selectedDocumentType, if (_fromDate != null) 'from_date': _fromDate!.toUtc().toIso8601String(), if (_toDate != null) 'to_date': _toDate!.toUtc().toIso8601String(), }, additionalParams: { if (_selectedDocumentType != null) 'document_type': _selectedDocumentType!, if (_fromDate != null) 'from_date': _fromDate!.toUtc().toIso8601String(), if (_toDate != null) 'to_date': _toDate!.toUtc().toIso8601String(), }, columns: [ // شماره سند TextColumn( 'code', 'شماره سند', width: ColumnWidth.medium, formatter: (item) => item.code, ), // نوع سند CustomColumn( 'document_type', 'نوع', width: ColumnWidth.medium, builder: (item, index) { final doc = item as DocumentModel; return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: _getDocumentTypeColor(doc.documentType).withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), border: Border.all( color: _getDocumentTypeColor(doc.documentType), width: 1, ), ), child: Text( doc.getDocumentTypeName(), style: TextStyle( color: _getDocumentTypeColor(doc.documentType), fontSize: 12, fontWeight: FontWeight.w600, ), ), ); }, ), // تاریخ سند TextColumn( 'document_date', 'تاریخ', width: ColumnWidth.medium, formatter: (item) => item.documentDateRaw ?? '-', ), // سال مالی TextColumn( 'fiscal_year_title', 'سال مالی', width: ColumnWidth.medium, formatter: (item) => item.fiscalYearTitle ?? '-', ), // بدهکار NumberColumn( 'total_debit', 'بدهکار', width: ColumnWidth.large, formatter: (item) => formatWithThousands(item.totalDebit.toInt()), suffix: ' ریال', ), // بستانکار NumberColumn( 'total_credit', 'بستانکار', width: ColumnWidth.large, formatter: (item) => formatWithThousands(item.totalCredit.toInt()), suffix: ' ریال', ), // وضعیت CustomColumn( 'is_proforma', 'وضعیت', width: ColumnWidth.small, builder: (item, index) { final doc = item as DocumentModel; return Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: doc.isProforma ? Colors.orange.withValues(alpha: 0.1) : Colors.green.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), ), child: Text( doc.statusText, style: TextStyle( color: doc.isProforma ? Colors.orange : Colors.green, fontSize: 11, ), ), ); }, ), // توضیحات TextColumn( 'description', 'توضیحات', width: ColumnWidth.large, formatter: (item) => item.description ?? '-', ), // عملیات ActionColumn( 'actions', 'عملیات', width: ColumnWidth.medium, actions: [ // مشاهده - برای همه اسناد DataTableAction( icon: Icons.visibility, label: 'مشاهده', onTap: (item) => _showDocumentDetails(item as DocumentModel), ), // ویرایش - فقط برای manual DataTableAction( icon: Icons.edit, label: 'ویرایش', onTap: (item) => _editDocument(item as DocumentModel), enabled: true, ), // حذف - فقط برای manual DataTableAction( icon: Icons.delete, label: 'حذف', onTap: (item) => _deleteDocument(item as DocumentModel), isDestructive: true, ), ], ), ], searchFields: ['code', 'description'], filterFields: ['document_type'], dateRangeField: 'document_date', showSearch: true, showFilters: true, showPagination: true, showColumnSearch: true, showRefreshButton: true, showClearFiltersButton: true, enableRowSelection: true, enableMultiRowSelection: true, showExportButtons: true, showExcelExport: true, showPdfExport: false, defaultPageSize: 50, pageSizeOptions: [20, 50, 100, 200], onRowSelectionChanged: (rows) { setState(() { _selectedCount = rows.length; }); }, ); } /// رنگ بر اساس نوع سند Color _getDocumentTypeColor(String type) { switch (type) { case 'manual': return Colors.blue; case 'expense': return Colors.red; case 'income': return Colors.green; case 'receipt': return Colors.teal; case 'payment': return Colors.orange; case 'transfer': return Colors.purple; case 'invoice': return Colors.indigo; default: return Colors.grey; } } /// نمایش جزئیات سند Future _showDocumentDetails(DocumentModel doc) async { await showDialog( context: context, builder: (context) => DocumentDetailsDialog( documentId: doc.id, calendarController: widget.calendarController, ), ); } /// ویرایش سند Future _editDocument(DocumentModel doc) async { if (!doc.isEditable) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('فقط اسناد دستی قابل ویرایش هستند'), backgroundColor: Colors.orange, ), ); return; } // بارگذاری جزئیات کامل سند (با سطرها) try { final fullDocument = await _service.getDocument(doc.id); if (!mounted) return; final result = await showDialog( context: context, barrierDismissible: false, builder: (context) => DocumentFormDialog( businessId: widget.businessId, calendarController: widget.calendarController, authStore: widget.authStore, apiClient: widget.apiClient, document: fullDocument, // حالت ویرایش fiscalYearId: fullDocument.fiscalYearId, currencyId: fullDocument.currencyId, ), ); if (result == true) { _refreshData(); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('خطا در بارگذاری سند: $e'), backgroundColor: Colors.red, ), ); } } } /// حذف سند Future _deleteDocument(DocumentModel doc) async { if (!doc.isDeletable) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('فقط اسناد دستی قابل حذف هستند'), backgroundColor: Colors.orange, ), ); return; } final confirmed = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('تأیید حذف'), content: Text('آیا از حذف سند ${doc.code} اطمینان دارید؟'), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: const Text('انصراف'), ), ElevatedButton( onPressed: () => Navigator.pop(context, true), style: ElevatedButton.styleFrom( backgroundColor: Colors.red, foregroundColor: Colors.white, ), child: const Text('حذف'), ), ], ), ); if (confirmed == true) { try { await _service.deleteDocument(doc.id); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('سند با موفقیت حذف شد')), ); _refreshData(); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('خطا در حذف سند: $e'), backgroundColor: Colors.red, ), ); } } } } /// حذف گروهی اسناد Future _handleBulkDelete() async { final confirmed = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('تأیید حذف گروهی'), content: Text( 'آیا از حذف $_selectedCount سند انتخاب شده اطمینان دارید؟\n\nتوجه: فقط اسناد دستی حذف خواهند شد.'), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: const Text('انصراف'), ), ElevatedButton( onPressed: () => Navigator.pop(context, true), style: ElevatedButton.styleFrom( backgroundColor: Colors.red, foregroundColor: Colors.white, ), child: const Text('حذف'), ), ], ), ); if (confirmed == true) { try { // دریافت آیتم‌های انتخاب شده از جدول final state = _tableKey.currentState; if (state != null) { final selectedRows = (state as dynamic).getSelectedRows() as List; final documentIds = selectedRows.map((doc) => doc.id).toList(); if (documentIds.isNotEmpty) { final result = await _service.bulkDeleteDocuments(documentIds); if (mounted) { final deletedCount = result['deleted_count'] as int; final skipped = result['skipped_auto_documents'] as List; String message = '$deletedCount سند با موفقیت حذف شد'; if (skipped.isNotEmpty) { message += '\n${skipped.length} سند اتوماتیک نادیده گرفته شد'; } ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(message)), ); _refreshData(); } } } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('خطا در حذف گروهی: $e'), backgroundColor: Colors.red, ), ); } } } } }