chore(import): improve error handling and add flyer integration
- Replace BadRequestException with UnauthorizedException for authentication failures in flyer-import and flyer-selection controllers - Add bulk selection endpoint in FlyerSelectionController for creating multiple selections in one request - Update FlyerSelectionModule to include new FlyerSelectionMatcherService and FlyerSelectionSyncController - Extend FlyerSelectionService with createMany method for bulk operations - Add new DTOs for bulk selection and receipt matching functionality - Update ReceiptImportService to accept FlyerSelectionService dependency and track successful rows - Extend SaveReceiptResponse with flyerAutoSync field for receipt-to-flyer matching results - Add new API paths for flyer import and selection endpoints - Update Flutter UI to include Flyer import tab and adjust tab controller length - Add new domain models and repository methods for flyer import functionality - Update test files to include new FlyerSelectionService dependency - Modify .kilo plan documentation to reflect current system architecture
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'import_repository.dart';
|
||||
|
||||
import 'import_repository.dart';
|
||||
|
||||
final importRepositoryProvider = Provider<ImportRepository>(
|
||||
(_) => ImportRepository(),
|
||||
);
|
||||
final importRepositoryProvider = Provider<ImportRepository>(
|
||||
(_) => ImportRepository(),
|
||||
);
|
||||
|
||||
@@ -5,10 +5,11 @@ import 'dart:typed_data';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'dart:developer' as developer;
|
||||
|
||||
import '../../../core/api/api_paths.dart';
|
||||
import '../../../core/api/api_exception.dart';
|
||||
import '../domain/help_text_content.dart';
|
||||
import '../domain/quick_import_result.dart';
|
||||
import '../../../core/api/api_paths.dart';
|
||||
import '../../../core/api/api_exception.dart';
|
||||
import '../domain/flyer_import_result.dart';
|
||||
import '../domain/help_text_content.dart';
|
||||
import '../domain/quick_import_result.dart';
|
||||
|
||||
/// Handles communication with the quick-import API endpoint.
|
||||
///
|
||||
@@ -60,7 +61,7 @@ class ImportRepository {
|
||||
|
||||
/// Upload a receipt file for parsing (Fas 6b).
|
||||
/// Returns a list of parsed receipt items.
|
||||
Future<List<ParsedReceiptItem>> importReceiptFile({
|
||||
Future<List<ParsedReceiptItem>> importReceiptFile({
|
||||
required Uint8List bytes,
|
||||
required String filename,
|
||||
String? token,
|
||||
@@ -142,7 +143,83 @@ class ImportRepository {
|
||||
developer.log('Exception during receipt import: $e', name: 'ImportRepository', error: e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<FlyerImportResult> importFlyerFile({
|
||||
required Uint8List bytes,
|
||||
required String filename,
|
||||
String? token,
|
||||
}) async {
|
||||
final uri = Uri.parse('$_baseUrl${FlyerImportApiPaths.parse}');
|
||||
final request = http.MultipartRequest('POST', uri);
|
||||
|
||||
if (token != null) {
|
||||
request.headers['Authorization'] = 'Bearer $token';
|
||||
}
|
||||
|
||||
request.files.add(
|
||||
http.MultipartFile.fromBytes('file', bytes, filename: filename),
|
||||
);
|
||||
|
||||
final streamed = await _client.send(request).timeout(
|
||||
const Duration(seconds: 120),
|
||||
onTimeout: () {
|
||||
throw ApiException(
|
||||
type: ApiErrorType.network,
|
||||
message: 'Flyerimporten tog för lång tid. Försök igen.',
|
||||
);
|
||||
},
|
||||
);
|
||||
final response = await http.Response.fromStream(streamed);
|
||||
|
||||
if (response.statusCode < 200 || response.statusCode >= 300) {
|
||||
throw ApiException(
|
||||
type: _mapStatusCodeToErrorType(response.statusCode),
|
||||
message: 'Fel vid flyerimport: ${response.body}',
|
||||
statusCode: response.statusCode,
|
||||
);
|
||||
}
|
||||
|
||||
final parsed = _parseResponse(response);
|
||||
if (parsed is! Map<String, dynamic>) {
|
||||
throw ApiException(
|
||||
type: ApiErrorType.unknown,
|
||||
message: 'Felaktigt svar från flyerimport.',
|
||||
);
|
||||
}
|
||||
|
||||
return FlyerImportResult.fromJson(parsed);
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> createFlyerSelectionsBulk({
|
||||
required int sessionId,
|
||||
required List<Map<String, dynamic>> items,
|
||||
String? token,
|
||||
}) async {
|
||||
final uri = Uri.parse('$_baseUrl${FlyerSelectionApiPaths.bulkBySession(sessionId)}');
|
||||
final response = await _client.post(
|
||||
uri,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
if (token != null) 'Authorization': 'Bearer $token',
|
||||
},
|
||||
body: jsonEncode({
|
||||
'items': items,
|
||||
}),
|
||||
);
|
||||
|
||||
if (response.statusCode < 200 || response.statusCode >= 300) {
|
||||
throw ApiException(
|
||||
type: _mapStatusCodeToErrorType(response.statusCode),
|
||||
message: 'Kunde inte skapa flyer-selections: ${response.body}',
|
||||
statusCode: response.statusCode,
|
||||
);
|
||||
}
|
||||
|
||||
final parsed = _parseResponse(response);
|
||||
if (parsed is! List) return const [];
|
||||
return parsed.cast<Map<String, dynamic>>();
|
||||
}
|
||||
|
||||
/// Upload a file (PDF or image) for recipe extraction.
|
||||
///
|
||||
@@ -335,7 +412,7 @@ class ImportRepository {
|
||||
);
|
||||
}
|
||||
|
||||
final result = _parseResponse(response) as Map<String, dynamic>;
|
||||
final result = _parseResponse(response) as Map<String, dynamic>;
|
||||
developer.log('saveReceipt succeeded: ${result['created']} created, ${result['merged']} merged', name: 'ImportRepository');
|
||||
return result;
|
||||
} catch (e) {
|
||||
|
||||
Reference in New Issue
Block a user