add telegram alert for marine report module
This commit is contained in:
parent
05d29bc107
commit
033391b770
@ -132,9 +132,10 @@ void main() async {
|
|||||||
|
|
||||||
// --- START: Instantiate Marine Report Services ---
|
// --- START: Instantiate Marine Report Services ---
|
||||||
final MarineNpeReportService marineNpeService = MarineNpeReportService(telegramService);
|
final MarineNpeReportService marineNpeService = MarineNpeReportService(telegramService);
|
||||||
final MarineManualPreDepartureService marinePreDepartureService = MarineManualPreDepartureService(apiService);
|
// FIX: Added telegramService to constructors below
|
||||||
final MarineManualSondeCalibrationService marineSondeCalibrationService = MarineManualSondeCalibrationService(apiService);
|
final MarineManualPreDepartureService marinePreDepartureService = MarineManualPreDepartureService(apiService, telegramService);
|
||||||
final MarineManualEquipmentMaintenanceService marineEquipmentMaintenanceService = MarineManualEquipmentMaintenanceService(apiService);
|
final MarineManualSondeCalibrationService marineSondeCalibrationService = MarineManualSondeCalibrationService(apiService, telegramService);
|
||||||
|
final MarineManualEquipmentMaintenanceService marineEquipmentMaintenanceService = MarineManualEquipmentMaintenanceService(apiService, telegramService);
|
||||||
// --- END: Instantiate Marine Report Services ---
|
// --- END: Instantiate Marine Report Services ---
|
||||||
|
|
||||||
telegramService.setApiService(apiService);
|
telegramService.setApiService(apiService);
|
||||||
|
|||||||
@ -4,9 +4,7 @@ import 'dart:convert';
|
|||||||
|
|
||||||
class MarineManualEquipmentMaintenanceData {
|
class MarineManualEquipmentMaintenanceData {
|
||||||
int? conductedByUserId;
|
int? conductedByUserId;
|
||||||
// --- START: ADDED FIELD ---
|
|
||||||
String? conductedByUserName;
|
String? conductedByUserName;
|
||||||
// --- END: ADDED FIELD ---
|
|
||||||
String? maintenanceDate;
|
String? maintenanceDate;
|
||||||
String? lastMaintenanceDate;
|
String? lastMaintenanceDate;
|
||||||
String? scheduleMaintenance;
|
String? scheduleMaintenance;
|
||||||
@ -30,11 +28,9 @@ class MarineManualEquipmentMaintenanceData {
|
|||||||
String? vanDornNewSerial;
|
String? vanDornNewSerial;
|
||||||
Map<String, Map<String, String>> vanDornReplacements = {};
|
Map<String, Map<String, String>> vanDornReplacements = {};
|
||||||
|
|
||||||
// --- START: Added Fields ---
|
|
||||||
String? submissionStatus;
|
String? submissionStatus;
|
||||||
String? submissionMessage;
|
String? submissionMessage;
|
||||||
String? reportId;
|
String? reportId;
|
||||||
// --- END: Added Fields ---
|
|
||||||
|
|
||||||
|
|
||||||
// Constructor to initialize maps
|
// Constructor to initialize maps
|
||||||
@ -76,7 +72,6 @@ class MarineManualEquipmentMaintenanceData {
|
|||||||
vanDornReplacements[item] = {'Last Date': '', 'New Date': ''});
|
vanDornReplacements[item] = {'Last Date': '', 'New Date': ''});
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- START: ADDED METHOD ---
|
|
||||||
/// Creates a JSON object for offline database storage.
|
/// Creates a JSON object for offline database storage.
|
||||||
Map<String, dynamic> toDbJson() {
|
Map<String, dynamic> toDbJson() {
|
||||||
return {
|
return {
|
||||||
@ -104,7 +99,6 @@ class MarineManualEquipmentMaintenanceData {
|
|||||||
'reportId': reportId,
|
'reportId': reportId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// --- END: ADDED METHOD ---
|
|
||||||
|
|
||||||
// MODIFIED: This method now builds the complex nested structure the PHP controller expects.
|
// MODIFIED: This method now builds the complex nested structure the PHP controller expects.
|
||||||
Map<String, dynamic> toApiFormData() {
|
Map<String, dynamic> toApiFormData() {
|
||||||
@ -178,4 +172,32 @@ class MarineManualEquipmentMaintenanceData {
|
|||||||
'van_dorn_replacements': vanDornReplacementsList,
|
'van_dorn_replacements': vanDornReplacementsList,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- START: ADDED TELEGRAM METHOD ---
|
||||||
|
String generateTelegramAlertMessage() {
|
||||||
|
final buffer = StringBuffer()
|
||||||
|
..writeln('🛠 *Equipment Maintenance Report Submitted*')
|
||||||
|
..writeln()
|
||||||
|
..writeln('*Conducted By:* ${conductedByUserName ?? "N/A"}')
|
||||||
|
..writeln('*Date:* ${maintenanceDate ?? "N/A"}')
|
||||||
|
..writeln('*Time:* ${timeStart ?? "-"} to ${timeEnd ?? "-"}')
|
||||||
|
..writeln('*Location:* ${location ?? "N/A"}')
|
||||||
|
..writeln('*Type:* ${isReplacement ? "Replacement" : "Routine Check"}')
|
||||||
|
..writeln('*Schedule:* ${scheduleMaintenance ?? "N/A"}');
|
||||||
|
|
||||||
|
if (ysiSondeComments != null && ysiSondeComments!.isNotEmpty) {
|
||||||
|
buffer.writeln('\n*YSI Remarks:* $ysiSondeComments');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vanDornComments != null && vanDornComments!.isNotEmpty) {
|
||||||
|
buffer.writeln('\n*Van Dorn Remarks:* $vanDornComments');
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer
|
||||||
|
..writeln()
|
||||||
|
..writeln('*Status of Submission:* Successful');
|
||||||
|
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
// --- END: ADDED TELEGRAM METHOD ---
|
||||||
}
|
}
|
||||||
@ -113,16 +113,29 @@ class MarineManualNpeReportData {
|
|||||||
add('npe_time', eventTime);
|
add('npe_time', eventTime);
|
||||||
add('first_sampler_user_id', firstSamplerUserId);
|
add('first_sampler_user_id', firstSamplerUserId);
|
||||||
|
|
||||||
// add('npe_source_origin', sourceOrigin); // Disabled to prevent SQL error
|
// --- FIX START: Correctly map 'station_id' to specific API fields ---
|
||||||
|
|
||||||
if (selectedStation != null) {
|
if (selectedStation != null) {
|
||||||
add('station_id', selectedStation?['station_id'] ?? selectedStation?['tbl_station_id']);
|
// Check if it is a Manual Station (has 'man_station_code')
|
||||||
add('station_code', selectedStation?['man_station_code'] ?? selectedStation?['tbl_station_code']);
|
if (selectedStation!.containsKey('man_station_code')) {
|
||||||
add('station_name', selectedStation?['man_station_name'] ?? selectedStation?['tbl_station_name']);
|
// Map generic 'station_id' to API's expected 'man_station_id'
|
||||||
|
add('man_station_id', selectedStation!['station_id'] ?? selectedStation!['man_station_id']);
|
||||||
|
add('station_code', selectedStation!['man_station_code']);
|
||||||
|
add('station_name', selectedStation!['man_station_name']);
|
||||||
|
}
|
||||||
|
// Check if it is a Tarball Station (has 'tbl_station_code')
|
||||||
|
else if (selectedStation!.containsKey('tbl_station_code')) {
|
||||||
|
// Map generic 'station_id' to API's expected 'tbl_station_id'
|
||||||
|
add('tbl_station_id', selectedStation!['station_id'] ?? selectedStation!['tbl_station_id']);
|
||||||
|
add('station_code', selectedStation!['tbl_station_code']);
|
||||||
|
add('station_name', selectedStation!['tbl_station_name']);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
add('station_name', locationDescription);
|
// New Location: Use description
|
||||||
|
add('location_description', locationDescription);
|
||||||
add('state_name', stateName);
|
add('state_name', stateName);
|
||||||
}
|
}
|
||||||
|
// --- FIX END ---
|
||||||
|
|
||||||
add('latitude', latitude);
|
add('latitude', latitude);
|
||||||
add('longitude', longitude);
|
add('longitude', longitude);
|
||||||
add('npe_oxygen_sat', oxygenSaturation);
|
add('npe_oxygen_sat', oxygenSaturation);
|
||||||
@ -166,7 +179,6 @@ class MarineManualNpeReportData {
|
|||||||
String generateTelegramAlertMessage() {
|
String generateTelegramAlertMessage() {
|
||||||
String locationDesc;
|
String locationDesc;
|
||||||
|
|
||||||
// --- START: MODIFIED to include Station Code + Name ---
|
|
||||||
if (selectedStation != null) {
|
if (selectedStation != null) {
|
||||||
final code = selectedStation!['man_station_code'] ?? selectedStation!['tbl_station_code'] ?? 'N/A';
|
final code = selectedStation!['man_station_code'] ?? selectedStation!['tbl_station_code'] ?? 'N/A';
|
||||||
final name = selectedStation!['man_station_name'] ?? selectedStation!['tbl_station_name'] ?? 'N/A';
|
final name = selectedStation!['man_station_name'] ?? selectedStation!['tbl_station_name'] ?? 'N/A';
|
||||||
@ -174,7 +186,6 @@ class MarineManualNpeReportData {
|
|||||||
} else {
|
} else {
|
||||||
locationDesc = locationDescription ?? 'A custom location';
|
locationDesc = locationDescription ?? 'A custom location';
|
||||||
}
|
}
|
||||||
// --- END: MODIFIED ---
|
|
||||||
|
|
||||||
final buffer = StringBuffer()
|
final buffer = StringBuffer()
|
||||||
..writeln('🚨 *Notification of Pollution Event (NPE) Submitted:*')
|
..writeln('🚨 *Notification of Pollution Event (NPE) Submitted:*')
|
||||||
|
|||||||
@ -6,9 +6,7 @@ class MarineManualPreDepartureChecklistData {
|
|||||||
String? reporterName;
|
String? reporterName;
|
||||||
int? reporterUserId;
|
int? reporterUserId;
|
||||||
String? submissionDate;
|
String? submissionDate;
|
||||||
// --- START: ADDED FIELD ---
|
|
||||||
String? location;
|
String? location;
|
||||||
// --- END: ADDED FIELD ---
|
|
||||||
|
|
||||||
// Key: Item description, Value: true if 'Yes', false if 'No'
|
// Key: Item description, Value: true if 'Yes', false if 'No'
|
||||||
Map<String, bool> checklistItems = {};
|
Map<String, bool> checklistItems = {};
|
||||||
@ -16,22 +14,19 @@ class MarineManualPreDepartureChecklistData {
|
|||||||
// Key: Item description, Value: Remarks text
|
// Key: Item description, Value: Remarks text
|
||||||
Map<String, String> remarks = {};
|
Map<String, String> remarks = {};
|
||||||
|
|
||||||
// --- START: Added Fields ---
|
|
||||||
String? submissionStatus;
|
String? submissionStatus;
|
||||||
String? submissionMessage;
|
String? submissionMessage;
|
||||||
String? reportId;
|
String? reportId;
|
||||||
// --- END: Added Fields ---
|
|
||||||
|
|
||||||
MarineManualPreDepartureChecklistData();
|
MarineManualPreDepartureChecklistData();
|
||||||
|
|
||||||
// --- START: ADDED METHOD ---
|
|
||||||
/// Creates a JSON object for offline database storage.
|
/// Creates a JSON object for offline database storage.
|
||||||
Map<String, dynamic> toDbJson() {
|
Map<String, dynamic> toDbJson() {
|
||||||
return {
|
return {
|
||||||
'reporterName': reporterName,
|
'reporterName': reporterName,
|
||||||
'reporterUserId': reporterUserId,
|
'reporterUserId': reporterUserId,
|
||||||
'submissionDate': submissionDate,
|
'submissionDate': submissionDate,
|
||||||
'location': location, // <-- ADDED
|
'location': location,
|
||||||
'checklistItems': checklistItems,
|
'checklistItems': checklistItems,
|
||||||
'remarks': remarks,
|
'remarks': remarks,
|
||||||
'submissionStatus': submissionStatus,
|
'submissionStatus': submissionStatus,
|
||||||
@ -39,7 +34,6 @@ class MarineManualPreDepartureChecklistData {
|
|||||||
'reportId': reportId,
|
'reportId': reportId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// --- END: ADDED METHOD ---
|
|
||||||
|
|
||||||
// MODIFIED: This method now builds the nested array structure the PHP controller expects.
|
// MODIFIED: This method now builds the nested array structure the PHP controller expects.
|
||||||
Map<String, dynamic> toApiFormData() {
|
Map<String, dynamic> toApiFormData() {
|
||||||
@ -57,12 +51,42 @@ class MarineManualPreDepartureChecklistData {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'reporter_user_id': reporterUserId.toString(), // The controller gets this from auth, but good to send.
|
'reporter_user_id': reporterUserId.toString(),
|
||||||
'submission_date': submissionDate,
|
'submission_date': submissionDate,
|
||||||
// Note: 'location' is not sent to the API in this method,
|
// Note: 'location' is not sent to the API in this method, but saved in db.json
|
||||||
// but it will be saved in the local log via toDbJson().
|
|
||||||
// If the API needs it, it must be added here.
|
|
||||||
'items': itemsList, // Send the formatted list
|
'items': itemsList, // Send the formatted list
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- START: ADDED TELEGRAM METHOD ---
|
||||||
|
String generateTelegramAlertMessage() {
|
||||||
|
int checkedCount = checklistItems.values.where((v) => v == true).length;
|
||||||
|
int totalCount = checklistItems.length;
|
||||||
|
|
||||||
|
final buffer = StringBuffer()
|
||||||
|
..writeln('🚤 *Pre-Departure Checklist Submitted*')
|
||||||
|
..writeln()
|
||||||
|
..writeln('*Reporter:* ${reporterName ?? "N/A"}')
|
||||||
|
..writeln('*Date:* ${submissionDate ?? "N/A"}')
|
||||||
|
..writeln('*Location:* ${location ?? "N/A"}')
|
||||||
|
..writeln('*Items Checked:* $checkedCount / $totalCount');
|
||||||
|
|
||||||
|
// Add remarks only if they exist for specific items
|
||||||
|
final activeRemarks = remarks.entries
|
||||||
|
.where((e) => e.value.trim().isNotEmpty)
|
||||||
|
.map((e) => "- ${e.key}: ${e.value}")
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
if (activeRemarks.isNotEmpty) {
|
||||||
|
buffer.writeln('\n*Specific Remarks:*');
|
||||||
|
buffer.writeAll(activeRemarks, '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer
|
||||||
|
..writeln()
|
||||||
|
..writeln('*Status of Submission:* Successful');
|
||||||
|
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
// --- END: ADDED TELEGRAM METHOD ---
|
||||||
}
|
}
|
||||||
@ -2,9 +2,7 @@
|
|||||||
|
|
||||||
class MarineManualSondeCalibrationData {
|
class MarineManualSondeCalibrationData {
|
||||||
int? calibratedByUserId;
|
int? calibratedByUserId;
|
||||||
// --- START: ADDED FIELD ---
|
|
||||||
String? calibratedByUserName;
|
String? calibratedByUserName;
|
||||||
// --- END: ADDED FIELD ---
|
|
||||||
|
|
||||||
// Header fields from PDF
|
// Header fields from PDF
|
||||||
String? sondeSerialNumber;
|
String? sondeSerialNumber;
|
||||||
@ -22,7 +20,7 @@ class MarineManualSondeCalibrationData {
|
|||||||
double? ph10Before;
|
double? ph10Before;
|
||||||
double? ph10After;
|
double? ph10After;
|
||||||
|
|
||||||
// Other parameters (Mv removed per PDF)
|
// Other parameters
|
||||||
double? condBefore;
|
double? condBefore;
|
||||||
double? condAfter;
|
double? condAfter;
|
||||||
double? doBefore;
|
double? doBefore;
|
||||||
@ -33,16 +31,13 @@ class MarineManualSondeCalibrationData {
|
|||||||
double? turbidity124After;
|
double? turbidity124After;
|
||||||
|
|
||||||
String? calibrationStatus;
|
String? calibrationStatus;
|
||||||
String? remarks; // Matches "COMMENT/OBSERVATION"
|
String? remarks;
|
||||||
|
|
||||||
// --- START: Added Fields ---
|
|
||||||
String? submissionStatus;
|
String? submissionStatus;
|
||||||
String? submissionMessage;
|
String? submissionMessage;
|
||||||
String? reportId;
|
String? reportId;
|
||||||
// --- END: Added Fields ---
|
|
||||||
|
|
||||||
Map<String, dynamic> toApiFormData() {
|
Map<String, dynamic> toApiFormData() {
|
||||||
// This flat structure matches MarineSondeCalibrationController.php
|
|
||||||
return {
|
return {
|
||||||
'calibrated_by_user_id': calibratedByUserId.toString(),
|
'calibrated_by_user_id': calibratedByUserId.toString(),
|
||||||
'sonde_serial_number': sondeSerialNumber,
|
'sonde_serial_number': sondeSerialNumber,
|
||||||
@ -70,12 +65,11 @@ class MarineManualSondeCalibrationData {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- START: ADDED toDbJson METHOD ---
|
|
||||||
/// Creates a JSON object for offline database storage.
|
/// Creates a JSON object for offline database storage.
|
||||||
Map<String, dynamic> toDbJson() {
|
Map<String, dynamic> toDbJson() {
|
||||||
return {
|
return {
|
||||||
'calibratedByUserId': calibratedByUserId,
|
'calibratedByUserId': calibratedByUserId,
|
||||||
'calibratedByUserName': calibratedByUserName, // <-- ADDED
|
'calibratedByUserName': calibratedByUserName,
|
||||||
'sondeSerialNumber': sondeSerialNumber,
|
'sondeSerialNumber': sondeSerialNumber,
|
||||||
'firmwareVersion': firmwareVersion,
|
'firmwareVersion': firmwareVersion,
|
||||||
'korVersion': korVersion,
|
'korVersion': korVersion,
|
||||||
@ -103,5 +97,31 @@ class MarineManualSondeCalibrationData {
|
|||||||
'reportId': reportId,
|
'reportId': reportId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// --- END: ADDED toDbJson METHOD ---
|
|
||||||
|
// --- START: ADDED TELEGRAM METHOD ---
|
||||||
|
String generateTelegramAlertMessage() {
|
||||||
|
final buffer = StringBuffer()
|
||||||
|
..writeln('⚖️ *Sonde Calibration Report Submitted*')
|
||||||
|
..writeln()
|
||||||
|
..writeln('*Calibrated By:* ${calibratedByUserName ?? "N/A"}')
|
||||||
|
..writeln('*Location:* ${location ?? "N/A"}')
|
||||||
|
..writeln('*Sonde Serial:* ${sondeSerialNumber ?? "N/A"}')
|
||||||
|
..writeln('*Start:* ${startDateTime ?? "N/A"}')
|
||||||
|
..writeln('*End:* ${endDateTime ?? "N/A"}');
|
||||||
|
|
||||||
|
if (calibrationStatus != null && calibrationStatus!.isNotEmpty) {
|
||||||
|
buffer.writeln('*Result:* $calibrationStatus');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remarks != null && remarks!.isNotEmpty) {
|
||||||
|
buffer.writeln('\n*Remarks:* $remarks');
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer
|
||||||
|
..writeln()
|
||||||
|
..writeln('*Status of Submission:* Successful');
|
||||||
|
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
// --- END: ADDED TELEGRAM METHOD ---
|
||||||
}
|
}
|
||||||
@ -368,12 +368,29 @@ class _NPEReportFromInSituState extends State<NPEReportFromInSitu> with WidgetsB
|
|||||||
if (_isPickingImage) return;
|
if (_isPickingImage) return;
|
||||||
setState(() => _isPickingImage = true);
|
setState(() => _isPickingImage = true);
|
||||||
|
|
||||||
|
// --- FIX START: Extract correct code and name for proper filename generation ---
|
||||||
|
String stationName = _locationController.text;
|
||||||
|
String stationCode = 'NA';
|
||||||
|
|
||||||
|
// Check _npeData first as it is the source of truth for the selected station
|
||||||
|
if (_npeData.selectedStation != null) {
|
||||||
|
stationCode = _npeData.selectedStation!['man_station_code'] ??
|
||||||
|
_npeData.selectedStation!['tbl_station_code'] ??
|
||||||
|
'NA';
|
||||||
|
}
|
||||||
|
// --- FIX END ---
|
||||||
|
|
||||||
final watermarkData = InSituSamplingData()
|
final watermarkData = InSituSamplingData()
|
||||||
..samplingDate = _eventDateTimeController.text.split(' ')[0]
|
..samplingDate = _eventDateTimeController.text.split(' ')[0]
|
||||||
..samplingTime = _eventDateTimeController.text.split(' ').length > 1 ? _eventDateTimeController.text.split(' ')[1] : ''
|
..samplingTime = _eventDateTimeController.text.split(' ').length > 1 ? _eventDateTimeController.text.split(' ')[1] : ''
|
||||||
..currentLatitude = _latController.text
|
..currentLatitude = _latController.text
|
||||||
..currentLongitude = _longController.text
|
..currentLongitude = _longController.text
|
||||||
..selectedStation = {'man_station_name': _locationController.text};
|
// --- FIX: Pass BOTH code and name to the image service ---
|
||||||
|
..selectedStation = {
|
||||||
|
'man_station_name': stationName,
|
||||||
|
'man_station_code': stationCode,
|
||||||
|
'tbl_station_code': stationCode, // Fallback depending on processor logic
|
||||||
|
};
|
||||||
|
|
||||||
final file = await _samplingService.pickAndProcessImage(
|
final file = await _samplingService.pickAndProcessImage(
|
||||||
source,
|
source,
|
||||||
|
|||||||
@ -249,12 +249,29 @@ class _NPEReportFromTarballState extends State<NPEReportFromTarball> with Widget
|
|||||||
if (_isPickingImage) return;
|
if (_isPickingImage) return;
|
||||||
setState(() => _isPickingImage = true);
|
setState(() => _isPickingImage = true);
|
||||||
|
|
||||||
|
// --- FIX START: Extract correct code and name for proper filename generation ---
|
||||||
|
String stationName = _locationController.text;
|
||||||
|
String stationCode = 'NA';
|
||||||
|
|
||||||
|
// Check _npeData first as it is the source of truth for the selected station
|
||||||
|
if (_npeData.selectedStation != null) {
|
||||||
|
stationCode = _npeData.selectedStation!['man_station_code'] ??
|
||||||
|
_npeData.selectedStation!['tbl_station_code'] ??
|
||||||
|
'NA';
|
||||||
|
}
|
||||||
|
// --- FIX END ---
|
||||||
|
|
||||||
final watermarkData = InSituSamplingData()
|
final watermarkData = InSituSamplingData()
|
||||||
..samplingDate = _eventDateTimeController.text.split(' ')[0]
|
..samplingDate = _eventDateTimeController.text.split(' ')[0]
|
||||||
..samplingTime = _eventDateTimeController.text.split(' ').length > 1 ? _eventDateTimeController.text.split(' ')[1] : ''
|
..samplingTime = _eventDateTimeController.text.split(' ').length > 1 ? _eventDateTimeController.text.split(' ')[1] : ''
|
||||||
..currentLatitude = _latController.text
|
..currentLatitude = _latController.text
|
||||||
..currentLongitude = _longController.text
|
..currentLongitude = _longController.text
|
||||||
..selectedStation = {'man_station_name': _locationController.text};
|
// --- FIX: Pass BOTH code and name to the image service ---
|
||||||
|
..selectedStation = {
|
||||||
|
'man_station_name': stationName,
|
||||||
|
'man_station_code': stationCode,
|
||||||
|
'tbl_station_code': stationCode, // Fallback depending on processor logic
|
||||||
|
};
|
||||||
|
|
||||||
final file = await _samplingService.pickAndProcessImage(
|
final file = await _samplingService.pickAndProcessImage(
|
||||||
source,
|
source,
|
||||||
|
|||||||
@ -234,12 +234,28 @@ class _NPEReportNewLocationState extends State<NPEReportNewLocation> with Widget
|
|||||||
if (_isPickingImage) return;
|
if (_isPickingImage) return;
|
||||||
setState(() => _isPickingImage = true);
|
setState(() => _isPickingImage = true);
|
||||||
|
|
||||||
|
// --- FIX START: Sanitize Location Description for Filename ---
|
||||||
|
String rawLocation = _locationController.text.trim();
|
||||||
|
String filenamePrefix;
|
||||||
|
|
||||||
|
if (rawLocation.isNotEmpty) {
|
||||||
|
// Replace spaces with underscores for the filename
|
||||||
|
filenamePrefix = rawLocation.replaceAll(' ', '_');
|
||||||
|
} else {
|
||||||
|
// Fallback if the user hasn't typed anything yet
|
||||||
|
filenamePrefix = 'NEW_LOCATION';
|
||||||
|
}
|
||||||
|
// --- FIX END ---
|
||||||
|
|
||||||
final watermarkData = InSituSamplingData()
|
final watermarkData = InSituSamplingData()
|
||||||
..samplingDate = _eventDateTimeController.text.split(' ')[0]
|
..samplingDate = _eventDateTimeController.text.split(' ')[0]
|
||||||
..samplingTime = _eventDateTimeController.text.split(' ').length > 1 ? _eventDateTimeController.text.split(' ')[1] : ''
|
..samplingTime = _eventDateTimeController.text.split(' ').length > 1 ? _eventDateTimeController.text.split(' ')[1] : ''
|
||||||
..currentLatitude = _latController.text
|
..currentLatitude = _latController.text
|
||||||
..currentLongitude = _longController.text
|
..currentLongitude = _longController.text
|
||||||
..selectedStation = {'man_station_name': _locationController.text};
|
..selectedStation = {
|
||||||
|
'man_station_name': rawLocation, // Keep spaces for the Watermark text
|
||||||
|
'man_station_code': filenamePrefix, // Use underscores for the Filename
|
||||||
|
};
|
||||||
|
|
||||||
final file = await _samplingService.pickAndProcessImage(
|
final file = await _samplingService.pickAndProcessImage(
|
||||||
source,
|
source,
|
||||||
@ -618,7 +634,7 @@ class _NPEReportNewLocationState extends State<NPEReportNewLocation> with Widget
|
|||||||
|
|
||||||
Widget _buildNPEImagePicker({
|
Widget _buildNPEImagePicker({
|
||||||
required String title,
|
required String title,
|
||||||
File? imageFile,
|
imageFile,
|
||||||
required VoidCallback onClear,
|
required VoidCallback onClear,
|
||||||
required int imageNumber,
|
required int imageNumber,
|
||||||
required TextEditingController remarkController, // ADDED
|
required TextEditingController remarkController, // ADDED
|
||||||
|
|||||||
@ -37,6 +37,7 @@ class _SubmissionPreferencesSettingsScreenState
|
|||||||
{'key': 'marine_tarball', 'name': 'Marine Tarball'},
|
{'key': 'marine_tarball', 'name': 'Marine Tarball'},
|
||||||
{'key': 'marine_in_situ', 'name': 'Marine In-Situ'},
|
{'key': 'marine_in_situ', 'name': 'Marine In-Situ'},
|
||||||
{'key': 'marine_investigative', 'name': 'Marine Investigative'},
|
{'key': 'marine_investigative', 'name': 'Marine Investigative'},
|
||||||
|
{'key': 'marine_report', 'name': 'Marine Report'},
|
||||||
{'key': 'river_in_situ', 'name': 'River In-Situ'},
|
{'key': 'river_in_situ', 'name': 'River In-Situ'},
|
||||||
{'key': 'river_triennial', 'name': 'River Triennial'},
|
{'key': 'river_triennial', 'name': 'River Triennial'},
|
||||||
{'key': 'river_investigative', 'name': 'River Investigative'},
|
{'key': 'river_investigative', 'name': 'River Investigative'},
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import 'dart:io';
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
|
|
||||||
import '../auth_provider.dart';
|
import '../auth_provider.dart';
|
||||||
import '../models/marine_manual_equipment_maintenance_data.dart';
|
import '../models/marine_manual_equipment_maintenance_data.dart';
|
||||||
@ -14,40 +15,52 @@ 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/server_config_service.dart';
|
||||||
import 'package:environment_monitoring_app/services/retry_service.dart';
|
import 'package:environment_monitoring_app/services/retry_service.dart';
|
||||||
import 'package:environment_monitoring_app/services/submission_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/zipping_service.dart';
|
||||||
|
import 'user_preferences_service.dart'; // ADDED
|
||||||
|
import 'telegram_service.dart'; // --- ADDED IMPORT ---
|
||||||
|
|
||||||
import 'base_api_service.dart'; // Import for SessionExpiredException
|
import 'base_api_service.dart'; // Import for SessionExpiredException
|
||||||
|
|
||||||
class MarineManualEquipmentMaintenanceService {
|
class MarineManualEquipmentMaintenanceService {
|
||||||
// Use the new generic submission service
|
// Use the new generic submission service
|
||||||
final SubmissionApiService _submissionApiService = SubmissionApiService();
|
final SubmissionApiService _submissionApiService = SubmissionApiService();
|
||||||
|
final SubmissionFtpService _submissionFtpService = SubmissionFtpService();
|
||||||
|
final ZippingService _zippingService = ZippingService();
|
||||||
final LocalStorageService _localStorageService = LocalStorageService();
|
final LocalStorageService _localStorageService = LocalStorageService();
|
||||||
final ServerConfigService _serverConfigService = ServerConfigService();
|
final ServerConfigService _serverConfigService = ServerConfigService();
|
||||||
|
final UserPreferencesService _userPreferencesService = UserPreferencesService(); // ADDED
|
||||||
final DatabaseHelper _dbHelper = DatabaseHelper();
|
final DatabaseHelper _dbHelper = DatabaseHelper();
|
||||||
final RetryService _retryService = RetryService();
|
final RetryService _retryService = RetryService();
|
||||||
|
final TelegramService _telegramService; // --- ADDED FIELD ---
|
||||||
|
|
||||||
// Keep ApiService for getPreviousMaintenanceLogs
|
// Keep ApiService for getPreviousMaintenanceLogs
|
||||||
final ApiService _apiService;
|
final ApiService _apiService;
|
||||||
MarineManualEquipmentMaintenanceService(this._apiService);
|
|
||||||
|
|
||||||
// *** START: Renamed this method ***
|
// --- MODIFIED CONSTRUCTOR ---
|
||||||
|
MarineManualEquipmentMaintenanceService(this._apiService, this._telegramService);
|
||||||
|
|
||||||
|
/// Fetches all Maintenance logs stored locally on the device.
|
||||||
|
Future<List<Map<String, dynamic>>> getLocalMaintenanceLogs() async {
|
||||||
|
return await _localStorageService.getAllEquipmentMaintenanceLogs();
|
||||||
|
}
|
||||||
|
|
||||||
/// Main submission method with online/offline branching logic
|
/// Main submission method with online/offline branching logic
|
||||||
Future<Map<String, dynamic>> submitMaintenanceReport({
|
Future<Map<String, dynamic>> submitMaintenanceReport({
|
||||||
// *** END: Renamed this method ***
|
|
||||||
required MarineManualEquipmentMaintenanceData data,
|
required MarineManualEquipmentMaintenanceData data,
|
||||||
required AuthProvider authProvider,
|
required AuthProvider authProvider,
|
||||||
List<Map<String, dynamic>>? appSettings, // Added for consistency
|
List<Map<String, dynamic>>? appSettings,
|
||||||
BuildContext? context, // Added for consistency
|
BuildContext? context,
|
||||||
String? logDirectory,
|
String? logDirectory,
|
||||||
}) async {
|
}) async {
|
||||||
const String moduleName = 'marine_equipment_maintenance';
|
// Unified module name for preferences
|
||||||
|
const String moduleName = 'marine_report';
|
||||||
|
|
||||||
// --- START: ADDED LINE ---
|
|
||||||
// Populate the user name from the AuthProvider
|
// Populate the user name from the AuthProvider
|
||||||
data.conductedByUserName = authProvider.profileData?['first_name'] as String?;
|
data.conductedByUserName = authProvider.profileData?['first_name'] as String?;
|
||||||
// --- END: ADDED LINE ---
|
|
||||||
|
|
||||||
final connectivityResult = await Connectivity().checkConnectivity();
|
final connectivityResult = await Connectivity().checkConnectivity();
|
||||||
bool isOnline = connectivityResult != ConnectivityResult.none;
|
bool isOnline = !connectivityResult.contains(ConnectivityResult.none);
|
||||||
bool isOfflineSession = authProvider.isLoggedIn &&
|
bool isOfflineSession = authProvider.isLoggedIn &&
|
||||||
(authProvider.profileData?['token']
|
(authProvider.profileData?['token']
|
||||||
?.startsWith("offline-session-") ??
|
?.startsWith("offline-session-") ??
|
||||||
@ -93,56 +106,107 @@ class MarineManualEquipmentMaintenanceService {
|
|||||||
(await _serverConfigService.getActiveApiConfig())?['config_name']
|
(await _serverConfigService.getActiveApiConfig())?['config_name']
|
||||||
as String? ??
|
as String? ??
|
||||||
'Default';
|
'Default';
|
||||||
Map<String, dynamic> apiResult;
|
bool anyApiSuccess = false;
|
||||||
|
Map<String, dynamic> apiResult = {};
|
||||||
|
|
||||||
try {
|
// 1. API Submission
|
||||||
apiResult = await _submissionApiService.submitPost(
|
final pref = await _userPreferencesService.getModulePreference(moduleName);
|
||||||
moduleName: moduleName,
|
bool isApiEnabled = pref?['is_api_enabled'] ?? true;
|
||||||
endpoint: 'marine/maintenance', // Endpoint from marine_api_service.dart
|
|
||||||
body: data.toApiFormData(),
|
if (isApiEnabled) {
|
||||||
);
|
try {
|
||||||
} on SessionExpiredException {
|
|
||||||
final bool reloginSuccess = await authProvider.attemptSilentRelogin();
|
|
||||||
if (reloginSuccess) {
|
|
||||||
apiResult = await _submissionApiService.submitPost(
|
apiResult = await _submissionApiService.submitPost(
|
||||||
moduleName: moduleName,
|
moduleName: moduleName,
|
||||||
endpoint: 'marine/maintenance',
|
endpoint: 'marine/maintenance', // Endpoint from marine_api_service.dart
|
||||||
body: data.toApiFormData(),
|
body: data.toApiFormData(),
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
|
if (apiResult['success'] == false && (apiResult['message'] as String?)?.contains('Unauthorized') == true) {
|
||||||
|
// Handle silent relogin
|
||||||
|
if (await authProvider.attemptSilentRelogin()) {
|
||||||
|
apiResult = await _submissionApiService.submitPost(
|
||||||
|
moduleName: moduleName,
|
||||||
|
endpoint: 'marine/maintenance',
|
||||||
|
body: data.toApiFormData(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apiResult['success'] == true) {
|
||||||
|
anyApiSuccess = true;
|
||||||
|
data.reportId = apiResult['data']?['maintenance_id']?.toString();
|
||||||
|
}
|
||||||
|
} on SocketException catch (e) {
|
||||||
apiResult = {
|
apiResult = {
|
||||||
'success': false,
|
'success': false,
|
||||||
'message': 'Session expired. Please log in again.'
|
'message': "API submission failed with network error: $e"
|
||||||
|
};
|
||||||
|
// Queue API manually if failed
|
||||||
|
await _retryService.addApiToQueue(endpoint: 'marine/maintenance', method: 'POST', body: data.toApiFormData());
|
||||||
|
} on TimeoutException catch (e) {
|
||||||
|
apiResult = {
|
||||||
|
'success': false,
|
||||||
|
'message': "API submission timed out: $e"
|
||||||
|
};
|
||||||
|
await _retryService.addApiToQueue(endpoint: 'marine/maintenance', method: 'POST', body: data.toApiFormData());
|
||||||
|
} catch (e) {
|
||||||
|
apiResult = {
|
||||||
|
'success': false,
|
||||||
|
'message': 'An unexpected error occurred: $e'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} on SocketException catch (e) {
|
} else {
|
||||||
apiResult = {
|
anyApiSuccess = true; // Treat as success if disabled by user
|
||||||
'success': false,
|
|
||||||
'message': "API submission failed with network error: $e"
|
|
||||||
};
|
|
||||||
// submission_api_service will queue this failure
|
|
||||||
} on TimeoutException catch (e) {
|
|
||||||
apiResult = {
|
|
||||||
'success': false,
|
|
||||||
'message': "API submission timed out: $e"
|
|
||||||
};
|
|
||||||
// submission_api_service will queue this failure
|
|
||||||
} catch (e) {
|
|
||||||
apiResult = {
|
|
||||||
'success': false,
|
|
||||||
'message': 'An unexpected error occurred: $e'
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log the final result
|
// 2. FTP Submission (Data Zip Only - No Images for Maintenance)
|
||||||
final bool overallSuccess = apiResult['success'] == true;
|
Map<String, dynamic> ftpResults = {'statuses': []};
|
||||||
final String finalMessage =
|
bool anyFtpSuccess = false;
|
||||||
apiResult['message'] ?? (overallSuccess ? 'Submission successful.' : 'Submission failed.');
|
|
||||||
final String finalStatus = overallSuccess ? 'S4' : 'L1'; // S4 = API Success
|
|
||||||
|
|
||||||
if (overallSuccess) {
|
bool isFtpEnabled = pref?['is_ftp_enabled'] ?? true;
|
||||||
// Assuming the API returns an ID. Adjust 'maintenance_id' if needed.
|
// Check status to avoid duplicate uploads (L4 = API Fail, FTP Success; S4 = Both Success)
|
||||||
data.reportId = apiResult['data']?['maintenance_id']?.toString();
|
bool previousFtpSuccess = data.submissionStatus == 'L4' || data.submissionStatus == 'S4';
|
||||||
|
// Check active FTPs
|
||||||
|
final enabledFtpConfigs = await _userPreferencesService.getEnabledFtpConfigsForModule(moduleName);
|
||||||
|
|
||||||
|
if (!isFtpEnabled) {
|
||||||
|
ftpResults = {'statuses': [{'status': 'Skipped', 'message': 'FTP disabled by user.', 'success': true}]};
|
||||||
|
anyFtpSuccess = true;
|
||||||
|
} else if (previousFtpSuccess) {
|
||||||
|
debugPrint("FTP submission skipped: Already successful.");
|
||||||
|
ftpResults = {'statuses': [{'status': 'Skipped', 'message': 'Already successful.', 'success': true}]};
|
||||||
|
anyFtpSuccess = true;
|
||||||
|
} else if (enabledFtpConfigs.isEmpty) {
|
||||||
|
debugPrint("FTP submission skipped: No active FTP configurations found for $moduleName.");
|
||||||
|
ftpResults = {'statuses': [{'status': 'Skipped', 'message': 'No active FTP servers.', 'success': true}]};
|
||||||
|
anyFtpSuccess = true;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
ftpResults = await _generateAndUploadFtpFiles(data, serverName, moduleName);
|
||||||
|
anyFtpSuccess = !(ftpResults['statuses'] as List).any((status) => status['success'] == false && status['status'] != 'Not Configured');
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("FTP submission error: $e");
|
||||||
|
anyFtpSuccess = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Determine Final Status
|
||||||
|
final bool overallSuccess = anyApiSuccess || anyFtpSuccess;
|
||||||
|
String finalMessage;
|
||||||
|
String finalStatus;
|
||||||
|
|
||||||
|
if (anyApiSuccess && anyFtpSuccess) {
|
||||||
|
finalMessage = 'Maintenance Report submitted successfully to all destinations.';
|
||||||
|
finalStatus = 'S4';
|
||||||
|
} else if (anyApiSuccess && !anyFtpSuccess) {
|
||||||
|
finalMessage = 'Maintenance Report sent to API, but FTP upload failed.';
|
||||||
|
finalStatus = 'S3';
|
||||||
|
} else if (!anyApiSuccess && anyFtpSuccess) {
|
||||||
|
finalMessage = 'API submission failed, but file sent to FTP.';
|
||||||
|
finalStatus = 'L4';
|
||||||
|
} else {
|
||||||
|
finalMessage = apiResult['message'] ?? 'All submission attempts failed.';
|
||||||
|
finalStatus = 'L1';
|
||||||
}
|
}
|
||||||
|
|
||||||
await _logAndSave(
|
await _logAndSave(
|
||||||
@ -150,11 +214,18 @@ class MarineManualEquipmentMaintenanceService {
|
|||||||
status: finalStatus,
|
status: finalStatus,
|
||||||
message: finalMessage,
|
message: finalMessage,
|
||||||
apiResult: apiResult,
|
apiResult: apiResult,
|
||||||
|
ftpStatuses: ftpResults['statuses'],
|
||||||
serverName: serverName,
|
serverName: serverName,
|
||||||
logDirectory: logDirectory,
|
logDirectory: logDirectory,
|
||||||
);
|
);
|
||||||
|
|
||||||
return apiResult;
|
// --- START: ADDED TELEGRAM ALERT ---
|
||||||
|
if (overallSuccess) {
|
||||||
|
_handleSuccessAlert(data, authProvider);
|
||||||
|
}
|
||||||
|
// --- END: ADDED TELEGRAM ALERT ---
|
||||||
|
|
||||||
|
return {'success': overallSuccess, 'message': finalMessage};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles saving the submission to local storage and queuing for retry.
|
/// Handles saving the submission to local storage and queuing for retry.
|
||||||
@ -181,12 +252,13 @@ class MarineManualEquipmentMaintenanceService {
|
|||||||
status: 'Error',
|
status: 'Error',
|
||||||
message: message,
|
message: message,
|
||||||
apiResult: {},
|
apiResult: {},
|
||||||
|
ftpStatuses: [],
|
||||||
serverName: serverName);
|
serverName: serverName);
|
||||||
return {'success': false, 'message': message};
|
return {'success': false, 'message': message};
|
||||||
}
|
}
|
||||||
|
|
||||||
await _retryService.queueTask(
|
await _retryService.queueTask(
|
||||||
type: 'equipment_maintenance_submission', // New task type
|
type: 'equipment_maintenance_submission',
|
||||||
payload: {
|
payload: {
|
||||||
'module': moduleName,
|
'module': moduleName,
|
||||||
'localLogPath': localLogPath,
|
'localLogPath': localLogPath,
|
||||||
@ -199,12 +271,40 @@ class MarineManualEquipmentMaintenanceService {
|
|||||||
return {'success': true, 'message': successMessage};
|
return {'success': true, 'message': successMessage};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates zip files and uploads them via FTP.
|
||||||
|
Future<Map<String, dynamic>> _generateAndUploadFtpFiles(MarineManualEquipmentMaintenanceData data, String serverName, String moduleName) async {
|
||||||
|
final timestamp = data.maintenanceDate ?? DateTime.now().toIso8601String();
|
||||||
|
final baseFileName = 'maintenance_$timestamp';
|
||||||
|
|
||||||
|
final Directory? logDirectory = await _localStorageService.getLogDirectory(serverName: serverName, module: 'marine', subModule: 'marine_equipment_maintenance',);
|
||||||
|
final Directory? localSubmissionDir = logDirectory != null ? Directory(p.join(logDirectory.path, baseFileName)) : null;
|
||||||
|
if (localSubmissionDir != null && !await localSubmissionDir.exists()) await localSubmissionDir.create(recursive: true);
|
||||||
|
|
||||||
|
final dataZip = await _zippingService.createDataZip(
|
||||||
|
jsonDataMap: {'db.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, // Added ! to ensure non-nullable
|
||||||
|
remotePath: '/${p.basename(dataZip.path)}'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {'statuses': ftpDataResult['statuses'] ?? []};
|
||||||
|
}
|
||||||
|
|
||||||
/// Logs the submission to the local file system and the central SQL database.
|
/// Logs the submission to the local file system and the central SQL database.
|
||||||
Future<void> _logAndSave({
|
Future<void> _logAndSave({
|
||||||
required MarineManualEquipmentMaintenanceData data,
|
required MarineManualEquipmentMaintenanceData data,
|
||||||
required String status,
|
required String status,
|
||||||
required String message,
|
required String message,
|
||||||
required Map<String, dynamic> apiResult,
|
required Map<String, dynamic> apiResult,
|
||||||
|
required List<Map<String, dynamic>> ftpStatuses,
|
||||||
required String serverName,
|
required String serverName,
|
||||||
String? logDirectory,
|
String? logDirectory,
|
||||||
}) async {
|
}) async {
|
||||||
@ -213,24 +313,20 @@ class MarineManualEquipmentMaintenanceService {
|
|||||||
|
|
||||||
final fileTimestamp = data.maintenanceDate ?? DateTime.now().toIso8601String();
|
final fileTimestamp = data.maintenanceDate ?? DateTime.now().toIso8601String();
|
||||||
|
|
||||||
|
// Use the new toDbJson() method to get ALL data for logging
|
||||||
|
final Map<String, dynamic> logDataMap = data.toDbJson();
|
||||||
|
// Add submission-specific metadata
|
||||||
|
logDataMap['api_status'] = jsonEncode(apiResult);
|
||||||
|
logDataMap['ftp_status'] = jsonEncode(ftpStatuses);
|
||||||
|
logDataMap['serverConfigName'] = serverName;
|
||||||
|
|
||||||
|
|
||||||
if (logDirectory != null) {
|
if (logDirectory != null) {
|
||||||
// This is an update to an existing log file
|
// This is an update to an existing log file
|
||||||
// --- START: MODIFIED BLOCK ---
|
logDataMap['logDirectory'] = logDirectory;
|
||||||
final Map<String, dynamic> updatedLogData = data.toDbJson();
|
await _localStorageService.updateEquipmentMaintenanceLog(logDataMap);
|
||||||
// Add metadata
|
|
||||||
updatedLogData['submissionStatus'] = status;
|
|
||||||
updatedLogData['submissionMessage'] = message;
|
|
||||||
updatedLogData['logDirectory'] = logDirectory;
|
|
||||||
updatedLogData['serverConfigName'] = serverName;
|
|
||||||
updatedLogData['api_status'] = jsonEncode(apiResult);
|
|
||||||
// All other fields (ysiSondeChecks, etc.) are now in toDbJson()
|
|
||||||
// --- END: MODIFIED BLOCK ---
|
|
||||||
|
|
||||||
// This method is added to LocalStorageService
|
|
||||||
await _localStorageService.updateEquipmentMaintenanceLog(updatedLogData);
|
|
||||||
} else {
|
} else {
|
||||||
// This is a new log
|
// This is a new log
|
||||||
// This method is added to LocalStorageService
|
|
||||||
await _localStorageService.saveEquipmentMaintenanceData(data, serverName: serverName);
|
await _localStorageService.saveEquipmentMaintenanceData(data, serverName: serverName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,19 +338,16 @@ class MarineManualEquipmentMaintenanceService {
|
|||||||
'message': data.submissionMessage,
|
'message': data.submissionMessage,
|
||||||
'report_id': data.reportId,
|
'report_id': data.reportId,
|
||||||
'created_at': DateTime.now().toIso8601String(),
|
'created_at': DateTime.now().toIso8601String(),
|
||||||
// --- START: MODIFIED LINE ---
|
|
||||||
'form_data': jsonEncode(data.toDbJson()), // Log the full DbJson
|
'form_data': jsonEncode(data.toDbJson()), // Log the full DbJson
|
||||||
// --- END: MODIFIED LINE ---
|
|
||||||
'image_data': null, // No images
|
'image_data': null, // No images
|
||||||
'server_name': serverName,
|
'server_name': serverName,
|
||||||
'api_status': jsonEncode(apiResult),
|
'api_status': jsonEncode(apiResult),
|
||||||
'ftp_status': null, // No FTP
|
'ftp_status': jsonEncode(ftpStatuses),
|
||||||
};
|
};
|
||||||
await _dbHelper.saveSubmissionLog(logData);
|
await _dbHelper.saveSubmissionLog(logData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetches previous maintenance logs to populate the form
|
/// Fetches previous maintenance logs to populate the form
|
||||||
/// THIS METHOD IS UNCHANGED as it's a simple GET request.
|
|
||||||
Future<Map<String, dynamic>> getPreviousMaintenanceLogs({
|
Future<Map<String, dynamic>> getPreviousMaintenanceLogs({
|
||||||
required AuthProvider authProvider,
|
required AuthProvider authProvider,
|
||||||
}) async {
|
}) async {
|
||||||
@ -275,4 +368,18 @@ class MarineManualEquipmentMaintenanceService {
|
|||||||
return {'success': false, 'message': 'An unexpected error occurred: $e'};
|
return {'success': false, 'message': 'An unexpected error occurred: $e'};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- START: NEW TELEGRAM ALERT METHOD ---
|
||||||
|
Future<void> _handleSuccessAlert(MarineManualEquipmentMaintenanceData data, AuthProvider authProvider) async {
|
||||||
|
try {
|
||||||
|
final message = data.generateTelegramAlertMessage();
|
||||||
|
// Using 'marine_npe_report' ID/module config as requested
|
||||||
|
if (!await _telegramService.sendAlertImmediately('marine_npe_report', message, authProvider.appSettings)) {
|
||||||
|
await _telegramService.queueMessage('marine_npe_report', message, authProvider.appSettings);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("Telegram Alert Error (Maintenance): $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// --- END: NEW TELEGRAM ALERT METHOD ---
|
||||||
}
|
}
|
||||||
@ -5,6 +5,7 @@ import 'dart:io';
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
|
|
||||||
import '../auth_provider.dart';
|
import '../auth_provider.dart';
|
||||||
import '../models/marine_manual_pre_departure_checklist_data.dart';
|
import '../models/marine_manual_pre_departure_checklist_data.dart';
|
||||||
@ -14,44 +15,52 @@ 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/server_config_service.dart';
|
||||||
import 'package:environment_monitoring_app/services/retry_service.dart';
|
import 'package:environment_monitoring_app/services/retry_service.dart';
|
||||||
import 'package:environment_monitoring_app/services/submission_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/zipping_service.dart';
|
||||||
|
import 'user_preferences_service.dart'; // ADDED
|
||||||
|
import 'telegram_service.dart'; // --- ADDED IMPORT ---
|
||||||
|
|
||||||
import 'base_api_service.dart'; // Import for SessionExpiredException
|
import 'base_api_service.dart'; // Import for SessionExpiredException
|
||||||
|
|
||||||
class MarineManualPreDepartureService {
|
class MarineManualPreDepartureService {
|
||||||
// Use the new generic submission service
|
// Use the new generic submission service
|
||||||
final SubmissionApiService _submissionApiService = SubmissionApiService();
|
final SubmissionApiService _submissionApiService = SubmissionApiService();
|
||||||
|
final SubmissionFtpService _submissionFtpService = SubmissionFtpService();
|
||||||
|
final ZippingService _zippingService = ZippingService();
|
||||||
final LocalStorageService _localStorageService = LocalStorageService();
|
final LocalStorageService _localStorageService = LocalStorageService();
|
||||||
final ServerConfigService _serverConfigService = ServerConfigService();
|
final ServerConfigService _serverConfigService = ServerConfigService();
|
||||||
|
final UserPreferencesService _userPreferencesService = UserPreferencesService(); // ADDED
|
||||||
final DatabaseHelper _dbHelper = DatabaseHelper();
|
final DatabaseHelper _dbHelper = DatabaseHelper();
|
||||||
final RetryService _retryService = RetryService();
|
final RetryService _retryService = RetryService();
|
||||||
|
final TelegramService _telegramService; // --- ADDED FIELD ---
|
||||||
|
|
||||||
// The ApiService is kept only if other non-submission methods need it.
|
// The ApiService is kept only if other non-submission methods need it.
|
||||||
// For this refactor, we'll remove it from the constructor.
|
// --- MODIFIED CONSTRUCTOR ---
|
||||||
// final ApiService _apiService;
|
MarineManualPreDepartureService(ApiService apiService, this._telegramService);
|
||||||
// MarineManualPreDepartureService(this._apiService);
|
|
||||||
MarineManualPreDepartureService(ApiService apiService); // Keep constructor signature for main.dart
|
/// Fetches all Checklist logs stored locally on the device.
|
||||||
|
Future<List<Map<String, dynamic>>> getLocalChecklistLogs() async {
|
||||||
|
return await _localStorageService.getAllPreDepartureLogs();
|
||||||
|
}
|
||||||
|
|
||||||
/// Main submission method with online/offline branching logic
|
/// Main submission method with online/offline branching logic
|
||||||
Future<Map<String, dynamic>> submitChecklist({
|
Future<Map<String, dynamic>> submitChecklist({
|
||||||
required MarineManualPreDepartureChecklistData data,
|
required MarineManualPreDepartureChecklistData data,
|
||||||
required AuthProvider authProvider,
|
required AuthProvider authProvider,
|
||||||
List<Map<String, dynamic>>? appSettings, // Added for consistency
|
List<Map<String, dynamic>>? appSettings,
|
||||||
BuildContext? context, // Added for consistency
|
BuildContext? context,
|
||||||
String? logDirectory,
|
String? logDirectory,
|
||||||
}) async {
|
}) async {
|
||||||
const String moduleName = 'marine_pre_departure';
|
// Unified module name for preferences
|
||||||
|
const String moduleName = 'marine_report';
|
||||||
|
|
||||||
// --- START: ADDED LINE ---
|
|
||||||
// Populate the user name from the AuthProvider
|
// Populate the user name from the AuthProvider
|
||||||
data.reporterName = authProvider.profileData?['first_name'] as String?;
|
data.reporterName = authProvider.profileData?['first_name'] as String?;
|
||||||
// --- END: ADDED LINE ---
|
|
||||||
|
|
||||||
final connectivityResult = await Connectivity().checkConnectivity();
|
final connectivityResult = await Connectivity().checkConnectivity();
|
||||||
bool isOnline = connectivityResult != ConnectivityResult.none;
|
bool isOnline = !connectivityResult.contains(ConnectivityResult.none);
|
||||||
bool isOfflineSession = authProvider.isLoggedIn &&
|
bool isOfflineSession = authProvider.isLoggedIn &&
|
||||||
(authProvider.profileData?['token']
|
(authProvider.profileData?['token']?.startsWith("offline-session-") ?? false);
|
||||||
?.startsWith("offline-session-") ??
|
|
||||||
false);
|
|
||||||
|
|
||||||
if (isOnline && isOfflineSession) {
|
if (isOnline && isOfflineSession) {
|
||||||
debugPrint(
|
debugPrint(
|
||||||
@ -93,56 +102,108 @@ class MarineManualPreDepartureService {
|
|||||||
(await _serverConfigService.getActiveApiConfig())?['config_name']
|
(await _serverConfigService.getActiveApiConfig())?['config_name']
|
||||||
as String? ??
|
as String? ??
|
||||||
'Default';
|
'Default';
|
||||||
Map<String, dynamic> apiResult;
|
bool anyApiSuccess = false;
|
||||||
|
Map<String, dynamic> apiResult = {};
|
||||||
|
|
||||||
try {
|
// 1. API Submission
|
||||||
apiResult = await _submissionApiService.submitPost(
|
// Check if API is enabled in preferences
|
||||||
moduleName: moduleName,
|
final pref = await _userPreferencesService.getModulePreference(moduleName);
|
||||||
endpoint: 'marine/checklist', // Endpoint from marine_api_service.dart
|
bool isApiEnabled = pref?['is_api_enabled'] ?? true;
|
||||||
body: data.toApiFormData(),
|
|
||||||
);
|
if (isApiEnabled) {
|
||||||
} on SessionExpiredException {
|
try {
|
||||||
final bool reloginSuccess = await authProvider.attemptSilentRelogin();
|
|
||||||
if (reloginSuccess) {
|
|
||||||
apiResult = await _submissionApiService.submitPost(
|
apiResult = await _submissionApiService.submitPost(
|
||||||
moduleName: moduleName,
|
moduleName: moduleName,
|
||||||
endpoint: 'marine/checklist',
|
endpoint: 'marine/checklist', // Endpoint from marine_api_service.dart
|
||||||
body: data.toApiFormData(),
|
body: data.toApiFormData(),
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
|
if (apiResult['success'] == false && (apiResult['message'] as String?)?.contains('Unauthorized') == true) {
|
||||||
|
// Handle silent relogin
|
||||||
|
if (await authProvider.attemptSilentRelogin()) {
|
||||||
|
apiResult = await _submissionApiService.submitPost(
|
||||||
|
moduleName: moduleName,
|
||||||
|
endpoint: 'marine/checklist',
|
||||||
|
body: data.toApiFormData(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apiResult['success'] == true) {
|
||||||
|
anyApiSuccess = true;
|
||||||
|
data.reportId = apiResult['data']?['checklist_id']?.toString();
|
||||||
|
}
|
||||||
|
} on SocketException catch (e) {
|
||||||
apiResult = {
|
apiResult = {
|
||||||
'success': false,
|
'success': false,
|
||||||
'message': 'Session expired. Please log in again.'
|
'message': "API submission failed with network error: $e"
|
||||||
|
};
|
||||||
|
// Queue API manually
|
||||||
|
await _retryService.addApiToQueue(endpoint: 'marine/checklist', method: 'POST', body: data.toApiFormData());
|
||||||
|
} on TimeoutException catch (e) {
|
||||||
|
apiResult = {
|
||||||
|
'success': false,
|
||||||
|
'message': "API submission timed out: $e"
|
||||||
|
};
|
||||||
|
await _retryService.addApiToQueue(endpoint: 'marine/checklist', method: 'POST', body: data.toApiFormData());
|
||||||
|
} catch (e) {
|
||||||
|
apiResult = {
|
||||||
|
'success': false,
|
||||||
|
'message': 'An unexpected error occurred: $e'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} on SocketException catch (e) {
|
} else {
|
||||||
apiResult = {
|
anyApiSuccess = true; // Treated as success if disabled by user
|
||||||
'success': false,
|
|
||||||
'message': "API submission failed with network error: $e"
|
|
||||||
};
|
|
||||||
// submission_api_service will queue this failure
|
|
||||||
} on TimeoutException catch (e) {
|
|
||||||
apiResult = {
|
|
||||||
'success': false,
|
|
||||||
'message': "API submission timed out: $e"
|
|
||||||
};
|
|
||||||
// submission_api_service will queue this failure
|
|
||||||
} catch (e) {
|
|
||||||
apiResult = {
|
|
||||||
'success': false,
|
|
||||||
'message': 'An unexpected error occurred: $e'
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log the final result
|
// 2. FTP Submission (Data Zip Only - No Images for Checklist)
|
||||||
final bool overallSuccess = apiResult['success'] == true;
|
Map<String, dynamic> ftpResults = {'statuses': []};
|
||||||
final String finalMessage =
|
bool anyFtpSuccess = false;
|
||||||
apiResult['message'] ?? (overallSuccess ? 'Submission successful.' : 'Submission failed.');
|
|
||||||
final String finalStatus = overallSuccess ? 'S4' : 'L1'; // S4 = API Success
|
|
||||||
|
|
||||||
if (overallSuccess) {
|
bool isFtpEnabled = pref?['is_ftp_enabled'] ?? true;
|
||||||
// Assuming the API returns an ID. Adjust 'checklist_id' if needed.
|
// Check if this record was already successfully sent to FTP (L4 or S4 status)
|
||||||
data.reportId = apiResult['data']?['checklist_id']?.toString();
|
bool previousFtpSuccess = data.submissionStatus == 'L4' || data.submissionStatus == 'S4';
|
||||||
|
// Check if there are any active FTP configs for this module
|
||||||
|
final enabledFtpConfigs = await _userPreferencesService.getEnabledFtpConfigsForModule(moduleName);
|
||||||
|
|
||||||
|
if (!isFtpEnabled) {
|
||||||
|
ftpResults = {'statuses': [{'status': 'Skipped', 'message': 'FTP disabled by user.', 'success': true}]};
|
||||||
|
anyFtpSuccess = true;
|
||||||
|
} else if (previousFtpSuccess) {
|
||||||
|
debugPrint("FTP submission skipped: Already successful in previous attempt.");
|
||||||
|
ftpResults = {'statuses': [{'status': 'Skipped', 'message': 'Already successful.', 'success': true}]};
|
||||||
|
anyFtpSuccess = true;
|
||||||
|
} else if (enabledFtpConfigs.isEmpty) {
|
||||||
|
debugPrint("FTP submission skipped: No active FTP configurations found for $moduleName.");
|
||||||
|
ftpResults = {'statuses': [{'status': 'Skipped', 'message': 'No active FTP servers.', 'success': true}]};
|
||||||
|
anyFtpSuccess = true; // Treated as success to avoid indefinite L1 state
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
ftpResults = await _generateAndUploadFtpFiles(data, serverName, moduleName);
|
||||||
|
anyFtpSuccess = !(ftpResults['statuses'] as List).any((status) => status['success'] == false && status['status'] != 'Not Configured');
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("FTP submission error: $e");
|
||||||
|
anyFtpSuccess = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Determine Final Status
|
||||||
|
final bool overallSuccess = anyApiSuccess || anyFtpSuccess;
|
||||||
|
String finalMessage;
|
||||||
|
String finalStatus;
|
||||||
|
|
||||||
|
if (anyApiSuccess && anyFtpSuccess) {
|
||||||
|
finalMessage = 'Checklist submitted successfully to all destinations.';
|
||||||
|
finalStatus = 'S4';
|
||||||
|
} else if (anyApiSuccess && !anyFtpSuccess) {
|
||||||
|
finalMessage = 'Checklist sent to API, but FTP upload failed.';
|
||||||
|
finalStatus = 'S3';
|
||||||
|
} else if (!anyApiSuccess && anyFtpSuccess) {
|
||||||
|
finalMessage = 'API submission failed, but file sent to FTP.';
|
||||||
|
finalStatus = 'L4';
|
||||||
|
} else {
|
||||||
|
finalMessage = apiResult['message'] ?? 'All submission attempts failed.';
|
||||||
|
finalStatus = 'L1';
|
||||||
}
|
}
|
||||||
|
|
||||||
await _logAndSave(
|
await _logAndSave(
|
||||||
@ -150,11 +211,18 @@ class MarineManualPreDepartureService {
|
|||||||
status: finalStatus,
|
status: finalStatus,
|
||||||
message: finalMessage,
|
message: finalMessage,
|
||||||
apiResult: apiResult,
|
apiResult: apiResult,
|
||||||
|
ftpStatuses: ftpResults['statuses'],
|
||||||
serverName: serverName,
|
serverName: serverName,
|
||||||
logDirectory: logDirectory,
|
logDirectory: logDirectory,
|
||||||
);
|
);
|
||||||
|
|
||||||
return apiResult;
|
// --- START: ADDED TELEGRAM ALERT ---
|
||||||
|
if (overallSuccess) {
|
||||||
|
_handleSuccessAlert(data, authProvider);
|
||||||
|
}
|
||||||
|
// --- END: ADDED TELEGRAM ALERT ---
|
||||||
|
|
||||||
|
return {'success': overallSuccess, 'message': finalMessage};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles saving the submission to local storage and queuing for retry.
|
/// Handles saving the submission to local storage and queuing for retry.
|
||||||
@ -181,6 +249,7 @@ class MarineManualPreDepartureService {
|
|||||||
status: 'Error',
|
status: 'Error',
|
||||||
message: message,
|
message: message,
|
||||||
apiResult: {},
|
apiResult: {},
|
||||||
|
ftpStatuses: [],
|
||||||
serverName: serverName);
|
serverName: serverName);
|
||||||
return {'success': false, 'message': message};
|
return {'success': false, 'message': message};
|
||||||
}
|
}
|
||||||
@ -199,12 +268,47 @@ class MarineManualPreDepartureService {
|
|||||||
return {'success': true, 'message': successMessage};
|
return {'success': true, 'message': successMessage};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates zip files and uploads them via FTP.
|
||||||
|
Future<Map<String, dynamic>> _generateAndUploadFtpFiles(MarineManualPreDepartureChecklistData data, String serverName, String moduleName) async {
|
||||||
|
final timestamp = data.submissionDate ?? DateTime.now().toIso8601String();
|
||||||
|
final baseFileName = 'checklist_$timestamp';
|
||||||
|
|
||||||
|
final Directory? logDirectory = await _localStorageService.getLogDirectory(
|
||||||
|
serverName: serverName,
|
||||||
|
module: 'marine',
|
||||||
|
subModule: 'marine_pre_departure',
|
||||||
|
);
|
||||||
|
|
||||||
|
final Directory? localSubmissionDir = logDirectory != null ? Directory(p.join(logDirectory.path, baseFileName)) : null;
|
||||||
|
if (localSubmissionDir != null && !await localSubmissionDir.exists()) {
|
||||||
|
await localSubmissionDir.create(recursive: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
final dataZip = await _zippingService.createDataZip(
|
||||||
|
jsonDataMap: {'db.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, // Added ! to ensure non-nullable if needed, but flow guarantees it's checked or handled
|
||||||
|
remotePath: '/${p.basename(dataZip.path)}'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {'statuses': ftpDataResult['statuses'] ?? []};
|
||||||
|
}
|
||||||
|
|
||||||
/// Logs the submission to the local file system and the central SQL database.
|
/// Logs the submission to the local file system and the central SQL database.
|
||||||
Future<void> _logAndSave({
|
Future<void> _logAndSave({
|
||||||
required MarineManualPreDepartureChecklistData data,
|
required MarineManualPreDepartureChecklistData data,
|
||||||
required String status,
|
required String status,
|
||||||
required String message,
|
required String message,
|
||||||
required Map<String, dynamic> apiResult,
|
required Map<String, dynamic> apiResult,
|
||||||
|
required List<Map<String, dynamic>> ftpStatuses,
|
||||||
required String serverName,
|
required String serverName,
|
||||||
String? logDirectory,
|
String? logDirectory,
|
||||||
}) async {
|
}) async {
|
||||||
@ -223,6 +327,7 @@ class MarineManualPreDepartureService {
|
|||||||
updatedLogData['logDirectory'] = logDirectory;
|
updatedLogData['logDirectory'] = logDirectory;
|
||||||
updatedLogData['serverConfigName'] = serverName;
|
updatedLogData['serverConfigName'] = serverName;
|
||||||
updatedLogData['api_status'] = jsonEncode(apiResult);
|
updatedLogData['api_status'] = jsonEncode(apiResult);
|
||||||
|
updatedLogData['ftp_status'] = jsonEncode(ftpStatuses);
|
||||||
// All other fields are now in toDbJson()
|
// All other fields are now in toDbJson()
|
||||||
// --- END: MODIFIED BLOCK ---
|
// --- END: MODIFIED BLOCK ---
|
||||||
|
|
||||||
@ -248,8 +353,22 @@ class MarineManualPreDepartureService {
|
|||||||
'image_data': null, // No images
|
'image_data': null, // No images
|
||||||
'server_name': serverName,
|
'server_name': serverName,
|
||||||
'api_status': jsonEncode(apiResult),
|
'api_status': jsonEncode(apiResult),
|
||||||
'ftp_status': null, // No FTP
|
'ftp_status': jsonEncode(ftpStatuses),
|
||||||
};
|
};
|
||||||
await _dbHelper.saveSubmissionLog(logData);
|
await _dbHelper.saveSubmissionLog(logData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- START: NEW TELEGRAM ALERT METHOD ---
|
||||||
|
Future<void> _handleSuccessAlert(MarineManualPreDepartureChecklistData data, AuthProvider authProvider) async {
|
||||||
|
try {
|
||||||
|
final message = data.generateTelegramAlertMessage();
|
||||||
|
// Using 'marine_npe_report' ID/module config as requested
|
||||||
|
if (!await _telegramService.sendAlertImmediately('marine_npe_report', message, authProvider.appSettings)) {
|
||||||
|
await _telegramService.queueMessage('marine_npe_report', message, authProvider.appSettings);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("Telegram Alert Error (Checklist): $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// --- END: NEW TELEGRAM ALERT METHOD ---
|
||||||
}
|
}
|
||||||
@ -5,6 +5,7 @@ import 'dart:io';
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
|
|
||||||
import '../auth_provider.dart';
|
import '../auth_provider.dart';
|
||||||
import '../models/marine_manual_sonde_calibration_data.dart';
|
import '../models/marine_manual_sonde_calibration_data.dart';
|
||||||
@ -14,44 +15,51 @@ 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/server_config_service.dart';
|
||||||
import 'package:environment_monitoring_app/services/retry_service.dart';
|
import 'package:environment_monitoring_app/services/retry_service.dart';
|
||||||
import 'package:environment_monitoring_app/services/submission_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/zipping_service.dart';
|
||||||
|
import 'user_preferences_service.dart'; // ADDED
|
||||||
|
import 'telegram_service.dart'; // --- ADDED IMPORT ---
|
||||||
|
|
||||||
import 'base_api_service.dart'; // Import for SessionExpiredException
|
import 'base_api_service.dart'; // Import for SessionExpiredException
|
||||||
|
|
||||||
class MarineManualSondeCalibrationService {
|
class MarineManualSondeCalibrationService {
|
||||||
// Use the new generic submission service
|
|
||||||
final SubmissionApiService _submissionApiService = SubmissionApiService();
|
final SubmissionApiService _submissionApiService = SubmissionApiService();
|
||||||
|
final SubmissionFtpService _submissionFtpService = SubmissionFtpService();
|
||||||
|
final ZippingService _zippingService = ZippingService();
|
||||||
final LocalStorageService _localStorageService = LocalStorageService();
|
final LocalStorageService _localStorageService = LocalStorageService();
|
||||||
final ServerConfigService _serverConfigService = ServerConfigService();
|
final ServerConfigService _serverConfigService = ServerConfigService();
|
||||||
|
final UserPreferencesService _userPreferencesService = UserPreferencesService(); // ADDED
|
||||||
final DatabaseHelper _dbHelper = DatabaseHelper();
|
final DatabaseHelper _dbHelper = DatabaseHelper();
|
||||||
final RetryService _retryService = RetryService();
|
final RetryService _retryService = RetryService();
|
||||||
|
final TelegramService _telegramService; // --- ADDED FIELD ---
|
||||||
|
|
||||||
// The ApiService is kept only if other non-submission methods need it.
|
// The ApiService is kept only if other non-submission methods need it.
|
||||||
// For this refactor, we'll remove it from the constructor.
|
// --- MODIFIED CONSTRUCTOR ---
|
||||||
// final ApiService _apiService;
|
MarineManualSondeCalibrationService(ApiService apiService, this._telegramService);
|
||||||
// MarineManualSondeCalibrationService(this._apiService);
|
|
||||||
MarineManualSondeCalibrationService(ApiService apiService); // Keep constructor signature for main.dart
|
/// Fetches all Calibration logs stored locally on the device.
|
||||||
|
Future<List<Map<String, dynamic>>> getLocalCalibrationLogs() async {
|
||||||
|
return await _localStorageService.getAllSondeCalibrationLogs();
|
||||||
|
}
|
||||||
|
|
||||||
/// Main submission method with online/offline branching logic
|
/// Main submission method with online/offline branching logic
|
||||||
Future<Map<String, dynamic>> submitCalibration({
|
Future<Map<String, dynamic>> submitCalibration({
|
||||||
required MarineManualSondeCalibrationData data,
|
required MarineManualSondeCalibrationData data,
|
||||||
required AuthProvider authProvider,
|
required AuthProvider authProvider,
|
||||||
List<Map<String, dynamic>>? appSettings, // Added for consistency
|
List<Map<String, dynamic>>? appSettings,
|
||||||
BuildContext? context, // Added for consistency
|
BuildContext? context,
|
||||||
String? logDirectory,
|
String? logDirectory,
|
||||||
}) async {
|
}) async {
|
||||||
const String moduleName = 'marine_sonde_calibration';
|
// Unified module name for preferences
|
||||||
|
const String moduleName = 'marine_report';
|
||||||
|
|
||||||
// --- START: ADDED LINE ---
|
|
||||||
// Populate the user name from the AuthProvider
|
// Populate the user name from the AuthProvider
|
||||||
data.calibratedByUserName = authProvider.profileData?['first_name'] as String?;
|
data.calibratedByUserName = authProvider.profileData?['first_name'] as String?;
|
||||||
// --- END: ADDED LINE ---
|
|
||||||
|
|
||||||
final connectivityResult = await Connectivity().checkConnectivity();
|
final connectivityResult = await Connectivity().checkConnectivity();
|
||||||
bool isOnline = connectivityResult != ConnectivityResult.none;
|
bool isOnline = !connectivityResult.contains(ConnectivityResult.none);
|
||||||
bool isOfflineSession = authProvider.isLoggedIn &&
|
bool isOfflineSession = authProvider.isLoggedIn &&
|
||||||
(authProvider.profileData?['token']
|
(authProvider.profileData?['token']?.startsWith("offline-session-") ?? false);
|
||||||
?.startsWith("offline-session-") ??
|
|
||||||
false);
|
|
||||||
|
|
||||||
if (isOnline && isOfflineSession) {
|
if (isOnline && isOfflineSession) {
|
||||||
debugPrint(
|
debugPrint(
|
||||||
@ -93,56 +101,108 @@ class MarineManualSondeCalibrationService {
|
|||||||
(await _serverConfigService.getActiveApiConfig())?['config_name']
|
(await _serverConfigService.getActiveApiConfig())?['config_name']
|
||||||
as String? ??
|
as String? ??
|
||||||
'Default';
|
'Default';
|
||||||
Map<String, dynamic> apiResult;
|
bool anyApiSuccess = false;
|
||||||
|
Map<String, dynamic> apiResult = {};
|
||||||
|
|
||||||
try {
|
// 1. API Submission
|
||||||
apiResult = await _submissionApiService.submitPost(
|
// Check if API is enabled in preferences
|
||||||
moduleName: moduleName,
|
final pref = await _userPreferencesService.getModulePreference(moduleName);
|
||||||
endpoint: 'marine/calibration', // Endpoint from marine_api_service.dart
|
bool isApiEnabled = pref?['is_api_enabled'] ?? true;
|
||||||
body: data.toApiFormData(),
|
|
||||||
);
|
if (isApiEnabled) {
|
||||||
} on SessionExpiredException {
|
try {
|
||||||
final bool reloginSuccess = await authProvider.attemptSilentRelogin();
|
|
||||||
if (reloginSuccess) {
|
|
||||||
apiResult = await _submissionApiService.submitPost(
|
apiResult = await _submissionApiService.submitPost(
|
||||||
moduleName: moduleName,
|
moduleName: moduleName,
|
||||||
endpoint: 'marine/calibration',
|
endpoint: 'marine/calibration', // Endpoint from marine_api_service.dart
|
||||||
body: data.toApiFormData(),
|
body: data.toApiFormData(),
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
|
if (apiResult['success'] == false && (apiResult['message'] as String?)?.contains('Unauthorized') == true) {
|
||||||
|
// Handle silent relogin
|
||||||
|
if (await authProvider.attemptSilentRelogin()) {
|
||||||
|
apiResult = await _submissionApiService.submitPost(
|
||||||
|
moduleName: moduleName,
|
||||||
|
endpoint: 'marine/calibration',
|
||||||
|
body: data.toApiFormData(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apiResult['success'] == true) {
|
||||||
|
anyApiSuccess = true;
|
||||||
|
data.reportId = apiResult['data']?['calibration_id']?.toString();
|
||||||
|
}
|
||||||
|
} on SocketException catch (e) {
|
||||||
apiResult = {
|
apiResult = {
|
||||||
'success': false,
|
'success': false,
|
||||||
'message': 'Session expired. Please log in again.'
|
'message': "API submission failed with network error: $e"
|
||||||
|
};
|
||||||
|
// Queue API manually
|
||||||
|
await _retryService.addApiToQueue(endpoint: 'marine/calibration', method: 'POST', body: data.toApiFormData());
|
||||||
|
} on TimeoutException catch (e) {
|
||||||
|
apiResult = {
|
||||||
|
'success': false,
|
||||||
|
'message': "API submission timed out: $e"
|
||||||
|
};
|
||||||
|
await _retryService.addApiToQueue(endpoint: 'marine/calibration', method: 'POST', body: data.toApiFormData());
|
||||||
|
} catch (e) {
|
||||||
|
apiResult = {
|
||||||
|
'success': false,
|
||||||
|
'message': 'An unexpected error occurred: $e'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} on SocketException catch (e) {
|
} else {
|
||||||
apiResult = {
|
anyApiSuccess = true; // Treated as success if disabled by user
|
||||||
'success': false,
|
|
||||||
'message': "API submission failed with network error: $e"
|
|
||||||
};
|
|
||||||
// submission_api_service will queue this failure
|
|
||||||
} on TimeoutException catch (e) {
|
|
||||||
apiResult = {
|
|
||||||
'success': false,
|
|
||||||
'message': "API submission timed out: $e"
|
|
||||||
};
|
|
||||||
// submission_api_service will queue this failure
|
|
||||||
} catch (e) {
|
|
||||||
apiResult = {
|
|
||||||
'success': false,
|
|
||||||
'message': 'An unexpected error occurred: $e'
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log the final result
|
// 2. FTP Submission (Data Zip Only - No Images for Calibration)
|
||||||
final bool overallSuccess = apiResult['success'] == true;
|
Map<String, dynamic> ftpResults = {'statuses': []};
|
||||||
final String finalMessage =
|
bool anyFtpSuccess = false;
|
||||||
apiResult['message'] ?? (overallSuccess ? 'Submission successful.' : 'Submission failed.');
|
|
||||||
final String finalStatus = overallSuccess ? 'S4' : 'L1'; // S4 = API Success
|
|
||||||
|
|
||||||
if (overallSuccess) {
|
bool isFtpEnabled = pref?['is_ftp_enabled'] ?? true;
|
||||||
// Assuming the API returns an ID. Adjust 'calibration_id' if needed.
|
// Check status to avoid duplicate uploads (L4 = API Fail, FTP Success; S4 = Both Success)
|
||||||
data.reportId = apiResult['data']?['calibration_id']?.toString();
|
bool previousFtpSuccess = data.submissionStatus == 'L4' || data.submissionStatus == 'S4';
|
||||||
|
// Check active FTPs
|
||||||
|
final enabledFtpConfigs = await _userPreferencesService.getEnabledFtpConfigsForModule(moduleName);
|
||||||
|
|
||||||
|
if (!isFtpEnabled) {
|
||||||
|
ftpResults = {'statuses': [{'status': 'Skipped', 'message': 'FTP disabled by user.', 'success': true}]};
|
||||||
|
anyFtpSuccess = true;
|
||||||
|
} else if (previousFtpSuccess) {
|
||||||
|
debugPrint("FTP submission skipped: Already successful in previous attempt.");
|
||||||
|
ftpResults = {'statuses': [{'status': 'Skipped', 'message': 'Already successful.', 'success': true}]};
|
||||||
|
anyFtpSuccess = true;
|
||||||
|
} else if (enabledFtpConfigs.isEmpty) {
|
||||||
|
debugPrint("FTP submission skipped: No active FTP configurations found for $moduleName.");
|
||||||
|
ftpResults = {'statuses': [{'status': 'Skipped', 'message': 'No active FTP servers.', 'success': true}]};
|
||||||
|
anyFtpSuccess = true;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
ftpResults = await _generateAndUploadFtpFiles(data, serverName, moduleName);
|
||||||
|
anyFtpSuccess = !(ftpResults['statuses'] as List).any((status) => status['success'] == false && status['status'] != 'Not Configured');
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("FTP submission error: $e");
|
||||||
|
anyFtpSuccess = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Determine Final Status
|
||||||
|
final bool overallSuccess = anyApiSuccess || anyFtpSuccess;
|
||||||
|
String finalMessage;
|
||||||
|
String finalStatus;
|
||||||
|
|
||||||
|
if (anyApiSuccess && anyFtpSuccess) {
|
||||||
|
finalMessage = 'Calibration submitted successfully to all destinations.';
|
||||||
|
finalStatus = 'S4';
|
||||||
|
} else if (anyApiSuccess && !anyFtpSuccess) {
|
||||||
|
finalMessage = 'Calibration sent to API, but FTP upload failed.';
|
||||||
|
finalStatus = 'S3';
|
||||||
|
} else if (!anyApiSuccess && anyFtpSuccess) {
|
||||||
|
finalMessage = 'API submission failed, but file sent to FTP.';
|
||||||
|
finalStatus = 'L4';
|
||||||
|
} else {
|
||||||
|
finalMessage = apiResult['message'] ?? 'All submission attempts failed.';
|
||||||
|
finalStatus = 'L1';
|
||||||
}
|
}
|
||||||
|
|
||||||
await _logAndSave(
|
await _logAndSave(
|
||||||
@ -150,11 +210,18 @@ class MarineManualSondeCalibrationService {
|
|||||||
status: finalStatus,
|
status: finalStatus,
|
||||||
message: finalMessage,
|
message: finalMessage,
|
||||||
apiResult: apiResult,
|
apiResult: apiResult,
|
||||||
|
ftpStatuses: ftpResults['statuses'],
|
||||||
serverName: serverName,
|
serverName: serverName,
|
||||||
logDirectory: logDirectory,
|
logDirectory: logDirectory,
|
||||||
);
|
);
|
||||||
|
|
||||||
return apiResult;
|
// --- START: ADDED TELEGRAM ALERT ---
|
||||||
|
if (overallSuccess) {
|
||||||
|
_handleSuccessAlert(data, authProvider);
|
||||||
|
}
|
||||||
|
// --- END: ADDED TELEGRAM ALERT ---
|
||||||
|
|
||||||
|
return {'success': overallSuccess, 'message': finalMessage};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles saving the submission to local storage and queuing for retry.
|
/// Handles saving the submission to local storage and queuing for retry.
|
||||||
@ -181,6 +248,7 @@ class MarineManualSondeCalibrationService {
|
|||||||
status: 'Error',
|
status: 'Error',
|
||||||
message: message,
|
message: message,
|
||||||
apiResult: {},
|
apiResult: {},
|
||||||
|
ftpStatuses: [],
|
||||||
serverName: serverName);
|
serverName: serverName);
|
||||||
return {'success': false, 'message': message};
|
return {'success': false, 'message': message};
|
||||||
}
|
}
|
||||||
@ -199,12 +267,47 @@ class MarineManualSondeCalibrationService {
|
|||||||
return {'success': true, 'message': successMessage};
|
return {'success': true, 'message': successMessage};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates zip files and uploads them via FTP.
|
||||||
|
Future<Map<String, dynamic>> _generateAndUploadFtpFiles(MarineManualSondeCalibrationData data, String serverName, String moduleName) async {
|
||||||
|
final fileTimestamp = data.startDateTime?.replaceAll(':', '-').replaceAll(' ', '_') ?? DateTime.now().toIso8601String();
|
||||||
|
final baseFileName = 'calibration_${data.sondeSerialNumber}_$fileTimestamp';
|
||||||
|
|
||||||
|
final Directory? logDirectory = await _localStorageService.getLogDirectory(
|
||||||
|
serverName: serverName,
|
||||||
|
module: 'marine',
|
||||||
|
subModule: 'marine_sonde_calibration',
|
||||||
|
);
|
||||||
|
|
||||||
|
final Directory? localSubmissionDir = logDirectory != null ? Directory(p.join(logDirectory.path, baseFileName)) : null;
|
||||||
|
if (localSubmissionDir != null && !await localSubmissionDir.exists()) {
|
||||||
|
await localSubmissionDir.create(recursive: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
final dataZip = await _zippingService.createDataZip(
|
||||||
|
jsonDataMap: {'db.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, // Added ! to ensure non-nullable
|
||||||
|
remotePath: '/${p.basename(dataZip.path)}'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {'statuses': ftpDataResult['statuses'] ?? []};
|
||||||
|
}
|
||||||
|
|
||||||
/// Logs the submission to the local file system and the central SQL database.
|
/// Logs the submission to the local file system and the central SQL database.
|
||||||
Future<void> _logAndSave({
|
Future<void> _logAndSave({
|
||||||
required MarineManualSondeCalibrationData data,
|
required MarineManualSondeCalibrationData data,
|
||||||
required String status,
|
required String status,
|
||||||
required String message,
|
required String message,
|
||||||
required Map<String, dynamic> apiResult,
|
required Map<String, dynamic> apiResult,
|
||||||
|
required List<Map<String, dynamic>> ftpStatuses,
|
||||||
required String serverName,
|
required String serverName,
|
||||||
String? logDirectory,
|
String? logDirectory,
|
||||||
}) async {
|
}) async {
|
||||||
@ -213,14 +316,12 @@ class MarineManualSondeCalibrationService {
|
|||||||
|
|
||||||
final fileTimestamp = data.startDateTime?.replaceAll(':', '-').replaceAll(' ', '_') ?? DateTime.now().toIso8601String();
|
final fileTimestamp = data.startDateTime?.replaceAll(':', '-').replaceAll(' ', '_') ?? DateTime.now().toIso8601String();
|
||||||
|
|
||||||
// --- START: MODIFIED BLOCK ---
|
|
||||||
// Use the new toDbJson() method to get ALL data for logging
|
// Use the new toDbJson() method to get ALL data for logging
|
||||||
final Map<String, dynamic> logDataMap = data.toDbJson();
|
final Map<String, dynamic> logDataMap = data.toDbJson();
|
||||||
// Add submission-specific metadata
|
// Add submission-specific metadata
|
||||||
logDataMap['api_status'] = jsonEncode(apiResult);
|
logDataMap['api_status'] = jsonEncode(apiResult);
|
||||||
|
logDataMap['ftp_status'] = jsonEncode(ftpStatuses);
|
||||||
logDataMap['serverConfigName'] = serverName;
|
logDataMap['serverConfigName'] = serverName;
|
||||||
// --- END: MODIFIED BLOCK ---
|
|
||||||
|
|
||||||
|
|
||||||
if (logDirectory != null) {
|
if (logDirectory != null) {
|
||||||
// This is an update to an existing log file
|
// This is an update to an existing log file
|
||||||
@ -228,7 +329,6 @@ class MarineManualSondeCalibrationService {
|
|||||||
await _localStorageService.updateSondeCalibrationLog(logDataMap);
|
await _localStorageService.updateSondeCalibrationLog(logDataMap);
|
||||||
} else {
|
} else {
|
||||||
// This is a new log
|
// This is a new log
|
||||||
// Pass the complete data object, which now includes the user name
|
|
||||||
await _localStorageService.saveSondeCalibrationData(data, serverName: serverName);
|
await _localStorageService.saveSondeCalibrationData(data, serverName: serverName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,12 +340,28 @@ class MarineManualSondeCalibrationService {
|
|||||||
'message': data.submissionMessage,
|
'message': data.submissionMessage,
|
||||||
'report_id': data.reportId,
|
'report_id': data.reportId,
|
||||||
'created_at': DateTime.now().toIso8601String(),
|
'created_at': DateTime.now().toIso8601String(),
|
||||||
'form_data': jsonEncode(data.toDbJson()), // <-- Use toDbJson here
|
// --- START: MODIFIED LINE ---
|
||||||
|
'form_data': jsonEncode(data.toDbJson()), // Log the full DbJson
|
||||||
|
// --- END: MODIFIED LINE ---
|
||||||
'image_data': null, // No images
|
'image_data': null, // No images
|
||||||
'server_name': serverName,
|
'server_name': serverName,
|
||||||
'api_status': jsonEncode(apiResult),
|
'api_status': jsonEncode(apiResult),
|
||||||
'ftp_status': null, // No FTP
|
'ftp_status': jsonEncode(ftpStatuses),
|
||||||
};
|
};
|
||||||
await _dbHelper.saveSubmissionLog(logData);
|
await _dbHelper.saveSubmissionLog(logData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- START: NEW TELEGRAM ALERT METHOD ---
|
||||||
|
Future<void> _handleSuccessAlert(MarineManualSondeCalibrationData data, AuthProvider authProvider) async {
|
||||||
|
try {
|
||||||
|
final message = data.generateTelegramAlertMessage();
|
||||||
|
// Using 'marine_npe_report' ID/module config as requested
|
||||||
|
if (!await _telegramService.sendAlertImmediately('marine_npe_report', message, authProvider.appSettings)) {
|
||||||
|
await _telegramService.queueMessage('marine_npe_report', message, authProvider.appSettings);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("Telegram Alert Error (Calibration): $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// --- END: NEW TELEGRAM ALERT METHOD ---
|
||||||
}
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
// lib/services/marine_tarball_sampling_service.dart
|
// lib/services/marine_npe_report_service.dart
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
@ -16,9 +16,8 @@ import 'submission_api_service.dart';
|
|||||||
import 'submission_ftp_service.dart';
|
import 'submission_ftp_service.dart';
|
||||||
import 'telegram_service.dart';
|
import 'telegram_service.dart';
|
||||||
import 'retry_service.dart';
|
import 'retry_service.dart';
|
||||||
import 'api_service.dart';
|
|
||||||
import 'package:environment_monitoring_app/services/database_helper.dart';
|
import 'package:environment_monitoring_app/services/database_helper.dart';
|
||||||
|
import 'user_preferences_service.dart'; // ADDED
|
||||||
|
|
||||||
class MarineNpeReportService {
|
class MarineNpeReportService {
|
||||||
final SubmissionApiService _submissionApiService = SubmissionApiService();
|
final SubmissionApiService _submissionApiService = SubmissionApiService();
|
||||||
@ -26,35 +25,34 @@ class MarineNpeReportService {
|
|||||||
final ZippingService _zippingService = ZippingService();
|
final ZippingService _zippingService = ZippingService();
|
||||||
final LocalStorageService _localStorageService = LocalStorageService();
|
final LocalStorageService _localStorageService = LocalStorageService();
|
||||||
final ServerConfigService _serverConfigService = ServerConfigService();
|
final ServerConfigService _serverConfigService = ServerConfigService();
|
||||||
|
final UserPreferencesService _userPreferencesService = UserPreferencesService(); // ADDED
|
||||||
final DatabaseHelper _dbHelper = DatabaseHelper();
|
final DatabaseHelper _dbHelper = DatabaseHelper();
|
||||||
final RetryService _retryService = RetryService();
|
final RetryService _retryService = RetryService();
|
||||||
final TelegramService _telegramService;
|
final TelegramService _telegramService;
|
||||||
|
|
||||||
MarineNpeReportService(this._telegramService);
|
MarineNpeReportService(this._telegramService);
|
||||||
|
|
||||||
|
Future<List<Map<String, dynamic>>> getLocalNpeLogs() async {
|
||||||
|
return await _localStorageService.getAllNpeLogs();
|
||||||
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> submitNpeReport({
|
Future<Map<String, dynamic>> submitNpeReport({
|
||||||
required MarineManualNpeReportData data,
|
required MarineManualNpeReportData data,
|
||||||
required AuthProvider authProvider,
|
required AuthProvider authProvider,
|
||||||
String? logDirectory,
|
String? logDirectory,
|
||||||
}) async {
|
}) async {
|
||||||
const String moduleName = 'marine_npe_report';
|
const String moduleName = 'marine_report';
|
||||||
|
|
||||||
final connectivityResult = await Connectivity().checkConnectivity();
|
final connectivityResult = await Connectivity().checkConnectivity();
|
||||||
bool isOnline = connectivityResult != ConnectivityResult.none;
|
bool isOnline = !connectivityResult.contains(ConnectivityResult.none);
|
||||||
bool isOfflineSession = authProvider.isLoggedIn && (authProvider.profileData?['token']?.startsWith("offline-session-") ?? false);
|
bool isOfflineSession = authProvider.isLoggedIn && (authProvider.profileData?['token']?.startsWith("offline-session-") ?? false);
|
||||||
|
|
||||||
if (isOnline && isOfflineSession) {
|
if (isOnline && isOfflineSession) {
|
||||||
debugPrint("NPE submission online during offline session. Attempting auto-relogin...");
|
|
||||||
final bool transitionSuccess = await authProvider.checkAndTransitionToOnlineSession();
|
final bool transitionSuccess = await authProvider.checkAndTransitionToOnlineSession();
|
||||||
if (transitionSuccess) {
|
if (transitionSuccess) isOfflineSession = false; else isOnline = false;
|
||||||
isOfflineSession = false;
|
|
||||||
} else {
|
|
||||||
isOnline = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isOnline && !isOfflineSession) {
|
if (isOnline && !isOfflineSession) {
|
||||||
debugPrint("Proceeding with direct ONLINE NPE submission...");
|
|
||||||
return await _performNpeOnlineSubmission(
|
return await _performNpeOnlineSubmission(
|
||||||
data: data,
|
data: data,
|
||||||
moduleName: moduleName,
|
moduleName: moduleName,
|
||||||
@ -62,7 +60,6 @@ class MarineNpeReportService {
|
|||||||
logDirectory: logDirectory,
|
logDirectory: logDirectory,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
debugPrint("Proceeding with OFFLINE NPE queuing mechanism...");
|
|
||||||
return await _performNpeOfflineQueuing(
|
return await _performNpeOfflineQueuing(
|
||||||
data: data,
|
data: data,
|
||||||
moduleName: moduleName,
|
moduleName: moduleName,
|
||||||
@ -85,92 +82,92 @@ class MarineNpeReportService {
|
|||||||
Map<String, dynamic> apiDataResult = {};
|
Map<String, dynamic> apiDataResult = {};
|
||||||
Map<String, dynamic> apiImageResult = {};
|
Map<String, dynamic> apiImageResult = {};
|
||||||
|
|
||||||
try {
|
// 1. API Submission
|
||||||
// --- MODIFIED: Use the new endpoint path for data ---
|
// Check if API is enabled in preferences
|
||||||
apiDataResult = await _submissionApiService.submitPost(
|
final pref = await _userPreferencesService.getModulePreference(moduleName);
|
||||||
moduleName: moduleName,
|
bool isApiEnabled = pref?['is_api_enabled'] ?? true;
|
||||||
endpoint: 'marine/npe/report', // <-- Updated endpoint
|
|
||||||
body: data.toApiFormData(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (apiDataResult['success'] == false &&
|
if (isApiEnabled) {
|
||||||
(apiDataResult['message'] as String?)?.contains('Unauthorized') == true) {
|
try {
|
||||||
final bool reloginSuccess = await authProvider.attemptSilentRelogin();
|
apiDataResult = await _submissionApiService.submitPost(
|
||||||
if (reloginSuccess) {
|
moduleName: moduleName,
|
||||||
apiDataResult = await _submissionApiService.submitPost(
|
endpoint: 'marine/npe/report',
|
||||||
moduleName: moduleName,
|
body: data.toApiFormData(),
|
||||||
endpoint: 'marine/npe/report', // <-- Updated endpoint
|
);
|
||||||
body: data.toApiFormData(),
|
|
||||||
);
|
if (apiDataResult['success'] == false && (apiDataResult['message'] as String?)?.contains('Unauthorized') == true) {
|
||||||
|
if (await authProvider.attemptSilentRelogin()) {
|
||||||
|
apiDataResult = await _submissionApiService.submitPost(
|
||||||
|
moduleName: moduleName,
|
||||||
|
endpoint: 'marine/npe/report',
|
||||||
|
body: data.toApiFormData(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (apiDataResult['success'] == true) {
|
if (apiDataResult['success'] == true) {
|
||||||
anyApiSuccess = true;
|
anyApiSuccess = true;
|
||||||
data.reportId = apiDataResult['data']?['npe_id']?.toString();
|
data.reportId = apiDataResult['data']?['npe_id']?.toString();
|
||||||
|
|
||||||
if (data.reportId != null) {
|
if (data.reportId != null && finalImageFiles.isNotEmpty) {
|
||||||
if (finalImageFiles.isNotEmpty) {
|
|
||||||
// --- MODIFIED: Use the new endpoint path for images ---
|
|
||||||
apiImageResult = await _submissionApiService.submitMultipart(
|
apiImageResult = await _submissionApiService.submitMultipart(
|
||||||
moduleName: moduleName,
|
moduleName: moduleName,
|
||||||
endpoint: 'marine/npe/images', // <-- Updated endpoint
|
endpoint: 'marine/npe/images',
|
||||||
fields: {'npe_id': data.reportId!},
|
fields: {'npe_id': data.reportId!},
|
||||||
files: finalImageFiles,
|
files: finalImageFiles,
|
||||||
);
|
);
|
||||||
if (apiImageResult['success'] != true) anyApiSuccess = false;
|
if (apiImageResult['success'] != true) anyApiSuccess = false;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
anyApiSuccess = false;
|
|
||||||
apiDataResult['message'] = 'API Error: Submission succeeded but did not return a record ID.';
|
|
||||||
}
|
}
|
||||||
|
} on SocketException catch (e) {
|
||||||
|
anyApiSuccess = false;
|
||||||
|
apiDataResult = {'success': false, 'message': "API Network Error: $e"};
|
||||||
|
await _retryService.addApiToQueue(endpoint: 'marine/npe/report', method: 'POST', body: data.toApiFormData());
|
||||||
|
} on TimeoutException catch (e) {
|
||||||
|
anyApiSuccess = false;
|
||||||
|
apiDataResult = {'success': false, 'message': "API Timeout: $e"};
|
||||||
|
await _retryService.addApiToQueue(endpoint: 'marine/npe/report', method: 'POST', body: data.toApiFormData());
|
||||||
}
|
}
|
||||||
} on SocketException catch (e) {
|
} else {
|
||||||
anyApiSuccess = false;
|
anyApiSuccess = true; // Treated as success if disabled by user
|
||||||
apiDataResult = {'success': false, 'message': "API submission failed with network error: $e"};
|
|
||||||
// --- MODIFIED: Update queue with new endpoints ---
|
|
||||||
await _retryService.addApiToQueue(endpoint: 'marine/npe/report', method: 'POST', body: data.toApiFormData());
|
|
||||||
if (finalImageFiles.isNotEmpty && data.reportId != null) {
|
|
||||||
await _retryService.addApiToQueue(endpoint: 'marine/npe/images', method: 'POST_MULTIPART', fields: {'npe_id': data.reportId!}, files: finalImageFiles);
|
|
||||||
}
|
|
||||||
} on TimeoutException catch (e) {
|
|
||||||
anyApiSuccess = false;
|
|
||||||
apiDataResult = {'success': false, 'message': "API submission timed out: $e"};
|
|
||||||
// --- MODIFIED: Update queue with new endpoint ---
|
|
||||||
await _retryService.addApiToQueue(endpoint: 'marine/npe/report', method: 'POST', body: data.toApiFormData());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. FTP Submission
|
||||||
Map<String, dynamic> ftpResults = {'statuses': []};
|
Map<String, dynamic> ftpResults = {'statuses': []};
|
||||||
bool anyFtpSuccess = false;
|
bool anyFtpSuccess = false;
|
||||||
try {
|
|
||||||
ftpResults = await _generateAndUploadFtpFiles(data, finalImageFiles, serverName, moduleName);
|
|
||||||
anyFtpSuccess = !(ftpResults['statuses'] as List).any((status) => status['success'] == false && status['status'] != 'Not Configured');
|
|
||||||
} on SocketException catch (e) {
|
|
||||||
debugPrint("FTP submission failed with network error: $e");
|
|
||||||
anyFtpSuccess = false;
|
|
||||||
} on TimeoutException catch (e) {
|
|
||||||
debugPrint("FTP submission timed out: $e");
|
|
||||||
anyFtpSuccess = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final bool overallSuccess = anyApiSuccess || anyFtpSuccess;
|
bool isFtpEnabled = pref?['is_ftp_enabled'] ?? true;
|
||||||
String finalMessage;
|
// Check if this record was already successfully sent to FTP (L4 or S4 status)
|
||||||
String finalStatus;
|
bool previousFtpSuccess = data.submissionStatus == 'L4' || data.submissionStatus == 'S4';
|
||||||
|
// Check if there are any active FTP configs for this module
|
||||||
|
final enabledFtpConfigs = await _userPreferencesService.getEnabledFtpConfigsForModule(moduleName);
|
||||||
|
|
||||||
if (anyApiSuccess && anyFtpSuccess) {
|
if (!isFtpEnabled) {
|
||||||
finalMessage = 'NPE Report submitted successfully to all destinations.';
|
ftpResults = {'statuses': [{'status': 'Skipped', 'message': 'FTP disabled by user.', 'success': true}]};
|
||||||
finalStatus = 'S4';
|
anyFtpSuccess = true;
|
||||||
} else if (anyApiSuccess && !anyFtpSuccess) {
|
} else if (previousFtpSuccess) {
|
||||||
finalMessage = 'NPE Report sent to API, but some FTP uploads failed and were queued.';
|
debugPrint("FTP submission skipped: Already successful in previous attempt.");
|
||||||
finalStatus = 'S3';
|
ftpResults = {'statuses': [{'status': 'Skipped', 'message': 'Already successful.', 'success': true}]};
|
||||||
} else if (!anyApiSuccess && anyFtpSuccess) {
|
anyFtpSuccess = true;
|
||||||
finalMessage = 'API submission for NPE Report failed and was queued, but files sent to FTP.';
|
} else if (enabledFtpConfigs.isEmpty) {
|
||||||
finalStatus = 'L4';
|
debugPrint("FTP submission skipped: No active FTP configurations found for $moduleName.");
|
||||||
|
ftpResults = {'statuses': [{'status': 'Skipped', 'message': 'No active FTP servers.', 'success': true}]};
|
||||||
|
anyFtpSuccess = true; // Treated as success to avoid indefinite L1 state
|
||||||
} else {
|
} else {
|
||||||
finalMessage = 'All NPE Report submission attempts failed and have been queued for retry.';
|
try {
|
||||||
finalStatus = 'L1';
|
ftpResults = await _generateAndUploadFtpFiles(data, finalImageFiles, serverName, moduleName);
|
||||||
|
anyFtpSuccess = !(ftpResults['statuses'] as List).any((status) => status['success'] == false && status['status'] != 'Not Configured');
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("FTP Error: $e");
|
||||||
|
anyFtpSuccess = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. Determine Final Status
|
||||||
|
final bool overallSuccess = anyApiSuccess || anyFtpSuccess;
|
||||||
|
String finalStatus = (anyApiSuccess && anyFtpSuccess) ? 'S4' : (anyApiSuccess ? 'S3' : (anyFtpSuccess ? 'L4' : 'L1'));
|
||||||
|
String finalMessage = overallSuccess ? 'Submission successful.' : 'Submission failed/queued.';
|
||||||
|
|
||||||
await _logAndSave(
|
await _logAndSave(
|
||||||
data: data,
|
data: data,
|
||||||
status: finalStatus,
|
status: finalStatus,
|
||||||
@ -182,9 +179,7 @@ class MarineNpeReportService {
|
|||||||
logDirectory: logDirectory,
|
logDirectory: logDirectory,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (overallSuccess) {
|
if (overallSuccess) _handleNpeSuccessAlert(data, authProvider);
|
||||||
_handleNpeSuccessAlert(data, authProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {'success': overallSuccess, 'message': finalMessage, 'reportId': data.reportId};
|
return {'success': overallSuccess, 'message': finalMessage, 'reportId': data.reportId};
|
||||||
}
|
}
|
||||||
@ -197,56 +192,54 @@ class MarineNpeReportService {
|
|||||||
final serverName = serverConfig?['config_name'] as String? ?? 'Default';
|
final serverName = serverConfig?['config_name'] as String? ?? 'Default';
|
||||||
|
|
||||||
data.submissionStatus = 'L1';
|
data.submissionStatus = 'L1';
|
||||||
data.submissionMessage = 'NPE Report queued due to being offline.';
|
data.submissionMessage = 'Queued (Offline)';
|
||||||
|
|
||||||
final String? localLogPath = await _localStorageService.saveNpeReportData(data, serverName: serverName);
|
final String? localLogPath = await _localStorageService.saveNpeReportData(data, serverName: serverName);
|
||||||
|
|
||||||
if (localLogPath == null) {
|
if (localLogPath == null) return {'success': false, 'message': "Failed to save locally."};
|
||||||
const message = "Failed to save NPE report to local device storage.";
|
|
||||||
await _logAndSave(data: data, status: 'Error', message: message, apiResults: [], ftpStatuses: [], serverName: serverName, finalImageFiles: {});
|
|
||||||
return {'success': false, 'message': message};
|
|
||||||
}
|
|
||||||
|
|
||||||
await _retryService.queueTask(
|
await _retryService.queueTask(
|
||||||
type: 'npe_submission',
|
type: 'npe_submission',
|
||||||
payload: {
|
payload: {'module': moduleName, 'localLogPath': localLogPath, 'serverConfig': serverConfig},
|
||||||
'module': moduleName,
|
|
||||||
'localLogPath': localLogPath,
|
|
||||||
'serverConfig': serverConfig,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const successMessage = "No internet connection. NPE Report has been saved and queued for upload.";
|
return {'success': true, 'message': "Saved locally and queued for upload."};
|
||||||
return {'success': true, 'message': successMessage};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> _generateAndUploadFtpFiles(MarineManualNpeReportData data, Map<String, File> imageFiles, String serverName, String moduleName) async {
|
Future<Map<String, dynamic>> _generateAndUploadFtpFiles(MarineManualNpeReportData data, Map<String, File> imageFiles, String serverName, String moduleName) async {
|
||||||
final stationCode = data.selectedStation?['man_station_code'] ?? data.selectedStation?['tbl_station_code'] ?? 'CUSTOM_LOC';
|
// --- FIX START: Logic to determine correct Station Code or Sanitized New Location ---
|
||||||
|
String stationCode;
|
||||||
|
|
||||||
|
if (data.selectedStation != null) {
|
||||||
|
// If it's an existing station (Manual or Tarball)
|
||||||
|
stationCode = data.selectedStation?['man_station_code'] ??
|
||||||
|
data.selectedStation?['tbl_station_code'] ??
|
||||||
|
'NA';
|
||||||
|
} else {
|
||||||
|
// If it's a New Location, use the description and replace spaces with underscores
|
||||||
|
String rawDesc = data.locationDescription?.trim() ?? 'NEW_LOCATION';
|
||||||
|
if (rawDesc.isEmpty) rawDesc = 'NEW_LOCATION';
|
||||||
|
stationCode = rawDesc.replaceAll(' ', '_');
|
||||||
|
}
|
||||||
|
// --- FIX END ---
|
||||||
|
|
||||||
final fileTimestamp = "${data.eventDate}_${data.eventTime}".replaceAll(':', '-').replaceAll(' ', '_');
|
final fileTimestamp = "${data.eventDate}_${data.eventTime}".replaceAll(':', '-').replaceAll(' ', '_');
|
||||||
final baseFileName = '${stationCode}_${fileTimestamp}_NPE';
|
final baseFileName = '${stationCode}_${fileTimestamp}_NPE';
|
||||||
|
|
||||||
final Directory? logDirectory = await _localStorageService.getLogDirectory(
|
final Directory? logDirectory = await _localStorageService.getLogDirectory(serverName: serverName, module: 'marine', subModule: 'marine_npe_report');
|
||||||
serverName: serverName,
|
|
||||||
module: 'marine',
|
|
||||||
subModule: 'marine_npe_report',
|
|
||||||
);
|
|
||||||
final Directory? localSubmissionDir = logDirectory != null ? Directory(p.join(logDirectory.path, data.reportId ?? baseFileName)) : null;
|
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);
|
if (localSubmissionDir != null && !await localSubmissionDir.exists()) await localSubmissionDir.create(recursive: true);
|
||||||
}
|
|
||||||
|
|
||||||
final dataZip = await _zippingService.createDataZip(
|
final dataZip = await _zippingService.createDataZip(
|
||||||
jsonDataMap: {'db.json': jsonEncode(data.toDbJson())},
|
jsonDataMap: {'db.json': jsonEncode(data.toDbJson())},
|
||||||
baseFileName: baseFileName,
|
baseFileName: baseFileName,
|
||||||
destinationDir: localSubmissionDir,
|
destinationDir: localSubmissionDir,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> ftpDataResult = {'success': true, 'statuses': []};
|
Map<String, dynamic> ftpDataResult = {'success': true, 'statuses': []};
|
||||||
if (dataZip != null) {
|
if (dataZip != null) {
|
||||||
ftpDataResult = await _submissionFtpService.submit(
|
ftpDataResult = await _submissionFtpService.submit(moduleName: moduleName, fileToUpload: dataZip, remotePath: '/${p.basename(dataZip.path)}');
|
||||||
moduleName: moduleName,
|
|
||||||
fileToUpload: dataZip,
|
|
||||||
remotePath: '/${p.basename(dataZip.path)}',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final imageZip = await _zippingService.createImageZip(
|
final imageZip = await _zippingService.createImageZip(
|
||||||
@ -256,11 +249,7 @@ class MarineNpeReportService {
|
|||||||
);
|
);
|
||||||
Map<String, dynamic> ftpImageResult = {'success': true, 'statuses': []};
|
Map<String, dynamic> ftpImageResult = {'success': true, 'statuses': []};
|
||||||
if (imageZip != null) {
|
if (imageZip != null) {
|
||||||
ftpImageResult = await _submissionFtpService.submit(
|
ftpImageResult = await _submissionFtpService.submit(moduleName: moduleName, fileToUpload: imageZip, remotePath: '/${p.basename(imageZip.path)}');
|
||||||
moduleName: moduleName,
|
|
||||||
fileToUpload: imageZip,
|
|
||||||
remotePath: '/${p.basename(imageZip.path)}',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -308,8 +297,8 @@ class MarineNpeReportService {
|
|||||||
'submission_id': data.reportId ?? fileTimestamp,
|
'submission_id': data.reportId ?? fileTimestamp,
|
||||||
'module': 'marine',
|
'module': 'marine',
|
||||||
'type': 'NPE',
|
'type': 'NPE',
|
||||||
'status': data.submissionStatus,
|
'status': status,
|
||||||
'message': data.submissionMessage,
|
'message': message,
|
||||||
'report_id': data.reportId,
|
'report_id': data.reportId,
|
||||||
'created_at': DateTime.now().toIso8601String(),
|
'created_at': DateTime.now().toIso8601String(),
|
||||||
'form_data': jsonEncode(data.toDbJson()),
|
'form_data': jsonEncode(data.toDbJson()),
|
||||||
@ -324,12 +313,9 @@ class MarineNpeReportService {
|
|||||||
Future<void> _handleNpeSuccessAlert(MarineManualNpeReportData data, AuthProvider authProvider) async {
|
Future<void> _handleNpeSuccessAlert(MarineManualNpeReportData data, AuthProvider authProvider) async {
|
||||||
try {
|
try {
|
||||||
final message = data.generateTelegramAlertMessage();
|
final message = data.generateTelegramAlertMessage();
|
||||||
final bool wasSent = await _telegramService.sendAlertImmediately('marine_npe_report', message, authProvider.appSettings);
|
if (!await _telegramService.sendAlertImmediately('marine_npe_report', message, authProvider.appSettings)) {
|
||||||
if (!wasSent) {
|
|
||||||
await _telegramService.queueMessage('marine_npe_report', message, authProvider.appSettings);
|
await _telegramService.queueMessage('marine_npe_report', message, authProvider.appSettings);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) { debugPrint("Telegram Alert Error: $e"); }
|
||||||
debugPrint("Failed to handle NPE Telegram alert: $e");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -17,6 +17,7 @@ class UserPreferencesService {
|
|||||||
{'key': 'marine_tarball', 'name': 'Marine Tarball'},
|
{'key': 'marine_tarball', 'name': 'Marine Tarball'},
|
||||||
{'key': 'marine_in_situ', 'name': 'Marine In-Situ'},
|
{'key': 'marine_in_situ', 'name': 'Marine In-Situ'},
|
||||||
{'key': 'marine_investigative', 'name': 'Marine Investigative'},
|
{'key': 'marine_investigative', 'name': 'Marine Investigative'},
|
||||||
|
{'key': 'marine_report', 'name': 'Marine Report'},
|
||||||
{'key': 'river_in_situ', 'name': 'River In-Situ'},
|
{'key': 'river_in_situ', 'name': 'River In-Situ'},
|
||||||
{'key': 'river_triennial', 'name': 'River Triennial'},
|
{'key': 'river_triennial', 'name': 'River Triennial'},
|
||||||
{'key': 'river_investigative', 'name': 'River Investigative'},
|
{'key': 'river_investigative', 'name': 'River Investigative'},
|
||||||
@ -52,9 +53,21 @@ class UserPreferencesService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 2. Determine default API links
|
// 2. Determine default API links
|
||||||
// This is correct: Tick any API server marked as 'is_active' by default.
|
|
||||||
final defaultApiLinks = allApiConfigs.map((config) {
|
final defaultApiLinks = allApiConfigs.map((config) {
|
||||||
bool isEnabled = (config['is_active'] == 1 || config['is_active'] == true);
|
bool isActive = (config['is_active'] == 1 || config['is_active'] == true);
|
||||||
|
bool isPstwHq = (config['config_name'] == 'PSTW_HQ');
|
||||||
|
|
||||||
|
bool isEnabled;
|
||||||
|
|
||||||
|
// --- MODIFIED: Special logic for Marine Report ---
|
||||||
|
// For marine_report, ONLY tick PSTW_HQ by default. Ignore other active APIs.
|
||||||
|
if (moduleKey == 'marine_report') {
|
||||||
|
isEnabled = isPstwHq;
|
||||||
|
} else {
|
||||||
|
// For other modules, tick if Active OR PSTW_HQ
|
||||||
|
isEnabled = isActive || isPstwHq;
|
||||||
|
}
|
||||||
|
|
||||||
return {...config, 'is_enabled': isEnabled};
|
return {...config, 'is_enabled': isEnabled};
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
@ -149,8 +162,15 @@ class UserPreferencesService {
|
|||||||
isEnabled = matchingLink['is_enabled'] as bool? ?? false;
|
isEnabled = matchingLink['is_enabled'] as bool? ?? false;
|
||||||
} else {
|
} else {
|
||||||
// No preference saved for this config. Apply default logic.
|
// No preference saved for this config. Apply default logic.
|
||||||
// (This handles newly synced configs automatically)
|
bool isActive = (config['is_active'] == 1 || config['is_active'] == true);
|
||||||
isEnabled = (config['is_active'] == 1 || config['is_active'] == true);
|
bool isPstwHq = (config['config_name'] == 'PSTW_HQ');
|
||||||
|
|
||||||
|
// --- MODIFIED: Special logic for Marine Report ---
|
||||||
|
if (moduleName == 'marine_report') {
|
||||||
|
isEnabled = isPstwHq;
|
||||||
|
} else {
|
||||||
|
isEnabled = isActive || isPstwHq;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// --- END MODIFICATION ---
|
// --- END MODIFICATION ---
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user