mirror of
https://github.com/koloideal/Casha.git
synced 2026-06-10 10:25:28 +03:00
stableee
This commit is contained in:
@@ -18,10 +18,7 @@ class AppDatabase extends _$AppDatabase {
|
||||
@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) '
|
||||
@@ -31,17 +28,13 @@ class AppDatabase extends _$AppDatabase {
|
||||
}
|
||||
|
||||
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();
|
||||
@@ -49,36 +42,23 @@ class AppDatabase extends _$AppDatabase {
|
||||
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 ---');
|
||||
},
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// TRANSACTIONS
|
||||
// ============================================================================
|
||||
|
||||
/// Get all transactions ordered by date descending
|
||||
Future<List<dynamic>> getAllTransactions() {
|
||||
return (select(
|
||||
transactions,
|
||||
)..orderBy([(t) => OrderingTerm.desc(t.date)])).get();
|
||||
}
|
||||
|
||||
/// Get transactions by date range
|
||||
Future<List<dynamic>> getTransactionsByDateRange(
|
||||
DateTime start,
|
||||
DateTime end,
|
||||
@@ -90,7 +70,6 @@ class AppDatabase extends _$AppDatabase {
|
||||
.get();
|
||||
}
|
||||
|
||||
/// Get transactions by type
|
||||
Future<List<dynamic>> getTransactionsByType(String type) {
|
||||
return (select(transactions)
|
||||
..where((t) => t.type.equals(type))
|
||||
@@ -98,7 +77,6 @@ class AppDatabase extends _$AppDatabase {
|
||||
.get();
|
||||
}
|
||||
|
||||
/// Get transactions by category
|
||||
Future<List<dynamic>> getTransactionsByCategory(String category) {
|
||||
return (select(transactions)
|
||||
..where((t) => t.category.equals(category))
|
||||
@@ -106,7 +84,6 @@ class AppDatabase extends _$AppDatabase {
|
||||
.get();
|
||||
}
|
||||
|
||||
/// Search transactions by note or category
|
||||
Future<List<dynamic>> searchTransactions(String query) {
|
||||
final lowerQuery = query.toLowerCase();
|
||||
return (select(transactions)
|
||||
@@ -119,14 +96,12 @@ class AppDatabase extends _$AppDatabase {
|
||||
.get();
|
||||
}
|
||||
|
||||
/// Get transaction by ID
|
||||
Future<dynamic> getTransactionById(String id) {
|
||||
return (select(
|
||||
transactions,
|
||||
)..where((t) => t.id.equals(id))).getSingleOrNull();
|
||||
}
|
||||
|
||||
/// Insert transaction
|
||||
Future<Result<void>> insertTransaction(
|
||||
TransactionsCompanion transaction,
|
||||
) async {
|
||||
@@ -135,7 +110,6 @@ class AppDatabase extends _$AppDatabase {
|
||||
});
|
||||
}
|
||||
|
||||
/// Update transaction
|
||||
Future<Result<void>> updateTransaction(dynamic transaction) async {
|
||||
return asyncResultOf(() async {
|
||||
final companion = transaction as TransactionsCompanion;
|
||||
@@ -149,7 +123,6 @@ class AppDatabase extends _$AppDatabase {
|
||||
});
|
||||
}
|
||||
|
||||
/// Delete transaction
|
||||
Future<Result<void>> deleteTransaction(String id) async {
|
||||
return asyncResultOf(() async {
|
||||
final deleted = await (delete(
|
||||
@@ -162,90 +135,64 @@ class AppDatabase extends _$AppDatabase {
|
||||
});
|
||||
}
|
||||
|
||||
/// Delete all transactions
|
||||
Future<void> deleteAllTransactions() {
|
||||
return delete(transactions).go();
|
||||
}
|
||||
|
||||
/// Get recurring transactions that need processing
|
||||
Future<List<dynamic>> getRecurringTransactions() {
|
||||
return (select(
|
||||
transactions,
|
||||
)..where((t) => t.recurrence.equals('none').not())).get();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CATEGORIES
|
||||
// ============================================================================
|
||||
|
||||
/// Get all categories
|
||||
Future<List<Category>> getAllCategories() {
|
||||
return select(categories).get();
|
||||
}
|
||||
|
||||
/// Get categories by type
|
||||
Future<List<Category>> getCategoriesByType(String type) {
|
||||
return (select(categories)..where((c) => c.type.equals(type))).get();
|
||||
}
|
||||
|
||||
/// Insert category
|
||||
Future<int> insertCategory(CategoriesCompanion category) {
|
||||
return into(categories).insert(category);
|
||||
}
|
||||
|
||||
/// Update category
|
||||
Future<bool> updateCategory(Category category) {
|
||||
return update(categories).replace(category);
|
||||
}
|
||||
|
||||
/// Delete category
|
||||
Future<int> deleteCategory(int id) {
|
||||
return (delete(categories)..where((c) => c.id.equals(id))).go();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// BUDGETS
|
||||
// ============================================================================
|
||||
|
||||
/// Get budget for month/year
|
||||
Future<Budget?> getBudget(int month, int year) {
|
||||
return (select(budgets)
|
||||
..where((b) => b.month.equals(month) & b.year.equals(year)))
|
||||
.getSingleOrNull();
|
||||
}
|
||||
|
||||
/// Insert or update budget
|
||||
Future<void> upsertBudget(BudgetsCompanion budget) {
|
||||
return into(budgets).insertOnConflictUpdate(budget);
|
||||
}
|
||||
|
||||
/// Delete budget
|
||||
Future<int> deleteBudget(int id) {
|
||||
return (delete(budgets)..where((b) => b.id.equals(id))).go();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// EXCHANGE RATES
|
||||
// ============================================================================
|
||||
|
||||
/// Get exchange rate
|
||||
Future<ExchangeRate?> getExchangeRate(String from, String to) {
|
||||
return (select(exchangeRates)
|
||||
..where((r) => r.fromCurrency.equals(from) & r.toCurrency.equals(to)))
|
||||
.getSingleOrNull();
|
||||
}
|
||||
|
||||
/// Insert or update exchange rate
|
||||
Future<void> upsertExchangeRate(ExchangeRatesCompanion rate) {
|
||||
return into(exchangeRates).insertOnConflictUpdate(rate);
|
||||
}
|
||||
|
||||
/// Get all exchange rates
|
||||
Future<List<ExchangeRate>> getAllExchangeRates() {
|
||||
return select(exchangeRates).get();
|
||||
}
|
||||
|
||||
/// Delete old exchange rates (older than 24 hours)
|
||||
Future<int> deleteOldExchangeRates() {
|
||||
final yesterday = DateTime.now().subtract(const Duration(hours: 24));
|
||||
return (delete(
|
||||
@@ -253,11 +200,6 @@ class AppDatabase extends _$AppDatabase {
|
||||
)..where((r) => r.updatedAt.isSmallerThanValue(yesterday))).go();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// STATISTICS & AGGREGATIONS
|
||||
// ============================================================================
|
||||
|
||||
/// Get total balance
|
||||
Future<double> getTotalBalance() async {
|
||||
final txs = await getAllTransactions();
|
||||
return txs.fold<double>(0.0, (sum, tx) {
|
||||
@@ -265,7 +207,6 @@ class AppDatabase extends _$AppDatabase {
|
||||
});
|
||||
}
|
||||
|
||||
/// Get total income for date range
|
||||
Future<double> getTotalIncome(DateTime start, DateTime end) async {
|
||||
final txs = await getTransactionsByDateRange(start, end);
|
||||
return txs
|
||||
@@ -273,7 +214,6 @@ class AppDatabase extends _$AppDatabase {
|
||||
.fold<double>(0.0, (sum, tx) => sum + tx.amount);
|
||||
}
|
||||
|
||||
/// Get total expense for date range
|
||||
Future<double> getTotalExpense(DateTime start, DateTime end) async {
|
||||
final txs = await getTransactionsByDateRange(start, end);
|
||||
return txs
|
||||
@@ -281,7 +221,6 @@ class AppDatabase extends _$AppDatabase {
|
||||
.fold<double>(0.0, (sum, tx) => sum + tx.amount);
|
||||
}
|
||||
|
||||
/// Get category totals for date range
|
||||
Future<Map<String, double>> getCategoryTotals(
|
||||
DateTime start,
|
||||
DateTime end,
|
||||
@@ -298,11 +237,6 @@ class AppDatabase extends _$AppDatabase {
|
||||
return totals;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ACCOUNTS
|
||||
// ============================================================================
|
||||
|
||||
/// Update an account
|
||||
Future<void> updateAccount(AccountsCompanion account) async {
|
||||
await update(accounts).replace(account);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import 'package:drift/drift.dart';
|
||||
|
||||
/// Transactions table
|
||||
class Transactions extends Table {
|
||||
TextColumn get id => text()();
|
||||
RealColumn get amount => real()();
|
||||
TextColumn get category => text()();
|
||||
TextColumn get type => text()(); // 'income' or 'expense'
|
||||
TextColumn get type => text()();
|
||||
DateTimeColumn get date => dateTime()();
|
||||
TextColumn get note => text().nullable()();
|
||||
TextColumn get recurrence => text().withDefault(const Constant('none'))();
|
||||
@@ -19,18 +18,16 @@ class Transactions extends Table {
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
|
||||
/// Categories table for custom categories
|
||||
class Categories extends Table {
|
||||
IntColumn get id => integer().autoIncrement()();
|
||||
TextColumn get name => text().withLength(min: 1, max: 50)();
|
||||
TextColumn get type => text()(); // 'income' or 'expense'
|
||||
TextColumn get type => text()();
|
||||
TextColumn get icon => text().nullable()();
|
||||
TextColumn get color => text().nullable()();
|
||||
BoolColumn get isDefault => boolean().withDefault(const Constant(false))();
|
||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||
}
|
||||
|
||||
/// Budgets table for monthly budgets
|
||||
class Budgets extends Table {
|
||||
IntColumn get id => integer().autoIncrement()();
|
||||
RealColumn get amount => real()();
|
||||
@@ -40,7 +37,6 @@ class Budgets extends Table {
|
||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||
}
|
||||
|
||||
/// Exchange rates cache
|
||||
class ExchangeRates extends Table {
|
||||
IntColumn get id => integer().autoIncrement()();
|
||||
TextColumn get fromCurrency => text()();
|
||||
@@ -49,7 +45,6 @@ class ExchangeRates extends Table {
|
||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
|
||||
}
|
||||
|
||||
/// Accounts table for multi-account support
|
||||
class Accounts extends Table {
|
||||
IntColumn get id => integer().autoIncrement()();
|
||||
TextColumn get name => text()();
|
||||
|
||||
@@ -21,7 +21,6 @@ class AccountRepository {
|
||||
.watch()
|
||||
.asyncMap((rows) async {
|
||||
if (rows.isEmpty) {
|
||||
// Fallback: insert default account if none exists
|
||||
await _db.into(_db.accounts).insert(
|
||||
AccountsCompanion.insert(
|
||||
name: 'main',
|
||||
@@ -30,7 +29,6 @@ class AccountRepository {
|
||||
sortOrder: const Value(0),
|
||||
),
|
||||
);
|
||||
// Re-query after insert
|
||||
final newRows = await (_db.select(_db.accounts)
|
||||
..orderBy([(a) => OrderingTerm.asc(a.sortOrder)]))
|
||||
.get();
|
||||
@@ -63,8 +61,6 @@ class AccountRepository {
|
||||
var rows = await (_db.select(_db.accounts)
|
||||
..orderBy([(a) => OrderingTerm.asc(a.sortOrder)]))
|
||||
.get();
|
||||
|
||||
// Fallback: insert default account if none exists
|
||||
if (rows.isEmpty) {
|
||||
try {
|
||||
await _db.into(_db.accounts).insert(
|
||||
@@ -78,7 +74,6 @@ class AccountRepository {
|
||||
} catch (e) {
|
||||
// Ignore if already exists
|
||||
}
|
||||
// Re-query after insert
|
||||
rows = await (_db.select(_db.accounts)
|
||||
..orderBy([(a) => OrderingTerm.asc(a.sortOrder)]))
|
||||
.get();
|
||||
@@ -95,7 +90,6 @@ class AccountRepository {
|
||||
))
|
||||
.toList();
|
||||
} catch (e) {
|
||||
// Return empty list on error
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -116,11 +110,9 @@ class AccountRepository {
|
||||
);
|
||||
}
|
||||
|
||||
// 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(
|
||||
@@ -131,7 +123,6 @@ class AccountRepository {
|
||||
),
|
||||
);
|
||||
|
||||
// Query the newly created account
|
||||
final newRow = await (_db.select(_db.accounts)
|
||||
..where((a) => a.isMain.equals(true)))
|
||||
.getSingleOrNull();
|
||||
@@ -150,7 +141,6 @@ class AccountRepository {
|
||||
// Ignore insert errors
|
||||
}
|
||||
|
||||
// Final fallback to prevent crashes
|
||||
return model.Account(
|
||||
id: 1,
|
||||
name: 'main',
|
||||
|
||||
@@ -8,7 +8,6 @@ class TransactionRepository {
|
||||
|
||||
TransactionRepository(this._db);
|
||||
|
||||
/// Get all transactions
|
||||
Future<Result<List<model.Transaction>>> getAll() async {
|
||||
return asyncResultOf(() async {
|
||||
final transactions = await _db.getAllTransactions();
|
||||
@@ -16,7 +15,6 @@ class TransactionRepository {
|
||||
});
|
||||
}
|
||||
|
||||
/// Get transactions by date range
|
||||
Future<Result<List<model.Transaction>>> getByDateRange(
|
||||
DateTime start,
|
||||
DateTime end,
|
||||
@@ -27,7 +25,6 @@ class TransactionRepository {
|
||||
});
|
||||
}
|
||||
|
||||
/// Get transactions by type
|
||||
Future<Result<List<model.Transaction>>> getByType(
|
||||
model.TransactionType type,
|
||||
) async {
|
||||
@@ -37,7 +34,6 @@ class TransactionRepository {
|
||||
});
|
||||
}
|
||||
|
||||
/// Search transactions
|
||||
Future<Result<List<model.Transaction>>> search(String query) async {
|
||||
return asyncResultOf(() async {
|
||||
final transactions = await _db.searchTransactions(query);
|
||||
@@ -45,7 +41,6 @@ class TransactionRepository {
|
||||
});
|
||||
}
|
||||
|
||||
/// Get transaction by ID
|
||||
Future<Result<model.Transaction?>> getById(String id) async {
|
||||
return asyncResultOf(() async {
|
||||
final transaction = await _db.getTransactionById(id);
|
||||
@@ -53,31 +48,17 @@ class TransactionRepository {
|
||||
});
|
||||
}
|
||||
|
||||
/// Add transaction
|
||||
Future<Result<void>> 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) ---');
|
||||
});
|
||||
}
|
||||
|
||||
/// Update transaction
|
||||
Future<Result<void>> update(model.Transaction transaction) async {
|
||||
return asyncResultOf(() async {
|
||||
final dbTransaction = _toDbModel(transaction);
|
||||
@@ -89,19 +70,16 @@ class TransactionRepository {
|
||||
});
|
||||
}
|
||||
|
||||
/// Delete transaction
|
||||
Future<Result<void>> delete(String id) async {
|
||||
return _db.deleteTransaction(id);
|
||||
}
|
||||
|
||||
/// Delete all transactions
|
||||
Future<Result<void>> deleteAll() async {
|
||||
return asyncResultOf(() async {
|
||||
await _db.deleteAllTransactions();
|
||||
});
|
||||
}
|
||||
|
||||
/// Get recurring transactions
|
||||
Future<Result<List<model.Transaction>>> getRecurring() async {
|
||||
return asyncResultOf(() async {
|
||||
final transactions = await _db.getRecurringTransactions();
|
||||
@@ -109,28 +87,24 @@ class TransactionRepository {
|
||||
});
|
||||
}
|
||||
|
||||
/// Get total balance
|
||||
Future<Result<double>> getTotalBalance() async {
|
||||
return asyncResultOf(() async {
|
||||
return await _db.getTotalBalance();
|
||||
});
|
||||
}
|
||||
|
||||
/// Get total income for date range
|
||||
Future<Result<double>> getTotalIncome(DateTime start, DateTime end) async {
|
||||
return asyncResultOf(() async {
|
||||
return await _db.getTotalIncome(start, end);
|
||||
});
|
||||
}
|
||||
|
||||
/// Get total expense for date range
|
||||
Future<Result<double>> getTotalExpense(DateTime start, DateTime end) async {
|
||||
return asyncResultOf(() async {
|
||||
return await _db.getTotalExpense(start, end);
|
||||
});
|
||||
}
|
||||
|
||||
/// Get category totals
|
||||
Future<Result<Map<String, double>>> getCategoryTotals(
|
||||
DateTime start,
|
||||
DateTime end,
|
||||
@@ -141,11 +115,6 @@ class TransactionRepository {
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CONVERTERS
|
||||
// ============================================================================
|
||||
|
||||
/// Convert database model to app model
|
||||
model.Transaction _toModel(dynamic dbTransaction) {
|
||||
return model.Transaction(
|
||||
id: dbTransaction.id as String,
|
||||
@@ -164,9 +133,7 @@ class TransactionRepository {
|
||||
);
|
||||
}
|
||||
|
||||
/// Convert app model to database model
|
||||
dynamic _toDbModel(model.Transaction transaction) {
|
||||
// This will be replaced with proper TransactionData after code generation
|
||||
return TransactionsCompanion(
|
||||
id: Value(transaction.id),
|
||||
amount: Value(transaction.amount),
|
||||
@@ -183,10 +150,8 @@ class TransactionRepository {
|
||||
);
|
||||
}
|
||||
|
||||
/// Convert app model to companion for insert
|
||||
TransactionsCompanion _toCompanion(model.Transaction transaction) {
|
||||
try {
|
||||
print('_toCompanion: Creating companion for transaction ${transaction.id}');
|
||||
final companion = TransactionsCompanion(
|
||||
id: Value(transaction.id),
|
||||
amount: Value(transaction.amount),
|
||||
@@ -200,17 +165,12 @@ class TransactionRepository {
|
||||
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');
|
||||
} catch (e, _) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse recurrence type
|
||||
model.RecurrenceType _parseRecurrence(String recurrence) {
|
||||
return model.RecurrenceType.values.firstWhere(
|
||||
(e) => e.name == recurrence,
|
||||
|
||||
Reference in New Issue
Block a user