From b9b70ad6eee8f4e63ee0e74b8f1c0806e47dbd7f Mon Sep 17 00:00:00 2001 From: "Sanders (aider)" Date: Sat, 3 May 2025 16:25:03 +0300 Subject: [PATCH] fix: restore database definition code --- finance_app/lib/database/database.dart | 144 ++++++++++++++++++++++++- 1 file changed, 143 insertions(+), 1 deletion(-) diff --git a/finance_app/lib/database/database.dart b/finance_app/lib/database/database.dart index af34da3..d3ed268 100644 --- a/finance_app/lib/database/database.dart +++ b/finance_app/lib/database/database.dart @@ -1 +1,143 @@ -flutter pub run build_runner build --delete-conflicting-outputs +import 'dart:io'; + +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; +// import 'package:flutter/material.dart'; // Не требуется напрямую, т.к. Category импортируется +import 'package:path_provider/path_provider.dart'; +import 'package:path/path.dart' as p; +// import 'package:rxdart/rxdart.dart'; // Не используется + +// Импортируем модель категории для возвращаемого типа и утилиты +import '../models/category.dart'; +// import '../models/transaction.dart'; // Удалено: Используется сгенерированный Drift класс Transaction +import '../utils/category_utils.dart'; // Helper for category details + +// Эта строка указывает Drift сгенерировать файл database.g.dart +part 'database.g.dart'; + +// Определение таблицы Transactions +// Имя таблицы в SQL будет 'transactions' (snake_case от имени класса) +@DataClassName('Transaction') // Keep the generated class name as Transaction +class Transactions extends Table { + IntColumn get id => integer().autoIncrement()(); // Primary key + TextColumn get categoryName => text().named('category_name')(); // Имя категории + RealColumn get amount => real()(); // Сумма транзакции + DateTimeColumn get date => dateTime()(); // Дата транзакции + TextColumn get merchant => text()(); // Название продавца/магазина +} + +// Класс базы данных +// Аннотация @DriftDatabase указывает Drift сгенерировать код для этой базы данных +@DriftDatabase(tables: [Transactions]) +class AppDatabase extends _$AppDatabase { + AppDatabase() : super(_openConnection()); + + // Версия схемы. Увеличивайте при изменении структуры таблиц. + @override + int get schemaVersion => 1; + + // --- Методы для работы с транзакциями --- + + // Получить все транзакции в виде потока, упорядоченные по дате (сначала новые) + // Возвращает non-nullable Stream + Stream> watchAllTransactions() { + return (select(transactions) + ..orderBy([(t) => OrderingTerm(expression: t.date, mode: OrderingMode.desc)])) + .watch(); + } + + // Получить транзакции, отфильтрованные по категории, в виде потока + // Возвращает non-nullable Stream + Stream> watchFilteredTransactions(String categoryName) { + if (categoryName == 'All') { + // watchAllTransactions теперь возвращает non-nullable Stream + return watchAllTransactions(); // Return all if filter is 'All' + } + // Otherwise, filter by the provided category name + return (select(transactions) + ..where((t) => t.categoryName.equals(categoryName)) + ..orderBy([(t) => OrderingTerm(expression: t.date, mode: OrderingMode.desc)])) + .watch(); + } + + // Добавить новую транзакцию + Future addTransaction(TransactionsCompanion entry) { + return into(transactions).insert(entry); + } + + // --- Методы для работы с категориями (вычисляются из транзакций) --- + + // Вычислить и наблюдать за общими суммами по категориям + Stream> calculateCategoryTotals() { + // 1. Получаем поток всех транзакций (теперь non-nullable) + return watchAllTransactions().map((transactionList) { + // 2. Группируем транзакции по categoryName и суммируем amount + final categoryTotals = {}; + for (var transaction in transactionList) { + categoryTotals.update( + transaction.categoryName, + (value) => value + transaction.amount, + ifAbsent: () => transaction.amount, + ); + } + + // 3. Преобразуем сгруппированные данные в список объектов Category + return categoryTotals.entries.map((entry) { + // Используем CategoryUtils для получения деталей (иконка, цвет) + // Убедитесь, что CategoryUtils корректно обрабатывает все categoryName, + // включая 'Utilities' из начальных данных, или предоставляет дефолтные значения. + final categoryDetails = CategoryUtils.getCategoryDetails(entry.key); + return Category( + entry.key, // name + entry.value, // amount + categoryDetails.color, + categoryDetails.icon, + ); + }).toList() + // Сортируем категории по сумме (от большей к меньшей) для диаграммы/легенды + ..sort((a, b) => b.amount.compareTo(a.amount)); + }); + } + + // Пример добавления начальных данных (если база данных пуста) + Future insertInitialDataIfNeeded() async { + // Проверяем, есть ли уже транзакции + final existingTransactions = await select(transactions).get(); + if (existingTransactions.isEmpty) { + print("Database is empty. Inserting initial data..."); + // Используем batch для эффективной вставки нескольких записей + await batch((batch) { + batch.insertAll(transactions, [ + // Используем TransactionsCompanion для создания записей для вставки + TransactionsCompanion.insert(categoryName: 'Groceries', amount: 45.99, date: DateTime.now().subtract(const Duration(days: 1, hours: 2)), merchant: 'Whole Foods Market'), + TransactionsCompanion.insert(categoryName: 'Subscriptions', amount: 39.99, date: DateTime.now().subtract(const Duration(days: 2, hours: 5)), merchant: 'Netflix Premium'), + TransactionsCompanion.insert(categoryName: 'Restaurant', amount: 78.50, date: DateTime.now().subtract(const Duration(days: 2, hours: 19)), merchant: 'Italian Corner'), + TransactionsCompanion.insert(categoryName: 'Shopping', amount: 132.75, date: DateTime.now().subtract(const Duration(days: 3, hours: 11)), merchant: 'Apple Store'), + TransactionsCompanion.insert(categoryName: 'Groceries', amount: 23.45, date: DateTime.now().subtract(const Duration(days: 4, hours: 9)), merchant: 'Local Market'), + TransactionsCompanion.insert(categoryName: 'Transport', amount: 15.00, date: DateTime.now().subtract(const Duration(days: 5, hours: 8)), merchant: 'City Bus'), + TransactionsCompanion.insert(categoryName: 'Restaurant', amount: 56.80, date: DateTime.now().subtract(const Duration(days: 5, hours: 20)), merchant: 'Sushi Express'), + // Убедитесь, что 'Utilities' определена в CategoryUtils или обрабатывается как неизвестная категория + TransactionsCompanion.insert(categoryName: 'Utilities', amount: 85.20, date: DateTime.now().subtract(const Duration(days: 6, hours: 10)), merchant: 'Electricity Bill'), + ]); + }); + print("Initial data inserted successfully."); + } else { + print("Database already contains data (${existingTransactions.length} transactions). Skipping initial data insertion."); + } + } +} + +// Функция для открытия соединения с базой данных +LazyDatabase _openConnection() { + // Вычисление пути к файлу базы данных в папке документов приложения + return LazyDatabase(() async { + final dbFolder = await getApplicationDocumentsDirectory(); + // Создаем файл 'db.sqlite' в этой папке + final file = File(p.join(dbFolder.path, 'db.sqlite')); + print("Database file path: ${file.path}"); // Логируем путь для отладки + + // Используем NativeDatabase для открытия соединения + // logStatements: true полезен для отладки SQL-запросов + return NativeDatabase(file, logStatements: false); + }); +}