From 2591e9a7a917f84435245ada74c2f510dd19354f Mon Sep 17 00:00:00 2001 From: Babak Alizadeh Date: Tue, 7 Oct 2025 01:32:30 +0330 Subject: [PATCH] progress in invoices --- hesabixUI/hesabix_ui/lib/core/auth_store.dart | 1 + .../lib/pages/business/new_invoice_page.dart | 31 +++++ .../banking/currency_picker_widget.dart | 127 +++++++++++------- .../lib/widgets/invoice/line_items_table.dart | 11 +- 4 files changed, 121 insertions(+), 49 deletions(-) diff --git a/hesabixUI/hesabix_ui/lib/core/auth_store.dart b/hesabixUI/hesabix_ui/lib/core/auth_store.dart index 0923d5c..218739c 100644 --- a/hesabixUI/hesabix_ui/lib/core/auth_store.dart +++ b/hesabixUI/hesabix_ui/lib/core/auth_store.dart @@ -479,6 +479,7 @@ class AuthStore with ChangeNotifier { Future _ensureCurrencyForBusiness() async { final business = _currentBusiness; if (business == null) return; + // اگر ارزی انتخاب نشده، یا کد/شناسه فعلی جزو ارزهای کسب‌وکار نیست final allowedCodes = business.currencies.map((c) => c.code).toSet(); final allowedIds = business.currencies.map((c) => c.id).toSet(); diff --git a/hesabixUI/hesabix_ui/lib/pages/business/new_invoice_page.dart b/hesabixUI/hesabix_ui/lib/pages/business/new_invoice_page.dart index e7dca42..c4741aa 100644 --- a/hesabixUI/hesabix_ui/lib/pages/business/new_invoice_page.dart +++ b/hesabixUI/hesabix_ui/lib/pages/business/new_invoice_page.dart @@ -18,6 +18,8 @@ import '../../models/customer_model.dart'; import '../../models/person_model.dart'; import '../../widgets/invoice/line_items_table.dart'; import '../../utils/number_formatters.dart'; +import '../../services/currency_service.dart'; +import '../../core/api_client.dart'; class NewInvoicePage extends StatefulWidget { final int businessId; @@ -62,8 +64,37 @@ class _NewInvoicePageState extends State with SingleTickerProvid void initState() { super.initState(); _tabController = TabController(length: 4, vsync: this); // شروع با 4 تب + // تنظیم نوع فاکتور پیش‌فرض + _selectedInvoiceType = InvoiceType.sales; // تنظیم ارز پیش‌فرض از AuthStore _selectedCurrencyId = widget.authStore.selectedCurrencyId; + // اگر ارز انتخاب نشده، ارز پیش‌فرض را بارگذاری کن + if (_selectedCurrencyId == null) { + _loadDefaultCurrency(); + } + // تنظیم تاریخ‌های پیش‌فرض + _invoiceDate = DateTime.now(); + _dueDate = DateTime.now(); + } + + Future _loadDefaultCurrency() async { + try { + final currencyService = CurrencyService(ApiClient()); + final currencies = await currencyService.listBusinessCurrencies(businessId: widget.businessId); + if (currencies.isNotEmpty) { + // ارز پیش‌فرض را پیدا کن + final defaultCurrency = currencies.firstWhere( + (c) => c['is_default'] == true, + orElse: () => currencies.first, + ); + setState(() { + _selectedCurrencyId = defaultCurrency['id'] as int; + }); + // ارز پیش‌فرض بارگذاری شد + } + } catch (e) { + // خطا در بارگذاری ارز پیش‌فرض + } } diff --git a/hesabixUI/hesabix_ui/lib/widgets/banking/currency_picker_widget.dart b/hesabixUI/hesabix_ui/lib/widgets/banking/currency_picker_widget.dart index 70aac44..00df5d0 100644 --- a/hesabixUI/hesabix_ui/lib/widgets/banking/currency_picker_widget.dart +++ b/hesabixUI/hesabix_ui/lib/widgets/banking/currency_picker_widget.dart @@ -29,13 +29,25 @@ class _CurrencyPickerWidgetState extends State { List> _currencies = []; bool _isLoading = false; String? _error; + int? _selectedValue; @override void initState() { super.initState(); + _selectedValue = widget.selectedCurrencyId; _loadCurrencies(); } + @override + void didUpdateWidget(CurrencyPickerWidget oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.selectedCurrencyId != widget.selectedCurrencyId) { + setState(() { + _selectedValue = widget.selectedCurrencyId; + }); + } + } + Future _loadCurrencies() async { setState(() { _isLoading = true; @@ -61,64 +73,90 @@ class _CurrencyPickerWidgetState extends State { @override Widget build(BuildContext context) { if (_isLoading) { - return const SizedBox( + return SizedBox( height: 56, - child: Center( - child: CircularProgressIndicator(), + child: InputDecorator( + decoration: InputDecoration( + labelText: widget.label ?? 'ارز', + hintText: widget.hintText ?? 'انتخاب ارز', + border: const OutlineInputBorder(), + enabled: false, + ), + child: const Center( + child: CircularProgressIndicator(), + ), ), ); } if (_error != null) { - return Container( + return SizedBox( height: 56, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - border: Border.all(color: Colors.red), - borderRadius: BorderRadius.circular(8), - ), - child: Row( - children: [ - const Icon(Icons.error_outline, color: Colors.red), - const SizedBox(width: 8), - Expanded( - child: Text( - 'خطا در بارگذاری ارزها: $_error', - style: const TextStyle(color: Colors.red), + child: InputDecorator( + decoration: InputDecoration( + labelText: widget.label ?? 'ارز', + hintText: widget.hintText ?? 'انتخاب ارز', + border: OutlineInputBorder( + borderSide: BorderSide(color: Colors.red), + ), + enabled: false, + ), + child: Row( + children: [ + const Icon(Icons.error_outline, color: Colors.red), + const SizedBox(width: 8), + Expanded( + child: Text( + 'خطا در بارگذاری ارزها', + style: const TextStyle(color: Colors.red), + ), ), - ), - TextButton( - onPressed: _loadCurrencies, - child: const Text('تلاش مجدد'), - ), - ], + TextButton( + onPressed: _loadCurrencies, + child: const Text('تلاش مجدد'), + ), + ], + ), ), ); } if (_currencies.isEmpty) { - return Container( + return SizedBox( height: 56, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - border: Border.all(color: Colors.grey), - borderRadius: BorderRadius.circular(8), - ), - child: const Center( - child: Text('هیچ ارزی یافت نشد'), + child: InputDecorator( + decoration: InputDecoration( + labelText: widget.label ?? 'ارز', + hintText: widget.hintText ?? 'انتخاب ارز', + border: const OutlineInputBorder(), + enabled: false, + ), + child: const Center( + child: Text('هیچ ارزی یافت نشد'), + ), ), ); } - return DropdownButtonFormField( - value: widget.selectedCurrencyId, - onChanged: widget.enabled ? widget.onChanged : null, - decoration: InputDecoration( - labelText: widget.label ?? 'ارز', - hintText: widget.hintText ?? 'انتخاب ارز', - border: const OutlineInputBorder(), - enabled: widget.enabled, - ), + return SizedBox( + height: 56, // ارتفاع ثابت مثل سایر فیلدها + child: InputDecorator( + decoration: InputDecoration( + labelText: widget.label ?? 'ارز', + hintText: widget.hintText ?? 'انتخاب ارز', + border: const OutlineInputBorder(), + enabled: widget.enabled, + ), + child: DropdownButtonHideUnderline( + child: DropdownButton( + value: _selectedValue, + isExpanded: true, + onChanged: widget.enabled ? (value) { + setState(() { + _selectedValue = value; + }); + widget.onChanged(value); + } : null, items: _currencies.map((currency) { final isDefault = currency['is_default'] == true; return DropdownMenuItem( @@ -153,12 +191,9 @@ class _CurrencyPickerWidgetState extends State { ), ); }).toList(), - validator: (value) { - if (value == null) { - return 'انتخاب ارز الزامی است'; - } - return null; - }, + ), + ), + ), ); } } diff --git a/hesabixUI/hesabix_ui/lib/widgets/invoice/line_items_table.dart b/hesabixUI/hesabix_ui/lib/widgets/invoice/line_items_table.dart index d80fc4d..539bbe7 100644 --- a/hesabixUI/hesabix_ui/lib/widgets/invoice/line_items_table.dart +++ b/hesabixUI/hesabix_ui/lib/widgets/invoice/line_items_table.dart @@ -164,7 +164,8 @@ class _InvoiceLineItemsTableState extends State { if (!isTaxable) return 0; final v = p['purchase_tax_rate']; - if (v is num && v > 0) return v; + final rate = _toNum(v); + if (rate > 0) return rate; // اگر محصول نرخ مالیات خرید نداشته باشد، از نرخ پیش‌فرض استفاده کن return _getDefaultTaxRateForInvoiceType(); } @@ -174,7 +175,8 @@ class _InvoiceLineItemsTableState extends State { if (!isTaxable) return 0; final v = p['sales_tax_rate']; - if (v is num && v > 0) return v; + final rate = _toNum(v); + if (rate > 0) return rate; // اگر محصول نرخ مالیات فروش نداشته باشد، از نرخ پیش‌فرض استفاده کن return _getDefaultTaxRateForInvoiceType(); } @@ -353,8 +355,11 @@ class _InvoiceLineItemsTableState extends State { _notify(); return; } + final mainUnit = p['main_unit']?.toString(); final secondaryUnit = p['secondary_unit']?.toString(); + final taxRate = _defaultTaxRateFromProduct(p); + final updated = item.copyWith( productId: _toInt(p['id']), productCode: p['code']?.toString(), @@ -365,7 +370,7 @@ class _InvoiceLineItemsTableState extends State { selectedUnit: mainUnit, baseSalesPriceMainUnit: _toNum(p['base_sales_price']), basePurchasePriceMainUnit: _toNum(p['base_purchase_price']), - taxRate: _defaultTaxRateFromProduct(p), + taxRate: taxRate, minOrderQty: _toInt(p['min_order_qty']), trackInventory: p['track_inventory'] == true, );