2658 lines
82 KiB
Dart
2658 lines
82 KiB
Dart
import 'dart:convert';
|
|
import 'package:get/get.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:image_picker/image_picker.dart';
|
|
import 'package:intl/intl.dart';
|
|
|
|
// Helper Services
|
|
import 'package:on_field_work/helpers/services/auth_service.dart';
|
|
import 'package:on_field_work/helpers/services/api_endpoints.dart';
|
|
import 'package:on_field_work/helpers/services/storage/local_storage.dart';
|
|
import 'package:jwt_decoder/jwt_decoder.dart';
|
|
import 'package:on_field_work/helpers/services/app_logger.dart';
|
|
import 'package:on_field_work/helpers/utils/encryption_helper.dart';
|
|
|
|
// Models
|
|
import 'package:on_field_work/model/dashboard/project_progress_model.dart';
|
|
import 'package:on_field_work/model/dashboard/dashboard_tasks_model.dart';
|
|
import 'package:on_field_work/model/dashboard/dashboard_teams_model.dart';
|
|
import 'package:on_field_work/model/document/document_filter_model.dart';
|
|
import 'package:on_field_work/model/document/documents_list_model.dart';
|
|
import 'package:on_field_work/model/document/master_document_tags.dart';
|
|
import 'package:on_field_work/model/document/master_document_type_model.dart';
|
|
import 'package:on_field_work/model/document/document_details_model.dart';
|
|
import 'package:on_field_work/model/document/document_version_model.dart';
|
|
import 'package:on_field_work/model/attendance/organization_per_project_list_model.dart';
|
|
import 'package:on_field_work/model/tenant/tenant_services_model.dart';
|
|
import 'package:on_field_work/model/dailyTaskPlanning/daily_task_model.dart';
|
|
import 'package:on_field_work/model/dailyTaskPlanning/daily_progress_report_filter_response_model.dart';
|
|
import 'package:on_field_work/model/all_organization_model.dart';
|
|
import 'package:on_field_work/model/dashboard/pending_expenses_model.dart';
|
|
import 'package:on_field_work/model/dashboard/expense_type_report_model.dart';
|
|
import 'package:on_field_work/model/dashboard/monthly_expence_model.dart';
|
|
import 'package:on_field_work/model/finance/expense_category_model.dart';
|
|
import 'package:on_field_work/model/finance/currency_list_model.dart';
|
|
import 'package:on_field_work/model/finance/payment_payee_request_model.dart';
|
|
import 'package:on_field_work/model/finance/payment_request_list_model.dart';
|
|
import 'package:on_field_work/model/finance/payment_request_filter.dart';
|
|
import 'package:on_field_work/model/finance/payment_request_details_model.dart';
|
|
import 'package:on_field_work/model/finance/advance_payment_model.dart';
|
|
import 'package:on_field_work/model/service_project/service_projects_list_model.dart';
|
|
import 'package:on_field_work/model/service_project/service_projects_details_model.dart';
|
|
import 'package:on_field_work/model/service_project/job_list_model.dart';
|
|
import 'package:on_field_work/model/service_project/service_project_job_detail_model.dart';
|
|
import 'package:on_field_work/model/service_project/job_attendance_logs_model.dart';
|
|
import 'package:on_field_work/model/service_project/job_allocation_model.dart';
|
|
import 'package:on_field_work/model/service_project/service_project_branches_model.dart';
|
|
import 'package:on_field_work/model/service_project/job_status_response.dart';
|
|
import 'package:on_field_work/model/service_project/job_comments.dart';
|
|
import 'package:on_field_work/model/employees/employee_model.dart';
|
|
import 'package:on_field_work/model/infra_project/infra_project_list.dart';
|
|
import 'package:on_field_work/model/infra_project/infra_project_details.dart';
|
|
import 'package:on_field_work/model/dashboard/collection_overview_model.dart';
|
|
import 'package:on_field_work/model/dashboard/purchase_invoice_model.dart';
|
|
|
|
class ApiService {
|
|
static const bool enableLogs = true;
|
|
static const Duration extendedTimeout = Duration(seconds: 60);
|
|
|
|
static void _log(String message, {LogLevel level = LogLevel.info}) {
|
|
if (enableLogs) {
|
|
logSafe(message, level: level);
|
|
}
|
|
}
|
|
|
|
/// Utility to get the token, handle expiry, and refresh if needed.
|
|
static Future<String?> _getToken() async {
|
|
final token = LocalStorage.getJwtToken();
|
|
|
|
if (token == null) {
|
|
_log("No JWT token found. Logging out...", level: LogLevel.error);
|
|
await LocalStorage.logout();
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
final expirationDate = JwtDecoder.getExpirationDate(token);
|
|
final difference = expirationDate.difference(DateTime.now());
|
|
|
|
if (JwtDecoder.isExpired(token) || difference.inMinutes < 2) {
|
|
_log(
|
|
"Token expired or near expiry (${difference.inSeconds}s). Attempting refresh...",
|
|
level: LogLevel.warning,
|
|
);
|
|
final refreshed = await AuthService.refreshToken();
|
|
if (refreshed) {
|
|
return LocalStorage.getJwtToken();
|
|
} else {
|
|
_log("Token refresh failed. Logging out.", level: LogLevel.error);
|
|
await LocalStorage.logout();
|
|
return null;
|
|
}
|
|
}
|
|
} catch (e) {
|
|
_log("Token decoding error: $e", level: LogLevel.error);
|
|
await LocalStorage.logout();
|
|
return null;
|
|
}
|
|
|
|
return token;
|
|
}
|
|
|
|
static Map<String, String> _headers(String token) => {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': 'Bearer $token',
|
|
};
|
|
|
|
// --- Centralized Response Parsing and Decryption ---
|
|
|
|
/// Handles decryption, status code checks, and returns the 'data' payload or the full JSON.
|
|
static dynamic _parseAndDecryptResponse(http.Response response,
|
|
{String label = '', bool returnFullResponse = false}) {
|
|
final body = response.body.trim();
|
|
_log("$label Encrypted Response (Status ${response.statusCode}): $body",
|
|
level: LogLevel.debug);
|
|
|
|
if (body.isEmpty) {
|
|
_log("Empty response body for [$label]");
|
|
return null;
|
|
}
|
|
|
|
// Decrypt the Base64 string and auto-decode JSON
|
|
final decryptedJson = decryptResponse(body);
|
|
|
|
if (decryptedJson == null) {
|
|
_log("Decryption failed for [$label]. Cannot parse response.",
|
|
level: LogLevel.error);
|
|
return null;
|
|
}
|
|
|
|
// Handle non-200 or failure scenarios
|
|
if (response.statusCode != 200 ||
|
|
decryptedJson is! Map ||
|
|
decryptedJson['success'] != true) {
|
|
final message = decryptedJson is Map
|
|
? decryptedJson['message'] ?? 'Unknown error'
|
|
: 'Invalid decrypted response format.';
|
|
_log("API Error [$label] (Status ${response.statusCode}): $message",
|
|
level: LogLevel.warning);
|
|
return null;
|
|
}
|
|
|
|
_log(
|
|
"$label Decrypted Data: ${decryptedJson['data'] ?? 'Success (No data field)'}");
|
|
|
|
return returnFullResponse ? decryptedJson : decryptedJson['data'];
|
|
}
|
|
|
|
// --- Generic Request Execution Layer ---
|
|
|
|
/// Executes any HTTP request (GET, POST, PUT, DELETE, PATCH) safely with token management and retry logic.
|
|
static Future<http.Response?> _safeApiCall(
|
|
String endpoint, {
|
|
String method = 'GET',
|
|
Map<String, String>? queryParams,
|
|
dynamic body,
|
|
Map<String, String>? additionalHeaders,
|
|
Duration customTimeout = extendedTimeout,
|
|
bool hasRetried = false,
|
|
}) async {
|
|
String? token = await _getToken();
|
|
if (token == null) {
|
|
_log("Token is null. Cannot proceed with $method request.",
|
|
level: LogLevel.error);
|
|
return null;
|
|
}
|
|
|
|
final uri = Uri.parse("${ApiEndpoints.baseUrl}$endpoint").replace(
|
|
queryParameters: queryParams,
|
|
);
|
|
|
|
final headers = {
|
|
..._headers(token),
|
|
if (additionalHeaders != null) ...additionalHeaders,
|
|
};
|
|
|
|
_log("Initiating $method request to $uri", level: LogLevel.debug);
|
|
if (body != null) {
|
|
_log("Request Body: ${body is String ? body : jsonEncode(body)}",
|
|
level: LogLevel.debug);
|
|
}
|
|
|
|
try {
|
|
http.Response response;
|
|
final encodedBody = (body != null) ? jsonEncode(body) : null;
|
|
|
|
switch (method) {
|
|
case 'GET':
|
|
response =
|
|
await http.get(uri, headers: headers).timeout(customTimeout);
|
|
break;
|
|
case 'POST':
|
|
response = await http
|
|
.post(uri, headers: headers, body: encodedBody)
|
|
.timeout(customTimeout);
|
|
break;
|
|
case 'PUT':
|
|
response = await http
|
|
.put(uri, headers: headers, body: encodedBody)
|
|
.timeout(customTimeout);
|
|
break;
|
|
case 'DELETE':
|
|
response =
|
|
await http.delete(uri, headers: headers).timeout(customTimeout);
|
|
break;
|
|
case 'PATCH':
|
|
response = await http
|
|
.patch(uri, headers: headers, body: encodedBody)
|
|
.timeout(customTimeout);
|
|
break;
|
|
default:
|
|
throw UnsupportedError("Unsupported HTTP method: $method");
|
|
}
|
|
|
|
_log("$method Response Status: ${response.statusCode}",
|
|
level: LogLevel.debug);
|
|
|
|
if (response.statusCode == 401 && !hasRetried) {
|
|
_log("Unauthorized (401). Attempting token refresh and retry...",
|
|
level: LogLevel.warning);
|
|
if (await AuthService.refreshToken()) {
|
|
return await _safeApiCall(
|
|
endpoint,
|
|
method: method,
|
|
queryParams: queryParams,
|
|
body: body,
|
|
additionalHeaders: additionalHeaders,
|
|
customTimeout: customTimeout,
|
|
hasRetried: true, // Prevent infinite loop
|
|
);
|
|
}
|
|
_log("Token refresh failed on 401. Logging out user.",
|
|
level: LogLevel.error);
|
|
await LocalStorage.logout();
|
|
}
|
|
|
|
return response;
|
|
} catch (e, stack) {
|
|
_log("HTTP $method Exception: $e\n$stack", level: LogLevel.error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// --- Public API Methods (Utilizing Generic Call) ---
|
|
|
|
/// ============================================
|
|
/// DASHBOARD
|
|
/// ============================================
|
|
|
|
static Future<PurchaseInvoiceOverviewResponse?> getPurchaseInvoiceOverview({
|
|
String? projectId,
|
|
}) async {
|
|
final queryParams = <String, String>{};
|
|
if (projectId != null && projectId.isNotEmpty) {
|
|
queryParams['projectId'] = projectId;
|
|
}
|
|
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.getPurchaseInvoiceOverview,
|
|
method: 'GET',
|
|
queryParams: queryParams,
|
|
);
|
|
|
|
if (response == null) return null;
|
|
|
|
final parsedJson = _parseAndDecryptResponse(
|
|
response,
|
|
label: "PurchaseInvoiceOverview",
|
|
returnFullResponse: true,
|
|
);
|
|
if (parsedJson == null) return null;
|
|
|
|
return PurchaseInvoiceOverviewResponse.fromJson(parsedJson);
|
|
}
|
|
|
|
static Future<CollectionOverviewResponse?> getCollectionOverview({
|
|
String? projectId,
|
|
}) async {
|
|
final queryParams = <String, String>{};
|
|
if (projectId != null && projectId.isNotEmpty) {
|
|
queryParams['projectId'] = projectId;
|
|
}
|
|
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.getCollectionOverview,
|
|
method: 'GET',
|
|
queryParams: queryParams,
|
|
);
|
|
|
|
if (response == null) return null;
|
|
|
|
final parsedJson = _parseAndDecryptResponse(response,
|
|
label: "CollectionOverview", returnFullResponse: true);
|
|
|
|
if (parsedJson == null) return null;
|
|
|
|
return CollectionOverviewResponse.fromJson(parsedJson);
|
|
}
|
|
|
|
static Future<DashboardTasks?> getDashboardTasks(
|
|
{required String projectId}) async {
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.getDashboardTasks,
|
|
method: 'GET',
|
|
queryParams: {'projectId': projectId},
|
|
);
|
|
|
|
if (response == null) return null;
|
|
|
|
final jsonResponse = _parseAndDecryptResponse(response,
|
|
label: "DashboardTasks", returnFullResponse: true);
|
|
if (jsonResponse == null) return null;
|
|
|
|
return DashboardTasks.fromJson(jsonResponse);
|
|
}
|
|
|
|
static Future<DashboardTeams?> getDashboardTeams(
|
|
{required String projectId}) async {
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.getDashboardTeams,
|
|
method: 'GET',
|
|
queryParams: {'projectId': projectId},
|
|
);
|
|
|
|
if (response == null) return null;
|
|
|
|
final jsonResponse = _parseAndDecryptResponse(response,
|
|
label: "DashboardTeams", returnFullResponse: true);
|
|
if (jsonResponse == null) return null;
|
|
|
|
return DashboardTeams.fromJson(jsonResponse);
|
|
}
|
|
|
|
static Future<List<dynamic>?> getDashboardAttendanceOverview(
|
|
String projectId, int days) async {
|
|
if (projectId.isEmpty) throw ArgumentError('projectId must not be empty');
|
|
if (days <= 0) throw ArgumentError('days must be greater than 0');
|
|
|
|
final endpoint =
|
|
"${ApiEndpoints.getDashboardAttendanceOverview}/$projectId";
|
|
final response = await _safeApiCall(
|
|
endpoint,
|
|
method: 'GET',
|
|
queryParams: {'days': days.toString()},
|
|
);
|
|
|
|
if (response == null) return null;
|
|
return _parseAndDecryptResponse(response,
|
|
label: 'Dashboard Attendance Overview');
|
|
}
|
|
|
|
static Future<ProjectResponse?> getProjectProgress({
|
|
required String projectId,
|
|
required int days,
|
|
DateTime? fromDate,
|
|
}) async {
|
|
final actualFromDate = fromDate ?? DateTime.now();
|
|
|
|
final queryParams = {
|
|
"projectId": projectId,
|
|
"days": days.toString(),
|
|
"FromDate":
|
|
DateFormat("yyyy-MM-dd HH:mm:ss.SSSSSS").format(actualFromDate),
|
|
};
|
|
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.getDashboardProjectProgress,
|
|
method: 'GET',
|
|
queryParams: queryParams,
|
|
);
|
|
|
|
if (response == null) return null;
|
|
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "ProjectProgress", returnFullResponse: true);
|
|
if (parsed == null) return null;
|
|
|
|
return ProjectResponse.fromJson(parsed);
|
|
}
|
|
|
|
static Future<PendingExpensesResponse?> getPendingExpensesApi({
|
|
required String projectId,
|
|
}) async {
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.getPendingExpenses,
|
|
method: 'GET',
|
|
queryParams: {'projectId': projectId},
|
|
);
|
|
|
|
if (response == null) return null;
|
|
|
|
final jsonResponse = _parseAndDecryptResponse(response,
|
|
label: "Pending Expenses", returnFullResponse: true);
|
|
if (jsonResponse == null) return null;
|
|
|
|
return PendingExpensesResponse.fromJson(jsonResponse);
|
|
}
|
|
|
|
static Future<ExpenseTypeReportResponse?> getExpenseTypeReportApi({
|
|
required String projectId,
|
|
required DateTime startDate,
|
|
required DateTime endDate,
|
|
}) async {
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.getExpenseTypeReport,
|
|
method: 'GET',
|
|
queryParams: {
|
|
'projectId': projectId,
|
|
'startDate': startDate.toIso8601String(),
|
|
'endDate': endDate.toIso8601String(),
|
|
},
|
|
);
|
|
|
|
if (response == null) return null;
|
|
|
|
final jsonResponse = _parseAndDecryptResponse(response,
|
|
label: "Expense Category Report", returnFullResponse: true);
|
|
if (jsonResponse == null) return null;
|
|
|
|
return ExpenseTypeReportResponse.fromJson(jsonResponse);
|
|
}
|
|
|
|
static Future<DashboardMonthlyExpenseResponse?>
|
|
getDashboardMonthlyExpensesApi({
|
|
String? categoryId,
|
|
int months = 12,
|
|
}) async {
|
|
final queryParams = {
|
|
'months': months.toString(),
|
|
if (categoryId != null && categoryId.isNotEmpty) 'categoryId': categoryId,
|
|
};
|
|
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.getDashboardMonthlyExpenses,
|
|
method: 'GET',
|
|
queryParams: queryParams,
|
|
);
|
|
|
|
if (response == null) return null;
|
|
|
|
final jsonResponse = _parseAndDecryptResponse(response,
|
|
label: "Dashboard Monthly Expenses", returnFullResponse: true);
|
|
if (jsonResponse == null) return null;
|
|
|
|
return DashboardMonthlyExpenseResponse.fromJson(jsonResponse);
|
|
}
|
|
|
|
/// ============================================
|
|
/// INFRA PROJECT
|
|
/// ============================================
|
|
|
|
static Future<ProjectsResponse?> getInfraProjectsList({
|
|
int pageSize = 20,
|
|
int pageNumber = 1,
|
|
String searchString = "",
|
|
}) async {
|
|
final queryParams = {
|
|
"pageSize": pageSize.toString(),
|
|
"pageNumber": pageNumber.toString(),
|
|
"searchString": searchString,
|
|
};
|
|
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.getInfraProjectsList,
|
|
method: 'GET',
|
|
queryParams: queryParams,
|
|
);
|
|
|
|
if (response == null) return null;
|
|
|
|
final parsedJson = _parseAndDecryptResponse(response,
|
|
label: "InfraProjectsList", returnFullResponse: true);
|
|
if (parsedJson == null) return null;
|
|
|
|
return ProjectsResponse.fromJson(parsedJson);
|
|
}
|
|
|
|
static Future<ProjectDetailsResponse?> getInfraProjectDetails({
|
|
required String projectId,
|
|
}) async {
|
|
final endpoint = "${ApiEndpoints.getInfraProjectDetail}/$projectId";
|
|
final response = await _safeApiCall(endpoint, method: 'GET');
|
|
|
|
if (response == null) return null;
|
|
|
|
final parsedJson = _parseAndDecryptResponse(response,
|
|
label: "InfraProjectDetails", returnFullResponse: true);
|
|
if (parsedJson == null) return null;
|
|
|
|
return ProjectDetailsResponse.fromJson(parsedJson);
|
|
}
|
|
|
|
/// ============================================
|
|
/// SERVICE PROJECT
|
|
/// ============================================
|
|
|
|
static Future<ServiceProjectListModel?> getServiceProjectsListApi({
|
|
int pageNumber = 1,
|
|
int pageSize = 20,
|
|
}) async {
|
|
final queryParams = {
|
|
'pageNumber': pageNumber.toString(),
|
|
'pageSize': pageSize.toString(),
|
|
};
|
|
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.getServiceProjectsList,
|
|
method: 'GET',
|
|
queryParams: queryParams,
|
|
);
|
|
|
|
if (response == null) return null;
|
|
|
|
final jsonResponse = _parseAndDecryptResponse(response,
|
|
label: "Service Project List", returnFullResponse: true);
|
|
if (jsonResponse == null) return null;
|
|
|
|
return ServiceProjectListModel.fromJson(jsonResponse);
|
|
}
|
|
|
|
static Future<ServiceProjectDetailModel?> getServiceProjectDetailApi(
|
|
String projectId) async {
|
|
final endpoint = "${ApiEndpoints.getServiceProjectDetail}/$projectId";
|
|
final response = await _safeApiCall(endpoint, method: 'GET');
|
|
|
|
if (response == null) return null;
|
|
|
|
final jsonResponse = _parseAndDecryptResponse(response,
|
|
label: "Service Project Detail", returnFullResponse: true);
|
|
if (jsonResponse == null) return null;
|
|
|
|
return ServiceProjectDetailModel.fromJson(jsonResponse);
|
|
}
|
|
|
|
static Future<JobResponse?> getServiceProjectJobListApi({
|
|
required String projectId,
|
|
int pageNumber = 1,
|
|
int pageSize = 20,
|
|
bool isActive = true,
|
|
bool isArchive = false,
|
|
}) async {
|
|
final queryParams = {
|
|
'projectId': projectId,
|
|
'pageNumber': pageNumber.toString(),
|
|
'pageSize': pageSize.toString(),
|
|
'isActive': isActive.toString(),
|
|
if (isArchive) 'isArchive': 'true',
|
|
};
|
|
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.getServiceProjectJobList,
|
|
method: 'GET',
|
|
queryParams: queryParams,
|
|
);
|
|
|
|
if (response == null) return null;
|
|
|
|
final jsonResponse = _parseAndDecryptResponse(response,
|
|
label: isArchive
|
|
? "Archived Service Project Job List"
|
|
: "Active Service Project Job List",
|
|
returnFullResponse: true);
|
|
|
|
if (jsonResponse == null) return null;
|
|
|
|
return JobResponse.fromJson(jsonResponse);
|
|
}
|
|
|
|
static Future<String?> createServiceProjectJobApi({
|
|
required String title,
|
|
required String description,
|
|
required String projectId,
|
|
required List<Map<String, dynamic>> assignees,
|
|
required DateTime startDate,
|
|
required DateTime dueDate,
|
|
required List<Map<String, dynamic>> tags,
|
|
required String? branchId,
|
|
}) async {
|
|
final body = {
|
|
"title": title,
|
|
"description": description,
|
|
"projectId": projectId,
|
|
"assignees": assignees,
|
|
"startDate": startDate.toIso8601String(),
|
|
"dueDate": dueDate.toIso8601String(),
|
|
"tags": tags,
|
|
"projectBranchId": branchId,
|
|
};
|
|
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.createServiceProjectJob,
|
|
method: 'POST',
|
|
body: body,
|
|
);
|
|
|
|
if (response == null) return null;
|
|
|
|
final jsonResponse = _parseAndDecryptResponse(
|
|
response,
|
|
label: "Create Service Project Job",
|
|
returnFullResponse: true,
|
|
);
|
|
|
|
if (jsonResponse == null) return null;
|
|
|
|
return jsonResponse['data']?['id'];
|
|
}
|
|
|
|
static Future<JobDetailsResponse?> getServiceProjectJobDetailApi(
|
|
String jobId) async {
|
|
final endpoint = "${ApiEndpoints.getServiceProjectJobDetail}/$jobId";
|
|
|
|
final response = await _safeApiCall(endpoint, method: 'GET');
|
|
if (response == null) return null;
|
|
|
|
final jsonResponse = _parseAndDecryptResponse(response,
|
|
label: "Service Project Job Detail", returnFullResponse: true);
|
|
if (jsonResponse == null) return null;
|
|
|
|
return JobDetailsResponse.fromJson(jsonResponse);
|
|
}
|
|
|
|
static Future<bool> editServiceProjectJobApi({
|
|
required String jobId,
|
|
required List<Map<String, dynamic>> operations,
|
|
}) async {
|
|
final endpoint = "${ApiEndpoints.editServiceProjectJob}/$jobId";
|
|
// Using PATCH for partial update (JSON Patch format expected by server)
|
|
final response = await _safeApiCall(
|
|
endpoint,
|
|
method: 'PATCH',
|
|
body: operations,
|
|
);
|
|
|
|
if (response == null) return false;
|
|
|
|
// A PATCH request usually returns 200/204. Use parse to check for 'success' flag.
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "Edit Service Project Job", returnFullResponse: true);
|
|
return parsed != null && parsed['success'] == true;
|
|
}
|
|
|
|
// Add this method back to ApiService:
|
|
static Future<List<dynamic>?> getEmployees({
|
|
Map<String, String>? queryParams,
|
|
}) async {
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.getEmployeesWithoutPermission,
|
|
method: 'GET',
|
|
queryParams: queryParams,
|
|
);
|
|
if (response == null) return null;
|
|
return _parseAndDecryptResponse(response, label: 'Get Employees');
|
|
}
|
|
|
|
static Future<bool> updateServiceProjectJobAttendance({
|
|
required Map<String, dynamic> payload,
|
|
}) async {
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.serviceProjectUpateJobAttendance,
|
|
method: 'POST',
|
|
body: payload,
|
|
);
|
|
|
|
if (response == null) return false;
|
|
|
|
// Use parse to check for 'success' flag.
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "Update Job Attendance", returnFullResponse: true);
|
|
return parsed != null && parsed['success'] == true;
|
|
}
|
|
|
|
static Future<JobAttendanceResponse?> getJobAttendanceLog({
|
|
required String attendanceId,
|
|
}) async {
|
|
final endpoint =
|
|
"${ApiEndpoints.serviceProjectUpateJobAttendanceLog}/$attendanceId";
|
|
final response = await _safeApiCall(endpoint, method: 'GET');
|
|
|
|
if (response == null) return null;
|
|
|
|
final parsedJson = _parseAndDecryptResponse(response,
|
|
label: "JobAttendanceLog", returnFullResponse: true);
|
|
if (parsedJson == null) return null;
|
|
|
|
return JobAttendanceResponse.fromJson(parsedJson);
|
|
}
|
|
|
|
static Future<List<ServiceProjectAllocation>?>
|
|
getServiceProjectAllocationList({
|
|
required String projectId,
|
|
bool isActive = true,
|
|
}) async {
|
|
final queryParams = {
|
|
'projectId': projectId,
|
|
'isActive': isActive.toString(),
|
|
};
|
|
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.getServiceProjectUpateJobAllocationList,
|
|
method: 'GET',
|
|
queryParams: queryParams,
|
|
);
|
|
|
|
if (response == null) return null;
|
|
|
|
final data = _parseAndDecryptResponse(response,
|
|
label: "ServiceProjectAllocationList");
|
|
|
|
if (data is List) {
|
|
return data.map((e) => ServiceProjectAllocation.fromJson(e)).toList();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
static Future<bool> manageServiceProjectAllocation({
|
|
required List<Map<String, dynamic>> payload,
|
|
}) async {
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.manageServiceProjectUpateJobAllocation,
|
|
method: 'POST',
|
|
body: payload,
|
|
);
|
|
|
|
if (response == null) return false;
|
|
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "Manage Allocation", returnFullResponse: true);
|
|
return parsed != null && parsed['success'] == true;
|
|
}
|
|
|
|
static Future<List<TeamRole>?> getTeamRoles() async {
|
|
final response =
|
|
await _safeApiCall(ApiEndpoints.getTeamRoles, method: 'GET');
|
|
|
|
if (response == null) return null;
|
|
|
|
final data = _parseAndDecryptResponse(response, label: "TeamRoles");
|
|
if (data is List) {
|
|
return data
|
|
.map((e) => TeamRole.fromJson(e as Map<String, dynamic>))
|
|
.toList();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
static Future<ServiceProjectBranchesResponse?> getServiceProjectBranchesFull({
|
|
required String projectId,
|
|
int pageNumber = 1,
|
|
int pageSize = 20,
|
|
String searchString = '',
|
|
bool isActive = true,
|
|
}) async {
|
|
final queryParams = {
|
|
'pageNumber': pageNumber.toString(),
|
|
'pageSize': pageSize.toString(),
|
|
'searchString': searchString,
|
|
'isActive': isActive.toString(),
|
|
};
|
|
|
|
final endpoint = "${ApiEndpoints.getServiceProjectBranches}/$projectId";
|
|
|
|
final response = await _safeApiCall(
|
|
endpoint,
|
|
method: 'GET',
|
|
queryParams: queryParams,
|
|
);
|
|
|
|
if (response == null) return null;
|
|
|
|
final parsedJson = _parseAndDecryptResponse(
|
|
response,
|
|
label: "ServiceProjectBranchesFull",
|
|
returnFullResponse: true,
|
|
);
|
|
|
|
if (parsedJson == null) return null;
|
|
|
|
return ServiceProjectBranchesResponse.fromJson(parsedJson);
|
|
}
|
|
|
|
static Future<List<JobStatus>?> getMasterJobStatus({
|
|
required String statusId,
|
|
required String projectId,
|
|
}) async {
|
|
final queryParams = {
|
|
'statusId': statusId,
|
|
'projectId': projectId,
|
|
};
|
|
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.getMasterJobStatus,
|
|
method: 'GET',
|
|
queryParams: queryParams,
|
|
);
|
|
|
|
if (response == null) return null;
|
|
|
|
final data = _parseAndDecryptResponse(response, label: "MasterJobStatus");
|
|
|
|
if (data is List) {
|
|
return data.map((e) => JobStatus.fromJson(e)).toList();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
static Future<bool> addJobComment({
|
|
required String jobTicketId,
|
|
required String comment,
|
|
List<Map<String, dynamic>> attachments = const [],
|
|
}) async {
|
|
final body = {
|
|
"jobTicketId": jobTicketId,
|
|
"comment": comment,
|
|
"attachments": attachments,
|
|
};
|
|
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.addJobComment,
|
|
method: 'POST',
|
|
body: body,
|
|
);
|
|
|
|
if (response == null) return false;
|
|
|
|
// Check status 201 for success, or rely on decrypted 'success' flag
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "AddJobComment", returnFullResponse: true);
|
|
|
|
return response.statusCode == 201 ||
|
|
(parsed != null && parsed['success'] == true);
|
|
}
|
|
|
|
static Future<JobCommentResponse?> getJobCommentList({
|
|
required String jobTicketId,
|
|
int pageNumber = 1,
|
|
int pageSize = 20,
|
|
}) async {
|
|
final queryParams = {
|
|
'jobTicketId': jobTicketId,
|
|
'pageNumber': pageNumber.toString(),
|
|
'pageSize': pageSize.toString(),
|
|
};
|
|
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.getJobCommentList,
|
|
method: 'GET',
|
|
queryParams: queryParams,
|
|
);
|
|
|
|
if (response == null) return null;
|
|
|
|
final parsedJson = _parseAndDecryptResponse(response,
|
|
label: "JobCommentList", returnFullResponse: true);
|
|
if (parsedJson == null) return null;
|
|
|
|
return JobCommentResponse.fromJson(parsedJson);
|
|
}
|
|
|
|
/// ============================================
|
|
/// FINANCE & EXPENSE
|
|
/// ============================================
|
|
|
|
static Future<PaymentRequestPayeeResponse?>
|
|
getExpensePaymentRequestPayeeApi() async {
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.getExpensePaymentRequestPayee,
|
|
method: 'GET');
|
|
|
|
if (response == null) return null;
|
|
|
|
final jsonResponse = _parseAndDecryptResponse(response,
|
|
label: "Expense Payment Request Payee", returnFullResponse: true);
|
|
if (jsonResponse == null) return null;
|
|
|
|
return PaymentRequestPayeeResponse.fromJson(jsonResponse);
|
|
}
|
|
|
|
static Future<ExpenseCategoryResponse?>
|
|
getMasterExpenseCategoriesApi() async {
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.getMasterExpensesCategories,
|
|
method: 'GET');
|
|
|
|
if (response == null) return null;
|
|
|
|
final jsonResponse = _parseAndDecryptResponse(response,
|
|
label: "Master Expense Categories", returnFullResponse: true);
|
|
if (jsonResponse == null) return null;
|
|
|
|
return ExpenseCategoryResponse.fromJson(jsonResponse);
|
|
}
|
|
|
|
static Future<CurrencyListResponse?> getMasterCurrenciesApi() async {
|
|
final response =
|
|
await _safeApiCall(ApiEndpoints.getMasterCurrencies, method: 'GET');
|
|
|
|
if (response == null) return null;
|
|
|
|
final jsonResponse = _parseAndDecryptResponse(response,
|
|
label: "Master Currencies", returnFullResponse: true);
|
|
if (jsonResponse == null) return null;
|
|
|
|
return CurrencyListResponse.fromJson(jsonResponse);
|
|
}
|
|
|
|
static Future<bool> createExpensePaymentRequestApi({
|
|
required String title,
|
|
required String projectId,
|
|
required String expenseCategoryId,
|
|
required String currencyId,
|
|
required String payee,
|
|
required double amount,
|
|
DateTime? dueDate,
|
|
required String description,
|
|
required bool isAdvancePayment,
|
|
List<Map<String, dynamic>> billAttachments = const [],
|
|
}) async {
|
|
final body = {
|
|
"title": title,
|
|
"projectId": projectId,
|
|
"expenseCategoryId": expenseCategoryId,
|
|
"currencyId": currencyId,
|
|
"payee": payee,
|
|
"amount": amount,
|
|
"dueDate": dueDate?.toIso8601String(),
|
|
"description": description,
|
|
"isAdvancePayment": isAdvancePayment,
|
|
"billAttachments": billAttachments,
|
|
};
|
|
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.createExpensePaymentRequest,
|
|
method: 'POST',
|
|
body: body,
|
|
);
|
|
|
|
if (response == null) return false;
|
|
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "Create Payment Request", returnFullResponse: true);
|
|
return parsed != null && parsed['success'] == true;
|
|
}
|
|
|
|
static Future<PaymentRequestResponse?> getExpensePaymentRequestListApi({
|
|
bool isActive = true,
|
|
int pageSize = 20,
|
|
int pageNumber = 1,
|
|
Map<String, dynamic>? filter,
|
|
String searchString = '',
|
|
}) async {
|
|
final queryParams = {
|
|
'isActive': isActive.toString(),
|
|
'pageSize': pageSize.toString(),
|
|
'pageNumber': pageNumber.toString(),
|
|
'filter': jsonEncode(filter ??
|
|
{
|
|
"projectIds": [],
|
|
"statusIds": [],
|
|
"createdByIds": [],
|
|
"currencyIds": [],
|
|
"expenseCategoryIds": [],
|
|
"payees": [],
|
|
"startDate": null,
|
|
"endDate": null
|
|
}),
|
|
'searchString': searchString,
|
|
};
|
|
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.getExpensePaymentRequestList,
|
|
method: 'GET',
|
|
queryParams: queryParams,
|
|
);
|
|
|
|
if (response == null) return null;
|
|
|
|
final jsonResponse = _parseAndDecryptResponse(response,
|
|
label: "Expense Payment Request List", returnFullResponse: true);
|
|
if (jsonResponse == null) return null;
|
|
|
|
return PaymentRequestResponse.fromJson(jsonResponse);
|
|
}
|
|
|
|
static Future<PaymentRequestFilter?>
|
|
getExpensePaymentRequestFilterApi() async {
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.getExpensePaymentRequestFilter,
|
|
method: 'GET');
|
|
|
|
if (response == null) return null;
|
|
|
|
final jsonResponse = _parseAndDecryptResponse(response,
|
|
label: "Expense Payment Request Filter", returnFullResponse: true);
|
|
if (jsonResponse == null) return null;
|
|
|
|
return PaymentRequestFilter.fromJson(jsonResponse);
|
|
}
|
|
|
|
static Future<PaymentRequestDetail?> getExpensePaymentRequestDetailApi(
|
|
String paymentRequestId) async {
|
|
final endpoint =
|
|
"${ApiEndpoints.getExpensePaymentRequestDetails}/$paymentRequestId";
|
|
|
|
final response = await _safeApiCall(endpoint, method: 'GET');
|
|
if (response == null) return null;
|
|
|
|
final jsonResponse = _parseAndDecryptResponse(response,
|
|
label: "Expense Payment Request Detail", returnFullResponse: true);
|
|
if (jsonResponse == null) return null;
|
|
|
|
return PaymentRequestDetail.fromJson(jsonResponse);
|
|
}
|
|
|
|
static Future<bool> updateExpensePaymentRequestStatusApi({
|
|
required String paymentRequestId,
|
|
required String statusId,
|
|
required String comment,
|
|
String? paidTransactionId,
|
|
String? paidById,
|
|
DateTime? paidAt,
|
|
double? baseAmount,
|
|
double? taxAmount,
|
|
String? tdsPercentage,
|
|
}) async {
|
|
final body = {
|
|
"paymentRequestId": paymentRequestId,
|
|
"statusId": statusId,
|
|
"comment": comment,
|
|
"paidTransactionId": paidTransactionId,
|
|
"paidById": paidById,
|
|
"paidAt": paidAt?.toIso8601String(),
|
|
"baseAmount": baseAmount,
|
|
"taxAmount": taxAmount,
|
|
"tdsPercentage": tdsPercentage ?? "0",
|
|
};
|
|
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.updateExpensePaymentRequestStatus,
|
|
method: 'POST',
|
|
body: body,
|
|
);
|
|
|
|
if (response == null) return false;
|
|
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "Update PR Status", returnFullResponse: true);
|
|
return parsed != null && parsed['success'] == true;
|
|
}
|
|
|
|
static Future<bool> createExpenseForPRApi({
|
|
required String paymentModeId,
|
|
required String location,
|
|
required String gstNumber,
|
|
required String statusId,
|
|
required String paymentRequestId,
|
|
required String comment,
|
|
List<Map<String, dynamic>> billAttachments = const [],
|
|
}) async {
|
|
final body = {
|
|
"paymentModeId": paymentModeId,
|
|
"location": location,
|
|
"gstNumber": gstNumber,
|
|
"statusId": statusId,
|
|
"comment": comment,
|
|
"paymentRequestId": paymentRequestId,
|
|
"billAttachments": billAttachments,
|
|
};
|
|
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.createExpenseforPR,
|
|
method: 'POST',
|
|
body: body,
|
|
);
|
|
|
|
if (response == null) return false;
|
|
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "Create Expense for PR", returnFullResponse: true);
|
|
return parsed != null && parsed['success'] == true;
|
|
}
|
|
|
|
static Future<bool> editExpensePaymentRequestApi({
|
|
required String id,
|
|
required String title,
|
|
required String description,
|
|
required String payee,
|
|
required String currencyId,
|
|
required double amount,
|
|
required String dueDate,
|
|
required String projectId,
|
|
required String expenseCategoryId,
|
|
required bool isAdvancePayment,
|
|
List<Map<String, dynamic>> billAttachments = const [],
|
|
}) async {
|
|
final endpoint = "${ApiEndpoints.getExpensePaymentRequestEdit}/$id";
|
|
final body = {
|
|
"id": id,
|
|
"title": title,
|
|
"description": description,
|
|
"payee": payee,
|
|
"currencyId": currencyId,
|
|
"amount": amount,
|
|
"dueDate": dueDate,
|
|
"projectId": projectId,
|
|
"expenseCategoryId": expenseCategoryId,
|
|
"isAdvancePayment": isAdvancePayment,
|
|
"billAttachments": billAttachments,
|
|
};
|
|
|
|
final response = await _safeApiCall(
|
|
endpoint,
|
|
method: 'PUT',
|
|
body: body,
|
|
);
|
|
|
|
if (response == null) return false;
|
|
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "Edit Expense PR", returnFullResponse: true);
|
|
return parsed != null && parsed['success'] == true;
|
|
}
|
|
|
|
static Future<List<AdvancePayment>> getAdvancePayments(
|
|
String employeeId) async {
|
|
final endpoint = "${ApiEndpoints.getAdvancePayments}/$employeeId";
|
|
final response = await _safeApiCall(endpoint, method: 'GET');
|
|
|
|
if (response == null) return [];
|
|
|
|
final data = _parseAndDecryptResponse(response, label: 'Advance Payments');
|
|
if (data is List) {
|
|
return data.map((e) => AdvancePayment.fromJson(e)).toList();
|
|
}
|
|
return [];
|
|
}
|
|
|
|
static Future<List<dynamic>?> getMasterPaymentModes() async {
|
|
final response =
|
|
await _safeApiCall(ApiEndpoints.getMasterPaymentModes, method: 'GET');
|
|
if (response == null) return null;
|
|
return _parseAndDecryptResponse(response, label: 'Master Payment Modes');
|
|
}
|
|
|
|
static Future<List<dynamic>?> getMasterExpenseStatus() async {
|
|
final response =
|
|
await _safeApiCall(ApiEndpoints.getMasterExpenseStatus, method: 'GET');
|
|
if (response == null) return null;
|
|
return _parseAndDecryptResponse(response, label: 'Master Expense Status');
|
|
}
|
|
|
|
static Future<List<dynamic>?> getMasterExpenseTypes() async {
|
|
final response = await _safeApiCall(ApiEndpoints.getMasterExpenseCategory,
|
|
method: 'GET');
|
|
if (response == null) return null;
|
|
return _parseAndDecryptResponse(response,
|
|
label: 'Master Expense Categorys');
|
|
}
|
|
|
|
static Future<bool> createExpenseApi({
|
|
required String projectId,
|
|
required String expensesTypeId,
|
|
required String paymentModeId,
|
|
required String paidById,
|
|
required DateTime transactionDate,
|
|
required String transactionId,
|
|
required String description,
|
|
required String location,
|
|
required String supplerName,
|
|
required double amount,
|
|
required int noOfPersons,
|
|
required List<Map<String, dynamic>> billAttachments,
|
|
}) async {
|
|
final payload = {
|
|
"projectId": projectId,
|
|
"expenseCategoryId": expensesTypeId,
|
|
"paymentModeId": paymentModeId,
|
|
"paidById": paidById,
|
|
"transactionDate": transactionDate.toIso8601String(),
|
|
"transactionId": transactionId,
|
|
"description": description,
|
|
"location": location,
|
|
"supplerName": supplerName,
|
|
"amount": amount,
|
|
"noOfPersons": noOfPersons,
|
|
"billAttachments": billAttachments,
|
|
};
|
|
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.createExpense,
|
|
method: 'POST',
|
|
body: payload,
|
|
);
|
|
|
|
if (response == null) return false;
|
|
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "Create Expense", returnFullResponse: true);
|
|
return parsed != null && parsed['success'] == true;
|
|
}
|
|
|
|
static Future<Map<String, dynamic>?> getExpenseDetailsApi({
|
|
required String expenseId,
|
|
}) async {
|
|
final endpoint = "${ApiEndpoints.getExpenseDetails}/$expenseId";
|
|
final response = await _safeApiCall(endpoint, method: 'GET');
|
|
if (response == null) return null;
|
|
|
|
// NOTE: This API currently does NOT return encrypted response based on original implementation.
|
|
// Assuming the decrypted response returns the whole map containing 'data'
|
|
final jsonResponse = _parseAndDecryptResponse(response,
|
|
label: "Expense Details", returnFullResponse: true);
|
|
|
|
return (jsonResponse != null &&
|
|
jsonResponse['data'] is Map<String, dynamic>)
|
|
? jsonResponse['data']
|
|
: null;
|
|
}
|
|
|
|
static Future<bool> updateExpenseStatusApi({
|
|
required String expenseId,
|
|
required String statusId,
|
|
String? comment,
|
|
String? reimburseTransactionId,
|
|
String? reimburseDate,
|
|
String? reimbursedById,
|
|
double? baseAmount,
|
|
double? taxAmount,
|
|
double? tdsPercent,
|
|
double? netPayable,
|
|
}) async {
|
|
final payload = {
|
|
"expenseId": expenseId,
|
|
"statusId": statusId,
|
|
if (comment != null) "comment": comment,
|
|
if (reimburseTransactionId != null)
|
|
"reimburseTransactionId": reimburseTransactionId,
|
|
if (reimburseDate != null) "reimburseDate": reimburseDate,
|
|
if (reimbursedById != null) "reimburseById": reimbursedById,
|
|
if (baseAmount != null) "baseAmount": baseAmount,
|
|
if (taxAmount != null) "taxAmount": taxAmount,
|
|
if (tdsPercent != null) "tdsPercent": tdsPercent,
|
|
if (netPayable != null) "netPayable": netPayable,
|
|
};
|
|
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.updateExpenseStatus,
|
|
method: 'POST',
|
|
body: payload,
|
|
);
|
|
|
|
if (response == null) return false;
|
|
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "Update Expense Status", returnFullResponse: true);
|
|
return parsed != null && parsed['success'] == true;
|
|
}
|
|
|
|
static Future<bool> editExpenseApi({
|
|
required String expenseId,
|
|
required Map<String, dynamic> payload,
|
|
}) async {
|
|
final endpoint = "${ApiEndpoints.editExpense}/$expenseId";
|
|
final response = await _safeApiCall(
|
|
endpoint,
|
|
method: 'PUT',
|
|
body: payload,
|
|
);
|
|
|
|
if (response == null) return false;
|
|
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "Edit Expense", returnFullResponse: true);
|
|
return parsed != null && parsed['success'] == true;
|
|
}
|
|
|
|
static Future<bool> deleteExpense(String expenseId) async {
|
|
final endpoint = "${ApiEndpoints.deleteExpense}/$expenseId";
|
|
|
|
final response = await _safeApiCall(endpoint, method: 'DELETE');
|
|
|
|
if (response == null) return false;
|
|
|
|
// DELETE requests often return 200 with a success body, or 204 (No Content).
|
|
// Rely on parse function which checks for 200 and 'success: true'
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "Delete Expense", returnFullResponse: true);
|
|
return response.statusCode == 204 ||
|
|
(parsed != null && parsed['success'] == true);
|
|
}
|
|
|
|
static Future<Map<String, dynamic>?> getExpenseListApi({
|
|
String? filter,
|
|
int pageSize = 20,
|
|
int pageNumber = 1,
|
|
}) async {
|
|
final queryParams = {
|
|
'pageSize': pageSize.toString(),
|
|
'pageNumber': pageNumber.toString(),
|
|
if (filter?.isNotEmpty ?? false) 'filter': filter!,
|
|
};
|
|
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.getExpenseList,
|
|
method: 'GET',
|
|
queryParams: queryParams,
|
|
);
|
|
|
|
if (response == null) return null;
|
|
|
|
final jsonResponse = _parseAndDecryptResponse(response,
|
|
label: "Expense List", returnFullResponse: true);
|
|
return jsonResponse is Map<String, dynamic> ? jsonResponse : null;
|
|
}
|
|
|
|
/// ============================================
|
|
/// DOCUMENT MANAGEMENT
|
|
/// ============================================
|
|
|
|
static Future<DocumentFiltersResponse?> getDocumentFilters(
|
|
String entityTypeId) async {
|
|
final endpoint = "${ApiEndpoints.getDocumentFilter}/$entityTypeId";
|
|
final response = await _safeApiCall(endpoint, method: 'GET');
|
|
|
|
if (response == null) return null;
|
|
|
|
final jsonResponse = _parseAndDecryptResponse(response,
|
|
label: "Document Filters", returnFullResponse: true);
|
|
if (jsonResponse == null) return null;
|
|
|
|
return DocumentFiltersResponse.fromJson(jsonResponse);
|
|
}
|
|
|
|
static Future<DocumentsResponse?> getDocumentListApi({
|
|
required String entityTypeId,
|
|
required String entityId,
|
|
String filter = "",
|
|
String searchString = "",
|
|
bool isActive = true,
|
|
int pageNumber = 1,
|
|
int pageSize = 20,
|
|
}) async {
|
|
final endpoint =
|
|
"${ApiEndpoints.getDocumentList}/$entityTypeId/entity/$entityId";
|
|
final queryParams = {
|
|
"filter": filter,
|
|
"searchString": searchString,
|
|
"isActive": isActive.toString(),
|
|
"pageNumber": pageNumber.toString(),
|
|
"pageSize": pageSize.toString(),
|
|
};
|
|
|
|
final response =
|
|
await _safeApiCall(endpoint, method: 'GET', queryParams: queryParams);
|
|
|
|
if (response == null) return null;
|
|
|
|
final jsonResponse = _parseAndDecryptResponse(response,
|
|
label: "Document List", returnFullResponse: true);
|
|
if (jsonResponse == null) return null;
|
|
|
|
return DocumentsResponse.fromJson(jsonResponse);
|
|
}
|
|
|
|
static Future<TagResponse?> getMasterDocumentTagsApi() async {
|
|
final response =
|
|
await _safeApiCall(ApiEndpoints.getMasterDocumentTags, method: 'GET');
|
|
|
|
if (response == null) return null;
|
|
|
|
final jsonResponse = _parseAndDecryptResponse(response,
|
|
label: "Master Document Tags", returnFullResponse: true);
|
|
if (jsonResponse == null) return null;
|
|
|
|
return TagResponse.fromJson(jsonResponse);
|
|
}
|
|
|
|
static Future<bool> uploadDocumentApi({
|
|
required String name,
|
|
String? documentId,
|
|
String? description,
|
|
required String entityId,
|
|
required String documentTypeId,
|
|
required String fileName,
|
|
required String base64Data,
|
|
required String contentType,
|
|
required int fileSize,
|
|
String? fileDescription,
|
|
bool isActive = true,
|
|
List<Map<String, dynamic>> tags = const [],
|
|
}) async {
|
|
final payload = {
|
|
"name": name,
|
|
"documentId": documentId ?? "",
|
|
"description": description ?? "",
|
|
"entityId": entityId,
|
|
"documentTypeId": documentTypeId,
|
|
"attachment": {
|
|
"fileName": fileName,
|
|
"base64Data": base64Data,
|
|
"contentType": contentType,
|
|
"fileSize": fileSize,
|
|
"description": fileDescription ?? "",
|
|
"isActive": isActive,
|
|
},
|
|
"tags": tags.isNotEmpty
|
|
? tags
|
|
: [
|
|
{"name": "default", "isActive": true}
|
|
],
|
|
};
|
|
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.uploadDocument,
|
|
method: 'POST',
|
|
body: payload,
|
|
);
|
|
|
|
if (response == null) return false;
|
|
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "Upload Document", returnFullResponse: true);
|
|
return parsed != null && parsed['success'] == true;
|
|
}
|
|
|
|
static Future<DocumentTypesResponse?> getMasterDocumentTypesApi() async {
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.getMasterDocumentCategories,
|
|
method: 'GET');
|
|
|
|
if (response == null) return null;
|
|
|
|
final jsonResponse = _parseAndDecryptResponse(response,
|
|
label: "Master Document Types", returnFullResponse: true);
|
|
if (jsonResponse == null) return null;
|
|
|
|
return DocumentTypesResponse.fromJson(jsonResponse);
|
|
}
|
|
|
|
static Future<DocumentTypesResponse?> getDocumentTypesByCategoryApi(
|
|
String documentCategoryId) async {
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.getDocumentTypesByCategory,
|
|
method: 'GET',
|
|
queryParams: {"documentCategoryId": documentCategoryId},
|
|
);
|
|
|
|
if (response == null) return null;
|
|
|
|
final jsonResponse = _parseAndDecryptResponse(response,
|
|
label: "Document Types by Category", returnFullResponse: true);
|
|
if (jsonResponse == null) return null;
|
|
|
|
return DocumentTypesResponse.fromJson(jsonResponse);
|
|
}
|
|
|
|
static Future<DocumentDetailsResponse?> getDocumentDetailsApi(
|
|
String documentId) async {
|
|
final endpoint = "${ApiEndpoints.getDocumentDetails}/$documentId";
|
|
final response = await _safeApiCall(endpoint, method: 'GET');
|
|
|
|
if (response == null) return null;
|
|
|
|
final jsonResponse = _parseAndDecryptResponse(response,
|
|
label: "Document Details", returnFullResponse: true);
|
|
if (jsonResponse == null) return null;
|
|
|
|
return DocumentDetailsResponse.fromJson(jsonResponse);
|
|
}
|
|
|
|
static Future<DocumentVersionsResponse?> getDocumentVersionsApi({
|
|
required String parentAttachmentId,
|
|
int pageNumber = 1,
|
|
int pageSize = 20,
|
|
}) async {
|
|
final endpoint = "${ApiEndpoints.getDocumentVersions}/$parentAttachmentId";
|
|
final queryParams = {
|
|
"pageNumber": pageNumber.toString(),
|
|
"pageSize": pageSize.toString(),
|
|
};
|
|
|
|
final response =
|
|
await _safeApiCall(endpoint, method: 'GET', queryParams: queryParams);
|
|
|
|
if (response == null) return null;
|
|
|
|
final jsonResponse = _parseAndDecryptResponse(response,
|
|
label: "Document Versions", returnFullResponse: true);
|
|
if (jsonResponse == null) return null;
|
|
|
|
return DocumentVersionsResponse.fromJson(jsonResponse);
|
|
}
|
|
|
|
static Future<bool> editDocumentApi({
|
|
required String id,
|
|
required String name,
|
|
required String documentId,
|
|
String? description,
|
|
List<Map<String, dynamic>> tags = const [],
|
|
Map<String, dynamic>? attachment,
|
|
}) async {
|
|
final endpoint = "${ApiEndpoints.editDocument}/$id";
|
|
final payload = {
|
|
"id": id,
|
|
"name": name,
|
|
"documentId": documentId,
|
|
"description": description ?? "",
|
|
"tags": tags.isNotEmpty
|
|
? tags
|
|
: [
|
|
{"name": "default", "isActive": true}
|
|
],
|
|
"attachment": attachment,
|
|
};
|
|
|
|
final response = await _safeApiCall(endpoint, method: 'PUT', body: payload);
|
|
|
|
if (response == null) return false;
|
|
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "Edit Document", returnFullResponse: true);
|
|
return parsed != null && parsed['success'] == true;
|
|
}
|
|
|
|
static Future<bool> deleteDocumentApi({
|
|
required String id,
|
|
bool isActive = false,
|
|
}) async {
|
|
final endpoint = "${ApiEndpoints.deleteDocument}/$id";
|
|
final queryParams = {"isActive": isActive.toString()};
|
|
|
|
// Note: This endpoint is handled via DELETE with query params.
|
|
final response = await _safeApiCall(
|
|
endpoint,
|
|
method: 'DELETE',
|
|
queryParams: queryParams,
|
|
);
|
|
|
|
if (response == null) return false;
|
|
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "Delete Document", returnFullResponse: true);
|
|
return response.statusCode == 200 &&
|
|
(parsed != null && parsed['success'] == true);
|
|
}
|
|
|
|
static Future<String?> getPresignedUrlApi(String versionId) async {
|
|
final endpoint = "${ApiEndpoints.getDocumentVersion}/$versionId";
|
|
final response = await _safeApiCall(endpoint, method: 'GET');
|
|
|
|
if (response == null) return null;
|
|
|
|
final jsonResponse =
|
|
_parseAndDecryptResponse(response, label: "Pre-Signed URL");
|
|
|
|
return jsonResponse is String ? jsonResponse : null;
|
|
}
|
|
|
|
static Future<bool> verifyDocumentApi({
|
|
required String id,
|
|
bool isVerify = true,
|
|
}) async {
|
|
final endpoint = "${ApiEndpoints.verifyDocument}/$id";
|
|
final queryParams = {"isVerify": isVerify.toString()};
|
|
|
|
// Note: The original code used POST for this endpoint, retaining that.
|
|
final response = await _safeApiCall(
|
|
endpoint,
|
|
method: 'POST',
|
|
queryParams: queryParams,
|
|
body: {}, // Must send a body for POST
|
|
);
|
|
|
|
if (response == null) return false;
|
|
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "Verify Document", returnFullResponse: true);
|
|
return parsed != null && parsed['success'] == true;
|
|
}
|
|
|
|
/// ============================================
|
|
/// EMPLOYEE & ORGANIZATION
|
|
/// ============================================
|
|
|
|
static Future<List<dynamic>?> allEmployeesBasic({
|
|
bool allEmployee = true,
|
|
}) async {
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.getEmployeesWithoutPermission,
|
|
method: 'GET',
|
|
queryParams: {'allEmployee': allEmployee.toString()},
|
|
);
|
|
|
|
if (response == null) return null;
|
|
return _parseAndDecryptResponse(response, label: 'All Employees Basic');
|
|
}
|
|
|
|
static Future<List<dynamic>?> searchEmployeesBasic({
|
|
String? searchString,
|
|
}) async {
|
|
final queryParams = <String, String>{};
|
|
if (searchString != null && searchString.isNotEmpty) {
|
|
queryParams['searchString'] = searchString;
|
|
}
|
|
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.getEmployeesWithoutPermission,
|
|
method: 'GET',
|
|
queryParams: queryParams,
|
|
);
|
|
|
|
if (response == null) return null;
|
|
return _parseAndDecryptResponse(response, label: 'Search Employees Basic');
|
|
}
|
|
|
|
static Future<List<dynamic>?> getAllEmployeesByProject(String projectId,
|
|
{String? organizationId}) async {
|
|
if (projectId.isEmpty) throw ArgumentError('projectId must not be empty');
|
|
|
|
final endpoint = "${ApiEndpoints.getAllEmployeesByProject}/$projectId";
|
|
final queryParams = <String, String>{
|
|
if (organizationId != null && organizationId.isNotEmpty)
|
|
"organizationId": organizationId
|
|
};
|
|
|
|
final response = await _safeApiCall(
|
|
endpoint,
|
|
method: 'GET',
|
|
queryParams: queryParams,
|
|
);
|
|
if (response == null) return null;
|
|
|
|
return _parseAndDecryptResponse(response, label: 'Employees by Project');
|
|
}
|
|
|
|
static Future<List<dynamic>?> getEmployeesByProjectService(
|
|
String projectId, {
|
|
String? serviceId,
|
|
String? organizationId,
|
|
}) async {
|
|
if (projectId.isEmpty) throw ArgumentError('projectId must not be empty');
|
|
|
|
final queryParams = <String, String>{};
|
|
if (serviceId != null && serviceId.isNotEmpty) {
|
|
queryParams['serviceId'] = serviceId;
|
|
}
|
|
if (organizationId != null && organizationId.isNotEmpty) {
|
|
queryParams['organizationId'] = organizationId;
|
|
}
|
|
|
|
final endpoint = "${ApiEndpoints.getAllEmployeesByOrganization}/$projectId";
|
|
|
|
final response = await _safeApiCall(
|
|
endpoint,
|
|
method: 'GET',
|
|
queryParams: queryParams,
|
|
);
|
|
|
|
if (response == null) return null;
|
|
return _parseAndDecryptResponse(response,
|
|
label: 'Employees by Project Service');
|
|
}
|
|
|
|
static Future<List<dynamic>?> getAllEmployees(
|
|
{String? organizationId}) async {
|
|
final queryParams = <String, String>{
|
|
if (organizationId != null && organizationId.isNotEmpty)
|
|
"organizationId": organizationId
|
|
};
|
|
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.getAllEmployees,
|
|
method: 'GET',
|
|
queryParams: queryParams,
|
|
);
|
|
|
|
if (response == null) return null;
|
|
return _parseAndDecryptResponse(response, label: 'All Employees');
|
|
}
|
|
|
|
static Future<List<dynamic>?> getRoles() async {
|
|
final response = await _safeApiCall(ApiEndpoints.getRoles, method: 'GET');
|
|
if (response == null) return null;
|
|
return _parseAndDecryptResponse(response, label: 'Roles');
|
|
}
|
|
|
|
static Future<Map<String, dynamic>?> createEmployee({
|
|
String? id,
|
|
required String firstName,
|
|
required String lastName,
|
|
required String phoneNumber,
|
|
required String gender,
|
|
required String jobRoleId,
|
|
required String joiningDate,
|
|
String? email,
|
|
String? organizationId,
|
|
bool? hasApplicationAccess,
|
|
}) async {
|
|
final body = {
|
|
if (id != null) "id": id,
|
|
"firstName": firstName,
|
|
"lastName": lastName,
|
|
"phoneNumber": phoneNumber,
|
|
"gender": gender,
|
|
"jobRoleId": jobRoleId,
|
|
"joiningDate": joiningDate,
|
|
if (email != null && email.isNotEmpty) "email": email,
|
|
if (organizationId != null && organizationId.isNotEmpty)
|
|
"organizationId": organizationId,
|
|
if (hasApplicationAccess != null)
|
|
"hasApplicationAccess": hasApplicationAccess,
|
|
};
|
|
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.createEmployee,
|
|
method: 'POST',
|
|
body: body,
|
|
);
|
|
|
|
if (response == null) return null;
|
|
|
|
final json =
|
|
jsonDecode(response.body); // Non-encrypted body expected for this path?
|
|
|
|
// Assuming we need to check the raw JSON status since the model is unclear
|
|
return {
|
|
"success": response.statusCode == 200 && (json['success'] == true),
|
|
"data": json,
|
|
};
|
|
}
|
|
|
|
static Future<Map<String, dynamic>?> getEmployeeDetails(
|
|
String employeeId) async {
|
|
final url = "${ApiEndpoints.getEmployeeInfo}/$employeeId";
|
|
final response = await _safeApiCall(url, method: 'GET');
|
|
if (response == null) return null;
|
|
|
|
final data = _parseAndDecryptResponse(response, label: 'Employee Details');
|
|
return data is Map<String, dynamic> ? data : null;
|
|
}
|
|
|
|
static Future<List<dynamic>?> getAssignedProjects(String employeeId) async {
|
|
if (employeeId.isEmpty) throw ArgumentError("employeeId must not be empty");
|
|
|
|
final endpoint = "${ApiEndpoints.getAssignedProjects}/$employeeId";
|
|
final response = await _safeApiCall(endpoint, method: 'GET');
|
|
|
|
if (response == null) return null;
|
|
|
|
final parsed =
|
|
_parseAndDecryptResponse(response, label: "Assigned Projects");
|
|
return parsed is List ? parsed : null;
|
|
}
|
|
|
|
static Future<bool> assignProjects({
|
|
required String employeeId,
|
|
required List<Map<String, dynamic>> projects,
|
|
}) async {
|
|
if (employeeId.isEmpty) throw ArgumentError("employeeId must not be empty");
|
|
if (projects.isEmpty)
|
|
_log("Warning: Projects list is empty in assignProjects.");
|
|
|
|
final endpoint = "${ApiEndpoints.assignProjects}/$employeeId";
|
|
|
|
final response = await _safeApiCall(
|
|
endpoint,
|
|
method: 'POST',
|
|
body: projects,
|
|
);
|
|
|
|
if (response == null) return false;
|
|
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "Assign Projects", returnFullResponse: true);
|
|
return parsed != null && parsed['success'] == true;
|
|
}
|
|
|
|
static Future<List<dynamic>?> getOrganizationHierarchyList(
|
|
String employeeId) async {
|
|
if (employeeId.isEmpty) throw ArgumentError('employeeId must not be empty');
|
|
final endpoint = "${ApiEndpoints.getOrganizationHierarchyList}/$employeeId";
|
|
|
|
final response = await _safeApiCall(endpoint, method: 'GET');
|
|
if (response == null) return null;
|
|
|
|
return _parseAndDecryptResponse(response,
|
|
label: 'Organization Hierarchy List');
|
|
}
|
|
|
|
static Future<bool> manageOrganizationHierarchy({
|
|
required String employeeId,
|
|
required List<Map<String, dynamic>> payload,
|
|
}) async {
|
|
if (employeeId.isEmpty) throw ArgumentError('employeeId must not be empty');
|
|
|
|
final endpoint = "${ApiEndpoints.manageOrganizationHierarchy}/$employeeId";
|
|
|
|
final response = await _safeApiCall(
|
|
endpoint,
|
|
method: 'POST',
|
|
body: payload,
|
|
);
|
|
|
|
if (response == null) return false;
|
|
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "Manage Hierarchy", returnFullResponse: true);
|
|
return parsed != null && parsed['success'] == true;
|
|
}
|
|
|
|
/// ============================================
|
|
/// PROJECT MANAGEMENT
|
|
/// ============================================
|
|
|
|
static Future<bool> createProjectApi({
|
|
required String name,
|
|
required String projectAddress,
|
|
required String shortName,
|
|
required String contactPerson,
|
|
required DateTime startDate,
|
|
required DateTime endDate,
|
|
required String projectStatusId,
|
|
}) async {
|
|
final payload = {
|
|
"name": name,
|
|
"projectAddress": projectAddress,
|
|
"shortName": shortName,
|
|
"contactPerson": contactPerson,
|
|
"startDate": startDate.toIso8601String(),
|
|
"endDate": endDate.toIso8601String(),
|
|
"projectStatusId": projectStatusId,
|
|
};
|
|
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.createProject,
|
|
method: 'POST',
|
|
body: payload,
|
|
);
|
|
|
|
if (response == null) return false;
|
|
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "Create Project", returnFullResponse: true);
|
|
return parsed != null && parsed['success'] == true;
|
|
}
|
|
|
|
static Future<OrganizationListResponse?> getAssignedOrganizations(
|
|
String projectId) async {
|
|
final endpoint = "${ApiEndpoints.getAssignedOrganizations}/$projectId";
|
|
final response = await _safeApiCall(endpoint, method: 'GET');
|
|
|
|
if (response == null) return null;
|
|
|
|
final jsonResponse = _parseAndDecryptResponse(response,
|
|
label: "Assigned Organizations", returnFullResponse: true);
|
|
if (jsonResponse == null) return null;
|
|
|
|
return OrganizationListResponse.fromJson(jsonResponse);
|
|
}
|
|
|
|
static Future<AllOrganizationListResponse?> getAllOrganizations() async {
|
|
final response =
|
|
await _safeApiCall(ApiEndpoints.getAllOrganizations, method: 'GET');
|
|
|
|
if (response == null) return null;
|
|
|
|
final jsonResponse = _parseAndDecryptResponse(response,
|
|
label: "All Organizations", returnFullResponse: true);
|
|
if (jsonResponse == null) return null;
|
|
|
|
return AllOrganizationListResponse.fromJson(jsonResponse);
|
|
}
|
|
|
|
static Future<ServiceListResponse?> getAssignedServices(
|
|
String projectId) async {
|
|
final endpoint = "${ApiEndpoints.getAssignedServices}/$projectId";
|
|
final response = await _safeApiCall(endpoint, method: 'GET');
|
|
|
|
if (response == null) return null;
|
|
|
|
final jsonResponse = _parseAndDecryptResponse(response,
|
|
label: "Assigned Services", returnFullResponse: true);
|
|
if (jsonResponse == null) return null;
|
|
|
|
return ServiceListResponse.fromJson(jsonResponse);
|
|
}
|
|
|
|
/// ============================================
|
|
/// DAILY TASK PLANNING
|
|
/// ============================================
|
|
|
|
static Future<DailyProgressReportFilterResponse?> getDailyTaskFilter(
|
|
String projectId) async {
|
|
final endpoint =
|
|
"${ApiEndpoints.getDailyTaskProjectProgressFilter}/$projectId";
|
|
final response = await _safeApiCall(endpoint, method: 'GET');
|
|
|
|
if (response == null) return null;
|
|
|
|
final jsonResponse = _parseAndDecryptResponse(response,
|
|
label: "Daily Task Progress Filter", returnFullResponse: true);
|
|
if (jsonResponse == null) return null;
|
|
|
|
return DailyProgressReportFilterResponse.fromJson(jsonResponse);
|
|
}
|
|
|
|
static Future<List<TaskModel>?> getDailyTasks(
|
|
String projectId, {
|
|
Map<String, dynamic>? filter,
|
|
int pageNumber = 1,
|
|
int pageSize = 20,
|
|
}) async {
|
|
final query = {
|
|
"projectId": projectId,
|
|
"pageNumber": pageNumber.toString(),
|
|
"pageSize": pageSize.toString(),
|
|
if (filter != null) "filter": jsonEncode(filter),
|
|
};
|
|
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.getDailyTask,
|
|
method: 'GET',
|
|
queryParams: query,
|
|
);
|
|
|
|
if (response == null) return null;
|
|
|
|
final parsed = _parseAndDecryptResponse(response, label: 'Daily Tasks');
|
|
|
|
if (parsed != null && parsed is Map && parsed['data'] is List) {
|
|
return (parsed['data'] as List)
|
|
.map((e) => TaskModel.fromJson(e))
|
|
.toList();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
static Future<bool> reportTask({
|
|
required String id,
|
|
required int completedTask,
|
|
required String comment,
|
|
required List<Map<String, dynamic>> checkList,
|
|
List<Map<String, dynamic>>? images,
|
|
}) async {
|
|
final body = {
|
|
"id": id,
|
|
"completedTask": completedTask,
|
|
"comment": comment,
|
|
"reportedDate": DateTime.now().toUtc().toIso8601String(),
|
|
"checkList": checkList,
|
|
if (images != null && images.isNotEmpty) "images": images,
|
|
};
|
|
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.reportTask,
|
|
method: 'POST',
|
|
body: body,
|
|
);
|
|
|
|
if (response == null) return false;
|
|
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "Report Task", returnFullResponse: true);
|
|
if (parsed != null && parsed['success'] == true) {
|
|
Get.back(); // Retaining Get.back() as per original logic
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static Future<bool> commentTask({
|
|
required String id,
|
|
required String comment,
|
|
List<Map<String, dynamic>>? images,
|
|
}) async {
|
|
final body = {
|
|
"taskAllocationId": id,
|
|
"comment": comment,
|
|
"commentDate": DateTime.now().toUtc().toIso8601String(),
|
|
if (images != null && images.isNotEmpty) "images": images,
|
|
};
|
|
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.commentTask,
|
|
method: 'POST',
|
|
body: body,
|
|
);
|
|
|
|
if (response == null) return false;
|
|
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "Comment Task", returnFullResponse: true);
|
|
return parsed != null && parsed['success'] == true;
|
|
}
|
|
|
|
static Future<Map<String, dynamic>?> getInfraDetails(String projectId,
|
|
{String? serviceId}) async {
|
|
String endpoint = "/project/infra-details/$projectId";
|
|
final queryParams = <String, String>{
|
|
if (serviceId != null && serviceId.isNotEmpty) 'serviceId': serviceId,
|
|
};
|
|
|
|
final response = await _safeApiCall(
|
|
endpoint,
|
|
method: 'GET',
|
|
queryParams: queryParams,
|
|
);
|
|
if (response == null) return null;
|
|
|
|
return _parseAndDecryptResponse(response,
|
|
label: 'Infra Details',
|
|
returnFullResponse: true) as Map<String, dynamic>?;
|
|
}
|
|
|
|
static Future<Map<String, dynamic>?> getWorkItemsByWorkArea(String workAreaId,
|
|
{String? serviceId}) async {
|
|
String endpoint = "/project/tasks/$workAreaId";
|
|
final queryParams = <String, String>{
|
|
if (serviceId != null && serviceId.isNotEmpty) 'serviceId': serviceId,
|
|
};
|
|
|
|
final response = await _safeApiCall(
|
|
endpoint,
|
|
method: 'GET',
|
|
queryParams: queryParams,
|
|
);
|
|
if (response == null) return null;
|
|
|
|
return _parseAndDecryptResponse(response,
|
|
label: 'Work Items', returnFullResponse: true) as Map<String, dynamic>?;
|
|
}
|
|
|
|
static Future<bool> assignDailyTask({
|
|
required String workItemId,
|
|
required int plannedTask,
|
|
required String description,
|
|
required List<String> taskTeam,
|
|
DateTime? assignmentDate,
|
|
String? organizationId,
|
|
String? serviceId,
|
|
}) async {
|
|
final body = {
|
|
"workItemId": workItemId,
|
|
"plannedTask": plannedTask,
|
|
"description": description,
|
|
"taskTeam": taskTeam,
|
|
"organizationId": organizationId,
|
|
"serviceId": serviceId,
|
|
"assignmentDate":
|
|
(assignmentDate ?? DateTime.now()).toUtc().toIso8601String(),
|
|
};
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.assignDailyTask,
|
|
method: 'POST',
|
|
body: body,
|
|
);
|
|
|
|
if (response == null) return false;
|
|
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "Assign Daily Task", returnFullResponse: true);
|
|
if (parsed != null && parsed['success'] == true) {
|
|
Get.back(); // Retaining Get.back() as per original logic
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static Future<Map<String, dynamic>?> getWorkStatus() async {
|
|
final response =
|
|
await _safeApiCall(ApiEndpoints.getWorkStatus, method: 'GET');
|
|
if (response == null) return null;
|
|
|
|
return _parseAndDecryptResponse(response,
|
|
label: 'Work Status',
|
|
returnFullResponse: true) as Map<String, dynamic>?;
|
|
}
|
|
|
|
static Future<Map<String, dynamic>?> getMasterWorkCategories() async {
|
|
final response =
|
|
await _safeApiCall(ApiEndpoints.getmasterWorkCategories, method: 'GET');
|
|
if (response == null) return null;
|
|
|
|
return _parseAndDecryptResponse(response,
|
|
label: 'Master Work Categories',
|
|
returnFullResponse: true) as Map<String, dynamic>?;
|
|
}
|
|
|
|
static Future<bool> approveTask({
|
|
required String id,
|
|
required String comment,
|
|
required String workStatus,
|
|
required int approvedTask,
|
|
List<Map<String, dynamic>>? images,
|
|
}) async {
|
|
final body = {
|
|
"id": id,
|
|
"workStatus": workStatus,
|
|
"approvedTask": approvedTask,
|
|
"comment": comment,
|
|
if (images != null && images.isNotEmpty) "images": images,
|
|
};
|
|
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.approveReportAction,
|
|
method: 'POST',
|
|
body: body,
|
|
);
|
|
|
|
if (response == null) return false;
|
|
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "Approve Task", returnFullResponse: true);
|
|
return parsed != null && parsed['success'] == true;
|
|
}
|
|
|
|
static Future<bool> createTask({
|
|
required String parentTaskId,
|
|
required int plannedTask,
|
|
required String comment,
|
|
required String workAreaId,
|
|
required String activityId,
|
|
DateTime? assignmentDate,
|
|
required String categoryId,
|
|
}) async {
|
|
final body = [
|
|
{
|
|
"parentTaskId": parentTaskId,
|
|
"plannedWork": plannedTask,
|
|
"comment": comment,
|
|
"workAreaID": workAreaId,
|
|
"activityID": activityId,
|
|
"workCategoryId": categoryId,
|
|
'completedWork': 0,
|
|
}
|
|
];
|
|
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.assignTask,
|
|
method: 'POST',
|
|
body: body,
|
|
);
|
|
|
|
if (response == null) return false;
|
|
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "Create Task", returnFullResponse: true);
|
|
if (parsed != null && parsed['success'] == true) {
|
|
Get.back(); // Retaining Get.back() as per original logic
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// ============================================
|
|
/// LOGGING & MENU
|
|
/// ============================================
|
|
|
|
static Future<bool> postLogsApi(List<Map<String, dynamic>> logs) async {
|
|
// NOTE: This call retrieves the token directly and bypasses refresh logic
|
|
// to avoid an infinite loop if logging itself fails.
|
|
|
|
final token = await LocalStorage.getJwtToken();
|
|
if (token == null) {
|
|
_log("No token available. Skipping logs post.", level: LogLevel.warning);
|
|
return false;
|
|
}
|
|
|
|
final uri = Uri.parse("${ApiEndpoints.baseUrl}${ApiEndpoints.uploadLogs}");
|
|
final headers = {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': 'Bearer $token',
|
|
};
|
|
|
|
try {
|
|
final response = await http
|
|
.post(uri, headers: headers, body: jsonEncode(logs))
|
|
.timeout(ApiService.extendedTimeout);
|
|
|
|
_log("Post logs response status: ${response.statusCode}");
|
|
|
|
// Decryption chain for API response
|
|
final decryptedJson = decryptResponse(response.body);
|
|
|
|
if (response.statusCode == 200 &&
|
|
decryptedJson is Map &&
|
|
decryptedJson['success'] == true) {
|
|
_log("Logs posted successfully.");
|
|
return true;
|
|
}
|
|
_log(
|
|
"Failed to post logs: ${decryptedJson?['message'] ?? 'Unknown error'}",
|
|
level: LogLevel.warning);
|
|
} catch (e, stack) {
|
|
_log("Exception during postLogsApi: $e\n$stack", level: LogLevel.error);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// ApiService.dart (around line 1400)
|
|
static Future<Map<String, dynamic>?> getMenuApi() async {
|
|
final response =
|
|
await _safeApiCall(ApiEndpoints.getDynamicMenu, method: 'GET');
|
|
if (response == null) return null;
|
|
|
|
// Use the centralized parsing/decryption utility.
|
|
// The server is clearly sending an encrypted response based on the log error.
|
|
final jsonResponse = _parseAndDecryptResponse(
|
|
response,
|
|
label: "Dynamic Menu",
|
|
returnFullResponse: true,
|
|
);
|
|
|
|
// Return the full decrypted JSON map if parsing and 'success: true' check passed
|
|
return jsonResponse is Map<String, dynamic> ? jsonResponse : null;
|
|
}
|
|
|
|
/// ============================================
|
|
/// ATTENDANCE
|
|
/// ============================================
|
|
|
|
static Future<List<dynamic>?> getProjects() async {
|
|
final response =
|
|
await _safeApiCall(ApiEndpoints.getProjects, method: 'GET');
|
|
if (response == null) return null;
|
|
return _parseAndDecryptResponse(response, label: 'Projects');
|
|
}
|
|
|
|
static Future<List<dynamic>?> getGlobalProjects() async {
|
|
final response =
|
|
await _safeApiCall(ApiEndpoints.getGlobalProjects, method: 'GET');
|
|
if (response == null) return null;
|
|
return _parseAndDecryptResponse(response, label: 'Global Projects');
|
|
}
|
|
|
|
static Future<List<dynamic>?> getTodaysAttendance(
|
|
String projectId, {
|
|
String? organizationId,
|
|
}) async {
|
|
final query = {
|
|
"projectId": projectId,
|
|
if (organizationId != null) "organizationId": organizationId,
|
|
};
|
|
|
|
final response = await _safeApiCall(ApiEndpoints.getTodaysAttendance,
|
|
method: 'GET', queryParams: query);
|
|
if (response == null) return null;
|
|
return _parseAndDecryptResponse(response, label: 'Employees');
|
|
}
|
|
|
|
static Future<List<EmployeeModel>?> getAttendanceForDashboard(
|
|
String projectId) async {
|
|
final endpoint = ApiEndpoints.getAttendanceForDashboard.replaceFirst(
|
|
':projectId',
|
|
projectId,
|
|
);
|
|
|
|
final response = await _safeApiCall(endpoint, method: 'GET');
|
|
if (response == null) return null;
|
|
|
|
final data =
|
|
_parseAndDecryptResponse(response, label: 'Dashboard Attendance');
|
|
|
|
if (data is Map<String, dynamic>) {
|
|
return [EmployeeModel.fromJson(data)];
|
|
} else if (data is List) {
|
|
return data.map((e) => EmployeeModel.fromJson(e)).toList();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
static Future<List<dynamic>?> getRegularizationLogs(
|
|
String projectId, {
|
|
String? organizationId,
|
|
}) async {
|
|
final query = {
|
|
"projectId": projectId,
|
|
if (organizationId != null) "organizationId": organizationId,
|
|
};
|
|
|
|
final response = await _safeApiCall(ApiEndpoints.getRegularizationLogs,
|
|
method: 'GET', queryParams: query);
|
|
if (response == null) return null;
|
|
return _parseAndDecryptResponse(response, label: 'Regularization Logs');
|
|
}
|
|
|
|
static Future<List<dynamic>?> getAttendanceLogs(
|
|
String projectId, {
|
|
DateTime? dateFrom,
|
|
DateTime? dateTo,
|
|
String? organizationId,
|
|
}) async {
|
|
final query = {
|
|
"projectId": projectId,
|
|
if (dateFrom != null)
|
|
"dateFrom": DateFormat('yyyy-MM-dd').format(dateFrom),
|
|
if (dateTo != null) "dateTo": DateFormat('yyyy-MM-dd').format(dateTo),
|
|
if (organizationId != null) "organizationId": organizationId,
|
|
};
|
|
|
|
final response = await _safeApiCall(ApiEndpoints.getAttendanceLogs,
|
|
method: 'GET', queryParams: query);
|
|
if (response == null) return null;
|
|
return _parseAndDecryptResponse(response, label: 'Attendance Logs');
|
|
}
|
|
|
|
static Future<List<dynamic>?> getAttendanceLogView(String id) async {
|
|
final response = await _safeApiCall(
|
|
"${ApiEndpoints.getAttendanceLogView}/$id",
|
|
method: 'GET');
|
|
if (response == null) return null;
|
|
return _parseAndDecryptResponse(response, label: 'Log Details');
|
|
}
|
|
|
|
static Future<bool> uploadAttendanceImage(
|
|
String id,
|
|
String employeeId,
|
|
XFile? imageFile,
|
|
double latitude,
|
|
double longitude, {
|
|
required String imageName,
|
|
required String projectId,
|
|
String comment = "",
|
|
required int action,
|
|
bool imageCapture = true,
|
|
required String markTime,
|
|
required String date,
|
|
}) async {
|
|
final body = {
|
|
"id": id,
|
|
"employeeId": employeeId,
|
|
"projectId": projectId,
|
|
"markTime": markTime,
|
|
"comment": comment,
|
|
"action": action,
|
|
"date": date,
|
|
if (imageCapture) "latitude": '$latitude',
|
|
if (imageCapture) "longitude": '$longitude',
|
|
};
|
|
|
|
if (imageCapture && imageFile != null) {
|
|
try {
|
|
final bytes = await imageFile.readAsBytes();
|
|
final fileSize = await imageFile.length();
|
|
final contentType = "image/${imageFile.path.split('.').last}";
|
|
body["image"] = {
|
|
"fileName": imageName,
|
|
"contentType": contentType,
|
|
"fileSize": fileSize,
|
|
"description": "Employee attendance photo",
|
|
"base64Data": base64Encode(bytes),
|
|
};
|
|
} catch (e) {
|
|
_log("Image encoding error: $e", level: LogLevel.error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.uploadAttendanceImage,
|
|
method: 'POST',
|
|
body: body,
|
|
);
|
|
|
|
if (response == null) return false;
|
|
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "Upload Attendance Image", returnFullResponse: true);
|
|
return parsed != null && parsed['success'] == true;
|
|
}
|
|
|
|
static String generateImageName(String employeeId, int count) {
|
|
final now = DateTime.now();
|
|
final dateStr = DateFormat('yyyyMMdd_HHmmss').format(now);
|
|
final imageNumber = count.toString().padLeft(3, '0');
|
|
return "${employeeId}_${dateStr}_$imageNumber.jpg";
|
|
}
|
|
|
|
/// ============================================
|
|
/// DIRECTORY
|
|
/// ============================================
|
|
|
|
static Future<Map<String, dynamic>?> getContactBucketList() async {
|
|
final response =
|
|
await _safeApiCall(ApiEndpoints.getDirectoryBucketList, method: 'GET');
|
|
if (response == null) return null;
|
|
return _parseAndDecryptResponse(response,
|
|
label: 'Contact Bucket List',
|
|
returnFullResponse: true) as Map<String, dynamic>?;
|
|
}
|
|
|
|
static Future<List<dynamic>?> getDirectoryData(
|
|
{required bool isActive}) async {
|
|
final queryParams = {"active": isActive.toString()};
|
|
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.getDirectoryContacts,
|
|
method: 'GET',
|
|
queryParams: queryParams,
|
|
);
|
|
if (response == null) return null;
|
|
return _parseAndDecryptResponse(response, label: 'Directory Data');
|
|
}
|
|
|
|
static Future<Map<String, dynamic>?> getContactTagList() async {
|
|
final response =
|
|
await _safeApiCall(ApiEndpoints.getDirectoryContactTags, method: 'GET');
|
|
if (response == null) return null;
|
|
return _parseAndDecryptResponse(response,
|
|
label: 'Contact Tag List',
|
|
returnFullResponse: true) as Map<String, dynamic>?;
|
|
}
|
|
|
|
static Future<Map<String, dynamic>?> getContactCategoryList() async {
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.getDirectoryContactCategory,
|
|
method: 'GET');
|
|
if (response == null) return null;
|
|
return _parseAndDecryptResponse(response,
|
|
label: 'Contact Category List',
|
|
returnFullResponse: true) as Map<String, dynamic>?;
|
|
}
|
|
|
|
static Future<List<String>> getOrganizationList() async {
|
|
final response = await _safeApiCall(ApiEndpoints.getDirectoryOrganization,
|
|
method: 'GET');
|
|
if (response == null) return [];
|
|
|
|
final body = _parseAndDecryptResponse(response, label: 'Organization List');
|
|
if (body is List) {
|
|
return List<String>.from(body);
|
|
}
|
|
return [];
|
|
}
|
|
|
|
static Future<bool> createContact(Map<String, dynamic> payload) async {
|
|
final response = await _safeApiCall(ApiEndpoints.createContact,
|
|
method: 'POST', body: payload);
|
|
if (response == null) return false;
|
|
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "Create Contact", returnFullResponse: true);
|
|
return parsed != null && parsed['success'] == true;
|
|
}
|
|
|
|
static Future<bool> updateContact(
|
|
String contactId, Map<String, dynamic> payload) async {
|
|
final endpoint = "${ApiEndpoints.updateContact}/$contactId";
|
|
final response = await _safeApiCall(endpoint, method: 'PUT', body: payload);
|
|
if (response == null) return false;
|
|
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "Update Contact", returnFullResponse: true);
|
|
return parsed != null && parsed['success'] == true;
|
|
}
|
|
|
|
static Future<bool> deleteDirectoryContact(String contactId) async {
|
|
// Note: The original implementation uses a DELETE request with an 'active=false' query param for soft delete.
|
|
final endpoint = "${ApiEndpoints.updateContact}/$contactId/";
|
|
final queryParams = {'active': 'false'};
|
|
|
|
final response = await _safeApiCall(
|
|
endpoint,
|
|
method: 'DELETE',
|
|
queryParams: queryParams,
|
|
);
|
|
|
|
if (response == null) return false;
|
|
|
|
// Assuming successful deletion is 200/204
|
|
return response.statusCode == 200 || response.statusCode == 204;
|
|
}
|
|
|
|
static Future<bool> restoreDirectoryContact(String contactId) async {
|
|
// Note: The original implementation uses a DELETE request with an 'active=true' query param for restore.
|
|
final endpoint = "${ApiEndpoints.updateContact}/$contactId/";
|
|
final queryParams = {'active': 'true'};
|
|
|
|
final response = await _safeApiCall(
|
|
endpoint,
|
|
method: 'DELETE',
|
|
queryParams: queryParams,
|
|
);
|
|
|
|
if (response == null) return false;
|
|
|
|
// Assuming successful restore is 200/204
|
|
return response.statusCode == 200 || response.statusCode == 204;
|
|
}
|
|
|
|
static Future<List<dynamic>?> getDirectoryComments(
|
|
String contactId, {
|
|
bool active = true,
|
|
}) async {
|
|
final endpoint = "${ApiEndpoints.getDirectoryNotes}/$contactId";
|
|
final queryParams = {'active': active.toString()};
|
|
final response =
|
|
await _safeApiCall(endpoint, method: 'GET', queryParams: queryParams);
|
|
if (response == null) return null;
|
|
|
|
final data =
|
|
_parseAndDecryptResponse(response, label: 'Directory Comments');
|
|
return data is List ? data : null;
|
|
}
|
|
|
|
static Future<bool> restoreContactComment(
|
|
String commentId,
|
|
bool isActive,
|
|
) async {
|
|
final endpoint = "${ApiEndpoints.updateDirectoryNotes}/$commentId";
|
|
final queryParams = {'active': isActive.toString()};
|
|
|
|
// Note: The original implementation used DELETE for status update
|
|
final response = await _safeApiCall(
|
|
endpoint,
|
|
method: 'DELETE',
|
|
queryParams: queryParams,
|
|
);
|
|
|
|
if (response == null) return false;
|
|
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "Restore/Delete Comment", returnFullResponse: true);
|
|
return parsed != null && parsed['success'] == true;
|
|
}
|
|
|
|
static Future<bool> updateContactComment(
|
|
String commentId, String note, String contactId) async {
|
|
final payload = {
|
|
"id": commentId,
|
|
"contactId": contactId,
|
|
"note": note,
|
|
};
|
|
final endpoint = "${ApiEndpoints.updateDirectoryNotes}/$commentId";
|
|
final headers = {"comment-id": commentId}; // Per original logic
|
|
|
|
final response = await _safeApiCall(
|
|
endpoint,
|
|
method: 'PUT',
|
|
body: payload,
|
|
additionalHeaders: headers,
|
|
);
|
|
|
|
if (response == null) return false;
|
|
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "Update Comment", returnFullResponse: true);
|
|
return parsed != null && parsed['success'] == true;
|
|
}
|
|
|
|
static Future<bool> addContactComment(String note, String contactId) async {
|
|
final payload = {
|
|
"note": note,
|
|
"contactId": contactId,
|
|
};
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.updateDirectoryNotes,
|
|
method: 'POST',
|
|
body: payload,
|
|
);
|
|
|
|
if (response == null) return false;
|
|
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "Add Comment", returnFullResponse: true);
|
|
return parsed != null && parsed['success'] == true;
|
|
}
|
|
|
|
static Future<Map<String, dynamic>?> getDirectoryNotes({
|
|
int pageSize = 1000,
|
|
int pageNumber = 1,
|
|
}) async {
|
|
final queryParams = {
|
|
'pageSize': pageSize.toString(),
|
|
'pageNumber': pageNumber.toString(),
|
|
};
|
|
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.getDirectoryNotes,
|
|
method: 'GET',
|
|
queryParams: queryParams,
|
|
);
|
|
if (response == null) return null;
|
|
|
|
return _parseAndDecryptResponse(response, label: 'Directory Notes')
|
|
as Map<String, dynamic>?;
|
|
}
|
|
|
|
static Future<bool> createBucket({
|
|
required String name,
|
|
required String description,
|
|
}) async {
|
|
final payload = {
|
|
"name": name,
|
|
"description": description,
|
|
};
|
|
final response = await _safeApiCall(
|
|
ApiEndpoints.createBucket,
|
|
method: 'POST',
|
|
body: payload,
|
|
);
|
|
if (response == null) return false;
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "Create Bucket", returnFullResponse: true);
|
|
return parsed != null && parsed['success'] == true;
|
|
}
|
|
|
|
static Future<bool> assignEmployeesToBucket({
|
|
required String bucketId,
|
|
required List<Map<String, dynamic>> employees,
|
|
}) async {
|
|
final endpoint = "${ApiEndpoints.assignBucket}/$bucketId";
|
|
|
|
final response = await _safeApiCall(
|
|
endpoint,
|
|
method: 'POST',
|
|
body: employees,
|
|
);
|
|
if (response == null) return false;
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "Assign Employees to Bucket", returnFullResponse: true);
|
|
return parsed != null && parsed['success'] == true;
|
|
}
|
|
|
|
static Future<bool> updateBucket({
|
|
required String id,
|
|
required String name,
|
|
required String description,
|
|
}) async {
|
|
final payload = {
|
|
"id": id,
|
|
"name": name,
|
|
"description": description,
|
|
};
|
|
final endpoint = "${ApiEndpoints.updateBucket}/$id";
|
|
|
|
final response = await _safeApiCall(
|
|
endpoint,
|
|
method: 'PUT',
|
|
body: payload,
|
|
);
|
|
if (response == null) return false;
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "Update Bucket", returnFullResponse: true);
|
|
return parsed != null && parsed['success'] == true;
|
|
}
|
|
|
|
static Future<bool> deleteBucket(String id) async {
|
|
final endpoint = "${ApiEndpoints.updateBucket}/$id";
|
|
final response = await _safeApiCall(endpoint, method: 'DELETE');
|
|
if (response == null) return false;
|
|
|
|
final parsed = _parseAndDecryptResponse(response,
|
|
label: "Delete Bucket", returnFullResponse: true);
|
|
return parsed != null && parsed['success'] == true;
|
|
}
|
|
}
|