Files
Aq-Accounting-Flutter/lib/providers/add_bill_provider.dart
T
2026-09-21 17:37:53 +08:00

115 lines
3.3 KiB
Dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../api/bill_api.dart';
import '../core/network/api_exception.dart';
import '../models/bill_request.dart';
import '../models/ocr_result.dart';
import '../utils/date_util.dart';
import 'bill_refresh.dart';
/// 手动记账表单状态
class AddBillFormState {
final int type; // 1=支出 2=收入
final int? categoryId;
final double? amount;
final String billDate; // yyyy-MM-dd
final String description;
final bool submitting;
final int revision; // 每次 fillFromOcr 外部填充时 +1,供 UI 同步非受控输入框
const AddBillFormState({
this.type = 1,
this.categoryId,
this.amount,
required this.billDate,
this.description = '',
this.submitting = false,
this.revision = 0,
});
AddBillFormState copyWith({
int? type,
int? categoryId,
Object? amount = _sentinel,
String? billDate,
String? description,
bool? submitting,
int? revision,
}) {
return AddBillFormState(
type: type ?? this.type,
categoryId: categoryId ?? this.categoryId,
amount: amount == _sentinel ? this.amount : amount as double?,
billDate: billDate ?? this.billDate,
description: description ?? this.description,
submitting: submitting ?? this.submitting,
revision: revision ?? this.revision,
);
}
static const _sentinel = Object();
}
class AddBillFormNotifier extends Notifier<AddBillFormState> {
@override
AddBillFormState build() => AddBillFormState(billDate: formatDate(DateTime.now()));
void setType(int type) {
if (type == state.type) return;
// 类型切换后分类需重选(对齐 uniApp watch 行为)
state = state.copyWith(type: type, categoryId: null);
}
void setCategory(int id) => state = state.copyWith(categoryId: id);
void setAmount(double? value) => state = state.copyWith(amount: value);
void setDate(String date) => state = state.copyWith(billDate: date);
void setDescription(String desc) => state = state.copyWith(description: desc);
/// 保存账单,成功返回 true
Future<bool> submit() async {
final s = state;
if (s.categoryId == null) throw ApiException(-1, '请选择分类');
if (s.amount == null || s.amount! <= 0) throw ApiException(-1, '请输入有效金额');
state = state.copyWith(submitting: true);
try {
await ref.read(billApiProvider).createBill(BillRequest(
categoryId: s.categoryId!,
amount: s.amount!,
description: s.description,
billDate: s.billDate,
type: s.type,
));
// 让首页列表与统计页重新取数
invalidateBillData(ref);
_resetAfterSubmit();
return true;
} finally {
state = state.copyWith(submitting: false);
}
}
void _resetAfterSubmit() {
state = AddBillFormState(
type: state.type,
billDate: formatDate(DateTime.now()),
);
}
/// OCR「选择此条」:按金额正负推断类型并填充表单
void fillFromOcr(OcrResult ocr) {
state = AddBillFormState(
type: ocr.isExpense ? 1 : 2,
amount: ocr.absAmount,
description: ocr.merchant ?? '',
billDate: parseBillDate(ocr.date),
revision: state.revision + 1,
);
}
}
final addBillFormProvider =
NotifierProvider<AddBillFormNotifier, AddBillFormState>(AddBillFormNotifier.new);