This commit is contained in:
Mohammed Al-Samarraie
2026-01-18 19:40:54 +03:00
parent 8adab4c4af
commit 79b53b6303
13 changed files with 771 additions and 94 deletions

View File

@@ -0,0 +1,124 @@
import 'package:dio/dio.dart';
import '../../core/error/exceptions.dart';
import '../../core/network/api_client.dart';
import '../dto/vacation_request_dto.dart';
import '../dto/vacation_response_dto.dart';
import '../dto/vacation_type_dto.dart';
abstract class VacationRemoteDataSource {
Future<VacationResponseDto> createVacation(VacationRequestDto request);
Future<VacationTypesResponseDto> getVacationTypes();
}
class VacationRemoteDataSourceImpl implements VacationRemoteDataSource {
final ApiClient apiClient;
VacationRemoteDataSourceImpl({required this.apiClient});
@override
Future<VacationResponseDto> createVacation(VacationRequestDto request) async {
try {
final response = await apiClient.post(
'/Vacation',
data: request.toJson(),
);
if (response.statusCode == 200 || response.statusCode == 201) {
final responseData = response.data;
if (responseData is Map<String, dynamic>) {
return VacationResponseDto.fromJson(responseData);
} else {
throw ServerException(
message: 'استجابة غير صحيحة من الخادم',
statusCode: response.statusCode,
);
}
} else {
throw ServerException(
message: 'فشل إنشاء طلب الإجازة',
statusCode: response.statusCode,
);
}
} on DioException catch (e) {
if (e.type == DioExceptionType.connectionTimeout ||
e.type == DioExceptionType.receiveTimeout) {
throw NetworkException(message: 'انتهت مهلة الاتصال');
} else if (e.type == DioExceptionType.connectionError) {
throw NetworkException(message: 'لا يوجد اتصال بالانترنيت');
} else if (e.response?.statusCode == 500) {
throw ServerException(message: 'خطأ في الخادم يرجى المحاولة لاحقا');
} else if (e.response != null) {
final message =
e.response?.data?['message'] ??
e.response?.data?['error'] ??
'فشل إنشاء طلب الإجازة';
throw ServerException(
message: message.toString(),
statusCode: e.response?.statusCode,
);
} else {
throw NetworkException(message: 'خطأ في الانترنيت يرجى المحاولة لاحقا');
}
} catch (e) {
if (e is ServerException || e is NetworkException) {
rethrow;
}
print('خطأ غير متوقع: $e');
throw ServerException(message: 'خطأ غير متوقع');
}
}
@override
Future<VacationTypesResponseDto> getVacationTypes() async {
try {
final response = await apiClient.get('/enums/vacation-types');
if (response.statusCode == 200) {
final responseData = response.data;
if (responseData is Map<String, dynamic>) {
return VacationTypesResponseDto.fromJson(responseData);
} else {
throw ServerException(
message: 'استجابة غير صحيحة من الخادم',
statusCode: response.statusCode,
);
}
} else {
throw ServerException(
message: 'فشل جلب أنواع الإجازات',
statusCode: response.statusCode,
);
}
} on DioException catch (e) {
if (e.type == DioExceptionType.connectionTimeout ||
e.type == DioExceptionType.receiveTimeout) {
throw NetworkException(message: 'انتهت مهلة الاتصال');
} else if (e.type == DioExceptionType.connectionError) {
throw NetworkException(message: 'لا يوجد اتصال بالانترنيت');
} else if (e.response?.statusCode == 500) {
throw ServerException(message: 'خطأ في الخادم يرجى المحاولة لاحقا');
} else if (e.response != null) {
final message =
e.response?.data?['message'] ??
e.response?.data?['error'] ??
'فشل جلب أنواع الإجازات';
throw ServerException(
message: message.toString(),
statusCode: e.response?.statusCode,
);
} else {
throw NetworkException(message: 'خطأ في الانترنيت يرجى المحاولة لاحقا');
}
} catch (e) {
if (e is ServerException || e is NetworkException) {
rethrow;
}
print('خطأ غير متوقع: $e');
throw ServerException(message: 'خطأ غير متوقع');
}
}
}

View File

@@ -0,0 +1,25 @@
class VacationRequestDto {
final String employeeId;
final DateTime startDate;
final DateTime endDate;
final String reason;
final int type;
VacationRequestDto({
required this.employeeId,
required this.startDate,
required this.endDate,
required this.reason,
required this.type,
});
Map<String, dynamic> toJson() {
return {
'employeeId': employeeId,
'startDate': startDate.toIso8601String(),
'endDate': endDate.toIso8601String(),
'reason': reason,
'type': type,
};
}
}

View File

@@ -0,0 +1,89 @@
class VacationResponseDto {
final int statusCode;
final bool isSuccess;
final String message;
final VacationDataDto? data;
VacationResponseDto({
required this.statusCode,
required this.isSuccess,
required this.message,
this.data,
});
factory VacationResponseDto.fromJson(Map<String, dynamic> json) {
return VacationResponseDto(
statusCode: json['statusCode'] ?? 0,
isSuccess: json['isSuccess'] ?? false,
message: json['message'] ?? '',
data: json['data'] != null ? VacationDataDto.fromJson(json['data']) : null,
);
}
}
class VacationDataDto {
final String employeeId;
final String? employeeFullName;
final DateTime startDate;
final DateTime endDate;
final String reason;
final String? submittedBy;
final String? submittedByUser;
final int state;
final int type;
final String id;
final DateTime? createdAt;
final DateTime? updatedAt;
final DateTime? deletedAt;
final bool? isDeleted;
VacationDataDto({
required this.employeeId,
this.employeeFullName,
required this.startDate,
required this.endDate,
required this.reason,
this.submittedBy,
this.submittedByUser,
required this.state,
required this.type,
required this.id,
this.createdAt,
this.updatedAt,
this.deletedAt,
this.isDeleted,
});
factory VacationDataDto.fromJson(Map<String, dynamic> json) {
return VacationDataDto(
employeeId: json['employeeId']?.toString() ?? '',
employeeFullName: json['employeeFullName'],
startDate: _parseDateTime(json['startDate'])!,
endDate: _parseDateTime(json['endDate'])!,
reason: json['reason']?.toString() ?? '',
submittedBy: json['submittedBy'],
submittedByUser: json['submittedByUser'],
state: json['state'] ?? 0,
type: json['type'] ?? 0,
id: json['id']?.toString() ?? '',
createdAt: _parseDateTime(json['createdAt']),
updatedAt: _parseDateTime(json['updatedAt']),
deletedAt: _parseDateTime(json['deletedAt']),
isDeleted: json['isDeleted'],
);
}
static DateTime? _parseDateTime(dynamic value) {
if (value == null) return null;
if (value is DateTime) return value;
if (value is String) {
try {
return DateTime.parse(value);
} catch (e) {
print('Error parsing date: $value - $e');
return null;
}
}
return null;
}
}

View File

@@ -0,0 +1,43 @@
class VacationTypesResponseDto {
final int statusCode;
final bool isSuccess;
final String? message;
final List<VacationTypeDto> data;
VacationTypesResponseDto({
required this.statusCode,
required this.isSuccess,
this.message,
required this.data,
});
factory VacationTypesResponseDto.fromJson(Map<String, dynamic> json) {
return VacationTypesResponseDto(
statusCode: json['statusCode'] ?? 0,
isSuccess: json['isSuccess'] ?? false,
message: json['message'],
data: json['data'] != null
? (json['data'] as List)
.map((item) => VacationTypeDto.fromJson(item))
.toList()
: [],
);
}
}
class VacationTypeDto {
final int value;
final String name;
VacationTypeDto({
required this.value,
required this.name,
});
factory VacationTypeDto.fromJson(Map<String, dynamic> json) {
return VacationTypeDto(
value: json['value'] ?? 0,
name: json['name']?.toString() ?? '',
);
}
}

View File

@@ -0,0 +1,93 @@
import 'package:dartz/dartz.dart';
import '../../core/error/exceptions.dart';
import '../../core/error/failures.dart';
import '../datasources/vacation_remote_data_source.dart';
import '../dto/vacation_request_dto.dart';
import '../../domain/models/vacation_request.dart';
import '../../domain/models/vacation_response_model.dart';
import '../../domain/models/vacation_type_model.dart';
import '../../domain/repositories/vacation_repository.dart';
class VacationRepositoryImpl implements VacationRepository {
final VacationRemoteDataSource remoteDataSource;
VacationRepositoryImpl({required this.remoteDataSource});
@override
Future<Either<Failure, VacationResponseModel>> createVacation(
VacationRequest request,
) async {
try {
final dto = VacationRequestDto(
employeeId: request.employeeId,
startDate: request.startDate,
endDate: request.endDate,
reason: request.reason,
type: request.type,
);
final responseDto = await remoteDataSource.createVacation(dto);
// Convert DTO to Model
final responseModel = VacationResponseModel(
statusCode: responseDto.statusCode,
isSuccess: responseDto.isSuccess,
message: responseDto.message,
data: responseDto.data != null
? VacationDataModel(
employeeId: responseDto.data!.employeeId,
employeeFullName: responseDto.data!.employeeFullName,
startDate: responseDto.data!.startDate,
endDate: responseDto.data!.endDate,
reason: responseDto.data!.reason,
submittedBy: responseDto.data!.submittedBy,
submittedByUser: responseDto.data!.submittedByUser,
state: responseDto.data!.state,
type: responseDto.data!.type,
id: responseDto.data!.id,
createdAt: responseDto.data!.createdAt,
updatedAt: responseDto.data!.updatedAt,
deletedAt: responseDto.data!.deletedAt,
isDeleted: responseDto.data!.isDeleted,
)
: null,
);
return Right(responseModel);
} on ServerException catch (e) {
return Left(ServerFailure(e.message));
} on NetworkException catch (e) {
return Left(NetworkFailure(e.message));
} catch (e) {
return Left(ServerFailure('خطأ غير متوقع: $e'));
}
}
@override
Future<Either<Failure, VacationTypesResponseModel>> getVacationTypes() async {
try {
final responseDto = await remoteDataSource.getVacationTypes();
// Convert DTO to Model
final responseModel = VacationTypesResponseModel(
statusCode: responseDto.statusCode,
isSuccess: responseDto.isSuccess,
message: responseDto.message,
data: responseDto.data
.map((dto) => VacationTypeModel(
value: dto.value,
name: dto.name,
))
.toList(),
);
return Right(responseModel);
} on ServerException catch (e) {
return Left(ServerFailure(e.message));
} on NetworkException catch (e) {
return Left(NetworkFailure(e.message));
} catch (e) {
return Left(ServerFailure('خطأ غير متوقع: $e'));
}
}
}