// lib/services/air_sampling_service.dart import 'dart:async'; import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:image_picker/image_picker.dart'; import 'package:path_provider/path_provider.dart'; import 'package:path/path.dart' as path; import 'package:image/image.dart' as img; import 'package:intl/intl.dart'; import 'dart:convert'; import '../models/air_installation_data.dart'; import '../models/air_collection_data.dart'; import 'api_service.dart'; import 'local_storage_service.dart'; import 'telegram_service.dart'; // --- ADDED: Import for the service that manages active server configurations --- import 'server_config_service.dart'; /// A dedicated service for handling all business logic for the Air Manual Sampling feature. class AirSamplingService { final ApiService _apiService; final DatabaseHelper _dbHelper; final TelegramService _telegramService; final ServerConfigService _serverConfigService = ServerConfigService(); // REVISED: Constructor now takes dependencies as parameters AirSamplingService(this._apiService, this._dbHelper, this._telegramService); // Helper method to create a map suitable for LocalStorageService (retains File objects) Map _toMapForLocalSave(dynamic data) { if (data is AirInstallationData) { final map = data.toMap(); // Get map with paths for DB logging // Overwrite paths with live File objects for local saving process map['imageFront'] = data.imageFront; map['imageBack'] = data.imageBack; map['imageLeft'] = data.imageLeft; map['imageRight'] = data.imageRight; map['optionalImage1'] = data.optionalImage1; map['optionalImage2'] = data.optionalImage2; map['optionalImage3'] = data.optionalImage3; map['optionalImage4'] = data.optionalImage4; if (data.collectionData != null) { map['collectionData'] = _toMapForLocalSave(data.collectionData); } return map; } else if (data is AirCollectionData) { final map = data.toMap(); // Get map with paths for DB logging // Overwrite paths with live File objects for local saving process map['imageFront'] = data.imageFront; map['imageBack'] = data.imageBack; map['imageLeft'] = data.imageLeft; map['imageRight'] = data.imageRight; map['imageChart'] = data.imageChart; map['imageFilterPaper'] = data.imageFilterPaper; map['optionalImage1'] = data.optionalImage1; map['optionalImage2'] = data.optionalImage2; map['optionalImage3'] = data.optionalImage3; map['optionalImage4'] = data.optionalImage4; return map; } return {}; } /// Picks an image from the specified source, adds a timestamp watermark, /// and saves it to a temporary directory with a standardized name. Future pickAndProcessImage( ImageSource source, { required String stationCode, required String imageInfo, String processType = 'INSTALL', required bool isRequired, }) async { final picker = ImagePicker(); final XFile? photo = await picker.pickImage( source: source, imageQuality: 85, maxWidth: 1024); if (photo == null) return null; final bytes = await photo.readAsBytes(); img.Image? originalImage = img.decodeImage(bytes); if (originalImage == null) return null; // MODIFIED: Enforce landscape orientation for required photos if (isRequired && originalImage.height > originalImage.width) { debugPrint("Image orientation check failed: Image must be in landscape mode."); return null; // Return null to indicate failure } final String watermarkTimestamp = DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.now()); final font = img.arial24; final textWidth = watermarkTimestamp.length * 12; img.fillRect(originalImage, x1: 5, y1: 5, x2: textWidth + 15, y2: 35, color: img.ColorRgb8(255, 255, 255)); img.drawString(originalImage, watermarkTimestamp, font: font, x: 10, y: 10, color: img.ColorRgb8(0, 0, 0)); final tempDir = await getTemporaryDirectory(); final fileTimestamp = watermarkTimestamp.replaceAll(':', '-').replaceAll(' ', '_'); final newFileName = "${stationCode}_${fileTimestamp}_${processType.toUpperCase()}_${imageInfo.replaceAll(' ', '')}.jpg"; final filePath = path.join(tempDir.path, newFileName); return File(filePath)..writeAsBytesSync(img.encodeJpg(originalImage)); } // MODIFIED: Method now requires the appSettings list to pass to TelegramService. Future _handleInstallationSuccessAlert(AirInstallationData data, List>? appSettings, {required bool isDataOnly}) async { try { final message = data.generateInstallationTelegramAlert(isDataOnly: isDataOnly); // Pass the appSettings list to the telegram service methods final bool wasSent = await _telegramService.sendAlertImmediately('air_manual', message, appSettings); if (!wasSent) { await _telegramService.queueMessage('air_manual', message, appSettings); } } catch (e) { debugPrint("Failed to handle Air Manual Installation Telegram alert: $e"); } } // MODIFIED: Method now requires the appSettings list to pass to TelegramService. Future _handleCollectionSuccessAlert(AirCollectionData data, AirInstallationData installationData, List>? appSettings, {required bool isDataOnly}) async { try { final message = data.generateCollectionTelegramAlert(installationData, isDataOnly: isDataOnly); // Pass the appSettings list to the telegram service methods final bool wasSent = await _telegramService.sendAlertImmediately('air_manual', message, appSettings); if (!wasSent) { await _telegramService.queueMessage('air_manual', message, appSettings); } } catch (e) { debugPrint("Failed to handle Air Manual Collection Telegram alert: $e"); } } // --- ADDED HELPER METHODS TO GET IMAGE PATHS FROM MODELS --- List _getInstallationImagePaths(AirInstallationData data) { final List files = [ data.imageFront, data.imageBack, data.imageLeft, data.imageRight, data.optionalImage1, data.optionalImage2, data.optionalImage3, data.optionalImage4, ]; return files.where((f) => f != null).map((f) => f!.path).toList(); } List _getCollectionImagePaths(AirCollectionData data) { final List files = [ data.imageFront, data.imageBack, data.imageLeft, data.imageRight, data.imageChart, data.imageFilterPaper, data.optionalImage1, data.optionalImage2, data.optionalImage3, data.optionalImage4, ]; return files.where((f) => f != null).map((f) => f!.path).toList(); } /// Orchestrates a two-step submission process for air installation samples. // MODIFIED: Method now requires the appSettings list to pass down the call stack. Future> submitInstallation(AirInstallationData data, List>? appSettings) async { // --- MODIFIED: Get the active server name to use for local storage --- final activeConfig = await _serverConfigService.getActiveApiConfig(); final serverName = activeConfig?['config_name'] as String? ?? 'Default'; final localStorageService = LocalStorageService(); // Instance for file system save // If the record's text data is already on the server, skip directly to image upload. if (data.status == 'L2_PENDING_IMAGES' && data.airManId != null) { debugPrint("Retrying image upload for existing record ID: ${data.airManId}"); final result = await _uploadInstallationImagesAndUpdate(data, appSettings, serverName: serverName); // LOG DEBUG START final logData = { 'submission_id': data.refID!, 'module': 'air', 'type': 'Installation', 'status': result['status'], 'message': result['message'], 'report_id': data.airManId.toString(), 'created_at': DateTime.now().toIso8601String(), 'form_data': jsonEncode(data.toMap()), 'image_data': jsonEncode(_getInstallationImagePaths(data)), 'server_name': serverName, 'api_status': jsonEncode([{"server_name": serverName, "status": "PENDING", "message": "Resubmitting images."}]), 'ftp_status': jsonEncode([{"server_name": "N/A", "status": "NOT_APPLICABLE", "message": "FTP not used for images."}]), }; debugPrint("DB LOGGING (Installation Retry): Status: ${logData['status']}, API Status: ${logData['api_status']}, FTP Status: ${logData['ftp_status']}"); // LOG DEBUG END // --- ADDED: Log the final result to the central database --- await _dbHelper.saveSubmissionLog(logData); return result; } // --- STEP 1: SUBMIT TEXT DATA --- debugPrint("Step 1: Submitting installation text data..."); final textDataResult = await _apiService.air.submitInstallation(data); // --- CRITICAL FIX: Save to local file system immediately regardless of API success --- data.status = 'L1'; // Temporary set status to Local Only // Use the special helper method to pass live File objects for copying final localSaveMap = _toMapForLocalSave(data); final localSaveResult = await localStorageService.saveAirSamplingRecord(localSaveMap, data.refID!, serverName: serverName); if (localSaveResult == null) { debugPrint("CRITICAL ERROR: Failed to save Air Installation record to local file system."); } // --- END CRITICAL FIX --- if (textDataResult['success'] != true) { debugPrint("Failed to submit text data. Reason: ${textDataResult['message']}"); final result = {'status': 'L1', 'message': 'No connection or server error. Installation data saved locally.'}; // LOG DEBUG START final logData = { 'submission_id': data.refID!, 'module': 'air', 'type': 'Installation', 'status': result['status'], 'message': result['message'], 'report_id': data.airManId.toString(), 'created_at': DateTime.now().toIso8601String(), 'form_data': jsonEncode(data.toMap()), 'image_data': jsonEncode(_getInstallationImagePaths(data)), 'server_name': serverName, 'api_status': jsonEncode([{"server_name": serverName, "status": "FAILED", "message": "API submission failed."}]), 'ftp_status': jsonEncode([{"server_name": "N/A", "status": "NOT_APPLICABLE", "message": "Not applicable."}]), }; debugPrint("DB LOGGING (Installation L1): Status: ${logData['status']}, API Status: ${logData['api_status']}, FTP Status: ${logData['ftp_status']}"); // LOG DEBUG END // --- ADDED: Log the failed submission to the central database --- await _dbHelper.saveSubmissionLog(logData); return result; } // --- NECESSARY FIX: Safely parse the record ID from the server response --- final dynamic recordIdFromServer = textDataResult['data']?['air_man_id']; if (recordIdFromServer == null) { debugPrint("Text data submitted, but did not receive a record ID."); final result = {'status': 'L1', 'message': 'Data submitted, but server response was invalid.'}; // LOG DEBUG START final logData = { 'submission_id': data.refID!, 'module': 'air', 'type': 'Installation', 'status': result['status'], 'message': result['message'], 'report_id': data.airManId.toString(), 'created_at': DateTime.now().toIso8601String(), 'form_data': jsonEncode(data.toMap()), 'image_data': jsonEncode(_getInstallationImagePaths(data)), 'server_name': serverName, 'api_status': jsonEncode([{"server_name": serverName, "status": "FAILED", "message": "Invalid response from server."}]), 'ftp_status': jsonEncode([{"server_name": "N/A", "status": "NOT_APPLICABLE", "message": "Not applicable."}]), }; debugPrint("DB LOGGING (Installation L1/Invalid ID): Status: ${logData['status']}, API Status: ${logData['api_status']}, FTP Status: ${logData['ftp_status']}"); // LOG DEBUG END // --- ADDED: Log the failed submission to the central database --- await _dbHelper.saveSubmissionLog(logData); return result; } final int? parsedRecordId = int.tryParse(recordIdFromServer.toString()); if (parsedRecordId == null) { debugPrint("Could not parse the received record ID: $recordIdFromServer"); final result = {'status': 'L1', 'message': 'Data submitted, but server response was invalid.'}; // LOG DEBUG START final logData = { 'submission_id': data.refID!, 'module': 'air', 'type': 'Installation', 'status': result['status'], 'message': result['message'], 'report_id': data.airManId.toString(), 'created_at': DateTime.now().toIso8601String(), 'form_data': jsonEncode(data.toMap()), 'image_data': jsonEncode(_getInstallationImagePaths(data)), 'server_name': serverName, 'api_status': jsonEncode([{"server_name": serverName, "status": "FAILED", "message": "Invalid response from server."}]), 'ftp_status': jsonEncode([{"server_name": "N/A", "status": "NOT_APPLICABLE", "message": "Not applicable."}]), }; debugPrint("DB LOGGING (Installation L1/Parse Fail): Status: ${logData['status']}, API Status: ${logData['api_status']}, FTP Status: ${logData['ftp_status']}"); // LOG DEBUG END // --- ADDED: Log the failed submission to the central database --- await _dbHelper.saveSubmissionLog(logData); return result; } data.airManId = parsedRecordId; // --- STEP 2: UPLOAD IMAGE FILES --- return await _uploadInstallationImagesAndUpdate(data, appSettings, serverName: serverName); } /// A reusable function for handling the image upload and local data update logic. // MODIFIED: Method now requires the serverName to pass to the save method. Future> _uploadInstallationImagesAndUpdate(AirInstallationData data, List>? appSettings, {required String serverName}) async { final filesToUpload = data.getImagesForUpload(); final localStorageService = LocalStorageService(); // Since text data was successfully submitted, the status moves to S1 (Server Pending) data.status = 'S1'; // We already saved the file in submitInstallation (L1 status). Now we update the status in the local file. await localStorageService.saveAirSamplingRecord(_toMapForLocalSave(data), data.refID!, serverName: serverName); if (filesToUpload.isEmpty) { debugPrint("No images to upload. Submission complete."); // LOG DEBUG START final logData = { 'submission_id': data.refID!, 'module': 'air', 'type': 'Installation', 'status': data.status, 'message': 'Installation data submitted successfully.', 'report_id': data.airManId.toString(), 'created_at': DateTime.now().toIso8601String(), 'form_data': jsonEncode(data.toMap()), 'image_data': jsonEncode(_getInstallationImagePaths(data)), 'server_name': serverName, 'api_status': jsonEncode([{"server_name": serverName, "status": "SUCCESS", "message": "Text data submitted."}]), 'ftp_status': jsonEncode([{"server_name": "N/A", "status": "NOT_REQUIRED", "message": "No images were attached."}]), }; debugPrint("DB LOGGING (Installation S1/Data Only): Status: ${logData['status']}, API Status: ${logData['api_status']}, FTP Status: ${logData['ftp_status']}"); // LOG DEBUG END // --- MODIFIED: Log the successful submission to the central database --- await _dbHelper.saveSubmissionLog(logData); _handleInstallationSuccessAlert(data, appSettings, isDataOnly: true); return {'status': 'S1', 'message': 'Installation data submitted successfully.'}; } debugPrint("Step 2: Uploading ${filesToUpload.length} images for record ID ${data.airManId}..."); final imageUploadResult = await _apiService.air.uploadInstallationImages( airManId: data.airManId.toString(), files: filesToUpload, ); if (imageUploadResult['success'] != true) { debugPrint("Image upload failed. Reason: ${imageUploadResult['message']}"); data.status = 'L2_PENDING_IMAGES'; final result = { 'status': 'L2_PENDING_IMAGES', 'message': 'Data submitted, but image upload failed. Saved locally for retry.', }; // Update the local file with the image failure status await localStorageService.saveAirSamplingRecord(_toMapForLocalSave(data), data.refID!, serverName: serverName); // LOG DEBUG START final logData = { 'submission_id': data.refID!, 'module': 'air', 'type': 'Installation', 'status': result['status'], 'message': result['message'], 'report_id': data.airManId.toString(), 'created_at': DateTime.now().toIso8601String(), 'form_data': jsonEncode(data.toMap()), 'image_data': jsonEncode(_getInstallationImagePaths(data)), 'server_name': serverName, 'api_status': jsonEncode([{"server_name": serverName, "status": "SUCCESS", "message": "Text data submitted."}]), 'ftp_status': jsonEncode([{"server_name": "N/A", "status": "FAILED", "message": "Image upload failed."}]), }; debugPrint("DB LOGGING (Installation L2/Image Fail): Status: ${logData['status']}, API Status: ${logData['api_status']}, FTP Status: ${logData['ftp_status']}"); // LOG DEBUG END // --- MODIFIED: Log the failed submission to the central database --- await _dbHelper.saveSubmissionLog(logData); return result; } debugPrint("Images uploaded successfully."); data.status = 'S2'; // Server Pending (images uploaded) final result = { 'status': 'S2', 'message': 'Installation data and images submitted successfully.', }; // LOG DEBUG START final logData = { 'submission_id': data.refID!, 'module': 'air', 'type': 'Installation', 'status': result['status'], 'message': result['message'], 'report_id': data.airManId.toString(), 'created_at': DateTime.now().toIso8601String(), 'form_data': jsonEncode(data.toMap()), 'image_data': jsonEncode(_getInstallationImagePaths(data)), 'server_name': serverName, 'api_status': jsonEncode([{"server_name": serverName, "status": "SUCCESS", "message": "Text and image data submitted."}]), 'ftp_status': jsonEncode([{"server_name": "N/A", "status": "NOT_APPLICABLE", "message": "Not applicable."}]), }; debugPrint("DB LOGGING (Installation S2/Success): Status: ${logData['status']}, API Status: ${logData['api_status']}, FTP Status: ${logData['ftp_status']}"); // LOG DEBUG END // --- MODIFIED: Log the successful submission to the central database --- await _dbHelper.saveSubmissionLog(logData); // Update the local file with the final success status await localStorageService.saveAirSamplingRecord(_toMapForLocalSave(data), data.refID!, serverName: serverName); _handleInstallationSuccessAlert(data, appSettings, isDataOnly: false); return result; } /// Submits only the collection data, linked to a previous installation. // MODIFIED: Method now requires the appSettings list to pass down the call stack. Future> submitCollection(AirCollectionData data, AirInstallationData installationData, List>? appSettings) async { // --- MODIFIED: Get the active server name to use for local storage --- final activeConfig = await _serverConfigService.getActiveApiConfig(); final serverName = activeConfig?['config_name'] as String? ?? 'Default'; final apiConfigs = (await _dbHelper.loadApiConfigs() ?? []).take(2).toList(); final localStorageService = LocalStorageService(); // If the record's text data is already on the server, skip directly to image upload. if (data.status == 'L4_PENDING_IMAGES' && data.airManId != null) { debugPrint("Retrying collection image upload for existing record ID: ${data.airManId}"); final result = await _uploadCollectionImagesAndUpdate(data, installationData, appSettings, serverName: serverName); // LOG DEBUG START final logData = { 'submission_id': data.installationRefID!, 'module': 'air', 'type': 'Collection', 'status': result['status'], 'message': result['message'], 'report_id': data.airManId.toString(), 'created_at': DateTime.now().toIso8601String(), 'form_data': jsonEncode(data.toMap()), 'image_data': jsonEncode(_getCollectionImagePaths(data)), 'server_name': serverName, 'api_status': jsonEncode([{"server_name": serverName, "status": "PENDING", "message": "Resubmitting images."}]), 'ftp_status': jsonEncode([{"server_name": "N/A", "status": "NOT_APPLICABLE", "message": "FTP not used."}]), }; debugPrint("DB LOGGING (Collection Retry): Status: ${logData['status']}, API Status: ${logData['api_status']}, FTP Status: ${logData['ftp_status']}"); // LOG DEBUG END // --- ADDED: Log the final result to the central database --- await _dbHelper.saveSubmissionLog(logData); return result; } // --- STEP 1: SUBMIT TEXT DATA --- debugPrint("Step 1: Submitting collection text data..."); final textDataResult = await _apiService.air.submitCollection(data); // --- CRITICAL FIX: Save to local file system immediately regardless of API success --- data.status = 'L3'; // Temporary set status to Local Only final localSaveMap = _toMapForLocalSave(data); final localSaveResult = await localStorageService.saveAirSamplingRecord(localSaveMap, data.installationRefID!, serverName: serverName); if (localSaveResult == null) { debugPrint("CRITICAL ERROR: Failed to save Air Collection record to local file system."); } // --- END CRITICAL FIX --- if (textDataResult['success'] != true) { debugPrint("Failed to submit collection text data. Reason: ${textDataResult['message']}"); final result = {'status': 'L3', 'message': 'No connection or server error. Collection data saved locally.'}; // LOG DEBUG START final logData = { 'submission_id': data.installationRefID!, 'module': 'air', 'type': 'Collection', 'status': result['status'], 'message': result['message'], 'report_id': data.airManId.toString(), 'created_at': DateTime.now().toIso8601String(), 'form_data': jsonEncode(data.toMap()), 'image_data': jsonEncode(_getCollectionImagePaths(data)), 'server_name': serverName, 'api_status': jsonEncode([{"server_name": serverName, "status": "FAILED", "message": "API submission failed."}]), 'ftp_status': jsonEncode([{"server_name": "N/A", "status": "NOT_APPLICABLE", "message": "Not applicable."}]), }; debugPrint("DB LOGGING (Collection L3): Status: ${logData['status']}, API Status: ${logData['api_status']}, FTP Status: ${logData['ftp_status']}"); // LOG DEBUG END // --- ADDED: Log the failed submission to the central database --- await _dbHelper.saveSubmissionLog(logData); return result; } debugPrint("Collection text data submitted successfully."); data.airManId = textDataResult['data']['air_man_id']; // --- STEP 2: UPLOAD IMAGE FILES --- return await _uploadCollectionImagesAndUpdate(data, installationData, appSettings, serverName: serverName); } /// A reusable function for handling the collection image upload and local data update logic. // MODIFIED: Method now requires the serverName to pass to the save method. Future> _uploadCollectionImagesAndUpdate(AirCollectionData data, AirInstallationData installationData, List>? appSettings, {required String serverName}) async { final filesToUpload = data.getImagesForUpload(); final localStorageService = LocalStorageService(); // Since text data was successfully submitted, the status moves to S3 (Server Pending) data.status = 'S3'; // Update local file status (which was already saved with L3 status) await localStorageService.saveAirSamplingRecord(_toMapForLocalSave(data), data.installationRefID!, serverName: serverName); if (filesToUpload.isEmpty) { debugPrint("No collection images to upload. Submission complete."); final result = {'status': 'S3', 'message': 'Collection data submitted successfully.'}; // LOG DEBUG START final logData = { 'submission_id': data.installationRefID!, 'module': 'air', 'type': 'Collection', 'status': result['status'], 'message': result['message'], 'report_id': data.airManId.toString(), 'created_at': DateTime.now().toIso8601String(), 'form_data': jsonEncode(data.toMap()), 'image_data': jsonEncode(_getCollectionImagePaths(data)), 'server_name': serverName, 'api_status': jsonEncode([{"server_name": serverName, "status": "SUCCESS", "message": "Text data submitted."}]), 'ftp_status': jsonEncode([{"server_name": "N/A", "status": "NOT_REQUIRED", "message": "No images were attached."}]), }; debugPrint("DB LOGGING (Collection S3/Data Only): Status: ${logData['status']}, API Status: ${logData['api_status']}, FTP Status: ${logData['ftp_status']}"); // LOG DEBUG END // --- MODIFIED: Log the successful submission to the central database --- await _dbHelper.saveSubmissionLog(logData); _handleCollectionSuccessAlert(data, installationData, appSettings, isDataOnly: true); return result; } debugPrint("Step 2: Uploading ${filesToUpload.length} collection images..."); final imageUploadResult = await _apiService.air.uploadCollectionImages( airManId: data.airManId.toString(), files: filesToUpload, ); if (imageUploadResult['success'] != true) { debugPrint("Image upload failed. Reason: ${imageUploadResult['message']}"); data.status = 'L4_PENDING_IMAGES'; final result = { 'status': 'L4_PENDING_IMAGES', 'message': 'Data submitted, but image upload failed. Saved locally for retry.', }; // Update the local file with the image failure status await localStorageService.saveAirSamplingRecord(_toMapForLocalSave(data), data.installationRefID!, serverName: serverName); // LOG DEBUG START final logData = { 'submission_id': data.installationRefID!, 'module': 'air', 'type': 'Collection', 'status': result['status'], 'message': result['message'], 'report_id': data.airManId.toString(), 'created_at': DateTime.now().toIso8601String(), 'form_data': jsonEncode(data.toMap()), 'image_data': jsonEncode(_getCollectionImagePaths(data)), 'server_name': serverName, 'api_status': jsonEncode([{"server_name": serverName, "status": "SUCCESS", "message": "Text data submitted."}]), 'ftp_status': jsonEncode([{"server_name": "N/A", "status": "FAILED", "message": "Image upload failed."}]), }; debugPrint("DB LOGGING (Collection L4/Image Fail): Status: ${logData['status']}, API Status: ${logData['api_status']}, FTP Status: ${logData['ftp_status']}"); // LOG DEBUG END // --- MODIFIED: Log the failed submission to the central database --- await _dbHelper.saveSubmissionLog(logData); return result; } debugPrint("Images uploaded successfully."); final result = { 'status': 'S3', 'message': 'Collection data and images submitted successfully.', }; // LOG DEBUG START final logData = { 'submission_id': data.installationRefID!, 'module': 'air', 'type': 'Collection', 'status': result['status'], 'message': result['message'], 'report_id': data.airManId.toString(), 'created_at': DateTime.now().toIso8601String(), 'form_data': jsonEncode(data.toMap()), 'image_data': jsonEncode(_getCollectionImagePaths(data)), 'server_name': serverName, 'api_status': jsonEncode([{"server_name": serverName, "status": "SUCCESS", "message": "Text and image data submitted."}]), 'ftp_status': jsonEncode([{"server_name": "N/A", "status": "NOT_APPLICABLE", "message": "Not applicable."}]), }; debugPrint("DB LOGGING (Collection S3/Success): Status: ${logData['status']}, API Status: ${logData['api_status']}, FTP Status: ${logData['ftp_status']}"); // LOG DEBUG END // --- MODIFIED: Log the successful submission to the central database --- await _dbHelper.saveSubmissionLog(logData); // Update the local file with the final success status await localStorageService.saveAirSamplingRecord(_toMapForLocalSave(data), data.installationRefID!, serverName: serverName); _handleCollectionSuccessAlert(data, installationData, appSettings, isDataOnly: false); return result; } /// Fetches installations that are pending collection from local storage. Future> getPendingInstallations() async { debugPrint("Fetching pending installations from local storage..."); final logs = await _dbHelper.loadSubmissionLogs(module: 'air'); final pendingInstallations = logs ?.where((log) { final status = log['status']; // --- CORRECTED --- // Only show installations that have been synced to the server (S1, S2). // 'L1' (Local only) records cannot be collected until they are synced. return status == 'S1' || status == 'S2'; }) .map((log) => AirInstallationData.fromJson(jsonDecode(log['form_data']))) .toList() ?? []; return pendingInstallations; } void dispose() { // Clean up any resources if necessary } }