115 lines
3.3 KiB
Dart
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);
|