From 8f03af4b0a932b26678c18c24addd2035f16efc9 Mon Sep 17 00:00:00 2001 From: kolo Date: Mon, 23 Mar 2026 12:17:04 +0300 Subject: [PATCH] update --- lib/data/database/app_database.dart | 35 +++++++++- lib/data/database/app_database.g.dart | 14 ++-- lib/data/database/tables.dart | 2 +- lib/data/repositories/account_repository.dart | 64 ++++++++++++++++--- .../repositories/transaction_repository.dart | 48 ++++++++++---- lib/features/add_transaction/screen.dart | 39 +++++++++-- 6 files changed, 164 insertions(+), 38 deletions(-) diff --git a/lib/data/database/app_database.dart b/lib/data/database/app_database.dart index 137362f..3b7caf1 100644 --- a/lib/data/database/app_database.dart +++ b/lib/data/database/app_database.dart @@ -13,12 +13,15 @@ class AppDatabase extends _$AppDatabase { AppDatabase() : super(_openConnection()); @override - int get schemaVersion => 4; + int get schemaVersion => 5; @override MigrationStrategy get migration => MigrationStrategy( onUpgrade: (migrator, from, to) async { + print('--- DATABASE MIGRATION: from=$from to=$to ---'); + if (from == 1) { + print('Migration: Creating accounts table'); await migrator.createTable(accounts); await customStatement( '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], ); } + if (from == 2) { + print('Migration: Adding currency column to accounts'); await customStatement( '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 ---'); }, ); diff --git a/lib/data/database/app_database.g.dart b/lib/data/database/app_database.g.dart index c8b00ea..d40031a 100644 --- a/lib/data/database/app_database.g.dart +++ b/lib/data/database/app_database.g.dart @@ -122,7 +122,8 @@ class $TransactionsTable extends Transactions aliasedName, false, type: DriftSqlType.int, - requiredDuringInsert: true, + requiredDuringInsert: false, + defaultValue: const Constant(1), ); static const VerificationMeta _createdAtMeta = const VerificationMeta( 'createdAt', @@ -241,8 +242,6 @@ class $TransactionsTable extends Transactions _accountIdMeta, accountId.isAcceptableOrUnknown(data['account_id']!, _accountIdMeta), ); - } else if (isInserting) { - context.missing(_accountIdMeta); } if (data.containsKey('created_at')) { context.handle( @@ -567,15 +566,14 @@ class TransactionsCompanion extends UpdateCompanion { this.lastOccurrence = const Value.absent(), this.currency = const Value.absent(), this.currencyCode = const Value.absent(), - required int accountId, + this.accountId = const Value.absent(), this.createdAt = const Value.absent(), this.rowid = const Value.absent(), }) : id = Value(id), amount = Value(amount), category = Value(category), type = Value(type), - date = Value(date), - accountId = Value(accountId); + date = Value(date); static Insertable custom({ Expression? id, Expression? amount, @@ -2324,7 +2322,7 @@ typedef $$TransactionsTableCreateCompanionBuilder = Value lastOccurrence, Value currency, Value currencyCode, - required int accountId, + Value accountId, Value createdAt, Value rowid, }); @@ -2608,7 +2606,7 @@ class $$TransactionsTableTableManager Value lastOccurrence = const Value.absent(), Value currency = const Value.absent(), Value currencyCode = const Value.absent(), - required int accountId, + Value accountId = const Value.absent(), Value createdAt = const Value.absent(), Value rowid = const Value.absent(), }) => TransactionsCompanion.insert( diff --git a/lib/data/database/tables.dart b/lib/data/database/tables.dart index f6fd03e..0e5c151 100644 --- a/lib/data/database/tables.dart +++ b/lib/data/database/tables.dart @@ -12,7 +12,7 @@ class Transactions extends Table { DateTimeColumn get lastOccurrence => dateTime().nullable()(); TextColumn get currency => text().withDefault(const Constant('\$'))(); 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)(); @override diff --git a/lib/data/repositories/account_repository.dart b/lib/data/repositories/account_repository.dart index 620beea..5c456e9 100644 --- a/lib/data/repositories/account_repository.dart +++ b/lib/data/repositories/account_repository.dart @@ -24,7 +24,7 @@ class AccountRepository { // Fallback: insert default account if none exists await _db.into(_db.accounts).insert( AccountsCompanion.insert( - name: 'Main', + name: 'main', isMain: const Value(true), currency: const Value('USD'), sortOrder: const Value(0), @@ -69,7 +69,7 @@ class AccountRepository { try { await _db.into(_db.accounts).insert( AccountsCompanion.insert( - name: 'Main', + name: 'main', isMain: const Value(true), currency: const Value('USD'), sortOrder: const Value(0), @@ -103,15 +103,61 @@ class AccountRepository { Future getMain() async { final row = await (_db.select(_db.accounts) ..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( - id: row.id, - name: row.name, - isMain: row.isMain, - sortOrder: row.sortOrder, - currency: row.currency, - createdAt: row.createdAt, + id: 1, + name: 'main', + isMain: true, + sortOrder: 0, + currency: 'USD', + createdAt: DateTime.now(), ); } } diff --git a/lib/data/repositories/transaction_repository.dart b/lib/data/repositories/transaction_repository.dart index a6ea7c3..0bd6b5e 100644 --- a/lib/data/repositories/transaction_repository.dart +++ b/lib/data/repositories/transaction_repository.dart @@ -56,12 +56,24 @@ class TransactionRepository { /// Add transaction Future> add(model.Transaction transaction) 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); + print('Companion created successfully'); + print('Companion: $companion'); + final result = await _db.insertTransaction(companion); + print('DB Insert finished. Result Success: ${result.isSuccess}'); if (result.isFailure) { + print('!!! DB INSERT FAILED: ${result.errorOrNull}'); throw Exception(result.errorOrNull); } + + print('--- SAVING TRANSACTION: END (SUCCESS) ---'); }); } @@ -173,19 +185,29 @@ class TransactionRepository { /// Convert app model to companion for insert TransactionsCompanion _toCompanion(model.Transaction transaction) { - return TransactionsCompanion( - id: Value(transaction.id), - amount: Value(transaction.amount), - category: Value(transaction.category), - type: Value(transaction.type.name), - date: Value(transaction.date), - note: Value(transaction.note), - recurrence: Value(transaction.recurrence.name), - lastOccurrence: Value(transaction.lastOccurrence), - currency: Value(transaction.currency), - currencyCode: Value(transaction.currencyCode), - accountId: Value(transaction.accountId), - ); + try { + print('_toCompanion: Creating companion for transaction ${transaction.id}'); + final companion = TransactionsCompanion( + id: Value(transaction.id), + amount: Value(transaction.amount), + category: Value(transaction.category), + type: Value(transaction.type.name), + date: Value(transaction.date), + note: Value(transaction.note), + recurrence: Value(transaction.recurrence.name), + lastOccurrence: Value(transaction.lastOccurrence), + 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 diff --git a/lib/features/add_transaction/screen.dart b/lib/features/add_transaction/screen.dart index 4bf5813..3d0b59e 100644 --- a/lib/features/add_transaction/screen.dart +++ b/lib/features/add_transaction/screen.dart @@ -154,14 +154,19 @@ class _AddTransactionScreenState extends ConsumerState : _noteController.text.trim(); 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); int accountId; if (activeAccount != null) { + print('Using active account ID: ${activeAccount.id}, Name: ${activeAccount.name}'); accountId = activeAccount.id; } else { + print('No active account. Fetching main account...'); final mainAccount = await ref.read(accountRepositoryProvider).getMain(); + print('Main account fetched: ID=${mainAccount.id}, Name: ${mainAccount.name}'); accountId = mainAccount.id; } @@ -176,21 +181,43 @@ class _AddTransactionScreenState extends ConsumerState currencyCode: state.overrideCurrencyCode, accountId: accountId, ); + + print('Transaction object created: ID=${tx.id}, AccId=${tx.accountId}'); + print('Calling provider to save...'); if (state.isEditing) { await ref.read(transactionsProvider.notifier).update(tx); + print('Update completed'); } 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(); - if (mounted) context.pop(); - } catch (e) { - // Handle error silently or show a snackbar + if (mounted) { + print('Popping screen...'); + context.pop(); + } + } catch (e, stack) { + print('!!! SAVE CRASHED !!!'); + print('Error: $e'); + print('Stack trace:'); + print(stack); + if (mounted) { 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 {