home added
This commit is contained in:
@@ -3,10 +3,12 @@ library;
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
|
||||
import '../models/app_overview.dart';
|
||||
import '../models/line_item.dart';
|
||||
import '../models/product.dart';
|
||||
import '../models/purchase.dart';
|
||||
@@ -16,15 +18,15 @@ import '../models/supermarket.dart';
|
||||
import '../utils/money.dart';
|
||||
import '../utils/supermarkets.dart';
|
||||
|
||||
class ProductRepository {
|
||||
class ProductRepository extends ChangeNotifier {
|
||||
ProductRepository({this.databasePath});
|
||||
|
||||
final String? databasePath;
|
||||
Database? _db;
|
||||
Future<Database>? _opening;
|
||||
|
||||
Future<Database> get _database async {
|
||||
_db ??= await _open();
|
||||
return _db!;
|
||||
Future<Database> get _database {
|
||||
_opening ??= _open();
|
||||
return _opening!;
|
||||
}
|
||||
|
||||
Future<Database> _open() async {
|
||||
@@ -33,7 +35,7 @@ class ProductRepository {
|
||||
p.join((await getApplicationDocumentsDirectory()).path, 'receipity.db');
|
||||
return openDatabase(
|
||||
path,
|
||||
version: 6,
|
||||
version: 7,
|
||||
onConfigure: (db) async {
|
||||
await db.execute('PRAGMA foreign_keys = ON');
|
||||
},
|
||||
@@ -67,6 +69,9 @@ class ProductRepository {
|
||||
if (oldVersion < 6) {
|
||||
await _upgradeShopsToV6(db);
|
||||
}
|
||||
if (oldVersion < 7) {
|
||||
await _upgradeShopItemsProductFk(db);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -135,13 +140,13 @@ class ProductRepository {
|
||||
CREATE TABLE shop_items (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
shop_id INTEGER NOT NULL,
|
||||
product_id INTEGER NOT NULL,
|
||||
product_id INTEGER,
|
||||
name TEXT NOT NULL,
|
||||
price REAL NOT NULL,
|
||||
quantity INTEGER,
|
||||
unit_price REAL,
|
||||
FOREIGN KEY (shop_id) REFERENCES shops(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (product_id) REFERENCES products(id)
|
||||
FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE SET NULL
|
||||
)
|
||||
''');
|
||||
await _createShopTagTable(db);
|
||||
@@ -169,6 +174,31 @@ class ProductRepository {
|
||||
await _createShopTagTable(db);
|
||||
}
|
||||
|
||||
Future<void> _upgradeShopItemsProductFk(DatabaseExecutor db) async {
|
||||
await db.execute('''
|
||||
CREATE TABLE shop_items_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
shop_id INTEGER NOT NULL,
|
||||
product_id INTEGER,
|
||||
name TEXT NOT NULL,
|
||||
price REAL NOT NULL,
|
||||
quantity INTEGER,
|
||||
unit_price REAL,
|
||||
FOREIGN KEY (shop_id) REFERENCES shops(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE SET NULL
|
||||
)
|
||||
''');
|
||||
await db.execute('''
|
||||
INSERT INTO shop_items_new (
|
||||
id, shop_id, product_id, name, price, quantity, unit_price
|
||||
)
|
||||
SELECT id, shop_id, product_id, name, price, quantity, unit_price
|
||||
FROM shop_items
|
||||
''');
|
||||
await db.execute('DROP TABLE shop_items');
|
||||
await db.execute('ALTER TABLE shop_items_new RENAME TO shop_items');
|
||||
}
|
||||
|
||||
Future<void> _createShoppingListTables(DatabaseExecutor db) async {
|
||||
await db.execute('''
|
||||
CREATE TABLE IF NOT EXISTS shopping_lists (
|
||||
@@ -268,6 +298,7 @@ class ProductRepository {
|
||||
}
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
return shopId;
|
||||
}
|
||||
|
||||
@@ -353,6 +384,51 @@ class ProductRepository {
|
||||
return rows.map(Product.fromMap).toList();
|
||||
}
|
||||
|
||||
Future<AppOverview> getOverview() async {
|
||||
final db = await _database;
|
||||
final productCount =
|
||||
Sqflite.firstIntValue(
|
||||
await db.rawQuery('SELECT COUNT(*) FROM products'),
|
||||
) ??
|
||||
0;
|
||||
final favouriteCount =
|
||||
Sqflite.firstIntValue(
|
||||
await db.rawQuery(
|
||||
'SELECT COUNT(*) FROM products WHERE favourite = 1',
|
||||
),
|
||||
) ??
|
||||
0;
|
||||
final tripCount =
|
||||
Sqflite.firstIntValue(await db.rawQuery('SELECT COUNT(*) FROM shops')) ??
|
||||
0;
|
||||
final listCount =
|
||||
Sqflite.firstIntValue(
|
||||
await db.rawQuery('SELECT COUNT(*) FROM shopping_lists'),
|
||||
) ??
|
||||
0;
|
||||
final now = DateTime.now();
|
||||
final monthStart = DateTime(now.year, now.month, 1).toIso8601String();
|
||||
final monthRows = await db.rawQuery(
|
||||
'''
|
||||
SELECT COUNT(*) AS trip_count, COALESCE(SUM(total), 0) AS month_total
|
||||
FROM shops
|
||||
WHERE shopped_at >= ?
|
||||
''',
|
||||
[monthStart],
|
||||
);
|
||||
final month = monthRows.first;
|
||||
final shops = await getShops();
|
||||
return AppOverview(
|
||||
productCount: productCount,
|
||||
favouriteCount: favouriteCount,
|
||||
tripCount: tripCount,
|
||||
monthTripCount: (month['trip_count'] as num?)?.toInt() ?? 0,
|
||||
monthTotal: (month['month_total'] as num?)?.toDouble() ?? 0,
|
||||
listCount: listCount,
|
||||
recentShops: shops.take(3).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<Product?> getProduct(int id) async {
|
||||
final db = await _database;
|
||||
final rows = await db.rawQuery(
|
||||
@@ -433,6 +509,7 @@ class ProductRepository {
|
||||
values['category'] = category.trim();
|
||||
}
|
||||
await db.update('products', values, where: 'id = ?', whereArgs: [id]);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> setFavourite(int id, bool favourite) =>
|
||||
@@ -444,6 +521,60 @@ class ProductRepository {
|
||||
return stored;
|
||||
}
|
||||
|
||||
Future<void> deleteProduct(int id) async {
|
||||
final db = await _database;
|
||||
final rows = await db.query(
|
||||
'products',
|
||||
columns: ['image_path'],
|
||||
where: 'id = ?',
|
||||
whereArgs: [id],
|
||||
limit: 1,
|
||||
);
|
||||
await db.transaction((txn) async {
|
||||
await txn.update(
|
||||
'shop_items',
|
||||
{'product_id': null},
|
||||
where: 'product_id = ?',
|
||||
whereArgs: [id],
|
||||
);
|
||||
await txn.update(
|
||||
'shopping_list_items',
|
||||
{'product_id': null},
|
||||
where: 'product_id = ?',
|
||||
whereArgs: [id],
|
||||
);
|
||||
await txn.delete('products', where: 'id = ?', whereArgs: [id]);
|
||||
});
|
||||
if (rows.isNotEmpty) {
|
||||
await _tryDeleteFile(rows.first['image_path'] as String?);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> deleteAllProducts() async {
|
||||
final db = await _database;
|
||||
final rows = await db.query('products', columns: ['image_path']);
|
||||
await db.transaction((txn) async {
|
||||
await txn.update('shop_items', {'product_id': null});
|
||||
await txn.update('shopping_list_items', {'product_id': null});
|
||||
await txn.delete('products');
|
||||
});
|
||||
for (final row in rows) {
|
||||
await _tryDeleteFile(row['image_path'] as String?);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> _tryDeleteFile(String? path) async {
|
||||
if (path == null || path.isEmpty) return;
|
||||
try {
|
||||
final file = File(path);
|
||||
if (await file.exists()) {
|
||||
await file.delete();
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
Future<List<Purchase>> getPurchases(int productId) async {
|
||||
final db = await _database;
|
||||
final rows = await db.rawQuery(
|
||||
@@ -576,6 +707,7 @@ class ProductRepository {
|
||||
await _replaceShopTags(txn, shopId: id, tags: tags);
|
||||
}
|
||||
});
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> _replaceShopTags(
|
||||
@@ -624,12 +756,8 @@ class ProductRepository {
|
||||
await txn.delete('shop_tags', where: 'shop_id = ?', whereArgs: [id]);
|
||||
await txn.delete('shops', where: 'id = ?', whereArgs: [id]);
|
||||
});
|
||||
final path = shop?.receiptImagePath;
|
||||
if (path == null || path.isEmpty) return;
|
||||
final file = File(path);
|
||||
if (await file.exists()) {
|
||||
await file.delete();
|
||||
}
|
||||
await _tryDeleteFile(shop?.receiptImagePath);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Removes one trip line. Catalog products are left unchanged.
|
||||
@@ -646,6 +774,7 @@ class ProductRepository {
|
||||
final shopId = rows.first['shop_id']! as int;
|
||||
await db.delete('shop_items', where: 'id = ?', whereArgs: [id]);
|
||||
await _recalculateShopTotal(shopId);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> updateShopItem({
|
||||
@@ -672,6 +801,7 @@ class ProductRepository {
|
||||
if (values.isEmpty) return;
|
||||
await db.update('shop_items', values, where: 'id = ?', whereArgs: [id]);
|
||||
await _recalculateShopTotal(shopId);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> _recalculateShopTotal(int shopId) async {
|
||||
@@ -732,11 +862,13 @@ class ProductRepository {
|
||||
Future<int> createShoppingList(String name) async {
|
||||
final db = await _database;
|
||||
final now = DateTime.now().toIso8601String();
|
||||
return db.insert('shopping_lists', {
|
||||
final id = await db.insert('shopping_lists', {
|
||||
'name': name.trim(),
|
||||
'created_at': now,
|
||||
'updated_at': now,
|
||||
});
|
||||
notifyListeners();
|
||||
return id;
|
||||
}
|
||||
|
||||
Future<void> renameShoppingList(int id, String name) async {
|
||||
@@ -747,6 +879,7 @@ class ProductRepository {
|
||||
where: 'id = ?',
|
||||
whereArgs: [id],
|
||||
);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> deleteShoppingList(int id) async {
|
||||
@@ -759,6 +892,7 @@ class ProductRepository {
|
||||
);
|
||||
await txn.delete('shopping_lists', where: 'id = ?', whereArgs: [id]);
|
||||
});
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<List<ShoppingListItem>> getShoppingListItems(int listId) async {
|
||||
@@ -792,6 +926,7 @@ class ProductRepository {
|
||||
'sort_order': (max ?? -1) + 1,
|
||||
});
|
||||
await _touchShoppingList(listId);
|
||||
notifyListeners();
|
||||
return id;
|
||||
}
|
||||
|
||||
@@ -822,6 +957,7 @@ class ProductRepository {
|
||||
whereArgs: [id],
|
||||
);
|
||||
await _touchShoppingList(rows.first['list_id']! as int);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> deleteShoppingListItem(int id) async {
|
||||
@@ -836,6 +972,7 @@ class ProductRepository {
|
||||
if (rows.isEmpty) return;
|
||||
await db.delete('shopping_list_items', where: 'id = ?', whereArgs: [id]);
|
||||
await _touchShoppingList(rows.first['list_id']! as int);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> _touchShoppingList(int id) async {
|
||||
@@ -1026,6 +1163,7 @@ class ProductRepository {
|
||||
await _resetSequence(txn, 'shopping_lists');
|
||||
await _resetSequence(txn, 'shopping_list_items');
|
||||
});
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
static const _supermarketColumns = {'id', 'name', 'color', 'sort_order'};
|
||||
|
||||
Reference in New Issue
Block a user