101 lines
3.5 KiB
Dart
101 lines
3.5 KiB
Dart
import 'package:sqflite/sqflite.dart';
|
|
import 'package:uuid/uuid.dart';
|
|
|
|
import '../models/inventory_models.dart';
|
|
import 'database_helper.dart';
|
|
|
|
class InventoryRepository {
|
|
InventoryRepository();
|
|
|
|
final DatabaseHelper _dbHelper = DatabaseHelper();
|
|
final Uuid _uuid = const Uuid();
|
|
|
|
Future<List<InventorySummary>> fetchSummaries({bool includeHidden = false}) async {
|
|
final db = await _dbHelper.database;
|
|
final whereClauses = <String>[];
|
|
if (!includeHidden) {
|
|
whereClauses.add('COALESCE(mh.is_hidden, p.is_hidden, 0) = 0');
|
|
}
|
|
final whereSql = whereClauses.isEmpty ? '' : 'WHERE ${whereClauses.join(' AND ')}';
|
|
|
|
final rows = await db.rawQuery('''
|
|
SELECT p.id, p.name, p.category, p.default_unit_price, p.stock_quantity,
|
|
MAX(m.created_at) AS last_movement_at
|
|
FROM products p
|
|
LEFT JOIN master_hidden mh ON mh.master_type = 'product' AND mh.master_id = p.id
|
|
LEFT JOIN inventory_movements m ON m.product_id = p.id
|
|
$whereSql
|
|
GROUP BY p.id
|
|
ORDER BY p.name COLLATE NOCASE ASC
|
|
''');
|
|
|
|
return rows.map((row) {
|
|
return InventorySummary(
|
|
productId: row['id'] as String,
|
|
productName: row['name'] as String? ?? '-',
|
|
stockQuantity: row['stock_quantity'] as int? ?? 0,
|
|
category: row['category'] as String?,
|
|
defaultUnitPrice: row['default_unit_price'] as int?,
|
|
lastMovementAt: row['last_movement_at'] != null ? DateTime.parse(row['last_movement_at'] as String) : null,
|
|
);
|
|
}).toList();
|
|
}
|
|
|
|
Future<List<InventoryMovement>> fetchMovements(String productId, {int limit = 50}) async {
|
|
final db = await _dbHelper.database;
|
|
final rows = await db.query(
|
|
'inventory_movements',
|
|
where: 'product_id = ?',
|
|
whereArgs: [productId],
|
|
orderBy: 'created_at DESC',
|
|
limit: limit,
|
|
);
|
|
return rows.map(InventoryMovement.fromMap).toList();
|
|
}
|
|
|
|
Future<InventorySummary> recordMovement({
|
|
required String productId,
|
|
required InventoryMovementType type,
|
|
required int quantity,
|
|
required int quantityDelta,
|
|
String? reference,
|
|
String? notes,
|
|
}) async {
|
|
final db = await _dbHelper.database;
|
|
late InventorySummary summary;
|
|
await db.transaction((txn) async {
|
|
final productRows = await txn.query('products', where: 'id = ?', whereArgs: [productId], limit: 1);
|
|
if (productRows.isEmpty) {
|
|
throw StateError('product not found: $productId');
|
|
}
|
|
final product = productRows.first;
|
|
final currentStock = product['stock_quantity'] as int? ?? 0;
|
|
final nextStock = currentStock + quantityDelta;
|
|
final now = DateTime.now();
|
|
final movement = InventoryMovement(
|
|
id: _uuid.v4(),
|
|
productId: productId,
|
|
productNameSnapshot: product['name'] as String? ?? '-',
|
|
type: type,
|
|
quantity: quantity,
|
|
quantityDelta: quantityDelta,
|
|
reference: reference,
|
|
notes: notes,
|
|
createdAt: now,
|
|
);
|
|
|
|
await txn.insert('inventory_movements', movement.toMap(), conflictAlgorithm: ConflictAlgorithm.replace);
|
|
await txn.update('products', {'stock_quantity': nextStock}, where: 'id = ?', whereArgs: [productId]);
|
|
|
|
summary = InventorySummary(
|
|
productId: productId,
|
|
productName: product['name'] as String? ?? '-',
|
|
stockQuantity: nextStock,
|
|
category: product['category'] as String?,
|
|
defaultUnitPrice: product['default_unit_price'] as int?,
|
|
lastMovementAt: now,
|
|
);
|
|
});
|
|
return summary;
|
|
}
|
|
}
|