// widgets/finance_entry_dialog.dart — 登记收支弹窗(财务屏 head 按钮 + 往来抽屉共用)。 // 字段:类型(收款/付款) + 往来单位(可搜索,选填) + 金额 + 日期 + 备注。 // 相比旧版补上了往来单位选择(修复手动收付款 partner_id 恒 NULL 的缺口)。 import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; import '../core/responsive/responsive.dart'; import '../core/theme/context_tokens.dart'; import '../core/utils/clock.dart'; import '../core/utils/dialog_util.dart'; import '../providers/finance_provider.dart'; import '../providers/partner_provider.dart'; import 'date_picker_field.dart'; import 'ds/ds_atoms.dart'; import 'searchable_option_field.dart'; import 'ds/ds_toast.dart'; /// 打开登记收支弹窗;[type]='receipt'|'payment' 预选类型,[partnerId] 预选往来单位。 Future showFinanceEntryDialog( BuildContext context, { String type = 'receipt', int? partnerId, }) { return showAppDialog( context: context, builder: (_) => _FinanceEntryDialog(type: type, partnerId: partnerId), ); } class _FinanceEntryDialog extends ConsumerStatefulWidget { final String type; final int? partnerId; const _FinanceEntryDialog({required this.type, this.partnerId}); @override ConsumerState<_FinanceEntryDialog> createState() => _FinanceEntryDialogState(); } class _FinanceEntryDialogState extends ConsumerState<_FinanceEntryDialog> { final _formKey = GlobalKey(); final _amountCtrl = TextEditingController(); final _remarkCtrl = TextEditingController(); late String _type = widget.type; late int? _partnerId = widget.partnerId; late String _date = DateFormat('yyyy-MM-dd').format(appNow()); bool _saving = false; @override void dispose() { _amountCtrl.dispose(); _remarkCtrl.dispose(); super.dispose(); } Future _save() async { if (!_formKey.currentState!.validate()) return; setState(() => _saving = true); try { await ref.read(financeRepositoryProvider).create({ 'type': _type, 'amount': double.parse(_amountCtrl.text.trim()), 'record_date': _date, if (_partnerId != null) 'partner_id': _partnerId, if (_remarkCtrl.text.trim().isNotEmpty) 'remark': _remarkCtrl.text.trim(), }); // 刷新流水 + 汇总 ref.read(financeListProvider.notifier).reload(); ref.invalidate(financePartnerRowsProvider); if (mounted) { Navigator.of(context).pop(); showDsToast(context, _type == 'receipt' ? '收款已登记 ✓' : '付款已登记 ✓', bg: context.tokens.success); } } catch (e) { if (mounted) { showDsToast(context, '登记失败:$e', bg: context.tokens.danger); } } finally { if (mounted) setState(() => _saving = false); } } @override Widget build(BuildContext context) { final partners = ref.watch(allPartnersProvider).valueOrNull?.data ?? []; return AlertDialog( title: const Text('登记收支'), content: SizedBox( width: context.dialogWidth(420), child: Form( key: _formKey, child: Column( mainAxisSize: MainAxisSize.min, children: [ Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: DsField('类型', required: true, input: DsSelect( value: _type, options: const [('receipt', '收款'), ('payment', '付款')], onChanged: (v) => setState(() => _type = v), )), ), const SizedBox(width: 14), Expanded( child: DsField('金额', required: true, input: TextFormField( controller: _amountCtrl, keyboardType: const TextInputType.numberWithOptions( decimal: true), decoration: const InputDecoration( hintText: '0.00', prefixText: '¥ '), validator: (v) { final n = double.tryParse((v ?? '').trim()); return (n == null || n <= 0) ? '请输入正确金额' : null; }, )), ), ]), const SizedBox(height: 14), DsField('往来单位(选填)', input: SearchableOptionField( options: [ for (final p in partners) OptionItem(id: p.id, name: p.name), ], selectedId: _partnerId, hint: '选择或搜索往来单位…', dialogTitle: '选择往来单位', onChanged: (id) => setState(() => _partnerId = id), )), const SizedBox(height: 14), DsField('日期', required: true, input: DatePickerField( value: _date, isRequired: true, onChanged: (v) => setState(() => _date = v ?? _date), )), const SizedBox(height: 14), DsField('备注', input: TextFormField( controller: _remarkCtrl, decoration: const InputDecoration(hintText: '备注说明(选填)'), maxLines: 2, )), ], ), ), ), actions: [ DsButton('取消', onPressed: _saving ? null : () => Navigator.of(context).pop()), DsButton(_saving ? '保存中…' : '保存', variant: DsBtnVariant.primary, onPressed: _saving ? null : _save), ], ); } }