Add transfer tracking and secure admin access

This commit is contained in:
2026-07-26 11:57:57 +08:00
parent 0738953e6d
commit 7df25edd96
111 changed files with 6379 additions and 1934 deletions
@@ -20,7 +20,9 @@ class _TransactionEditPageState extends State<TransactionEditPage> {
late final TextEditingController _amount;
late final TextEditingController _note;
late final TextEditingController _payment;
late final TextEditingController _counterparty;
late String _type;
late String _transferDirection;
late int _ledgerId;
late int _categoryId;
late DateTime _occurredAt;
@@ -37,7 +39,9 @@ class _TransactionEditPageState extends State<TransactionEditPage> {
);
_note = TextEditingController(text: transaction.note ?? '');
_payment = TextEditingController(text: transaction.paymentMethod ?? '');
_counterparty = TextEditingController(text: transaction.counterparty ?? '');
_type = transaction.type;
_transferDirection = transaction.transferDirection ?? 'out';
_ledgerId = transaction.ledgerId;
_categoryId = transaction.categoryId;
_occurredAt = transaction.occurredAt;
@@ -49,6 +53,7 @@ class _TransactionEditPageState extends State<TransactionEditPage> {
_amount.dispose();
_note.dispose();
_payment.dispose();
_counterparty.dispose();
super.dispose();
}
@@ -56,7 +61,12 @@ class _TransactionEditPageState extends State<TransactionEditPage> {
setState(() => _loading = true);
try {
await CurrentLedgerStore.instance.ensureLoaded();
final categories = await TxApi.categories(_type);
final categoryType = _type == 'transfer'
? _transferDirection == 'in'
? 'income'
: 'expense'
: _type;
final categories = await TxApi.categories(categoryType);
if (!categories.any((category) => category.id == _categoryId) &&
categories.isNotEmpty) {
_categoryId = categories.first.id;
@@ -75,6 +85,12 @@ class _TransactionEditPageState extends State<TransactionEditPage> {
await _loadCategories();
}
Future<void> _changeTransferDirection(String direction) async {
if (direction == _transferDirection) return;
setState(() => _transferDirection = direction);
await _loadCategories();
}
Future<void> _pickDateTime() async {
final value = await showJzDateTimeSheet(
context,
@@ -143,6 +159,11 @@ class _TransactionEditPageState extends State<TransactionEditPage> {
amount: amount,
note: _note.text.trim(),
paymentMethod: _payment.text.trim(),
transferDirection: _type == 'transfer' ? _transferDirection : null,
counterparty:
_type == 'transfer' && _counterparty.text.trim().isNotEmpty
? _counterparty.text.trim()
: null,
occurredAt: _occurredAt,
);
if (mounted) Navigator.pop(context, updated);
@@ -178,10 +199,28 @@ class _TransactionEditPageState extends State<TransactionEditPage> {
value: _type,
options: const [
JzOption(value: 'expense', label: '支出'),
JzOption(value: 'transfer', label: '转账'),
JzOption(value: 'income', label: '收入'),
],
onChanged: _changeType,
),
if (_type == 'transfer') ...[
SizedBox(height: 12),
JzSegmentedControl<String>(
value: _transferDirection,
options: const [
JzOption(value: 'out', label: '转出'),
JzOption(value: 'in', label: '转入'),
],
onChanged: _changeTransferDirection,
),
SizedBox(height: 12),
TextField(
controller: _counterparty,
maxLength: 40,
decoration: InputDecoration(labelText: '转账对方'),
),
],
SizedBox(height: 14),
TextField(
controller: _amount,