feat: add category update and delete methods
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user