650 lines
29 KiB
Dart
650 lines
29 KiB
Dart
// 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<String, dynamic> _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<File?> 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<void> _handleInstallationSuccessAlert(AirInstallationData data, List<Map<String, dynamic>>? 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<void> _handleCollectionSuccessAlert(AirCollectionData data, AirInstallationData installationData, List<Map<String, dynamic>>? 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<String> _getInstallationImagePaths(AirInstallationData data) {
|
|
final List<File?> 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<String> _getCollectionImagePaths(AirCollectionData data) {
|
|
final List<File?> 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<Map<String, dynamic>> submitInstallation(AirInstallationData data, List<Map<String, dynamic>>? 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<Map<String, dynamic>> _uploadInstallationImagesAndUpdate(AirInstallationData data, List<Map<String, dynamic>>? 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<Map<String, dynamic>> submitCollection(AirCollectionData data, AirInstallationData installationData, List<Map<String, dynamic>>? 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<Map<String, dynamic>> _uploadCollectionImagesAndUpdate(AirCollectionData data, AirInstallationData installationData, List<Map<String, dynamic>>? 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<List<AirInstallationData>> 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
|
|
}
|
|
} |