feat: add category update and delete methods

This commit is contained in:
2025-05-05 14:47:42 +03:00
parent 28a7bd6ea7
commit 4fee4b7eda
+120 -10
View File
@@ -158,9 +158,102 @@ class AppDatabase extends _$AppDatabase {
assert(entry.name.present && entry.name.value.isNotEmpty);
assert(entry.icon.present); // Icon can be empty string if needed
assert(entry.color.present);
// Не позволяем добавить категорию с именем 'Income' (case-insensitive)
assert(entry.name.value.toLowerCase() != 'income');
return into(categories).insert(entry);
}
// Обновить существующую категорию
// Принимает CategoriesCompanion, который должен содержать ID
// Возвращает true, если обновление прошло успешно
Future<bool> updateCategory(CategoriesCompanion entry) {
// Проверяем, что ID предоставлен для обновления
assert(entry.id.present);
// Проверяем, что имя не 'Income' (case-insensitive)
if (entry.name.present && entry.name.value.toLowerCase() == 'income') {
print("Error: Cannot rename category to 'Income'.");
return Future.value(false); // Запрещаем переименование в 'Income'
}
// Используем транзакцию для проверки и обновления
return transaction(() async {
// Находим категорию по ID перед обновлением
final existingCategory = await (select(categories)..where((c) => c.id.equals(entry.id.value))).getSingleOrNull();
// Проверяем, существует ли категория и не является ли она 'Income'
if (existingCategory == null) {
print("Error: Category with ID ${entry.id.value} not found for update.");
return false; // Категория не найдена
}
if (existingCategory.name == 'Income') {
print("Error: Cannot update the 'Income' category.");
return false; // Запрещаем обновление категории 'Income'
}
// Выполняем обновление
// Метод replace возвращает true, если запись была обновлена
final updated = await update(categories).replace(entry);
// Если имя категории было изменено, нужно обновить categoryName во всех связанных транзакциях
if (updated && entry.name.present && entry.name.value != existingCategory.name) {
print("Category name changed from '${existingCategory.name}' to '${entry.name.value}'. Updating transactions...");
final updatedTransactions = await (update(transactions)
..where((t) => t.categoryName.equals(existingCategory.name)))
.write(TransactionsCompanion(
categoryName: Value(entry.name.value),
));
print("Updated $updatedTransactions transactions with the new category name.");
}
return updated; // Возвращаем результат replace
});
}
// Удалить категорию по ID
// Возвращает количество удаленных строк (0 или 1)
Future<int> deleteCategory(int id) {
// Используем транзакцию для проверок и удаления
return transaction(() async {
// Находим категорию по ID перед удалением
final categoryToDelete = await (select(categories)..where((c) => c.id.equals(id))).getSingleOrNull();
// Проверяем, существует ли категория
if (categoryToDelete == null) {
print("Error: Category with ID $id not found for deletion.");
return 0; // Категория не найдена
}
// Запрещаем удаление категории 'Income'
if (categoryToDelete.name == 'Income') {
print("Error: Cannot delete the 'Income' category.");
return 0; // Возвращаем 0, т.к. ничего не удалено
}
// Проверяем, есть ли транзакции с этой категорией
// Используем имя категории для связи (т.к. нет внешнего ключа)
final query = selectOnly(transactions)
..addColumns([transactions.id.count()])
..where(transactions.categoryName.equals(categoryToDelete.name)); // Используем имя для связи
final result = await query.getSingleOrNull();
final transactionCount = result?.read(transactions.id.count()) ?? 0;
if (transactionCount > 0) {
print("Error: Cannot delete category '${categoryToDelete.name}' because it has $transactionCount associated transaction(s).");
// Можно выбросить исключение или вернуть 0, чтобы показать неудачу
// throw Exception("Cannot delete category with transactions.");
return 0; // Не удаляем, возвращаем 0
}
// Если транзакций нет, удаляем категорию
print("Deleting category '${categoryToDelete.name}' (ID: $id)...");
final deletedRows = await (delete(categories)..where((c) => c.id.equals(id))).go();
print("Deleted $deletedRows category row(s).");
return deletedRows; // Возвращаем количество удаленных строк
});
}
// Получить категорию по ID (если нужно)
Future<CategoryDb?> getCategoryById(int id) {
return (select(categories)..where((c) => c.id.equals(id))).getSingleOrNull();
@@ -171,8 +264,20 @@ class AppDatabase extends _$AppDatabase {
// Вычислить и наблюдать за общими суммами по категориям РАСХОДОВ
// Возвращает Stream<List<Category>> где Category - это класс модели из '../models/category.dart'
Stream<List<Category>> calculateCategoryTotals() {
// 1. Получаем поток всех транзакций
return watchAllTransactions().map((transactionList) {
// 1. Создаем поток, который объединяет транзакции и категории
final transactionsStream = watchAllTransactions();
final categoriesStream = watchAllCategoriesDb();
// Используем StreamZip для объединения последних данных из обоих потоков
return StreamZip([transactionsStream, categoriesStream]).map((data) {
final transactionList = data[0] as List<Transaction>;
final categoryList = data[1] as List<CategoryDb>;
// Создаем Map для быстрого доступа к деталям категории по имени
final categoryDetailsMap = {
for (var cat in categoryList) cat.name: cat
};
// 2. Фильтруем только расходы и группируем по categoryName, суммируя amount
final categoryTotals = <String, double>{};
for (var transaction in transactionList) {
@@ -186,16 +291,20 @@ class AppDatabase extends _$AppDatabase {
}
}
// 3. Преобразуем сгруппированные данные в список объектов Category (модель)
// 3. Преобразуем сгруппированные данные в список объектов Category (модель UI)
return categoryTotals.entries.map((entry) {
// Используем утилиту для получения деталей (иконка, цвет) по имени
// TODO: В будущем лучше получать детали напрямую из CategoryDb, если они там хранятся
final categoryDetails = CategoryUtils.getCategoryDetails(entry.key);
final categoryName = entry.key;
final totalAmount = entry.value;
// Получаем детали категории из Map (или используем дефолтные, если категория была удалена)
final categoryDb = categoryDetailsMap[categoryName];
final iconData = CategoryUtils.getIconFromString(categoryDb?.icon); // Используем утилиту
final colorData = categoryDb != null ? Color(categoryDb.color) : Colors.grey.shade500; // Цвет из БД или дефолтный
return Category( // Это Category из models/category.dart
entry.key, // name
entry.value, // amount
categoryDetails.colorCode,
categoryDetails.iconCode,
categoryName,
totalAmount,
colorData,
iconData,
);
}).toList()
// Сортируем категории по сумме (от большей к меньшей)
@@ -203,6 +312,7 @@ class AppDatabase extends _$AppDatabase {
});
}
// Вычислить и наблюдать за общей суммой ДОХОДОВ
Stream<double> watchTotalIncome() {
// Создаем выражение для суммы amount