This commit is contained in:
2026-03-23 12:17:04 +03:00
parent 598254556c
commit 8f03af4b0a
6 changed files with 164 additions and 38 deletions
+34 -1
View File
@@ -13,12 +13,15 @@ class AppDatabase extends _$AppDatabase {
AppDatabase() : super(_openConnection()); AppDatabase() : super(_openConnection());
@override @override
int get schemaVersion => 4; int get schemaVersion => 5;
@override @override
MigrationStrategy get migration => MigrationStrategy( MigrationStrategy get migration => MigrationStrategy(
onUpgrade: (migrator, from, to) async { onUpgrade: (migrator, from, to) async {
print('--- DATABASE MIGRATION: from=$from to=$to ---');
if (from == 1) { if (from == 1) {
print('Migration: Creating accounts table');
await migrator.createTable(accounts); await migrator.createTable(accounts);
await customStatement( await customStatement(
'INSERT INTO accounts (name, is_main, currency, sort_order, created_at) ' 'INSERT INTO accounts (name, is_main, currency, sort_order, created_at) '
@@ -26,11 +29,41 @@ class AppDatabase extends _$AppDatabase {
['main', 1, 'USD', 0, DateTime.now().millisecondsSinceEpoch], ['main', 1, 'USD', 0, DateTime.now().millisecondsSinceEpoch],
); );
} }
if (from == 2) { if (from == 2) {
print('Migration: Adding currency column to accounts');
await customStatement( await customStatement(
'ALTER TABLE accounts ADD COLUMN currency TEXT NOT NULL DEFAULT "USD"', 'ALTER TABLE accounts ADD COLUMN currency TEXT NOT NULL DEFAULT "USD"',
); );
} }
// Add account_id column to transactions if upgrading from version < 5
if (from < 5) {
print('Migration: Adding account_id column to transactions');
try {
// Check if column exists first
final result = await customSelect(
'PRAGMA table_info(transactions)',
).get();
final hasAccountId = result.any((row) => row.data['name'] == 'account_id');
if (!hasAccountId) {
print('Migration: account_id column does not exist, adding it now');
await customStatement(
'ALTER TABLE transactions ADD COLUMN account_id INTEGER NOT NULL DEFAULT 1',
);
print('Migration: account_id column added successfully');
} else {
print('Migration: account_id column already exists, skipping');
}
} catch (e) {
print('Migration: Error adding account_id column: $e');
// If the column already exists, this will fail, which is fine
}
}
print('--- DATABASE MIGRATION: COMPLETE ---');
}, },
); );
+6 -8
View File
@@ -122,7 +122,8 @@ class $TransactionsTable extends Transactions
aliasedName, aliasedName,
false, false,
type: DriftSqlType.int, type: DriftSqlType.int,
requiredDuringInsert: true, requiredDuringInsert: false,
defaultValue: const Constant(1),
); );
static const VerificationMeta _createdAtMeta = const VerificationMeta( static const VerificationMeta _createdAtMeta = const VerificationMeta(
'createdAt', 'createdAt',
@@ -241,8 +242,6 @@ class $TransactionsTable extends Transactions
_accountIdMeta, _accountIdMeta,
accountId.isAcceptableOrUnknown(data['account_id']!, _accountIdMeta), accountId.isAcceptableOrUnknown(data['account_id']!, _accountIdMeta),
); );
} else if (isInserting) {
context.missing(_accountIdMeta);
} }
if (data.containsKey('created_at')) { if (data.containsKey('created_at')) {
context.handle( context.handle(
@@ -567,15 +566,14 @@ class TransactionsCompanion extends UpdateCompanion<Transaction> {
this.lastOccurrence = const Value.absent(), this.lastOccurrence = const Value.absent(),
this.currency = const Value.absent(), this.currency = const Value.absent(),
this.currencyCode = const Value.absent(), this.currencyCode = const Value.absent(),
required int accountId, this.accountId = const Value.absent(),
this.createdAt = const Value.absent(), this.createdAt = const Value.absent(),
this.rowid = const Value.absent(), this.rowid = const Value.absent(),
}) : id = Value(id), }) : id = Value(id),
amount = Value(amount), amount = Value(amount),
category = Value(category), category = Value(category),
type = Value(type), type = Value(type),
date = Value(date), date = Value(date);
accountId = Value(accountId);
static Insertable<Transaction> custom({ static Insertable<Transaction> custom({
Expression<String>? id, Expression<String>? id,
Expression<double>? amount, Expression<double>? amount,
@@ -2324,7 +2322,7 @@ typedef $$TransactionsTableCreateCompanionBuilder =
Value<DateTime?> lastOccurrence, Value<DateTime?> lastOccurrence,
Value<String> currency, Value<String> currency,
Value<String> currencyCode, Value<String> currencyCode,
required int accountId, Value<int> accountId,
Value<DateTime> createdAt, Value<DateTime> createdAt,
Value<int> rowid, Value<int> rowid,
}); });
@@ -2608,7 +2606,7 @@ class $$TransactionsTableTableManager
Value<DateTime?> lastOccurrence = const Value.absent(), Value<DateTime?> lastOccurrence = const Value.absent(),
Value<String> currency = const Value.absent(), Value<String> currency = const Value.absent(),
Value<String> currencyCode = const Value.absent(), Value<String> currencyCode = const Value.absent(),
required int accountId, Value<int> accountId = const Value.absent(),
Value<DateTime> createdAt = const Value.absent(), Value<DateTime> createdAt = const Value.absent(),
Value<int> rowid = const Value.absent(), Value<int> rowid = const Value.absent(),
}) => TransactionsCompanion.insert( }) => TransactionsCompanion.insert(
+1 -1
View File
@@ -12,7 +12,7 @@ class Transactions extends Table {
DateTimeColumn get lastOccurrence => dateTime().nullable()(); DateTimeColumn get lastOccurrence => dateTime().nullable()();
TextColumn get currency => text().withDefault(const Constant('\$'))(); TextColumn get currency => text().withDefault(const Constant('\$'))();
TextColumn get currencyCode => text().withDefault(const Constant('USD'))(); TextColumn get currencyCode => text().withDefault(const Constant('USD'))();
IntColumn get accountId => integer()(); IntColumn get accountId => integer().withDefault(const Constant(1))();
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
@override @override
+55 -9
View File
@@ -24,7 +24,7 @@ class AccountRepository {
// Fallback: insert default account if none exists // Fallback: insert default account if none exists
await _db.into(_db.accounts).insert( await _db.into(_db.accounts).insert(
AccountsCompanion.insert( AccountsCompanion.insert(
name: 'Main', name: 'main',
isMain: const Value(true), isMain: const Value(true),
currency: const Value('USD'), currency: const Value('USD'),
sortOrder: const Value(0), sortOrder: const Value(0),
@@ -69,7 +69,7 @@ class AccountRepository {
try { try {
await _db.into(_db.accounts).insert( await _db.into(_db.accounts).insert(
AccountsCompanion.insert( AccountsCompanion.insert(
name: 'Main', name: 'main',
isMain: const Value(true), isMain: const Value(true),
currency: const Value('USD'), currency: const Value('USD'),
sortOrder: const Value(0), sortOrder: const Value(0),
@@ -103,15 +103,61 @@ class AccountRepository {
Future<model.Account> getMain() async { Future<model.Account> getMain() async {
final row = await (_db.select(_db.accounts) final row = await (_db.select(_db.accounts)
..where((a) => a.isMain.equals(true))) ..where((a) => a.isMain.equals(true)))
.getSingle(); .getSingleOrNull();
if (row != null) {
return model.Account(
id: row.id,
name: row.name,
isMain: row.isMain,
sortOrder: row.sortOrder,
currency: row.currency,
createdAt: row.createdAt,
);
}
// Fallback if no main account is found
final all = await getAll();
if (all.isNotEmpty) return all.first;
// Absolute fallback: create and return a default account
try {
await _db.into(_db.accounts).insert(
AccountsCompanion.insert(
name: 'main',
isMain: const Value(true),
currency: const Value('USD'),
sortOrder: const Value(0),
),
);
// Query the newly created account
final newRow = await (_db.select(_db.accounts)
..where((a) => a.isMain.equals(true)))
.getSingleOrNull();
if (newRow != null) {
return model.Account(
id: newRow.id,
name: newRow.name,
isMain: newRow.isMain,
sortOrder: newRow.sortOrder,
currency: newRow.currency,
createdAt: newRow.createdAt,
);
}
} catch (_) {
// Ignore insert errors
}
// Final fallback to prevent crashes
return model.Account( return model.Account(
id: row.id, id: 1,
name: row.name, name: 'main',
isMain: row.isMain, isMain: true,
sortOrder: row.sortOrder, sortOrder: 0,
currency: row.currency, currency: 'USD',
createdAt: row.createdAt, createdAt: DateTime.now(),
); );
} }
} }
@@ -56,12 +56,24 @@ class TransactionRepository {
/// Add transaction /// Add transaction
Future<Result<void>> add(model.Transaction transaction) async { Future<Result<void>> add(model.Transaction transaction) async {
return asyncResultOf(() async { return asyncResultOf(() async {
print('--- SAVING TRANSACTION: START ---');
print('Transaction data: ID=${transaction.id}, Amount=${transaction.amount}, AccId=${transaction.accountId}');
print('Category=${transaction.category}, Type=${transaction.type.name}');
print('Date=${transaction.date}, Currency=${transaction.currencyCode}');
final companion = _toCompanion(transaction); final companion = _toCompanion(transaction);
print('Companion created successfully');
print('Companion: $companion');
final result = await _db.insertTransaction(companion); final result = await _db.insertTransaction(companion);
print('DB Insert finished. Result Success: ${result.isSuccess}');
if (result.isFailure) { if (result.isFailure) {
print('!!! DB INSERT FAILED: ${result.errorOrNull}');
throw Exception(result.errorOrNull); throw Exception(result.errorOrNull);
} }
print('--- SAVING TRANSACTION: END (SUCCESS) ---');
}); });
} }
@@ -173,19 +185,29 @@ class TransactionRepository {
/// Convert app model to companion for insert /// Convert app model to companion for insert
TransactionsCompanion _toCompanion(model.Transaction transaction) { TransactionsCompanion _toCompanion(model.Transaction transaction) {
return TransactionsCompanion( try {
id: Value(transaction.id), print('_toCompanion: Creating companion for transaction ${transaction.id}');
amount: Value(transaction.amount), final companion = TransactionsCompanion(
category: Value(transaction.category), id: Value(transaction.id),
type: Value(transaction.type.name), amount: Value(transaction.amount),
date: Value(transaction.date), category: Value(transaction.category),
note: Value(transaction.note), type: Value(transaction.type.name),
recurrence: Value(transaction.recurrence.name), date: Value(transaction.date),
lastOccurrence: Value(transaction.lastOccurrence), note: Value(transaction.note),
currency: Value(transaction.currency), recurrence: Value(transaction.recurrence.name),
currencyCode: Value(transaction.currencyCode), lastOccurrence: Value(transaction.lastOccurrence),
accountId: Value(transaction.accountId), currency: Value(transaction.currency),
); currencyCode: Value(transaction.currencyCode),
accountId: Value(transaction.accountId),
);
print('_toCompanion: Companion created successfully');
return companion;
} catch (e, stack) {
print('!!! _toCompanion FAILED !!!');
print('Error: $e');
print('Stack: $stack');
rethrow;
}
} }
/// Parse recurrence type /// Parse recurrence type
+33 -6
View File
@@ -154,14 +154,19 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
: _noteController.text.trim(); : _noteController.text.trim();
try { try {
// Get account ID: use active account or fallback to main print('--- SUBMIT CLICKED ---');
print('Amount: $amount, Category: ${state.category}, Type: ${state.type.name}');
final activeAccount = ref.read(activeAccountProvider); final activeAccount = ref.read(activeAccountProvider);
int accountId; int accountId;
if (activeAccount != null) { if (activeAccount != null) {
print('Using active account ID: ${activeAccount.id}, Name: ${activeAccount.name}');
accountId = activeAccount.id; accountId = activeAccount.id;
} else { } else {
print('No active account. Fetching main account...');
final mainAccount = await ref.read(accountRepositoryProvider).getMain(); final mainAccount = await ref.read(accountRepositoryProvider).getMain();
print('Main account fetched: ID=${mainAccount.id}, Name: ${mainAccount.name}');
accountId = mainAccount.id; accountId = mainAccount.id;
} }
@@ -177,20 +182,42 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
accountId: accountId, accountId: accountId,
); );
print('Transaction object created: ID=${tx.id}, AccId=${tx.accountId}');
print('Calling provider to save...');
if (state.isEditing) { if (state.isEditing) {
await ref.read(transactionsProvider.notifier).update(tx); await ref.read(transactionsProvider.notifier).update(tx);
print('Update completed');
} else { } else {
await ref.read(transactionsProvider.notifier).add(tx); final res = await ref.read(transactionsProvider.notifier).add(tx);
print('Add completed. Result: ${res.isSuccess ? "SUCCESS" : "FAILURE"}');
if (res.isFailure) {
print('!!! Provider returned failure: ${res.errorOrNull}');
throw Exception(res.errorOrNull);
}
} }
print('Provider save completed successfully');
HapticService.medium(); HapticService.medium();
if (mounted) context.pop(); if (mounted) {
} catch (e) { print('Popping screen...');
// Handle error silently or show a snackbar context.pop();
}
} catch (e, stack) {
print('!!! SAVE CRASHED !!!');
print('Error: $e');
print('Stack trace:');
print(stack);
if (mounted) { if (mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error saving transaction: $e')), SnackBar(
content: Text('Save error: $e'),
backgroundColor: Colors.red,
duration: const Duration(seconds: 5),
),
); );
} }
} finally { } finally {