chnages has been made for the interactive face camera
This commit is contained in:
@@ -26,6 +26,8 @@ abstract class AttendanceRemoteDataSource {
|
||||
Future<List<OvertimeDto>> getExtraHours({required String employeeId});
|
||||
Future<List<RewardDto>> getRewards({required String employeeId});
|
||||
Future<List<PunishmentDto>> getPunishments({required String employeeId});
|
||||
Future<AttendanceRecordDto?> getLastRecord({required String employeeId});
|
||||
Future<bool> hasActiveLogin({required String employeeId});
|
||||
Future<SalaryResponseDto> calculateSalary({
|
||||
required String employeeId,
|
||||
required int month,
|
||||
@@ -171,12 +173,10 @@ class AttendanceRemoteDataSourceImpl implements AttendanceRemoteDataSource {
|
||||
queryParameters: {'IsDeleted': false, 'EmployeeId': employeeId},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||
final data = response.data;
|
||||
if (data is Map<String, dynamic> &&
|
||||
data['data'] != null &&
|
||||
data['data']['items'] is List) {
|
||||
final items = data['data']['items'] as List;
|
||||
if (data is Map<String, dynamic>) {
|
||||
final items = (data['data'] ?? data['Data'])?['items'] as List? ?? [];
|
||||
return items.map((e) => AttendanceRecordDto.fromJson(e)).toList();
|
||||
}
|
||||
return [];
|
||||
@@ -187,15 +187,82 @@ class AttendanceRemoteDataSourceImpl implements AttendanceRemoteDataSource {
|
||||
);
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
throw ServerException(
|
||||
message: e.message ?? 'Unknown error',
|
||||
statusCode: e.response?.statusCode,
|
||||
);
|
||||
_handleDioError(e, 'فشل في جلب البيانات');
|
||||
rethrow; // Should not reach here due to _handleDioError throwing
|
||||
} catch (e) {
|
||||
if (e is ServerException || e is NetworkException) rethrow;
|
||||
throw ServerException(message: 'خطأ غير متوقع');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<AttendanceRecordDto?> getLastRecord({
|
||||
required String employeeId,
|
||||
}) async {
|
||||
try {
|
||||
final response = await apiClient.get(
|
||||
'/Attendance',
|
||||
queryParameters: {
|
||||
'IsDeleted': false,
|
||||
'EmployeeId': employeeId,
|
||||
'PageNumber': 1,
|
||||
'PageSize': 1,
|
||||
'SortBy': 'CreatedAt',
|
||||
'SortDescending': true,
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||
final data = response.data;
|
||||
if (data is Map<String, dynamic>) {
|
||||
final items = (data['data'] ?? data['Data'])?['items'] as List? ?? [];
|
||||
if (items.isNotEmpty) {
|
||||
return AttendanceRecordDto.fromJson(items.first);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
throw ServerException(
|
||||
message: 'فشل في جلب آخر سجل',
|
||||
statusCode: response.statusCode,
|
||||
);
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
_handleDioError(e, 'فشل في جلب آخر سجل');
|
||||
rethrow;
|
||||
} catch (e) {
|
||||
if (e is ServerException || e is NetworkException) rethrow;
|
||||
throw ServerException(message: 'خطأ غير متوقع');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> hasActiveLogin({required String employeeId}) async {
|
||||
try {
|
||||
final last = await getLastRecord(employeeId: employeeId);
|
||||
if (last == null) return false;
|
||||
|
||||
// The API enforces a DAILY check ("Employee already logged in today")
|
||||
// So we check: has a login TODAY, regardless of logout status
|
||||
if (last.login != null) {
|
||||
final now = DateTime.now();
|
||||
final loginDate = last.login!;
|
||||
if (loginDate.year == now.year &&
|
||||
loginDate.month == now.month &&
|
||||
loginDate.day == now.day) {
|
||||
// Logged in today — if no logout, definitely active
|
||||
// If logout exists, they already completed today's cycle
|
||||
return last.logout == null;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch (e) {
|
||||
// If the check fails, let the user try — the API will reject if needed
|
||||
print('hasActiveLogin check failed: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<OvertimeDto>> getExtraHours({required String employeeId}) async {
|
||||
try {
|
||||
@@ -204,7 +271,7 @@ class AttendanceRemoteDataSourceImpl implements AttendanceRemoteDataSource {
|
||||
queryParameters: {'IsDeleted': false, 'EmployeeId': employeeId},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||
final responseData = response.data;
|
||||
|
||||
if (responseData is Map<String, dynamic>) {
|
||||
@@ -222,11 +289,10 @@ class AttendanceRemoteDataSourceImpl implements AttendanceRemoteDataSource {
|
||||
);
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
throw ServerException(
|
||||
message: e.message ?? 'Unknown error',
|
||||
statusCode: e.response?.statusCode,
|
||||
);
|
||||
_handleDioError(e, 'فشل في جلب البيانات');
|
||||
rethrow;
|
||||
} catch (e) {
|
||||
if (e is ServerException || e is NetworkException) rethrow;
|
||||
throw ServerException(message: 'خطأ غير متوقع');
|
||||
}
|
||||
}
|
||||
@@ -239,7 +305,7 @@ class AttendanceRemoteDataSourceImpl implements AttendanceRemoteDataSource {
|
||||
queryParameters: {'IsDeleted': false, 'EmployeeId': employeeId},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||
final responseData = response.data;
|
||||
if (responseData is Map<String, dynamic>) {
|
||||
return RewardListResponseDto.fromJson(responseData).items;
|
||||
@@ -252,11 +318,10 @@ class AttendanceRemoteDataSourceImpl implements AttendanceRemoteDataSource {
|
||||
);
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
throw ServerException(
|
||||
message: e.message ?? 'Unknown error',
|
||||
statusCode: e.response?.statusCode,
|
||||
);
|
||||
_handleDioError(e, 'فشل في جلب المكافآت');
|
||||
rethrow;
|
||||
} catch (e) {
|
||||
if (e is ServerException || e is NetworkException) rethrow;
|
||||
throw ServerException(message: 'خطأ غير متوقع');
|
||||
}
|
||||
}
|
||||
@@ -271,7 +336,7 @@ class AttendanceRemoteDataSourceImpl implements AttendanceRemoteDataSource {
|
||||
queryParameters: {'IsDeleted': false, 'EmployeeId': employeeId},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||
final responseData = response.data;
|
||||
if (responseData is Map<String, dynamic>) {
|
||||
return PunishmentListResponseDto.fromJson(responseData).items;
|
||||
@@ -284,11 +349,10 @@ class AttendanceRemoteDataSourceImpl implements AttendanceRemoteDataSource {
|
||||
);
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
throw ServerException(
|
||||
message: e.message ?? 'Unknown error',
|
||||
statusCode: e.response?.statusCode,
|
||||
);
|
||||
_handleDioError(e, 'فشل في جلب البيانات');
|
||||
rethrow;
|
||||
} catch (e) {
|
||||
if (e is ServerException || e is NetworkException) rethrow;
|
||||
throw ServerException(message: 'خطأ غير متوقع');
|
||||
}
|
||||
}
|
||||
@@ -309,17 +373,12 @@ class AttendanceRemoteDataSourceImpl implements AttendanceRemoteDataSource {
|
||||
},
|
||||
);
|
||||
|
||||
print('Salary Response Status: ${response.statusCode}');
|
||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||
final responseData = response.data;
|
||||
print(
|
||||
'Salary Response Data: $responseData (${responseData.runtimeType})',
|
||||
);
|
||||
|
||||
if (responseData is Map<String, dynamic>) {
|
||||
return SalaryResponseDto.fromJson(responseData);
|
||||
} else if (responseData is num) {
|
||||
// Handle case where API returns raw number
|
||||
return SalaryResponseDto(
|
||||
isSuccess: true,
|
||||
message: 'Success',
|
||||
@@ -327,7 +386,6 @@ class AttendanceRemoteDataSourceImpl implements AttendanceRemoteDataSource {
|
||||
);
|
||||
} else if (responseData is String &&
|
||||
double.tryParse(responseData) != null) {
|
||||
// Handle case where API returns raw numeric string
|
||||
return SalaryResponseDto(
|
||||
isSuccess: true,
|
||||
message: 'Success',
|
||||
@@ -335,23 +393,49 @@ class AttendanceRemoteDataSourceImpl implements AttendanceRemoteDataSource {
|
||||
);
|
||||
} else {
|
||||
throw ServerException(
|
||||
message: 'استجابة غير صحيحة من الخادم: $responseData',
|
||||
message: 'استجابة غير صحيحة من الخادم',
|
||||
statusCode: response.statusCode,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw ServerException(
|
||||
message: 'فشل في حساب الراتب (Status: ${response.statusCode})',
|
||||
message: 'فشل في حساب الراتب',
|
||||
statusCode: response.statusCode,
|
||||
);
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
throw ServerException(
|
||||
message: e.message ?? 'Unknown error',
|
||||
statusCode: e.response?.statusCode,
|
||||
);
|
||||
_handleDioError(e, 'فشل في حساب الراتب');
|
||||
rethrow;
|
||||
} catch (e) {
|
||||
if (e is ServerException || e is NetworkException) rethrow;
|
||||
throw ServerException(message: 'خطأ غير متوقع');
|
||||
}
|
||||
}
|
||||
|
||||
void _handleDioError(DioException e, String defaultMessage) {
|
||||
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 data = e.response?.data;
|
||||
String? message;
|
||||
|
||||
if (data is Map<String, dynamic>) {
|
||||
message = data['message']?.toString() ?? data['error']?.toString();
|
||||
} else if (data is String) {
|
||||
message = data;
|
||||
}
|
||||
|
||||
throw ServerException(
|
||||
message: message ?? defaultMessage,
|
||||
statusCode: e.response?.statusCode,
|
||||
);
|
||||
} else {
|
||||
throw NetworkException(message: 'خطأ في الانترنيت يرجى المحاولة لاحقا');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +133,34 @@ class AttendanceRepositoryImpl implements AttendanceRepository {
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<AttendanceModel?> getLastRecord({required String employeeId}) async {
|
||||
final dto = await remoteDataSource.getLastRecord(employeeId: employeeId);
|
||||
if (dto == null) return null;
|
||||
|
||||
int? hours;
|
||||
if (dto.login != null && dto.logout != null) {
|
||||
hours = dto.logout!.difference(dto.login!).inHours;
|
||||
}
|
||||
|
||||
return AttendanceModel(
|
||||
id: dto.id,
|
||||
employeeId: dto.employeeId,
|
||||
date: dto.createdAt ?? dto.login,
|
||||
loginTime: dto.login,
|
||||
logoutTime: dto.logout,
|
||||
workHours: hours,
|
||||
createdAt: dto.createdAt ?? dto.login,
|
||||
reason: dto.reason,
|
||||
isDeleted: dto.isDeleted,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> hasActiveLogin({required String employeeId}) async {
|
||||
return remoteDataSource.hasActiveLogin(employeeId: employeeId);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<SalaryModel> calculateSalary({
|
||||
required String employeeId,
|
||||
|
||||
Reference in New Issue
Block a user