352 lines
12 KiB
Dart
352 lines
12 KiB
Dart
/// 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<List<ProjectStatusModel>> getProjectStatusList();
|
|
|
|
/// Fetch project progress list from API (construction stages)
|
|
Future<List<ProjectProgressModel>> getProjectProgressList();
|
|
|
|
/// Fetch all submissions from remote API
|
|
Future<List<ProjectSubmissionModel>> getSubmissions({
|
|
int limitStart = 0,
|
|
int limitPageLength = 0,
|
|
});
|
|
|
|
/// Fetch project detail by name
|
|
/// Returns the full project detail as a model
|
|
Future<ProjectSubmissionModel> getSubmissionDetail(String name);
|
|
|
|
/// Create or update a project submission
|
|
/// Returns the project name (ID) from the API response
|
|
Future<String> 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<String> uploadProjectFile({
|
|
required String projectName,
|
|
required String filePath,
|
|
});
|
|
|
|
/// Delete a file from a project submission
|
|
/// [fileId] is the file ID to delete
|
|
/// [projectName] is the project name (docname)
|
|
Future<void> deleteProjectFile({
|
|
required String fileId,
|
|
required String projectName,
|
|
});
|
|
}
|
|
|
|
/// 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<List<ProjectStatusModel>> getProjectStatusList() async {
|
|
try {
|
|
final response = await _dioClient.post<Map<String, dynamic>>(
|
|
'${ApiConstants.frappeApiMethod}${ApiConstants.getProjectStatusList}',
|
|
data: <String, dynamic>{
|
|
'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<dynamic> statusList = message as List<dynamic>;
|
|
return statusList
|
|
.map((json) =>
|
|
ProjectStatusModel.fromJson(json as Map<String, dynamic>))
|
|
.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<List<ProjectProgressModel>> getProjectProgressList() async {
|
|
try {
|
|
final response = await _dioClient.post<Map<String, dynamic>>(
|
|
'${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<dynamic> progressList = message as List<dynamic>;
|
|
return progressList
|
|
.map((json) =>
|
|
ProjectProgressModel.fromJson(json as Map<String, dynamic>))
|
|
.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<List<ProjectSubmissionModel>> getSubmissions({
|
|
int limitStart = 0,
|
|
int limitPageLength = 0,
|
|
}) async {
|
|
try {
|
|
final response = await _dioClient.post<Map<String, dynamic>>(
|
|
'${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<dynamic> submissionsList = message as List<dynamic>;
|
|
return submissionsList
|
|
.map((json) =>
|
|
ProjectSubmissionModel.fromJson(json as Map<String, dynamic>))
|
|
.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<ProjectSubmissionModel> getSubmissionDetail(String name) async {
|
|
try {
|
|
final response = await _dioClient.post<Map<String, dynamic>>(
|
|
'${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<String, dynamic>?;
|
|
if (message == null) {
|
|
throw Exception('No message field in getProjectDetail response');
|
|
}
|
|
|
|
final detailData = message['data'] as Map<String, dynamic>?;
|
|
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<String> saveSubmission(ProjectSubmissionRequest request) async {
|
|
try {
|
|
final response = await _dioClient.post<Map<String, dynamic>>(
|
|
'${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<String, dynamic>?;
|
|
if (message == null) {
|
|
throw Exception('No message in saveProject response');
|
|
}
|
|
|
|
final messageData = message['data'] as Map<String, dynamic>?;
|
|
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<String> 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<Map<String, dynamic>>(
|
|
'${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');
|
|
}
|
|
}
|
|
|
|
/// Delete a file from a project submission
|
|
///
|
|
/// Calls: POST /api/method/frappe.desk.form.utils.remove_attach
|
|
/// Form-data: fid, dt, dn
|
|
@override
|
|
Future<void> deleteProjectFile({
|
|
required String fileId,
|
|
required String projectName,
|
|
}) async {
|
|
try {
|
|
final formData = FormData.fromMap({
|
|
'fid': fileId,
|
|
'dt': 'Architectural Project',
|
|
'dn': projectName,
|
|
});
|
|
|
|
final response = await _dioClient.post<Map<String, dynamic>>(
|
|
'${ApiConstants.frappeApiMethod}${ApiConstants.removeProjectFile}',
|
|
data: formData,
|
|
);
|
|
|
|
final data = response.data;
|
|
if (data == null) {
|
|
throw Exception('No data received from deleteProjectFile 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');
|
|
}
|
|
} catch (e) {
|
|
throw Exception('Failed to delete project file: $e');
|
|
}
|
|
}
|
|
}
|