Implement admin inventory management features including CRUD operations, merging, filtering, sorting, previewing, and security enhancements. Update documentation and add comprehensive test coverage for security and validation.
Test Suite / test (24.15.0) (push) Has been cancelled

This commit is contained in:
Nils-Johan Gynther
2026-05-10 00:20:25 +02:00
parent 65137b41fb
commit 1709bb1dad
33 changed files with 1879 additions and 71 deletions
@@ -6,6 +6,7 @@ import '../../../core/api/guarded_api_call.dart';
import '../../auth/data/auth_providers.dart';
import '../domain/admin_ai_categorize_result.dart';
import '../domain/admin_category_node.dart';
import '../domain/admin_inventory_item.dart';
import '../domain/admin_product.dart';
import '../domain/ai_model_info.dart';
import '../domain/pending_product.dart';
@@ -311,4 +312,103 @@ class AdminRepository {
Future<void> removeReceiptAlias(int id) =>
_deleteVoid(ReceiptAliasApiPaths.remove(id));
// ── Admin inventory (global tabellhantering) ─────────────────────────────
Future<List<AdminInventoryItem>> listAdminInventory({
int? userId,
String? sort,
}) {
final path = AdminInventoryApiPaths.withFilters(userId: userId, sort: sort);
return _getList(path, AdminInventoryItem.fromJson);
}
Future<AdminInventoryItem> createAdminInventory({
int? userId,
required int productId,
required double quantity,
required String unit,
String? location,
String? brand,
String? receiptName,
String? suitableFor,
String? comment,
}) {
return _post<AdminInventoryItem>(
AdminInventoryApiPaths.list,
body: {
if (userId != null) 'userId': userId,
'productId': productId,
'quantity': quantity,
'unit': unit,
if (location != null && location.trim().isNotEmpty)
'location': location.trim(),
if (brand != null && brand.trim().isNotEmpty) 'brand': brand.trim(),
if (receiptName != null && receiptName.trim().isNotEmpty)
'receiptName': receiptName.trim(),
if (suitableFor != null && suitableFor.trim().isNotEmpty)
'suitableFor': suitableFor.trim(),
if (comment != null && comment.trim().isNotEmpty)
'comment': comment.trim(),
},
parse: (d) =>
AdminInventoryItem.fromJson(Map<String, dynamic>.from(d as Map)),
);
}
Future<AdminInventoryItem> updateAdminInventory(
int inventoryId, {
int? productId,
double? quantity,
String? unit,
String? location,
String? brand,
String? receiptName,
String? suitableFor,
String? comment,
}) {
final body = <String, dynamic>{
if (productId != null) 'productId': productId,
if (quantity != null) 'quantity': quantity,
if (unit != null) 'unit': unit,
if (location != null) 'location': location,
if (brand != null) 'brand': brand,
if (receiptName != null) 'receiptName': receiptName,
if (suitableFor != null) 'suitableFor': suitableFor,
if (comment != null) 'comment': comment,
};
return _patch(
AdminInventoryApiPaths.update(inventoryId),
body: body,
parse: AdminInventoryItem.fromJson,
);
}
Future<void> removeAdminInventory(int inventoryId) =>
_deleteVoid(AdminInventoryApiPaths.remove(inventoryId));
Future<void> mergeAdminInventory({
required int sourceInventoryId,
required int targetInventoryId,
}) =>
_postVoid(AdminInventoryApiPaths.merge, {
'sourceInventoryId': sourceInventoryId,
'targetInventoryId': targetInventoryId,
});
Future<Map<String, dynamic>> previewAdminInventoryMerge({
required int sourceInventoryId,
required int targetInventoryId,
}) async {
final token = await _token();
final data = await guardedApiCall(
_ref,
() => _apiClient.getJson(
AdminInventoryApiPaths.mergePreview(sourceInventoryId, targetInventoryId),
token: token,
),
);
return Map<String, dynamic>.from(data as Map);
}
}