environment_monitoring_app/lib/services/marine_tarball_sampling_service.dart

194 lines
8.1 KiB
Dart

// lib/services/marine_tarball_sampling_service.dart
import 'dart:io';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:path/path.dart' as p;
import 'package:environment_monitoring_app/models/tarball_data.dart';
import 'package:environment_monitoring_app/services/local_storage_service.dart';
import 'package:environment_monitoring_app/services/server_config_service.dart';
import 'package:environment_monitoring_app/services/zipping_service.dart';
import 'package:environment_monitoring_app/services/api_service.dart';
import 'package:environment_monitoring_app/services/submission_api_service.dart';
import 'package:environment_monitoring_app/services/submission_ftp_service.dart';
import 'package:environment_monitoring_app/services/telegram_service.dart';
/// A dedicated service to handle all business logic for the Marine Tarball Sampling feature.
class MarineTarballSamplingService {
final SubmissionApiService _submissionApiService = SubmissionApiService();
final SubmissionFtpService _submissionFtpService = SubmissionFtpService();
final ZippingService _zippingService = ZippingService();
final LocalStorageService _localStorageService = LocalStorageService();
final ServerConfigService _serverConfigService = ServerConfigService();
final DatabaseHelper _dbHelper = DatabaseHelper();
// MODIFIED: Declare the service, but do not initialize it here.
final TelegramService _telegramService;
// ADDED: A constructor to accept the global TelegramService instance.
MarineTarballSamplingService(this._telegramService);
Future<Map<String, dynamic>> submitTarballSample({
required TarballSamplingData data,
required List<Map<String, dynamic>>? appSettings,
}) async {
const String moduleName = 'marine_tarball';
final serverName = (await _serverConfigService.getActiveApiConfig())?['config_name'] as String? ?? 'Default';
final imageFilesWithNulls = data.toImageFiles();
imageFilesWithNulls.removeWhere((key, value) => value == null);
final Map<String, File> finalImageFiles = imageFilesWithNulls.cast<String, File>();
// START CHANGE: Revert to the correct two-step API submission process
// --- Step 1A: API Data Submission ---
debugPrint("Step 1A: Submitting Tarball form data...");
final apiDataResult = await _submissionApiService.submitPost(
moduleName: moduleName,
endpoint: 'marine/tarball/sample',
body: data.toFormData(),
);
if (apiDataResult['success'] != true) {
// If the initial data submission fails, log and exit early.
await _logAndSave(data: data, status: 'L1', message: apiDataResult['message']!, apiResults: [apiDataResult], ftpStatuses: [], serverName: serverName, finalImageFiles: finalImageFiles);
return {'success': false, 'message': apiDataResult['message']};
}
final recordId = apiDataResult['data']?['autoid']?.toString();
if (recordId == null) {
await _logAndSave(data: data, status: 'L1', message: 'API Error: Missing record ID.', apiResults: [apiDataResult], ftpStatuses: [], serverName: serverName, finalImageFiles: finalImageFiles);
return {'success': false, 'message': 'API Error: Missing record ID.'};
}
data.reportId = recordId;
// --- Step 1B: API Image Submission ---
debugPrint("Step 1B: Submitting Tarball images...");
final apiImageResult = await _submissionApiService.submitMultipart(
moduleName: moduleName,
endpoint: 'marine/tarball/images',
fields: {'autoid': recordId},
files: finalImageFiles,
);
final bool apiSuccess = apiImageResult['success'] == true;
// END CHANGE
// --- Step 2: FTP Submission ---
final stationCode = data.selectedStation?['tbl_station_code'] ?? 'NA';
final fileTimestamp = "${data.samplingDate}_${data.samplingTime}".replaceAll(':', '-').replaceAll(' ', '_');
final baseFileName = '${stationCode}_$fileTimestamp';
final Directory? logDirectory = await _localStorageService.getLogDirectory(
serverName: serverName,
module: 'marine',
subModule: 'marine_tarball_sampling',
);
final Directory? localSubmissionDir = logDirectory != null ? Directory(p.join(logDirectory.path, data.reportId ?? baseFileName)) : null;
if (localSubmissionDir != null && !await localSubmissionDir.exists()) {
await localSubmissionDir.create(recursive: true);
}
final dataZip = await _zippingService.createDataZip(
jsonDataMap: {'data.json': jsonEncode(data.toDbJson())},
baseFileName: baseFileName,
destinationDir: localSubmissionDir,
);
Map<String, dynamic> ftpDataResult = {'success': true, 'statuses': []};
if (dataZip != null) {
ftpDataResult = await _submissionFtpService.submit(
moduleName: moduleName,
fileToUpload: dataZip,
remotePath: '/${p.basename(dataZip.path)}',
);
}
final imageZip = await _zippingService.createImageZip(
imageFiles: finalImageFiles.values.toList(),
baseFileName: baseFileName,
destinationDir: localSubmissionDir,
);
Map<String, dynamic> ftpImageResult = {'success': true, 'statuses': []};
if (imageZip != null) {
ftpImageResult = await _submissionFtpService.submit(
moduleName: moduleName,
fileToUpload: imageZip,
remotePath: '/${p.basename(imageZip.path)}',
);
}
final bool ftpSuccess = (ftpDataResult['success'] == true && ftpImageResult['success'] == true);
// --- Step 3: Finalize and Log ---
String finalStatus;
String finalMessage;
if (apiSuccess) {
finalStatus = ftpSuccess ? 'S4' : 'S3';
finalMessage = ftpSuccess ? 'Data submitted successfully.' : 'Data sent to API. FTP upload failed/queued.';
} else {
finalStatus = ftpSuccess ? 'L4' : 'L1';
finalMessage = ftpSuccess ? 'API failed, but files sent to FTP.' : 'All submission attempts failed.';
}
await _logAndSave(
data: data,
status: finalStatus,
message: finalMessage,
apiResults: [apiDataResult, apiImageResult], // Log results from both API steps
ftpStatuses: [...ftpDataResult['statuses'], ...ftpImageResult['statuses']],
serverName: serverName,
finalImageFiles: finalImageFiles
);
if (apiSuccess || ftpSuccess) {
_handleTarballSuccessAlert(data, appSettings, isDataOnly: !apiSuccess);
}
return {'success': apiSuccess || ftpSuccess, 'message': finalMessage, 'reportId': data.reportId};
}
// Added a helper to reduce code duplication in the main submit method
Future<void> _logAndSave({
required TarballSamplingData data,
required String status,
required String message,
required List<Map<String, dynamic>> apiResults,
required List<Map<String, dynamic>> ftpStatuses,
required String serverName,
required Map<String, File> finalImageFiles,
}) async {
data.submissionStatus = status;
data.submissionMessage = message;
final fileTimestamp = "${data.samplingDate}_${data.samplingTime}".replaceAll(':', '-').replaceAll(' ', '_');
await _localStorageService.saveTarballSamplingData(data, serverName: serverName);
final logData = {
'submission_id': data.reportId ?? fileTimestamp,
'module': 'marine',
'type': 'Tarball',
'status': status,
'message': message,
'report_id': data.reportId,
'created_at': DateTime.now().toIso8601String(),
'form_data': jsonEncode(data.toDbJson()),
'image_data': jsonEncode(finalImageFiles.values.map((f) => f.path).toList()),
'server_name': serverName,
'api_status': jsonEncode(apiResults),
'ftp_status': jsonEncode(ftpStatuses),
};
await _dbHelper.saveSubmissionLog(logData);
}
Future<void> _handleTarballSuccessAlert(TarballSamplingData data, List<Map<String, dynamic>>? appSettings, {required bool isDataOnly}) async {
try {
final message = data.generateTelegramAlertMessage(isDataOnly: isDataOnly);
final bool wasSent = await _telegramService.sendAlertImmediately('marine_tarball', message, appSettings);
if (!wasSent) {
await _telegramService.queueMessage('marine_tarball', message, appSettings);
}
} catch (e) {
debugPrint("Failed to handle Tarball Telegram alert: $e");
}
}
}