/// Submissions Remote Data Source /// /// Handles remote API calls for project submissions. library; import 'package:dio/dio.dart'; import 'package:worker/core/constants/api_constants.dart'; import 'package:worker/core/network/dio_client.dart'; import 'package:worker/features/projects/data/models/project_progress_model.dart'; import 'package:worker/features/projects/data/models/project_status_model.dart'; import 'package:worker/features/projects/data/models/project_submission_model.dart'; import 'package:worker/features/projects/data/models/project_submission_request.dart'; /// Submissions Remote Data Source /// /// Interface for remote project submission operations. abstract class SubmissionsRemoteDataSource { /// Fetch project status list from API Future> getProjectStatusList(); /// Fetch project progress list from API (construction stages) Future> getProjectProgressList(); /// Fetch all submissions from remote API Future> getSubmissions({ int limitStart = 0, int limitPageLength = 0, }); /// Fetch project detail by name /// Returns the full project detail as a model Future getSubmissionDetail(String name); /// Create or update a project submission /// Returns the project name (ID) from the API response Future saveSubmission(ProjectSubmissionRequest request); /// Upload a file for a project submission /// [projectName] is the project ID returned from saveSubmission /// [filePath] is the local path to the file /// Returns the uploaded file URL Future uploadProjectFile({ required String projectName, required String filePath, }); } /// Submissions Remote Data Source Implementation /// /// Uses Frappe API endpoints for project submissions. class SubmissionsRemoteDataSourceImpl implements SubmissionsRemoteDataSource { const SubmissionsRemoteDataSourceImpl(this._dioClient); final DioClient _dioClient; /// Get project status list /// /// Calls: POST /api/method/building_material.building_material.api.project.get_project_status_list /// Body: { "limit_start": 0, "limit_page_length": 0 } /// Returns: List of project statuses with labels and colors @override Future> getProjectStatusList() async { try { final response = await _dioClient.post>( '${ApiConstants.frappeApiMethod}${ApiConstants.getProjectStatusList}', data: { 'limit_start': 0, 'limit_page_length': 0, }, ); final data = response.data; if (data == null) { throw Exception('No data received from getProjectStatusList API'); } // API returns: { "message": [...] } final message = data['message']; if (message == null) { throw Exception('No message field in getProjectStatusList response'); } final List statusList = message as List; return statusList .map((json) => ProjectStatusModel.fromJson(json as Map)) .toList(); } catch (e) { throw Exception('Failed to get project status list: $e'); } } /// Get project progress list (construction stages) /// /// Calls: POST /api/method/frappe.client.get_list /// Body: { /// "doctype": "Progress of construction", /// "fields": ["name", "status"], /// "order_by": "number_of_display asc", /// "limit_page_length": 0 /// } /// Returns: List of construction progress stages @override Future> getProjectProgressList() async { try { final response = await _dioClient.post>( '${ApiConstants.frappeApiMethod}${ApiConstants.frappeGetList}', data: { 'doctype': 'Progress of construction', 'fields': ['name', 'status'], 'order_by': 'number_of_display asc', 'limit_page_length': 0, }, ); final data = response.data; if (data == null) { throw Exception('No data received from getProjectProgressList API'); } // API returns: { "message": [...] } final message = data['message']; if (message == null) { throw Exception('No message field in getProjectProgressList response'); } final List progressList = message as List; return progressList .map((json) => ProjectProgressModel.fromJson(json as Map)) .toList(); } catch (e) { throw Exception('Failed to get project progress list: $e'); } } /// Get list of project submissions /// /// Calls: POST /api/method/building_material.building_material.api.project.get_list /// Body: { "limit_start": 0, "limit_page_length": 0 } /// Returns: List of project submissions @override Future> getSubmissions({ int limitStart = 0, int limitPageLength = 0, }) async { try { final response = await _dioClient.post>( '${ApiConstants.frappeApiMethod}${ApiConstants.getProjectList}', data: { 'limit_start': limitStart, 'limit_page_length': limitPageLength, }, ); final data = response.data; if (data == null) { throw Exception('No data received from getProjectList API'); } // API returns: { "message": [...] } final message = data['message']; if (message == null) { throw Exception('No message field in getProjectList response'); } final List submissionsList = message as List; return submissionsList .map((json) => ProjectSubmissionModel.fromJson(json as Map)) .toList(); } catch (e) { throw Exception('Failed to get project submissions: $e'); } } /// Get project detail by name /// /// Calls: POST /api/method/building_material.building_material.api.project.get_detail /// Body: { "name": "#DA00011" } /// Response: { "message": { "success": true, "data": {...} } } /// Returns: Full project detail as model @override Future getSubmissionDetail(String name) async { try { final response = await _dioClient.post>( '${ApiConstants.frappeApiMethod}${ApiConstants.getProjectDetail}', data: {'name': name}, ); final data = response.data; if (data == null) { throw Exception('No data received from getProjectDetail API'); } // API returns: { "message": { "success": true, "data": {...} } } final message = data['message'] as Map?; if (message == null) { throw Exception('No message field in getProjectDetail response'); } final detailData = message['data'] as Map?; if (detailData == null) { throw Exception('No data field in getProjectDetail response'); } return ProjectSubmissionModel.fromJson(detailData); } catch (e) { throw Exception('Failed to get project detail: $e'); } } /// Save (create/update) a project submission /// /// Calls: POST /api/method/building_material.building_material.api.project.save /// Body: ProjectSubmissionRequest.toJson() /// Returns: Project name (ID) from response @override Future saveSubmission(ProjectSubmissionRequest request) async { try { final response = await _dioClient.post>( '${ApiConstants.frappeApiMethod}${ApiConstants.saveProject}', data: request.toJson(), ); final data = response.data; if (data == null) { throw Exception('No data received from saveProject API'); } // Check for error in response if (data['exc_type'] != null || data['exception'] != null) { final errorMessage = data['_server_messages'] ?? data['exception'] ?? 'Unknown error'; throw Exception('API error: $errorMessage'); } // Extract project name from response // Response format: { "message": { "success": true, "data": { "name": "#DA00007" } } } final message = data['message'] as Map?; if (message == null) { throw Exception('No message in saveProject response'); } final messageData = message['data'] as Map?; if (messageData == null || messageData['name'] == null) { throw Exception('No project name in saveProject response'); } return messageData['name'] as String; } catch (e) { throw Exception('Failed to save project submission: $e'); } } /// Upload a file for a project submission /// /// Calls: POST /api/method/upload_file /// Form-data: file, is_private, folder, doctype, docname, optimize /// Returns: Uploaded file URL @override Future uploadProjectFile({ required String projectName, required String filePath, }) async { try { final fileName = filePath.split('/').last; final formData = FormData.fromMap({ 'file': await MultipartFile.fromFile(filePath, filename: fileName), 'is_private': '0', 'folder': 'Home/Attachments', 'doctype': 'Architectural Project', 'docname': projectName, 'optimize': 'true', }); final response = await _dioClient.post>( '${ApiConstants.frappeApiMethod}${ApiConstants.uploadFile}', data: formData, ); final data = response.data; if (data == null) { throw Exception('No data received from uploadFile API'); } // Check for error in response if (data['exc_type'] != null || data['exception'] != null) { final errorMessage = data['_server_messages'] ?? data['exception'] ?? 'Unknown error'; throw Exception('API error: $errorMessage'); } // Extract file URL from response // Response format: { "message": { "file_url": "/files/...", ... } } final message = data['message']; if (message == null || message['file_url'] == null) { throw Exception('No file URL in uploadFile response'); } return message['file_url'] as String; } catch (e) { throw Exception('Failed to upload project file: $e'); } } }