progress in invoices
This commit is contained in:
parent
7f6a78f642
commit
2591e9a7a9
|
|
@ -479,6 +479,7 @@ class AuthStore with ChangeNotifier {
|
|||
Future<void> _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();
|
||||
|
|
|
|||
|
|
@ -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<NewInvoicePage> 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<void> _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) {
|
||||
// خطا در بارگذاری ارز پیشفرض
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -29,13 +29,25 @@ class _CurrencyPickerWidgetState extends State<CurrencyPickerWidget> {
|
|||
List<Map<String, dynamic>> _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<void> _loadCurrencies() async {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
|
|
@ -61,64 +73,90 @@ class _CurrencyPickerWidgetState extends State<CurrencyPickerWidget> {
|
|||
@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<int>(
|
||||
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<int>(
|
||||
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<int>(
|
||||
|
|
@ -153,12 +191,9 @@ class _CurrencyPickerWidgetState extends State<CurrencyPickerWidget> {
|
|||
),
|
||||
);
|
||||
}).toList(),
|
||||
validator: (value) {
|
||||
if (value == null) {
|
||||
return 'انتخاب ارز الزامی است';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -164,7 +164,8 @@ class _InvoiceLineItemsTableState extends State<InvoiceLineItemsTable> {
|
|||
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<InvoiceLineItemsTable> {
|
|||
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<InvoiceLineItemsTable> {
|
|||
_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<InvoiceLineItemsTable> {
|
|||
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,
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue