1111
This commit is contained in:
@@ -8,15 +8,21 @@ import '../network/api_client.dart';
|
|||||||
import '../../data/datasources/auth_remote_data_source.dart';
|
import '../../data/datasources/auth_remote_data_source.dart';
|
||||||
import '../../data/datasources/user_local_data_source.dart';
|
import '../../data/datasources/user_local_data_source.dart';
|
||||||
import '../../data/datasources/vacation_remote_data_source.dart';
|
import '../../data/datasources/vacation_remote_data_source.dart';
|
||||||
|
import '../../data/datasources/advance_remote_data_source.dart';
|
||||||
import '../../data/repositories/auth_repository_impl.dart';
|
import '../../data/repositories/auth_repository_impl.dart';
|
||||||
import '../../data/repositories/vacation_repository_impl.dart';
|
import '../../data/repositories/vacation_repository_impl.dart';
|
||||||
|
import '../../data/repositories/advance_repository_impl.dart';
|
||||||
import '../../domain/repositories/auth_repository.dart';
|
import '../../domain/repositories/auth_repository.dart';
|
||||||
import '../../domain/repositories/vacation_repository.dart';
|
import '../../domain/repositories/vacation_repository.dart';
|
||||||
|
import '../../domain/repositories/advance_repository.dart';
|
||||||
import '../../domain/usecases/login_usecase.dart';
|
import '../../domain/usecases/login_usecase.dart';
|
||||||
import '../../domain/usecases/attendance_login_usecase.dart';
|
import '../../domain/usecases/attendance_login_usecase.dart';
|
||||||
import '../../domain/usecases/attendance_logout_usecase.dart';
|
import '../../domain/usecases/attendance_logout_usecase.dart';
|
||||||
import '../../domain/usecases/create_vacation_usecase.dart';
|
import '../../domain/usecases/create_vacation_usecase.dart';
|
||||||
import '../../domain/usecases/get_vacation_types_usecase.dart';
|
import '../../domain/usecases/get_vacation_types_usecase.dart';
|
||||||
|
import '../../domain/usecases/get_vacations_usecase.dart';
|
||||||
|
import '../../domain/usecases/create_advance_usecase.dart';
|
||||||
|
import '../../domain/usecases/get_advances_usecase.dart';
|
||||||
import '../../presentation/blocs/login/login_bloc.dart';
|
import '../../presentation/blocs/login/login_bloc.dart';
|
||||||
|
|
||||||
final sl = GetIt.instance;
|
final sl = GetIt.instance;
|
||||||
@@ -78,4 +84,17 @@ Future<void> initializeDependencies() async {
|
|||||||
|
|
||||||
sl.registerLazySingleton(() => CreateVacationUseCase(repository: sl()));
|
sl.registerLazySingleton(() => CreateVacationUseCase(repository: sl()));
|
||||||
sl.registerLazySingleton(() => GetVacationTypesUseCase(repository: sl()));
|
sl.registerLazySingleton(() => GetVacationTypesUseCase(repository: sl()));
|
||||||
|
sl.registerLazySingleton(() => GetVacationsUseCase(repository: sl()));
|
||||||
|
|
||||||
|
// Advance
|
||||||
|
sl.registerLazySingleton<AdvanceRemoteDataSource>(
|
||||||
|
() => AdvanceRemoteDataSourceImpl(apiClient: sl()),
|
||||||
|
);
|
||||||
|
|
||||||
|
sl.registerLazySingleton<AdvanceRepository>(
|
||||||
|
() => AdvanceRepositoryImpl(remoteDataSource: sl()),
|
||||||
|
);
|
||||||
|
|
||||||
|
sl.registerLazySingleton(() => CreateAdvanceUseCase(repository: sl()));
|
||||||
|
sl.registerLazySingleton(() => GetAdvancesUseCase(repository: sl()));
|
||||||
}
|
}
|
||||||
|
|||||||
124
lib/data/datasources/advance_remote_data_source.dart
Normal file
124
lib/data/datasources/advance_remote_data_source.dart
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import '../../core/error/exceptions.dart';
|
||||||
|
import '../../core/network/api_client.dart';
|
||||||
|
import '../dto/advance_request_dto.dart';
|
||||||
|
import '../dto/advance_response_dto.dart';
|
||||||
|
import '../dto/advances_list_response_dto.dart';
|
||||||
|
|
||||||
|
abstract class AdvanceRemoteDataSource {
|
||||||
|
Future<AdvanceResponseDto> createAdvance(AdvanceRequestDto request);
|
||||||
|
Future<AdvancesListResponseDto> getAdvances();
|
||||||
|
}
|
||||||
|
|
||||||
|
class AdvanceRemoteDataSourceImpl implements AdvanceRemoteDataSource {
|
||||||
|
final ApiClient apiClient;
|
||||||
|
|
||||||
|
AdvanceRemoteDataSourceImpl({required this.apiClient});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<AdvanceResponseDto> createAdvance(AdvanceRequestDto request) async {
|
||||||
|
try {
|
||||||
|
final response = await apiClient.post(
|
||||||
|
'/SalaryInAdvance',
|
||||||
|
data: request.toJson(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||||
|
final responseData = response.data;
|
||||||
|
|
||||||
|
if (responseData is Map<String, dynamic>) {
|
||||||
|
return AdvanceResponseDto.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<AdvancesListResponseDto> getAdvances() async {
|
||||||
|
try {
|
||||||
|
final response = await apiClient.get('/SalaryInAdvance');
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final responseData = response.data;
|
||||||
|
|
||||||
|
if (responseData is Map<String, dynamic>) {
|
||||||
|
return AdvancesListResponseDto.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: 'خطأ غير متوقع');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,10 +4,12 @@ import '../../core/network/api_client.dart';
|
|||||||
import '../dto/vacation_request_dto.dart';
|
import '../dto/vacation_request_dto.dart';
|
||||||
import '../dto/vacation_response_dto.dart';
|
import '../dto/vacation_response_dto.dart';
|
||||||
import '../dto/vacation_type_dto.dart';
|
import '../dto/vacation_type_dto.dart';
|
||||||
|
import '../dto/vacations_list_response_dto.dart';
|
||||||
|
|
||||||
abstract class VacationRemoteDataSource {
|
abstract class VacationRemoteDataSource {
|
||||||
Future<VacationResponseDto> createVacation(VacationRequestDto request);
|
Future<VacationResponseDto> createVacation(VacationRequestDto request);
|
||||||
Future<VacationTypesResponseDto> getVacationTypes();
|
Future<VacationTypesResponseDto> getVacationTypes();
|
||||||
|
Future<VacationsListResponseDto> getVacations();
|
||||||
}
|
}
|
||||||
|
|
||||||
class VacationRemoteDataSourceImpl implements VacationRemoteDataSource {
|
class VacationRemoteDataSourceImpl implements VacationRemoteDataSource {
|
||||||
@@ -121,4 +123,56 @@ class VacationRemoteDataSourceImpl implements VacationRemoteDataSource {
|
|||||||
throw ServerException(message: 'خطأ غير متوقع');
|
throw ServerException(message: 'خطأ غير متوقع');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<VacationsListResponseDto> getVacations() async {
|
||||||
|
try {
|
||||||
|
final response = await apiClient.get('/Vacation');
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final responseData = response.data;
|
||||||
|
|
||||||
|
if (responseData is Map<String, dynamic>) {
|
||||||
|
return VacationsListResponseDto.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: 'خطأ غير متوقع');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
22
lib/data/dto/advance_request_dto.dart
Normal file
22
lib/data/dto/advance_request_dto.dart
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
class AdvanceRequestDto {
|
||||||
|
final String employeeId;
|
||||||
|
final DateTime date;
|
||||||
|
final double amount;
|
||||||
|
final String reason;
|
||||||
|
|
||||||
|
AdvanceRequestDto({
|
||||||
|
required this.employeeId,
|
||||||
|
required this.date,
|
||||||
|
required this.amount,
|
||||||
|
required this.reason,
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'employeeId': employeeId,
|
||||||
|
'date': date.toIso8601String(),
|
||||||
|
'amount': amount,
|
||||||
|
'reason': reason,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
86
lib/data/dto/advance_response_dto.dart
Normal file
86
lib/data/dto/advance_response_dto.dart
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
class AdvanceResponseDto {
|
||||||
|
final int statusCode;
|
||||||
|
final bool isSuccess;
|
||||||
|
final String? message;
|
||||||
|
final AdvanceDataDto? data;
|
||||||
|
|
||||||
|
AdvanceResponseDto({
|
||||||
|
required this.statusCode,
|
||||||
|
required this.isSuccess,
|
||||||
|
this.message,
|
||||||
|
this.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory AdvanceResponseDto.fromJson(Map<String, dynamic> json) {
|
||||||
|
return AdvanceResponseDto(
|
||||||
|
statusCode: json['statusCode'] ?? 0,
|
||||||
|
isSuccess: json['isSuccess'] ?? false,
|
||||||
|
message: json['message'],
|
||||||
|
data: json['data'] != null ? AdvanceDataDto.fromJson(json['data']) : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AdvanceDataDto {
|
||||||
|
final String employeeId;
|
||||||
|
final String? employeeFullName;
|
||||||
|
final DateTime date;
|
||||||
|
final double amount;
|
||||||
|
final String? submittedBy;
|
||||||
|
final String? submittedByUser;
|
||||||
|
final String reason;
|
||||||
|
final int state;
|
||||||
|
final String id;
|
||||||
|
final DateTime? createdAt;
|
||||||
|
final DateTime? updatedAt;
|
||||||
|
final DateTime? deletedAt;
|
||||||
|
final bool? isDeleted;
|
||||||
|
|
||||||
|
AdvanceDataDto({
|
||||||
|
required this.employeeId,
|
||||||
|
this.employeeFullName,
|
||||||
|
required this.date,
|
||||||
|
required this.amount,
|
||||||
|
this.submittedBy,
|
||||||
|
this.submittedByUser,
|
||||||
|
required this.reason,
|
||||||
|
required this.state,
|
||||||
|
required this.id,
|
||||||
|
this.createdAt,
|
||||||
|
this.updatedAt,
|
||||||
|
this.deletedAt,
|
||||||
|
this.isDeleted,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory AdvanceDataDto.fromJson(Map<String, dynamic> json) {
|
||||||
|
return AdvanceDataDto(
|
||||||
|
employeeId: json['employeeId']?.toString() ?? '',
|
||||||
|
employeeFullName: json['employeeFullName'],
|
||||||
|
date: _parseDateTime(json['date'])!,
|
||||||
|
amount: (json['amount'] is int) ? (json['amount'] as int).toDouble() : (json['amount'] as num).toDouble(),
|
||||||
|
submittedBy: json['submittedBy'],
|
||||||
|
submittedByUser: json['submittedByUser'],
|
||||||
|
reason: json['reason']?.toString() ?? '',
|
||||||
|
state: json['state'] ?? 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
54
lib/data/dto/advances_list_response_dto.dart
Normal file
54
lib/data/dto/advances_list_response_dto.dart
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import 'advance_response_dto.dart';
|
||||||
|
|
||||||
|
class AdvancesListResponseDto {
|
||||||
|
final int statusCode;
|
||||||
|
final bool isSuccess;
|
||||||
|
final String? message;
|
||||||
|
final AdvancesListDataDto? data;
|
||||||
|
|
||||||
|
AdvancesListResponseDto({
|
||||||
|
required this.statusCode,
|
||||||
|
required this.isSuccess,
|
||||||
|
this.message,
|
||||||
|
this.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory AdvancesListResponseDto.fromJson(Map<String, dynamic> json) {
|
||||||
|
return AdvancesListResponseDto(
|
||||||
|
statusCode: json['statusCode'] ?? 0,
|
||||||
|
isSuccess: json['isSuccess'] ?? false,
|
||||||
|
message: json['message'],
|
||||||
|
data: json['data'] != null ? AdvancesListDataDto.fromJson(json['data']) : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AdvancesListDataDto {
|
||||||
|
final List<AdvanceDataDto> items;
|
||||||
|
final int pageNumber;
|
||||||
|
final int pageSize;
|
||||||
|
final int totalCount;
|
||||||
|
final int totalPages;
|
||||||
|
|
||||||
|
AdvancesListDataDto({
|
||||||
|
required this.items,
|
||||||
|
required this.pageNumber,
|
||||||
|
required this.pageSize,
|
||||||
|
required this.totalCount,
|
||||||
|
required this.totalPages,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory AdvancesListDataDto.fromJson(Map<String, dynamic> json) {
|
||||||
|
return AdvancesListDataDto(
|
||||||
|
items: json['items'] != null
|
||||||
|
? (json['items'] as List)
|
||||||
|
.map((item) => AdvanceDataDto.fromJson(item))
|
||||||
|
.toList()
|
||||||
|
: [],
|
||||||
|
pageNumber: json['pageNumber'] ?? 1,
|
||||||
|
pageSize: json['pageSize'] ?? 15,
|
||||||
|
totalCount: json['totalCount'] ?? 0,
|
||||||
|
totalPages: json['totalPages'] ?? 1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
54
lib/data/dto/vacations_list_response_dto.dart
Normal file
54
lib/data/dto/vacations_list_response_dto.dart
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import 'vacation_response_dto.dart';
|
||||||
|
|
||||||
|
class VacationsListResponseDto {
|
||||||
|
final int statusCode;
|
||||||
|
final bool isSuccess;
|
||||||
|
final String? message;
|
||||||
|
final VacationsListDataDto? data;
|
||||||
|
|
||||||
|
VacationsListResponseDto({
|
||||||
|
required this.statusCode,
|
||||||
|
required this.isSuccess,
|
||||||
|
this.message,
|
||||||
|
this.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory VacationsListResponseDto.fromJson(Map<String, dynamic> json) {
|
||||||
|
return VacationsListResponseDto(
|
||||||
|
statusCode: json['statusCode'] ?? 0,
|
||||||
|
isSuccess: json['isSuccess'] ?? false,
|
||||||
|
message: json['message'],
|
||||||
|
data: json['data'] != null ? VacationsListDataDto.fromJson(json['data']) : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class VacationsListDataDto {
|
||||||
|
final List<VacationDataDto> items;
|
||||||
|
final int pageNumber;
|
||||||
|
final int pageSize;
|
||||||
|
final int totalCount;
|
||||||
|
final int totalPages;
|
||||||
|
|
||||||
|
VacationsListDataDto({
|
||||||
|
required this.items,
|
||||||
|
required this.pageNumber,
|
||||||
|
required this.pageSize,
|
||||||
|
required this.totalCount,
|
||||||
|
required this.totalPages,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory VacationsListDataDto.fromJson(Map<String, dynamic> json) {
|
||||||
|
return VacationsListDataDto(
|
||||||
|
items: json['items'] != null
|
||||||
|
? (json['items'] as List)
|
||||||
|
.map((item) => VacationDataDto.fromJson(item))
|
||||||
|
.toList()
|
||||||
|
: [],
|
||||||
|
pageNumber: json['pageNumber'] ?? 1,
|
||||||
|
pageSize: json['pageSize'] ?? 15,
|
||||||
|
totalCount: json['totalCount'] ?? 0,
|
||||||
|
totalPages: json['totalPages'] ?? 1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
109
lib/data/repositories/advance_repository_impl.dart
Normal file
109
lib/data/repositories/advance_repository_impl.dart
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import 'package:dartz/dartz.dart';
|
||||||
|
import '../../core/error/exceptions.dart';
|
||||||
|
import '../../core/error/failures.dart';
|
||||||
|
import '../datasources/advance_remote_data_source.dart';
|
||||||
|
import '../dto/advance_request_dto.dart';
|
||||||
|
import '../../domain/models/advance_request_model.dart';
|
||||||
|
import '../../domain/models/advances_list_response_model.dart';
|
||||||
|
import '../../domain/repositories/advance_repository.dart';
|
||||||
|
|
||||||
|
class AdvanceRepositoryImpl implements AdvanceRepository {
|
||||||
|
final AdvanceRemoteDataSource remoteDataSource;
|
||||||
|
|
||||||
|
AdvanceRepositoryImpl({required this.remoteDataSource});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Either<Failure, AdvanceResponseModel>> createAdvance(
|
||||||
|
AdvanceRequestModel request,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
final dto = AdvanceRequestDto(
|
||||||
|
employeeId: request.employeeId,
|
||||||
|
date: request.date,
|
||||||
|
amount: request.amount,
|
||||||
|
reason: request.reason,
|
||||||
|
);
|
||||||
|
|
||||||
|
final responseDto = await remoteDataSource.createAdvance(dto);
|
||||||
|
|
||||||
|
// Convert DTO to Model
|
||||||
|
final responseModel = AdvanceResponseModel(
|
||||||
|
statusCode: responseDto.statusCode,
|
||||||
|
isSuccess: responseDto.isSuccess,
|
||||||
|
message: responseDto.message,
|
||||||
|
data: responseDto.data != null
|
||||||
|
? AdvanceDataModel(
|
||||||
|
employeeId: responseDto.data!.employeeId,
|
||||||
|
employeeFullName: responseDto.data!.employeeFullName,
|
||||||
|
date: responseDto.data!.date,
|
||||||
|
amount: responseDto.data!.amount,
|
||||||
|
submittedBy: responseDto.data!.submittedBy,
|
||||||
|
submittedByUser: responseDto.data!.submittedByUser,
|
||||||
|
reason: responseDto.data!.reason,
|
||||||
|
state: responseDto.data!.state,
|
||||||
|
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, AdvancesListResponseModel>> getAdvances() async {
|
||||||
|
try {
|
||||||
|
final responseDto = await remoteDataSource.getAdvances();
|
||||||
|
|
||||||
|
// Convert DTO to Model
|
||||||
|
final responseModel = AdvancesListResponseModel(
|
||||||
|
statusCode: responseDto.statusCode,
|
||||||
|
isSuccess: responseDto.isSuccess,
|
||||||
|
message: responseDto.message,
|
||||||
|
data: responseDto.data != null
|
||||||
|
? AdvancesListDataModel(
|
||||||
|
items: responseDto.data!.items
|
||||||
|
.map((dto) => AdvanceDataModel(
|
||||||
|
employeeId: dto.employeeId,
|
||||||
|
employeeFullName: dto.employeeFullName,
|
||||||
|
date: dto.date,
|
||||||
|
amount: dto.amount,
|
||||||
|
submittedBy: dto.submittedBy,
|
||||||
|
submittedByUser: dto.submittedByUser,
|
||||||
|
reason: dto.reason,
|
||||||
|
state: dto.state,
|
||||||
|
id: dto.id,
|
||||||
|
createdAt: dto.createdAt,
|
||||||
|
updatedAt: dto.updatedAt,
|
||||||
|
deletedAt: dto.deletedAt,
|
||||||
|
isDeleted: dto.isDeleted,
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
pageNumber: responseDto.data!.pageNumber,
|
||||||
|
pageSize: responseDto.data!.pageSize,
|
||||||
|
totalCount: responseDto.data!.totalCount,
|
||||||
|
totalPages: responseDto.data!.totalPages,
|
||||||
|
)
|
||||||
|
: 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'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import '../dto/vacation_request_dto.dart';
|
|||||||
import '../../domain/models/vacation_request.dart';
|
import '../../domain/models/vacation_request.dart';
|
||||||
import '../../domain/models/vacation_response_model.dart';
|
import '../../domain/models/vacation_response_model.dart';
|
||||||
import '../../domain/models/vacation_type_model.dart';
|
import '../../domain/models/vacation_type_model.dart';
|
||||||
|
import '../../domain/models/vacations_list_response_model.dart';
|
||||||
import '../../domain/repositories/vacation_repository.dart';
|
import '../../domain/repositories/vacation_repository.dart';
|
||||||
|
|
||||||
class VacationRepositoryImpl implements VacationRepository {
|
class VacationRepositoryImpl implements VacationRepository {
|
||||||
@@ -90,4 +91,52 @@ class VacationRepositoryImpl implements VacationRepository {
|
|||||||
return Left(ServerFailure('خطأ غير متوقع: $e'));
|
return Left(ServerFailure('خطأ غير متوقع: $e'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Either<Failure, VacationsListResponseModel>> getVacations() async {
|
||||||
|
try {
|
||||||
|
final responseDto = await remoteDataSource.getVacations();
|
||||||
|
|
||||||
|
// Convert DTO to Model
|
||||||
|
final responseModel = VacationsListResponseModel(
|
||||||
|
statusCode: responseDto.statusCode,
|
||||||
|
isSuccess: responseDto.isSuccess,
|
||||||
|
message: responseDto.message,
|
||||||
|
data: responseDto.data != null
|
||||||
|
? VacationsListDataModel(
|
||||||
|
items: responseDto.data!.items
|
||||||
|
.map((dto) => VacationDataModel(
|
||||||
|
employeeId: dto.employeeId,
|
||||||
|
employeeFullName: dto.employeeFullName,
|
||||||
|
startDate: dto.startDate,
|
||||||
|
endDate: dto.endDate,
|
||||||
|
reason: dto.reason,
|
||||||
|
submittedBy: dto.submittedBy,
|
||||||
|
submittedByUser: dto.submittedByUser,
|
||||||
|
state: dto.state,
|
||||||
|
type: dto.type,
|
||||||
|
id: dto.id,
|
||||||
|
createdAt: dto.createdAt,
|
||||||
|
updatedAt: dto.updatedAt,
|
||||||
|
deletedAt: dto.deletedAt,
|
||||||
|
isDeleted: dto.isDeleted,
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
pageNumber: responseDto.data!.pageNumber,
|
||||||
|
pageSize: responseDto.data!.pageSize,
|
||||||
|
totalCount: responseDto.data!.totalCount,
|
||||||
|
totalPages: responseDto.data!.totalPages,
|
||||||
|
)
|
||||||
|
: 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'));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
59
lib/domain/models/advance_request_model.dart
Normal file
59
lib/domain/models/advance_request_model.dart
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
class AdvanceRequestModel {
|
||||||
|
final String employeeId;
|
||||||
|
final DateTime date;
|
||||||
|
final double amount;
|
||||||
|
final String reason;
|
||||||
|
|
||||||
|
AdvanceRequestModel({
|
||||||
|
required this.employeeId,
|
||||||
|
required this.date,
|
||||||
|
required this.amount,
|
||||||
|
required this.reason,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class AdvanceResponseModel {
|
||||||
|
final int statusCode;
|
||||||
|
final bool isSuccess;
|
||||||
|
final String? message;
|
||||||
|
final AdvanceDataModel? data;
|
||||||
|
|
||||||
|
AdvanceResponseModel({
|
||||||
|
required this.statusCode,
|
||||||
|
required this.isSuccess,
|
||||||
|
this.message,
|
||||||
|
this.data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class AdvanceDataModel {
|
||||||
|
final String employeeId;
|
||||||
|
final String? employeeFullName;
|
||||||
|
final DateTime date;
|
||||||
|
final double amount;
|
||||||
|
final String? submittedBy;
|
||||||
|
final String? submittedByUser;
|
||||||
|
final String reason;
|
||||||
|
final int state;
|
||||||
|
final String id;
|
||||||
|
final DateTime? createdAt;
|
||||||
|
final DateTime? updatedAt;
|
||||||
|
final DateTime? deletedAt;
|
||||||
|
final bool? isDeleted;
|
||||||
|
|
||||||
|
AdvanceDataModel({
|
||||||
|
required this.employeeId,
|
||||||
|
this.employeeFullName,
|
||||||
|
required this.date,
|
||||||
|
required this.amount,
|
||||||
|
this.submittedBy,
|
||||||
|
this.submittedByUser,
|
||||||
|
required this.reason,
|
||||||
|
required this.state,
|
||||||
|
required this.id,
|
||||||
|
this.createdAt,
|
||||||
|
this.updatedAt,
|
||||||
|
this.deletedAt,
|
||||||
|
this.isDeleted,
|
||||||
|
});
|
||||||
|
}
|
||||||
31
lib/domain/models/advances_list_response_model.dart
Normal file
31
lib/domain/models/advances_list_response_model.dart
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import 'advance_request_model.dart';
|
||||||
|
|
||||||
|
class AdvancesListResponseModel {
|
||||||
|
final int statusCode;
|
||||||
|
final bool isSuccess;
|
||||||
|
final String? message;
|
||||||
|
final AdvancesListDataModel? data;
|
||||||
|
|
||||||
|
AdvancesListResponseModel({
|
||||||
|
required this.statusCode,
|
||||||
|
required this.isSuccess,
|
||||||
|
this.message,
|
||||||
|
this.data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class AdvancesListDataModel {
|
||||||
|
final List<AdvanceDataModel> items;
|
||||||
|
final int pageNumber;
|
||||||
|
final int pageSize;
|
||||||
|
final int totalCount;
|
||||||
|
final int totalPages;
|
||||||
|
|
||||||
|
AdvancesListDataModel({
|
||||||
|
required this.items,
|
||||||
|
required this.pageNumber,
|
||||||
|
required this.pageSize,
|
||||||
|
required this.totalCount,
|
||||||
|
required this.totalPages,
|
||||||
|
});
|
||||||
|
}
|
||||||
31
lib/domain/models/vacations_list_response_model.dart
Normal file
31
lib/domain/models/vacations_list_response_model.dart
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import 'vacation_response_model.dart';
|
||||||
|
|
||||||
|
class VacationsListResponseModel {
|
||||||
|
final int statusCode;
|
||||||
|
final bool isSuccess;
|
||||||
|
final String? message;
|
||||||
|
final VacationsListDataModel? data;
|
||||||
|
|
||||||
|
VacationsListResponseModel({
|
||||||
|
required this.statusCode,
|
||||||
|
required this.isSuccess,
|
||||||
|
this.message,
|
||||||
|
this.data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class VacationsListDataModel {
|
||||||
|
final List<VacationDataModel> items;
|
||||||
|
final int pageNumber;
|
||||||
|
final int pageSize;
|
||||||
|
final int totalCount;
|
||||||
|
final int totalPages;
|
||||||
|
|
||||||
|
VacationsListDataModel({
|
||||||
|
required this.items,
|
||||||
|
required this.pageNumber,
|
||||||
|
required this.pageSize,
|
||||||
|
required this.totalCount,
|
||||||
|
required this.totalPages,
|
||||||
|
});
|
||||||
|
}
|
||||||
11
lib/domain/repositories/advance_repository.dart
Normal file
11
lib/domain/repositories/advance_repository.dart
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import 'package:dartz/dartz.dart';
|
||||||
|
import '../../core/error/failures.dart';
|
||||||
|
import '../models/advance_request_model.dart';
|
||||||
|
import '../models/advances_list_response_model.dart';
|
||||||
|
|
||||||
|
abstract class AdvanceRepository {
|
||||||
|
Future<Either<Failure, AdvanceResponseModel>> createAdvance(
|
||||||
|
AdvanceRequestModel request,
|
||||||
|
);
|
||||||
|
Future<Either<Failure, AdvancesListResponseModel>> getAdvances();
|
||||||
|
}
|
||||||
@@ -3,10 +3,12 @@ import '../../core/error/failures.dart';
|
|||||||
import '../models/vacation_request.dart';
|
import '../models/vacation_request.dart';
|
||||||
import '../models/vacation_response_model.dart';
|
import '../models/vacation_response_model.dart';
|
||||||
import '../models/vacation_type_model.dart';
|
import '../models/vacation_type_model.dart';
|
||||||
|
import '../models/vacations_list_response_model.dart';
|
||||||
|
|
||||||
abstract class VacationRepository {
|
abstract class VacationRepository {
|
||||||
Future<Either<Failure, VacationResponseModel>> createVacation(
|
Future<Either<Failure, VacationResponseModel>> createVacation(
|
||||||
VacationRequest request,
|
VacationRequest request,
|
||||||
);
|
);
|
||||||
Future<Either<Failure, VacationTypesResponseModel>> getVacationTypes();
|
Future<Either<Failure, VacationTypesResponseModel>> getVacationTypes();
|
||||||
|
Future<Either<Failure, VacationsListResponseModel>> getVacations();
|
||||||
}
|
}
|
||||||
|
|||||||
14
lib/domain/usecases/create_advance_usecase.dart
Normal file
14
lib/domain/usecases/create_advance_usecase.dart
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import 'package:dartz/dartz.dart';
|
||||||
|
import '../../core/error/failures.dart';
|
||||||
|
import '../models/advance_request_model.dart';
|
||||||
|
import '../repositories/advance_repository.dart';
|
||||||
|
|
||||||
|
class CreateAdvanceUseCase {
|
||||||
|
final AdvanceRepository repository;
|
||||||
|
|
||||||
|
CreateAdvanceUseCase({required this.repository});
|
||||||
|
|
||||||
|
Future<Either<Failure, AdvanceResponseModel>> call(AdvanceRequestModel request) {
|
||||||
|
return repository.createAdvance(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
14
lib/domain/usecases/get_advances_usecase.dart
Normal file
14
lib/domain/usecases/get_advances_usecase.dart
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import 'package:dartz/dartz.dart';
|
||||||
|
import '../../core/error/failures.dart';
|
||||||
|
import '../models/advances_list_response_model.dart';
|
||||||
|
import '../repositories/advance_repository.dart';
|
||||||
|
|
||||||
|
class GetAdvancesUseCase {
|
||||||
|
final AdvanceRepository repository;
|
||||||
|
|
||||||
|
GetAdvancesUseCase({required this.repository});
|
||||||
|
|
||||||
|
Future<Either<Failure, AdvancesListResponseModel>> call() {
|
||||||
|
return repository.getAdvances();
|
||||||
|
}
|
||||||
|
}
|
||||||
14
lib/domain/usecases/get_vacations_usecase.dart
Normal file
14
lib/domain/usecases/get_vacations_usecase.dart
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import 'package:dartz/dartz.dart';
|
||||||
|
import '../../core/error/failures.dart';
|
||||||
|
import '../models/vacations_list_response_model.dart';
|
||||||
|
import '../repositories/vacation_repository.dart';
|
||||||
|
|
||||||
|
class GetVacationsUseCase {
|
||||||
|
final VacationRepository repository;
|
||||||
|
|
||||||
|
GetVacationsUseCase({required this.repository});
|
||||||
|
|
||||||
|
Future<Either<Failure, VacationsListResponseModel>> call() {
|
||||||
|
return repository.getVacations();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,14 @@ import 'request_advance_scrren.dart';
|
|||||||
import '../../models/leave_request.dart';
|
import '../../models/leave_request.dart';
|
||||||
import '../../models/advance_request.dart';
|
import '../../models/advance_request.dart';
|
||||||
import '../../core/services/request_service.dart';
|
import '../../core/services/request_service.dart';
|
||||||
|
import '../../core/di/injection_container.dart';
|
||||||
|
import '../../domain/usecases/get_vacations_usecase.dart';
|
||||||
|
import '../../domain/usecases/get_advances_usecase.dart';
|
||||||
|
import '../../domain/models/vacations_list_response_model.dart';
|
||||||
|
import '../../domain/models/vacation_response_model.dart';
|
||||||
|
import '../../domain/models/advances_list_response_model.dart';
|
||||||
|
import '../../domain/models/advance_request_model.dart';
|
||||||
|
import '../../core/error/failures.dart';
|
||||||
|
|
||||||
class HolidayScreen extends StatefulWidget {
|
class HolidayScreen extends StatefulWidget {
|
||||||
final void Function(bool isScrollingDown)? onScrollEvent;
|
final void Function(bool isScrollingDown)? onScrollEvent;
|
||||||
@@ -24,8 +32,12 @@ class _HolidayScreenState extends State<HolidayScreen> {
|
|||||||
int activeTab = 0;
|
int activeTab = 0;
|
||||||
|
|
||||||
final RequestService _requestService = RequestService();
|
final RequestService _requestService = RequestService();
|
||||||
|
final GetVacationsUseCase _getVacationsUseCase = sl<GetVacationsUseCase>();
|
||||||
|
final GetAdvancesUseCase _getAdvancesUseCase = sl<GetAdvancesUseCase>();
|
||||||
List<LeaveRequest> _leaveRequests = [];
|
List<LeaveRequest> _leaveRequests = [];
|
||||||
List<AdvanceRequest> _advanceRequests = [];
|
List<AdvanceRequest> _advanceRequests = [];
|
||||||
|
bool _isLoadingVacations = false;
|
||||||
|
bool _isLoadingAdvances = false;
|
||||||
|
|
||||||
final ScrollController _scrollController = ScrollController();
|
final ScrollController _scrollController = ScrollController();
|
||||||
bool _showActions = true;
|
bool _showActions = true;
|
||||||
@@ -62,11 +74,18 @@ class _HolidayScreenState extends State<HolidayScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _initializeData() async {
|
void _initializeData() async {
|
||||||
_leaveRequests = await _requestService.getLeaveRequests();
|
// Load from API
|
||||||
_advanceRequests = await _requestService.getAdvanceRequests();
|
_loadVacationsFromAPI();
|
||||||
|
_loadAdvancesFromAPI();
|
||||||
|
|
||||||
|
// Also listen to local changes (for newly created requests)
|
||||||
_requestService.leaveRequestsStream.listen((requests) {
|
_requestService.leaveRequestsStream.listen((requests) {
|
||||||
if (mounted) setState(() => _leaveRequests = requests);
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
// Merge with API data if needed
|
||||||
|
_leaveRequests = requests;
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
_requestService.advanceRequestsStream.listen((requests) {
|
_requestService.advanceRequestsStream.listen((requests) {
|
||||||
@@ -74,6 +93,140 @@ class _HolidayScreenState extends State<HolidayScreen> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _loadVacationsFromAPI() async {
|
||||||
|
setState(() {
|
||||||
|
_isLoadingVacations = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
final result = await _getVacationsUseCase();
|
||||||
|
result.fold(
|
||||||
|
(failure) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_isLoadingVacations = false;
|
||||||
|
});
|
||||||
|
// Load from local service as fallback
|
||||||
|
_requestService.getLeaveRequests().then((requests) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_leaveRequests = requests;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(response) {
|
||||||
|
if (mounted && response.data != null) {
|
||||||
|
setState(() {
|
||||||
|
_leaveRequests = response.data!.items
|
||||||
|
.map((vacation) => _convertVacationToLeaveRequest(vacation))
|
||||||
|
.toList();
|
||||||
|
_isLoadingVacations = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
LeaveRequest _convertVacationToLeaveRequest(VacationDataModel vacation) {
|
||||||
|
// Convert state (0=waiting, 1=approved, 2=denied) to status string
|
||||||
|
String status = "waiting";
|
||||||
|
if (vacation.state == 1) {
|
||||||
|
status = "approved";
|
||||||
|
} else if (vacation.state == 2) {
|
||||||
|
status = "denied";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert type to Arabic name
|
||||||
|
String leaveTypeName = _getArabicVacationTypeName(vacation.type);
|
||||||
|
|
||||||
|
// Check if it's timed leave (same day but different times)
|
||||||
|
bool isTimedLeave = vacation.startDate.year == vacation.endDate.year &&
|
||||||
|
vacation.startDate.month == vacation.endDate.month &&
|
||||||
|
vacation.startDate.day == vacation.endDate.day &&
|
||||||
|
vacation.startDate.hour != vacation.endDate.hour;
|
||||||
|
|
||||||
|
return LeaveRequest(
|
||||||
|
id: vacation.id,
|
||||||
|
leaveType: leaveTypeName,
|
||||||
|
isTimedLeave: isTimedLeave,
|
||||||
|
fromDate: vacation.startDate,
|
||||||
|
toDate: vacation.endDate,
|
||||||
|
fromTime: TimeOfDay.fromDateTime(vacation.startDate),
|
||||||
|
toTime: TimeOfDay.fromDateTime(vacation.endDate),
|
||||||
|
reason: vacation.reason,
|
||||||
|
requestDate: vacation.createdAt ?? vacation.startDate,
|
||||||
|
status: status,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getArabicVacationTypeName(int type) {
|
||||||
|
switch (type) {
|
||||||
|
case 1:
|
||||||
|
return 'أجازة زمنية';
|
||||||
|
case 2:
|
||||||
|
return 'إجازة مرضية';
|
||||||
|
case 3:
|
||||||
|
return 'إجازة مدفوعة';
|
||||||
|
case 4:
|
||||||
|
return 'إجازة غير مدفوعة';
|
||||||
|
default:
|
||||||
|
return 'إجازة';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadAdvancesFromAPI() async {
|
||||||
|
setState(() {
|
||||||
|
_isLoadingAdvances = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
final result = await _getAdvancesUseCase();
|
||||||
|
result.fold(
|
||||||
|
(failure) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_isLoadingAdvances = false;
|
||||||
|
});
|
||||||
|
// Load from local service as fallback
|
||||||
|
_requestService.getAdvanceRequests().then((requests) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_advanceRequests = requests;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(response) {
|
||||||
|
if (mounted && response.data != null) {
|
||||||
|
setState(() {
|
||||||
|
_advanceRequests = response.data!.items
|
||||||
|
.map((advance) => _convertAdvanceToAdvanceRequest(advance))
|
||||||
|
.toList();
|
||||||
|
_isLoadingAdvances = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
AdvanceRequest _convertAdvanceToAdvanceRequest(AdvanceDataModel advance) {
|
||||||
|
// Convert state (0=waiting, 1=approved, 2=denied) to status string
|
||||||
|
String status = "waiting";
|
||||||
|
if (advance.state == 1) {
|
||||||
|
status = "approved";
|
||||||
|
} else if (advance.state == 2) {
|
||||||
|
status = "denied";
|
||||||
|
}
|
||||||
|
|
||||||
|
return AdvanceRequest(
|
||||||
|
id: advance.id,
|
||||||
|
amount: advance.amount,
|
||||||
|
reason: advance.reason,
|
||||||
|
status: status,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Stack(
|
return Stack(
|
||||||
@@ -259,6 +412,19 @@ class _HolidayScreenState extends State<HolidayScreen> {
|
|||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
|
|
||||||
Widget _buildLeaveRequestsSliver() {
|
Widget _buildLeaveRequestsSliver() {
|
||||||
|
if (_isLoadingVacations) {
|
||||||
|
return SliverToBoxAdapter(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(40.0),
|
||||||
|
child: Center(
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
color: Color(0xFF8EFDC2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (_leaveRequests.isEmpty) {
|
if (_leaveRequests.isEmpty) {
|
||||||
return SliverToBoxAdapter(
|
return SliverToBoxAdapter(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@@ -287,6 +453,19 @@ class _HolidayScreenState extends State<HolidayScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildAdvanceRequestsSliver() {
|
Widget _buildAdvanceRequestsSliver() {
|
||||||
|
if (_isLoadingAdvances) {
|
||||||
|
return SliverToBoxAdapter(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(40.0),
|
||||||
|
child: Center(
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
color: Color(0xFF8EFDC2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (_advanceRequests.isEmpty) {
|
if (_advanceRequests.isEmpty) {
|
||||||
return SliverToBoxAdapter(
|
return SliverToBoxAdapter(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
|
|||||||
@@ -5,6 +5,11 @@ import '../widgets/settings_bar.dart';
|
|||||||
import '../widgets/onboarding_button.dart';
|
import '../widgets/onboarding_button.dart';
|
||||||
import '../../models/advance_request.dart';
|
import '../../models/advance_request.dart';
|
||||||
import '../../core/services/request_service.dart';
|
import '../../core/services/request_service.dart';
|
||||||
|
import '../../core/di/injection_container.dart';
|
||||||
|
import '../../data/datasources/user_local_data_source.dart';
|
||||||
|
import '../../domain/usecases/create_advance_usecase.dart';
|
||||||
|
import '../../domain/models/advance_request_model.dart';
|
||||||
|
import '../../core/error/failures.dart';
|
||||||
|
|
||||||
class RequestAdvanceScreen extends StatefulWidget {
|
class RequestAdvanceScreen extends StatefulWidget {
|
||||||
const RequestAdvanceScreen({super.key});
|
const RequestAdvanceScreen({super.key});
|
||||||
@@ -23,16 +28,30 @@ class _RequestAdvanceScreenState extends State<RequestAdvanceScreen> {
|
|||||||
// Use the singleton instance
|
// Use the singleton instance
|
||||||
final RequestService _requestService = RequestService();
|
final RequestService _requestService = RequestService();
|
||||||
|
|
||||||
|
// Use case
|
||||||
|
final CreateAdvanceUseCase _createAdvanceUseCase = sl<CreateAdvanceUseCase>();
|
||||||
|
|
||||||
|
String _getFailureMessage(Failure failure) {
|
||||||
|
if (failure is ServerFailure) {
|
||||||
|
return failure.message;
|
||||||
|
} else if (failure is NetworkFailure) {
|
||||||
|
return failure.message;
|
||||||
|
}
|
||||||
|
return 'حدث خطأ غير متوقع';
|
||||||
|
}
|
||||||
|
|
||||||
// Method to save the advance request
|
// Method to save the advance request
|
||||||
Future<void> _saveAdvanceRequest() async {
|
Future<void> _saveAdvanceRequest() async {
|
||||||
if (amountController.text.isEmpty || reasonController.text.isEmpty) {
|
if (amountController.text.isEmpty || reasonController.text.isEmpty) {
|
||||||
// Show an error message if fields are empty
|
// Show an error message if fields are empty
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
if (mounted) {
|
||||||
const SnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
content: Text('الرجاء إدخال جميع الحقول'),
|
const SnackBar(
|
||||||
backgroundColor: Colors.red,
|
content: Text('الرجاء إدخال جميع الحقول'),
|
||||||
),
|
backgroundColor: Colors.red,
|
||||||
);
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,45 +59,96 @@ class _RequestAdvanceScreenState extends State<RequestAdvanceScreen> {
|
|||||||
final amount = double.tryParse(amountController.text);
|
final amount = double.tryParse(amountController.text);
|
||||||
if (amount == null || amount <= 0) {
|
if (amount == null || amount <= 0) {
|
||||||
// Show an error message if amount is invalid
|
// Show an error message if amount is invalid
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
if (mounted) {
|
||||||
const SnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
content: Text('الرجاء إدخال مبلغ صحيح'),
|
const SnackBar(
|
||||||
backgroundColor: Colors.red,
|
content: Text('الرجاء إدخال مبلغ صحيح'),
|
||||||
),
|
backgroundColor: Colors.red,
|
||||||
);
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new advance request with default status "waiting"
|
// Get employee ID
|
||||||
final advanceRequest = AdvanceRequest(
|
final employeeId = await sl<UserLocalDataSource>().getCachedEmployeeId();
|
||||||
id: DateTime.now().millisecondsSinceEpoch.toString(),
|
if (employeeId == null) {
|
||||||
amount: amount,
|
if (mounted) {
|
||||||
reason: reasonController.text,
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
status: "waiting", // Default status
|
const SnackBar(
|
||||||
);
|
content: Text('خطأ: لم يتم العثور على رقم الموظف'),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show loading indicator
|
||||||
|
if (mounted) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (context) => const Center(child: CircularProgressIndicator()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Save the advance request
|
// Create advance request model
|
||||||
await _requestService.addAdvanceRequest(advanceRequest);
|
final advanceRequestModel = AdvanceRequestModel(
|
||||||
|
employeeId: employeeId,
|
||||||
// Show a success message
|
date: DateTime.now(),
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
amount: amount,
|
||||||
const SnackBar(
|
reason: reasonController.text,
|
||||||
content: Text('تم إرسال طلب السلفة بنجاح'),
|
|
||||||
backgroundColor: Colors.green,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Navigate back to the previous screen
|
final result = await _createAdvanceUseCase(advanceRequestModel);
|
||||||
Navigator.pop(context);
|
|
||||||
|
if (mounted) {
|
||||||
|
Navigator.pop(context); // Close loading dialog
|
||||||
|
|
||||||
|
result.fold(
|
||||||
|
(failure) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(_getFailureMessage(failure)),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
(response) {
|
||||||
|
// Also save locally for UI display
|
||||||
|
final advanceRequest = AdvanceRequest(
|
||||||
|
id: response.data?.id ?? DateTime.now().millisecondsSinceEpoch.toString(),
|
||||||
|
amount: amount,
|
||||||
|
reason: reasonController.text,
|
||||||
|
status: "waiting", // Default status
|
||||||
|
);
|
||||||
|
_requestService.addAdvanceRequest(advanceRequest);
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('تم إرسال طلب السلفة بنجاح'),
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Navigate back to the previous screen
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Show an error message if something went wrong
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
Navigator.pop(context); // Close loading dialog
|
||||||
SnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
content: Text('حدث خطأ: $e'),
|
SnackBar(
|
||||||
backgroundColor: Colors.red,
|
content: Text('حدث خطأ غير متوقع: $e'),
|
||||||
),
|
backgroundColor: Colors.red,
|
||||||
);
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user