/// Local SQLite store of confirmed products. library; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; import 'package:sqflite/sqflite.dart'; import '../models/line_item.dart'; import '../models/product.dart'; class ProductRepository { Database? _db; Future get _database async { _db ??= await _open(); return _db!; } Future _open() async { final dir = await getApplicationDocumentsDirectory(); return openDatabase( p.join(dir.path, 'receipity.db'), version: 1, onCreate: (db, version) async { await db.execute(''' CREATE TABLE products ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, normalized_name TEXT NOT NULL UNIQUE, last_price REAL NOT NULL, times_seen INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ) '''); }, ); } static String normalizeName(String name) => name.trim().toUpperCase().replaceAll(RegExp(r'\s+'), ' '); Future upsertItems(List items) async { final db = await _database; final now = DateTime.now().toIso8601String(); await db.transaction((txn) async { for (final item in items) { final key = normalizeName(item.name); if (key.isEmpty) continue; final existing = await txn.query( 'products', where: 'normalized_name = ?', whereArgs: [key], limit: 1, ); if (existing.isEmpty) { await txn.insert('products', { 'name': item.name.trim(), 'normalized_name': key, 'last_price': item.price, 'times_seen': 1, 'created_at': now, 'updated_at': now, }); } else { await txn.update( 'products', { 'name': item.name.trim(), 'last_price': item.price, 'times_seen': (existing.first['times_seen']! as int) + 1, 'updated_at': now, }, where: 'id = ?', whereArgs: [existing.first['id']], ); } } }); } Future> getAll() async { final db = await _database; final rows = await db.query( 'products', orderBy: 'name COLLATE NOCASE ASC', ); return rows.map(Product.fromMap).toList(); } }