refactor: Repository 패턴 적용 및 Clean Architecture 완성
## 주요 변경사항 ### 🏗️ Architecture - Repository 패턴 전면 도입 (인터페이스/구현체 분리) - Domain Layer에 Repository 인터페이스 정의 - Data Layer에 Repository 구현체 배치 - UseCase 의존성을 Service에서 Repository로 전환 ### 📦 Dependency Injection - GetIt 기반 DI Container 재구성 (lib/injection_container.dart) - Repository 인터페이스와 구현체 등록 - Service와 Repository 공존 (마이그레이션 기간) ### 🔄 Migration Status 완료: - License 모듈 (6개 UseCase) - Warehouse Location 모듈 (5개 UseCase) 진행중: - Auth 모듈 (2/5 UseCase) - Company 모듈 (1/6 UseCase) 대기: - User 모듈 (7개 UseCase) - Equipment 모듈 (4개 UseCase) ### 🎯 Controller 통합 - 중복 Controller 제거 (with_usecase 버전) - 단일 Controller로 통합 - UseCase 패턴 직접 적용 ### 🧹 코드 정리 - 임시 파일 제거 (test_*.md, task.md) - Node.js 아티팩트 제거 (package.json) - 불필요한 테스트 파일 정리 ### ✅ 테스트 개선 - Real API 중심 테스트 구조 - Mock 제거, 실제 API 엔드포인트 사용 - 통합 테스트 프레임워크 강화 ## 기술적 영향 - 의존성 역전 원칙 적용 - 레이어 간 결합도 감소 - 테스트 용이성 향상 - 확장성 및 유지보수성 개선 ## 다음 단계 1. User/Equipment 모듈 Repository 마이그레이션 2. Service Layer 점진적 제거 3. 캐싱 전략 구현 4. 성능 최적화
This commit is contained in:
@@ -26,7 +26,7 @@ abstract class BaseListController<T> extends ChangeNotifier {
|
||||
int _currentPage = 1;
|
||||
|
||||
/// 페이지당 아이템 수
|
||||
int _pageSize = 20;
|
||||
int _pageSize = 10;
|
||||
|
||||
/// 더 많은 데이터가 있는지 여부
|
||||
bool _hasMore = true;
|
||||
@@ -39,6 +39,12 @@ abstract class BaseListController<T> extends ChangeNotifier {
|
||||
|
||||
BaseListController();
|
||||
|
||||
/// 컨트롤러 초기화 (페이지 크기 설정 및 데이터 로드)
|
||||
Future<void> initialize({int pageSize = 10}) async {
|
||||
_pageSize = pageSize;
|
||||
await loadData(isRefresh: true);
|
||||
}
|
||||
|
||||
// Getters
|
||||
List<T> get items => _filteredItems;
|
||||
bool get isLoading => _isLoading;
|
||||
@@ -84,13 +90,15 @@ abstract class BaseListController<T> extends ChangeNotifier {
|
||||
Future<void> loadData({
|
||||
bool isRefresh = false,
|
||||
Map<String, dynamic>? additionalFilters,
|
||||
int? targetPage, // 특정 페이지를 지정할 수 있도록 추가
|
||||
}) async {
|
||||
if (_isLoading) return;
|
||||
|
||||
if (isRefresh) {
|
||||
_currentPage = 1;
|
||||
_items.clear();
|
||||
_filteredItems.clear();
|
||||
_currentPage = targetPage ?? 1; // targetPage가 있으면 사용, 없으면 1
|
||||
// Freezed 객체의 unmodifiable list 문제 해결: clear() 대신 새 리스트 할당
|
||||
_items = [];
|
||||
_filteredItems = [];
|
||||
_hasMore = true;
|
||||
}
|
||||
|
||||
@@ -113,21 +121,20 @@ abstract class BaseListController<T> extends ChangeNotifier {
|
||||
);
|
||||
|
||||
if (isRefresh) {
|
||||
_items = result.items;
|
||||
// Freezed 객체의 unmodifiable list를 mutable list로 변환
|
||||
_items = List<T>.from(result.items);
|
||||
} else {
|
||||
_items.addAll(result.items);
|
||||
// 기존 리스트에 추가할 때도 새 리스트 생성
|
||||
_items = List<T>.from(_items)..addAll(result.items);
|
||||
}
|
||||
|
||||
// 메타데이터 업데이트
|
||||
// 메타데이터 업데이트 - 서버 응답의 페이지 번호로 동기화
|
||||
_total = result.meta.total;
|
||||
_totalPages = result.meta.totalPages;
|
||||
_hasMore = result.meta.hasNext;
|
||||
_currentPage = result.meta.currentPage; // 서버 응답의 페이지 번호로 동기화
|
||||
|
||||
_applyFiltering();
|
||||
|
||||
if (!isRefresh && result.items.isNotEmpty) {
|
||||
_currentPage++;
|
||||
}
|
||||
} catch (e) {
|
||||
if (e is AppFailure) {
|
||||
_error = ErrorHandler.getUserFriendlyMessage(e);
|
||||
@@ -144,15 +151,14 @@ abstract class BaseListController<T> extends ChangeNotifier {
|
||||
/// 다음 페이지 로드
|
||||
Future<void> loadNextPage() async {
|
||||
if (!_hasMore || _isLoading) return;
|
||||
_currentPage++; // 다음 페이지로 증가
|
||||
await loadData(isRefresh: false);
|
||||
}
|
||||
|
||||
/// 검색
|
||||
/// 검색 (서버 검색과 연동)
|
||||
void search(String query) {
|
||||
_searchQuery = query;
|
||||
_currentPage = 1;
|
||||
_applyFiltering();
|
||||
notifyListeners();
|
||||
loadData(isRefresh: true); // 서버 검색 수행
|
||||
}
|
||||
|
||||
/// 특정 페이지로 이동
|
||||
@@ -160,14 +166,13 @@ abstract class BaseListController<T> extends ChangeNotifier {
|
||||
if (page < 1 || page > _totalPages && _totalPages > 0) return;
|
||||
if (page == _currentPage) return;
|
||||
|
||||
_currentPage = page;
|
||||
loadData(isRefresh: true);
|
||||
loadData(isRefresh: true, targetPage: page);
|
||||
}
|
||||
|
||||
/// 필터링 적용
|
||||
void _applyFiltering() {
|
||||
if (_searchQuery.isEmpty) {
|
||||
_filteredItems = List.from(_items);
|
||||
_filteredItems = List<T>.from(_items);
|
||||
} else {
|
||||
_filteredItems = _items.where((item) => filterItem(item, _searchQuery)).toList();
|
||||
}
|
||||
|
||||
111
lib/core/storage/secure_storage.dart
Normal file
111
lib/core/storage/secure_storage.dart
Normal file
@@ -0,0 +1,111 @@
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
|
||||
/// 안전한 저장소 관리 클래스
|
||||
class SecureStorage {
|
||||
static const _storage = FlutterSecureStorage();
|
||||
|
||||
// 키 상수
|
||||
static const String _accessTokenKey = 'access_token';
|
||||
static const String _refreshTokenKey = 'refresh_token';
|
||||
static const String _userIdKey = 'user_id';
|
||||
static const String _userEmailKey = 'user_email';
|
||||
static const String _userNameKey = 'user_name';
|
||||
static const String _userRoleKey = 'user_role';
|
||||
|
||||
// 토큰 관련
|
||||
Future<void> saveAccessToken(String token) async {
|
||||
await _storage.write(key: _accessTokenKey, value: token);
|
||||
}
|
||||
|
||||
Future<String?> getAccessToken() async {
|
||||
return await _storage.read(key: _accessTokenKey);
|
||||
}
|
||||
|
||||
Future<void> saveRefreshToken(String token) async {
|
||||
await _storage.write(key: _refreshTokenKey, value: token);
|
||||
}
|
||||
|
||||
Future<String?> getRefreshToken() async {
|
||||
return await _storage.read(key: _refreshTokenKey);
|
||||
}
|
||||
|
||||
Future<void> saveTokens({
|
||||
required String accessToken,
|
||||
String? refreshToken,
|
||||
}) async {
|
||||
await saveAccessToken(accessToken);
|
||||
if (refreshToken != null) {
|
||||
await saveRefreshToken(refreshToken);
|
||||
}
|
||||
}
|
||||
|
||||
// 사용자 정보 관련
|
||||
Future<void> saveUserId(String userId) async {
|
||||
await _storage.write(key: _userIdKey, value: userId);
|
||||
}
|
||||
|
||||
Future<String?> getUserId() async {
|
||||
return await _storage.read(key: _userIdKey);
|
||||
}
|
||||
|
||||
Future<void> saveUserEmail(String email) async {
|
||||
await _storage.write(key: _userEmailKey, value: email);
|
||||
}
|
||||
|
||||
Future<String?> getUserEmail() async {
|
||||
return await _storage.read(key: _userEmailKey);
|
||||
}
|
||||
|
||||
Future<void> saveUserName(String name) async {
|
||||
await _storage.write(key: _userNameKey, value: name);
|
||||
}
|
||||
|
||||
Future<String?> getUserName() async {
|
||||
return await _storage.read(key: _userNameKey);
|
||||
}
|
||||
|
||||
Future<void> saveUserRole(String role) async {
|
||||
await _storage.write(key: _userRoleKey, value: role);
|
||||
}
|
||||
|
||||
Future<String?> getUserRole() async {
|
||||
return await _storage.read(key: _userRoleKey);
|
||||
}
|
||||
|
||||
Future<void> saveUserInfo({
|
||||
required String userId,
|
||||
required String email,
|
||||
required String name,
|
||||
required String role,
|
||||
}) async {
|
||||
await saveUserId(userId);
|
||||
await saveUserEmail(email);
|
||||
await saveUserName(name);
|
||||
await saveUserRole(role);
|
||||
}
|
||||
|
||||
// 로그인 여부 확인
|
||||
Future<bool> isLoggedIn() async {
|
||||
final token = await getAccessToken();
|
||||
return token != null && token.isNotEmpty;
|
||||
}
|
||||
|
||||
// 전체 삭제
|
||||
Future<void> clearAll() async {
|
||||
await _storage.deleteAll();
|
||||
}
|
||||
|
||||
// 토큰만 삭제
|
||||
Future<void> clearTokens() async {
|
||||
await _storage.delete(key: _accessTokenKey);
|
||||
await _storage.delete(key: _refreshTokenKey);
|
||||
}
|
||||
|
||||
// 사용자 정보만 삭제
|
||||
Future<void> clearUserInfo() async {
|
||||
await _storage.delete(key: _userIdKey);
|
||||
await _storage.delete(key: _userEmailKey);
|
||||
await _storage.delete(key: _userNameKey);
|
||||
await _storage.delete(key: _userRoleKey);
|
||||
}
|
||||
}
|
||||
100
lib/data/datasources/interceptors/api_interceptor.dart
Normal file
100
lib/data/datasources/interceptors/api_interceptor.dart
Normal file
@@ -0,0 +1,100 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../../../core/storage/secure_storage.dart';
|
||||
|
||||
/// API 요청/응답 인터셉터
|
||||
class ApiInterceptor extends Interceptor {
|
||||
final SecureStorage _storage;
|
||||
|
||||
ApiInterceptor(this._storage);
|
||||
|
||||
@override
|
||||
void onRequest(
|
||||
RequestOptions options,
|
||||
RequestInterceptorHandler handler,
|
||||
) async {
|
||||
// 토큰 추가
|
||||
final token = await _storage.getAccessToken();
|
||||
if (token != null) {
|
||||
options.headers['Authorization'] = 'Bearer $token';
|
||||
}
|
||||
|
||||
// Content-Type 헤더 추가
|
||||
if (options.data != null && options.data is! FormData) {
|
||||
options.headers['Content-Type'] = 'application/json';
|
||||
}
|
||||
|
||||
// 디버그 모드에서 요청 로깅
|
||||
if (kDebugMode) {
|
||||
print('🚀 API Request: ${options.method} ${options.path}');
|
||||
if (options.queryParameters.isNotEmpty) {
|
||||
print(' Query: ${options.queryParameters}');
|
||||
}
|
||||
if (options.data != null) {
|
||||
print(' Body: ${options.data}');
|
||||
}
|
||||
}
|
||||
|
||||
handler.next(options);
|
||||
}
|
||||
|
||||
@override
|
||||
void onResponse(
|
||||
Response response,
|
||||
ResponseInterceptorHandler handler,
|
||||
) {
|
||||
// 디버그 모드에서 응답 로깅
|
||||
if (kDebugMode) {
|
||||
print('✅ API Response: ${response.statusCode} ${response.requestOptions.path}');
|
||||
if (response.data != null) {
|
||||
print(' Data: ${response.data}');
|
||||
}
|
||||
}
|
||||
|
||||
handler.next(response);
|
||||
}
|
||||
|
||||
@override
|
||||
void onError(
|
||||
DioException err,
|
||||
ErrorInterceptorHandler handler,
|
||||
) async {
|
||||
// 디버그 모드에서 에러 로깅
|
||||
if (kDebugMode) {
|
||||
print('❌ API Error: ${err.requestOptions.path}');
|
||||
print(' Error Type: ${err.type}');
|
||||
print(' Message: ${err.message}');
|
||||
if (err.response != null) {
|
||||
print(' Status: ${err.response?.statusCode}');
|
||||
print(' Data: ${err.response?.data}');
|
||||
}
|
||||
}
|
||||
|
||||
// 401 Unauthorized 처리
|
||||
if (err.response?.statusCode == 401) {
|
||||
// 토큰 갱신 시도
|
||||
final refreshToken = await _storage.getRefreshToken();
|
||||
if (refreshToken != null) {
|
||||
try {
|
||||
// 토큰 갱신 로직 (필요시 구현)
|
||||
// final newToken = await _refreshToken(refreshToken);
|
||||
// await _storage.saveTokens(newToken);
|
||||
|
||||
// 원래 요청 재시도
|
||||
// final options = err.requestOptions;
|
||||
// options.headers['Authorization'] = 'Bearer $newToken';
|
||||
// final response = await dio.fetch(options);
|
||||
// return handler.resolve(response);
|
||||
} catch (e) {
|
||||
// 토큰 갱신 실패 시 로그아웃 처리
|
||||
await _storage.clearAll();
|
||||
}
|
||||
} else {
|
||||
// 리프레시 토큰이 없으면 로그아웃 처리
|
||||
await _storage.clearAll();
|
||||
}
|
||||
}
|
||||
|
||||
handler.next(err);
|
||||
}
|
||||
}
|
||||
@@ -88,13 +88,13 @@ class LoggingInterceptor extends Interceptor {
|
||||
debugPrint('║ $line');
|
||||
});
|
||||
debugPrint('║ ... (${lines.length - 50} lines omitted) ...');
|
||||
lines.skip(lines.length - 25).forEach((line) {
|
||||
for (final line in lines.skip(lines.length - 25)) {
|
||||
debugPrint('║ $line');
|
||||
});
|
||||
}
|
||||
} else {
|
||||
lines.forEach((line) {
|
||||
for (final line in lines) {
|
||||
debugPrint('║ $line');
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('║ ${response.data}');
|
||||
|
||||
@@ -4,11 +4,37 @@ import 'package:superport/core/errors/exceptions.dart';
|
||||
import 'package:superport/data/datasources/remote/api_client.dart';
|
||||
import 'package:superport/data/models/user/user_dto.dart';
|
||||
|
||||
@lazySingleton
|
||||
class UserRemoteDataSource {
|
||||
abstract class UserRemoteDataSource {
|
||||
Future<UserListDto> getUsers({
|
||||
int page = 1,
|
||||
int perPage = 20,
|
||||
bool? isActive,
|
||||
int? companyId,
|
||||
String? role,
|
||||
});
|
||||
|
||||
Future<UserDto> getUser(int id);
|
||||
Future<UserDto> createUser(CreateUserRequest request);
|
||||
Future<UserDto> updateUser(int id, UpdateUserRequest request);
|
||||
Future<void> deleteUser(int id);
|
||||
Future<UserDto> changeUserStatus(int id, ChangeStatusRequest request);
|
||||
Future<void> changePassword(int id, ChangePasswordRequest request);
|
||||
Future<bool> checkDuplicateUsername(String username);
|
||||
Future<UserListDto> searchUsers({
|
||||
required String query,
|
||||
int? companyId,
|
||||
String? status,
|
||||
String? permissionLevel,
|
||||
int page = 1,
|
||||
int perPage = 20,
|
||||
});
|
||||
}
|
||||
|
||||
@LazySingleton(as: UserRemoteDataSource)
|
||||
class UserRemoteDataSourceImpl implements UserRemoteDataSource {
|
||||
final ApiClient _apiClient;
|
||||
|
||||
UserRemoteDataSource() : _apiClient = ApiClient();
|
||||
UserRemoteDataSourceImpl(this._apiClient);
|
||||
|
||||
/// 사용자 목록 조회
|
||||
Future<UserListDto> getUsers({
|
||||
@@ -40,7 +66,7 @@ class UserRemoteDataSource {
|
||||
// role이 null인 경우 기본값 설정
|
||||
final users = data.map((json) {
|
||||
if (json['role'] == null) {
|
||||
json['role'] = 'staff'; // 기본값
|
||||
json['role'] = 'member'; // 기본값
|
||||
}
|
||||
return UserDto.fromJson(json);
|
||||
}).toList();
|
||||
|
||||
@@ -23,6 +23,11 @@ abstract class WarehouseLocationRemoteDataSource {
|
||||
int page = 1,
|
||||
int perPage = 20,
|
||||
});
|
||||
|
||||
// Repository에서 사용하는 추가 메서드들
|
||||
Future<void> updateWarehouseLocationStatus(int id, bool isActive);
|
||||
Future<bool> checkWarehouseHasEquipment(int id);
|
||||
Future<bool> checkDuplicateWarehouseName(String name);
|
||||
}
|
||||
|
||||
@LazySingleton(as: WarehouseLocationRemoteDataSource)
|
||||
@@ -266,6 +271,56 @@ class WarehouseLocationRemoteDataSourceImpl implements WarehouseLocationRemoteDa
|
||||
}
|
||||
}
|
||||
|
||||
// Repository에서 사용하는 추가 메서드들 구현
|
||||
@override
|
||||
Future<void> updateWarehouseLocationStatus(int id, bool isActive) async {
|
||||
try {
|
||||
await _apiClient.patch(
|
||||
'${ApiEndpoints.warehouseLocations}/$id/status',
|
||||
data: {'is_active': isActive},
|
||||
);
|
||||
} catch (e) {
|
||||
throw _handleError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> checkWarehouseHasEquipment(int id) async {
|
||||
try {
|
||||
final response = await _apiClient.get(
|
||||
'${ApiEndpoints.warehouseLocations}/$id/has-equipment',
|
||||
);
|
||||
|
||||
if (response.data != null && response.data['success'] == true) {
|
||||
return response.data['data']['has_equipment'] ?? false;
|
||||
}
|
||||
return false;
|
||||
} catch (e) {
|
||||
// 오류 시 기본값 false 반환
|
||||
debugPrint('📦 창고 장비 보유 여부 확인 중 오류: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> checkDuplicateWarehouseName(String name) async {
|
||||
try {
|
||||
final response = await _apiClient.get(
|
||||
'${ApiEndpoints.warehouseLocations}/check-duplicate',
|
||||
queryParameters: {'name': name},
|
||||
);
|
||||
|
||||
if (response.data != null && response.data['success'] == true) {
|
||||
return response.data['data']['is_duplicate'] ?? false;
|
||||
}
|
||||
return false;
|
||||
} catch (e) {
|
||||
// 오류 시 기본값 false 반환 (중복이 아니라고 가정)
|
||||
debugPrint('📦 중복 창고명 확인 중 오류: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Exception _handleError(dynamic error) {
|
||||
if (error is ApiException) {
|
||||
return error;
|
||||
|
||||
@@ -8,8 +8,8 @@ enum UserRole {
|
||||
admin,
|
||||
@JsonValue('manager')
|
||||
manager,
|
||||
@JsonValue('staff')
|
||||
staff,
|
||||
@JsonValue('member')
|
||||
member,
|
||||
}
|
||||
|
||||
@freezed
|
||||
@@ -17,13 +17,16 @@ class UserDto with _$UserDto {
|
||||
const factory UserDto({
|
||||
required int id,
|
||||
required String username,
|
||||
required String email,
|
||||
required String name,
|
||||
String? email,
|
||||
String? phone,
|
||||
required String role,
|
||||
@JsonKey(name: 'company_id') int? companyId,
|
||||
@JsonKey(name: 'company_name') String? companyName,
|
||||
@JsonKey(name: 'branch_id') int? branchId,
|
||||
@JsonKey(name: 'branch_name') String? branchName,
|
||||
@JsonKey(name: 'is_active') required bool isActive,
|
||||
@JsonKey(name: 'last_login_at') DateTime? lastLoginAt,
|
||||
@JsonKey(name: 'created_at') required DateTime createdAt,
|
||||
@JsonKey(name: 'updated_at') required DateTime updatedAt,
|
||||
}) = _UserDto;
|
||||
@@ -36,7 +39,7 @@ class UserDto with _$UserDto {
|
||||
class CreateUserRequest with _$CreateUserRequest {
|
||||
const factory CreateUserRequest({
|
||||
required String username,
|
||||
required String email,
|
||||
String? email,
|
||||
required String password,
|
||||
required String name,
|
||||
String? phone,
|
||||
@@ -59,6 +62,7 @@ class UpdateUserRequest with _$UpdateUserRequest {
|
||||
String? role,
|
||||
@JsonKey(name: 'company_id') int? companyId,
|
||||
@JsonKey(name: 'branch_id') int? branchId,
|
||||
@JsonKey(name: 'is_active') bool? isActive,
|
||||
}) = _UpdateUserRequest;
|
||||
|
||||
factory UpdateUserRequest.fromJson(Map<String, dynamic> json) =>
|
||||
@@ -88,6 +92,8 @@ class ChangePasswordRequest with _$ChangePasswordRequest {
|
||||
|
||||
@freezed
|
||||
class UserListDto with _$UserListDto {
|
||||
const UserListDto._();
|
||||
|
||||
const factory UserListDto({
|
||||
required List<UserDto> users,
|
||||
required int total,
|
||||
@@ -96,7 +102,35 @@ class UserListDto with _$UserListDto {
|
||||
@JsonKey(name: 'total_pages') required int totalPages,
|
||||
}) = _UserListDto;
|
||||
|
||||
// 페이지네이션 응답과 호환성을 위한 getter들
|
||||
List<UserDto> get items => users;
|
||||
int get size => perPage;
|
||||
int get totalElements => total;
|
||||
bool get first => page <= 1;
|
||||
bool get last => page >= totalPages;
|
||||
|
||||
factory UserListDto.fromJson(Map<String, dynamic> json) =>
|
||||
_$UserListDtoFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class UserDetailDto with _$UserDetailDto {
|
||||
const factory UserDetailDto({
|
||||
required UserDto user,
|
||||
}) = _UserDetailDto;
|
||||
|
||||
factory UserDetailDto.fromJson(Map<String, dynamic> json) =>
|
||||
_$UserDetailDtoFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class UserResponse with _$UserResponse {
|
||||
const factory UserResponse({
|
||||
required UserDto user,
|
||||
String? message,
|
||||
}) = _UserResponse;
|
||||
|
||||
factory UserResponse.fromJson(Map<String, dynamic> json) =>
|
||||
_$UserResponseFromJson(json);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,16 +22,22 @@ UserDto _$UserDtoFromJson(Map<String, dynamic> json) {
|
||||
mixin _$UserDto {
|
||||
int get id => throw _privateConstructorUsedError;
|
||||
String get username => throw _privateConstructorUsedError;
|
||||
String get email => throw _privateConstructorUsedError;
|
||||
String get name => throw _privateConstructorUsedError;
|
||||
String? get email => throw _privateConstructorUsedError;
|
||||
String? get phone => throw _privateConstructorUsedError;
|
||||
String get role => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'company_id')
|
||||
int? get companyId => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'company_name')
|
||||
String? get companyName => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'branch_id')
|
||||
int? get branchId => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'branch_name')
|
||||
String? get branchName => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'is_active')
|
||||
bool get isActive => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'last_login_at')
|
||||
DateTime? get lastLoginAt => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'created_at')
|
||||
DateTime get createdAt => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'updated_at')
|
||||
@@ -54,13 +60,16 @@ abstract class $UserDtoCopyWith<$Res> {
|
||||
$Res call(
|
||||
{int id,
|
||||
String username,
|
||||
String email,
|
||||
String name,
|
||||
String? email,
|
||||
String? phone,
|
||||
String role,
|
||||
@JsonKey(name: 'company_id') int? companyId,
|
||||
@JsonKey(name: 'company_name') String? companyName,
|
||||
@JsonKey(name: 'branch_id') int? branchId,
|
||||
@JsonKey(name: 'branch_name') String? branchName,
|
||||
@JsonKey(name: 'is_active') bool isActive,
|
||||
@JsonKey(name: 'last_login_at') DateTime? lastLoginAt,
|
||||
@JsonKey(name: 'created_at') DateTime createdAt,
|
||||
@JsonKey(name: 'updated_at') DateTime updatedAt});
|
||||
}
|
||||
@@ -82,13 +91,16 @@ class _$UserDtoCopyWithImpl<$Res, $Val extends UserDto>
|
||||
$Res call({
|
||||
Object? id = null,
|
||||
Object? username = null,
|
||||
Object? email = null,
|
||||
Object? name = null,
|
||||
Object? email = freezed,
|
||||
Object? phone = freezed,
|
||||
Object? role = null,
|
||||
Object? companyId = freezed,
|
||||
Object? companyName = freezed,
|
||||
Object? branchId = freezed,
|
||||
Object? branchName = freezed,
|
||||
Object? isActive = null,
|
||||
Object? lastLoginAt = freezed,
|
||||
Object? createdAt = null,
|
||||
Object? updatedAt = null,
|
||||
}) {
|
||||
@@ -101,14 +113,14 @@ class _$UserDtoCopyWithImpl<$Res, $Val extends UserDto>
|
||||
? _value.username
|
||||
: username // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
email: null == email
|
||||
? _value.email
|
||||
: email // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
email: freezed == email
|
||||
? _value.email
|
||||
: email // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
phone: freezed == phone
|
||||
? _value.phone
|
||||
: phone // ignore: cast_nullable_to_non_nullable
|
||||
@@ -121,14 +133,26 @@ class _$UserDtoCopyWithImpl<$Res, $Val extends UserDto>
|
||||
? _value.companyId
|
||||
: companyId // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
companyName: freezed == companyName
|
||||
? _value.companyName
|
||||
: companyName // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
branchId: freezed == branchId
|
||||
? _value.branchId
|
||||
: branchId // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
branchName: freezed == branchName
|
||||
? _value.branchName
|
||||
: branchName // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
isActive: null == isActive
|
||||
? _value.isActive
|
||||
: isActive // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
lastLoginAt: freezed == lastLoginAt
|
||||
? _value.lastLoginAt
|
||||
: lastLoginAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
createdAt: null == createdAt
|
||||
? _value.createdAt
|
||||
: createdAt // ignore: cast_nullable_to_non_nullable
|
||||
@@ -151,13 +175,16 @@ abstract class _$$UserDtoImplCopyWith<$Res> implements $UserDtoCopyWith<$Res> {
|
||||
$Res call(
|
||||
{int id,
|
||||
String username,
|
||||
String email,
|
||||
String name,
|
||||
String? email,
|
||||
String? phone,
|
||||
String role,
|
||||
@JsonKey(name: 'company_id') int? companyId,
|
||||
@JsonKey(name: 'company_name') String? companyName,
|
||||
@JsonKey(name: 'branch_id') int? branchId,
|
||||
@JsonKey(name: 'branch_name') String? branchName,
|
||||
@JsonKey(name: 'is_active') bool isActive,
|
||||
@JsonKey(name: 'last_login_at') DateTime? lastLoginAt,
|
||||
@JsonKey(name: 'created_at') DateTime createdAt,
|
||||
@JsonKey(name: 'updated_at') DateTime updatedAt});
|
||||
}
|
||||
@@ -177,13 +204,16 @@ class __$$UserDtoImplCopyWithImpl<$Res>
|
||||
$Res call({
|
||||
Object? id = null,
|
||||
Object? username = null,
|
||||
Object? email = null,
|
||||
Object? name = null,
|
||||
Object? email = freezed,
|
||||
Object? phone = freezed,
|
||||
Object? role = null,
|
||||
Object? companyId = freezed,
|
||||
Object? companyName = freezed,
|
||||
Object? branchId = freezed,
|
||||
Object? branchName = freezed,
|
||||
Object? isActive = null,
|
||||
Object? lastLoginAt = freezed,
|
||||
Object? createdAt = null,
|
||||
Object? updatedAt = null,
|
||||
}) {
|
||||
@@ -196,14 +226,14 @@ class __$$UserDtoImplCopyWithImpl<$Res>
|
||||
? _value.username
|
||||
: username // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
email: null == email
|
||||
? _value.email
|
||||
: email // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
email: freezed == email
|
||||
? _value.email
|
||||
: email // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
phone: freezed == phone
|
||||
? _value.phone
|
||||
: phone // ignore: cast_nullable_to_non_nullable
|
||||
@@ -216,14 +246,26 @@ class __$$UserDtoImplCopyWithImpl<$Res>
|
||||
? _value.companyId
|
||||
: companyId // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
companyName: freezed == companyName
|
||||
? _value.companyName
|
||||
: companyName // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
branchId: freezed == branchId
|
||||
? _value.branchId
|
||||
: branchId // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
branchName: freezed == branchName
|
||||
? _value.branchName
|
||||
: branchName // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
isActive: null == isActive
|
||||
? _value.isActive
|
||||
: isActive // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
lastLoginAt: freezed == lastLoginAt
|
||||
? _value.lastLoginAt
|
||||
: lastLoginAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
createdAt: null == createdAt
|
||||
? _value.createdAt
|
||||
: createdAt // ignore: cast_nullable_to_non_nullable
|
||||
@@ -242,13 +284,16 @@ class _$UserDtoImpl implements _UserDto {
|
||||
const _$UserDtoImpl(
|
||||
{required this.id,
|
||||
required this.username,
|
||||
required this.email,
|
||||
required this.name,
|
||||
this.email,
|
||||
this.phone,
|
||||
required this.role,
|
||||
@JsonKey(name: 'company_id') this.companyId,
|
||||
@JsonKey(name: 'company_name') this.companyName,
|
||||
@JsonKey(name: 'branch_id') this.branchId,
|
||||
@JsonKey(name: 'branch_name') this.branchName,
|
||||
@JsonKey(name: 'is_active') required this.isActive,
|
||||
@JsonKey(name: 'last_login_at') this.lastLoginAt,
|
||||
@JsonKey(name: 'created_at') required this.createdAt,
|
||||
@JsonKey(name: 'updated_at') required this.updatedAt});
|
||||
|
||||
@@ -260,10 +305,10 @@ class _$UserDtoImpl implements _UserDto {
|
||||
@override
|
||||
final String username;
|
||||
@override
|
||||
final String email;
|
||||
@override
|
||||
final String name;
|
||||
@override
|
||||
final String? email;
|
||||
@override
|
||||
final String? phone;
|
||||
@override
|
||||
final String role;
|
||||
@@ -271,12 +316,21 @@ class _$UserDtoImpl implements _UserDto {
|
||||
@JsonKey(name: 'company_id')
|
||||
final int? companyId;
|
||||
@override
|
||||
@JsonKey(name: 'company_name')
|
||||
final String? companyName;
|
||||
@override
|
||||
@JsonKey(name: 'branch_id')
|
||||
final int? branchId;
|
||||
@override
|
||||
@JsonKey(name: 'branch_name')
|
||||
final String? branchName;
|
||||
@override
|
||||
@JsonKey(name: 'is_active')
|
||||
final bool isActive;
|
||||
@override
|
||||
@JsonKey(name: 'last_login_at')
|
||||
final DateTime? lastLoginAt;
|
||||
@override
|
||||
@JsonKey(name: 'created_at')
|
||||
final DateTime createdAt;
|
||||
@override
|
||||
@@ -285,7 +339,7 @@ class _$UserDtoImpl implements _UserDto {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UserDto(id: $id, username: $username, email: $email, name: $name, phone: $phone, role: $role, companyId: $companyId, branchId: $branchId, isActive: $isActive, createdAt: $createdAt, updatedAt: $updatedAt)';
|
||||
return 'UserDto(id: $id, username: $username, name: $name, email: $email, phone: $phone, role: $role, companyId: $companyId, companyName: $companyName, branchId: $branchId, branchName: $branchName, isActive: $isActive, lastLoginAt: $lastLoginAt, createdAt: $createdAt, updatedAt: $updatedAt)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -296,16 +350,22 @@ class _$UserDtoImpl implements _UserDto {
|
||||
(identical(other.id, id) || other.id == id) &&
|
||||
(identical(other.username, username) ||
|
||||
other.username == username) &&
|
||||
(identical(other.email, email) || other.email == email) &&
|
||||
(identical(other.name, name) || other.name == name) &&
|
||||
(identical(other.email, email) || other.email == email) &&
|
||||
(identical(other.phone, phone) || other.phone == phone) &&
|
||||
(identical(other.role, role) || other.role == role) &&
|
||||
(identical(other.companyId, companyId) ||
|
||||
other.companyId == companyId) &&
|
||||
(identical(other.companyName, companyName) ||
|
||||
other.companyName == companyName) &&
|
||||
(identical(other.branchId, branchId) ||
|
||||
other.branchId == branchId) &&
|
||||
(identical(other.branchName, branchName) ||
|
||||
other.branchName == branchName) &&
|
||||
(identical(other.isActive, isActive) ||
|
||||
other.isActive == isActive) &&
|
||||
(identical(other.lastLoginAt, lastLoginAt) ||
|
||||
other.lastLoginAt == lastLoginAt) &&
|
||||
(identical(other.createdAt, createdAt) ||
|
||||
other.createdAt == createdAt) &&
|
||||
(identical(other.updatedAt, updatedAt) ||
|
||||
@@ -314,8 +374,22 @@ class _$UserDtoImpl implements _UserDto {
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, id, username, email, name, phone,
|
||||
role, companyId, branchId, isActive, createdAt, updatedAt);
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
id,
|
||||
username,
|
||||
name,
|
||||
email,
|
||||
phone,
|
||||
role,
|
||||
companyId,
|
||||
companyName,
|
||||
branchId,
|
||||
branchName,
|
||||
isActive,
|
||||
lastLoginAt,
|
||||
createdAt,
|
||||
updatedAt);
|
||||
|
||||
/// Create a copy of UserDto
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -337,13 +411,16 @@ abstract class _UserDto implements UserDto {
|
||||
const factory _UserDto(
|
||||
{required final int id,
|
||||
required final String username,
|
||||
required final String email,
|
||||
required final String name,
|
||||
final String? email,
|
||||
final String? phone,
|
||||
required final String role,
|
||||
@JsonKey(name: 'company_id') final int? companyId,
|
||||
@JsonKey(name: 'company_name') final String? companyName,
|
||||
@JsonKey(name: 'branch_id') final int? branchId,
|
||||
@JsonKey(name: 'branch_name') final String? branchName,
|
||||
@JsonKey(name: 'is_active') required final bool isActive,
|
||||
@JsonKey(name: 'last_login_at') final DateTime? lastLoginAt,
|
||||
@JsonKey(name: 'created_at') required final DateTime createdAt,
|
||||
@JsonKey(name: 'updated_at') required final DateTime updatedAt}) =
|
||||
_$UserDtoImpl;
|
||||
@@ -355,10 +432,10 @@ abstract class _UserDto implements UserDto {
|
||||
@override
|
||||
String get username;
|
||||
@override
|
||||
String get email;
|
||||
@override
|
||||
String get name;
|
||||
@override
|
||||
String? get email;
|
||||
@override
|
||||
String? get phone;
|
||||
@override
|
||||
String get role;
|
||||
@@ -366,12 +443,21 @@ abstract class _UserDto implements UserDto {
|
||||
@JsonKey(name: 'company_id')
|
||||
int? get companyId;
|
||||
@override
|
||||
@JsonKey(name: 'company_name')
|
||||
String? get companyName;
|
||||
@override
|
||||
@JsonKey(name: 'branch_id')
|
||||
int? get branchId;
|
||||
@override
|
||||
@JsonKey(name: 'branch_name')
|
||||
String? get branchName;
|
||||
@override
|
||||
@JsonKey(name: 'is_active')
|
||||
bool get isActive;
|
||||
@override
|
||||
@JsonKey(name: 'last_login_at')
|
||||
DateTime? get lastLoginAt;
|
||||
@override
|
||||
@JsonKey(name: 'created_at')
|
||||
DateTime get createdAt;
|
||||
@override
|
||||
@@ -393,7 +479,7 @@ CreateUserRequest _$CreateUserRequestFromJson(Map<String, dynamic> json) {
|
||||
/// @nodoc
|
||||
mixin _$CreateUserRequest {
|
||||
String get username => throw _privateConstructorUsedError;
|
||||
String get email => throw _privateConstructorUsedError;
|
||||
String? get email => throw _privateConstructorUsedError;
|
||||
String get password => throw _privateConstructorUsedError;
|
||||
String get name => throw _privateConstructorUsedError;
|
||||
String? get phone => throw _privateConstructorUsedError;
|
||||
@@ -421,7 +507,7 @@ abstract class $CreateUserRequestCopyWith<$Res> {
|
||||
@useResult
|
||||
$Res call(
|
||||
{String username,
|
||||
String email,
|
||||
String? email,
|
||||
String password,
|
||||
String name,
|
||||
String? phone,
|
||||
@@ -446,7 +532,7 @@ class _$CreateUserRequestCopyWithImpl<$Res, $Val extends CreateUserRequest>
|
||||
@override
|
||||
$Res call({
|
||||
Object? username = null,
|
||||
Object? email = null,
|
||||
Object? email = freezed,
|
||||
Object? password = null,
|
||||
Object? name = null,
|
||||
Object? phone = freezed,
|
||||
@@ -459,10 +545,10 @@ class _$CreateUserRequestCopyWithImpl<$Res, $Val extends CreateUserRequest>
|
||||
? _value.username
|
||||
: username // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
email: null == email
|
||||
email: freezed == email
|
||||
? _value.email
|
||||
: email // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
as String?,
|
||||
password: null == password
|
||||
? _value.password
|
||||
: password // ignore: cast_nullable_to_non_nullable
|
||||
@@ -501,7 +587,7 @@ abstract class _$$CreateUserRequestImplCopyWith<$Res>
|
||||
@useResult
|
||||
$Res call(
|
||||
{String username,
|
||||
String email,
|
||||
String? email,
|
||||
String password,
|
||||
String name,
|
||||
String? phone,
|
||||
@@ -524,7 +610,7 @@ class __$$CreateUserRequestImplCopyWithImpl<$Res>
|
||||
@override
|
||||
$Res call({
|
||||
Object? username = null,
|
||||
Object? email = null,
|
||||
Object? email = freezed,
|
||||
Object? password = null,
|
||||
Object? name = null,
|
||||
Object? phone = freezed,
|
||||
@@ -537,10 +623,10 @@ class __$$CreateUserRequestImplCopyWithImpl<$Res>
|
||||
? _value.username
|
||||
: username // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
email: null == email
|
||||
email: freezed == email
|
||||
? _value.email
|
||||
: email // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
as String?,
|
||||
password: null == password
|
||||
? _value.password
|
||||
: password // ignore: cast_nullable_to_non_nullable
|
||||
@@ -574,7 +660,7 @@ class __$$CreateUserRequestImplCopyWithImpl<$Res>
|
||||
class _$CreateUserRequestImpl implements _CreateUserRequest {
|
||||
const _$CreateUserRequestImpl(
|
||||
{required this.username,
|
||||
required this.email,
|
||||
this.email,
|
||||
required this.password,
|
||||
required this.name,
|
||||
this.phone,
|
||||
@@ -588,7 +674,7 @@ class _$CreateUserRequestImpl implements _CreateUserRequest {
|
||||
@override
|
||||
final String username;
|
||||
@override
|
||||
final String email;
|
||||
final String? email;
|
||||
@override
|
||||
final String password;
|
||||
@override
|
||||
@@ -653,7 +739,7 @@ class _$CreateUserRequestImpl implements _CreateUserRequest {
|
||||
abstract class _CreateUserRequest implements CreateUserRequest {
|
||||
const factory _CreateUserRequest(
|
||||
{required final String username,
|
||||
required final String email,
|
||||
final String? email,
|
||||
required final String password,
|
||||
required final String name,
|
||||
final String? phone,
|
||||
@@ -668,7 +754,7 @@ abstract class _CreateUserRequest implements CreateUserRequest {
|
||||
@override
|
||||
String get username;
|
||||
@override
|
||||
String get email;
|
||||
String? get email;
|
||||
@override
|
||||
String get password;
|
||||
@override
|
||||
@@ -707,6 +793,8 @@ mixin _$UpdateUserRequest {
|
||||
int? get companyId => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'branch_id')
|
||||
int? get branchId => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'is_active')
|
||||
bool? get isActive => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this UpdateUserRequest to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@@ -731,7 +819,8 @@ abstract class $UpdateUserRequestCopyWith<$Res> {
|
||||
String? phone,
|
||||
String? role,
|
||||
@JsonKey(name: 'company_id') int? companyId,
|
||||
@JsonKey(name: 'branch_id') int? branchId});
|
||||
@JsonKey(name: 'branch_id') int? branchId,
|
||||
@JsonKey(name: 'is_active') bool? isActive});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -756,6 +845,7 @@ class _$UpdateUserRequestCopyWithImpl<$Res, $Val extends UpdateUserRequest>
|
||||
Object? role = freezed,
|
||||
Object? companyId = freezed,
|
||||
Object? branchId = freezed,
|
||||
Object? isActive = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
name: freezed == name
|
||||
@@ -786,6 +876,10 @@ class _$UpdateUserRequestCopyWithImpl<$Res, $Val extends UpdateUserRequest>
|
||||
? _value.branchId
|
||||
: branchId // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
isActive: freezed == isActive
|
||||
? _value.isActive
|
||||
: isActive // ignore: cast_nullable_to_non_nullable
|
||||
as bool?,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
@@ -805,7 +899,8 @@ abstract class _$$UpdateUserRequestImplCopyWith<$Res>
|
||||
String? phone,
|
||||
String? role,
|
||||
@JsonKey(name: 'company_id') int? companyId,
|
||||
@JsonKey(name: 'branch_id') int? branchId});
|
||||
@JsonKey(name: 'branch_id') int? branchId,
|
||||
@JsonKey(name: 'is_active') bool? isActive});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -828,6 +923,7 @@ class __$$UpdateUserRequestImplCopyWithImpl<$Res>
|
||||
Object? role = freezed,
|
||||
Object? companyId = freezed,
|
||||
Object? branchId = freezed,
|
||||
Object? isActive = freezed,
|
||||
}) {
|
||||
return _then(_$UpdateUserRequestImpl(
|
||||
name: freezed == name
|
||||
@@ -858,6 +954,10 @@ class __$$UpdateUserRequestImplCopyWithImpl<$Res>
|
||||
? _value.branchId
|
||||
: branchId // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
isActive: freezed == isActive
|
||||
? _value.isActive
|
||||
: isActive // ignore: cast_nullable_to_non_nullable
|
||||
as bool?,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -872,7 +972,8 @@ class _$UpdateUserRequestImpl implements _UpdateUserRequest {
|
||||
this.phone,
|
||||
this.role,
|
||||
@JsonKey(name: 'company_id') this.companyId,
|
||||
@JsonKey(name: 'branch_id') this.branchId});
|
||||
@JsonKey(name: 'branch_id') this.branchId,
|
||||
@JsonKey(name: 'is_active') this.isActive});
|
||||
|
||||
factory _$UpdateUserRequestImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$UpdateUserRequestImplFromJson(json);
|
||||
@@ -893,10 +994,13 @@ class _$UpdateUserRequestImpl implements _UpdateUserRequest {
|
||||
@override
|
||||
@JsonKey(name: 'branch_id')
|
||||
final int? branchId;
|
||||
@override
|
||||
@JsonKey(name: 'is_active')
|
||||
final bool? isActive;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UpdateUserRequest(name: $name, email: $email, password: $password, phone: $phone, role: $role, companyId: $companyId, branchId: $branchId)';
|
||||
return 'UpdateUserRequest(name: $name, email: $email, password: $password, phone: $phone, role: $role, companyId: $companyId, branchId: $branchId, isActive: $isActive)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -913,13 +1017,15 @@ class _$UpdateUserRequestImpl implements _UpdateUserRequest {
|
||||
(identical(other.companyId, companyId) ||
|
||||
other.companyId == companyId) &&
|
||||
(identical(other.branchId, branchId) ||
|
||||
other.branchId == branchId));
|
||||
other.branchId == branchId) &&
|
||||
(identical(other.isActive, isActive) ||
|
||||
other.isActive == isActive));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType, name, email, password, phone, role, companyId, branchId);
|
||||
int get hashCode => Object.hash(runtimeType, name, email, password, phone,
|
||||
role, companyId, branchId, isActive);
|
||||
|
||||
/// Create a copy of UpdateUserRequest
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -946,7 +1052,8 @@ abstract class _UpdateUserRequest implements UpdateUserRequest {
|
||||
final String? phone,
|
||||
final String? role,
|
||||
@JsonKey(name: 'company_id') final int? companyId,
|
||||
@JsonKey(name: 'branch_id') final int? branchId}) =
|
||||
@JsonKey(name: 'branch_id') final int? branchId,
|
||||
@JsonKey(name: 'is_active') final bool? isActive}) =
|
||||
_$UpdateUserRequestImpl;
|
||||
|
||||
factory _UpdateUserRequest.fromJson(Map<String, dynamic> json) =
|
||||
@@ -968,6 +1075,9 @@ abstract class _UpdateUserRequest implements UpdateUserRequest {
|
||||
@override
|
||||
@JsonKey(name: 'branch_id')
|
||||
int? get branchId;
|
||||
@override
|
||||
@JsonKey(name: 'is_active')
|
||||
bool? get isActive;
|
||||
|
||||
/// Create a copy of UpdateUserRequest
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -1467,14 +1577,15 @@ class __$$UserListDtoImplCopyWithImpl<$Res>
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$UserListDtoImpl implements _UserListDto {
|
||||
class _$UserListDtoImpl extends _UserListDto {
|
||||
const _$UserListDtoImpl(
|
||||
{required final List<UserDto> users,
|
||||
required this.total,
|
||||
required this.page,
|
||||
@JsonKey(name: 'per_page') required this.perPage,
|
||||
@JsonKey(name: 'total_pages') required this.totalPages})
|
||||
: _users = users;
|
||||
: _users = users,
|
||||
super._();
|
||||
|
||||
factory _$UserListDtoImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$UserListDtoImplFromJson(json);
|
||||
@@ -1542,7 +1653,7 @@ class _$UserListDtoImpl implements _UserListDto {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _UserListDto implements UserListDto {
|
||||
abstract class _UserListDto extends UserListDto {
|
||||
const factory _UserListDto(
|
||||
{required final List<UserDto> users,
|
||||
required final int total,
|
||||
@@ -1550,6 +1661,7 @@ abstract class _UserListDto implements UserListDto {
|
||||
@JsonKey(name: 'per_page') required final int perPage,
|
||||
@JsonKey(name: 'total_pages') required final int totalPages}) =
|
||||
_$UserListDtoImpl;
|
||||
const _UserListDto._() : super._();
|
||||
|
||||
factory _UserListDto.fromJson(Map<String, dynamic> json) =
|
||||
_$UserListDtoImpl.fromJson;
|
||||
@@ -1574,3 +1686,350 @@ abstract class _UserListDto implements UserListDto {
|
||||
_$$UserListDtoImplCopyWith<_$UserListDtoImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
UserDetailDto _$UserDetailDtoFromJson(Map<String, dynamic> json) {
|
||||
return _UserDetailDto.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$UserDetailDto {
|
||||
UserDto get user => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this UserDetailDto to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of UserDetailDto
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$UserDetailDtoCopyWith<UserDetailDto> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $UserDetailDtoCopyWith<$Res> {
|
||||
factory $UserDetailDtoCopyWith(
|
||||
UserDetailDto value, $Res Function(UserDetailDto) then) =
|
||||
_$UserDetailDtoCopyWithImpl<$Res, UserDetailDto>;
|
||||
@useResult
|
||||
$Res call({UserDto user});
|
||||
|
||||
$UserDtoCopyWith<$Res> get user;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$UserDetailDtoCopyWithImpl<$Res, $Val extends UserDetailDto>
|
||||
implements $UserDetailDtoCopyWith<$Res> {
|
||||
_$UserDetailDtoCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of UserDetailDto
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? user = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
user: null == user
|
||||
? _value.user
|
||||
: user // ignore: cast_nullable_to_non_nullable
|
||||
as UserDto,
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
/// Create a copy of UserDetailDto
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$UserDtoCopyWith<$Res> get user {
|
||||
return $UserDtoCopyWith<$Res>(_value.user, (value) {
|
||||
return _then(_value.copyWith(user: value) as $Val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$UserDetailDtoImplCopyWith<$Res>
|
||||
implements $UserDetailDtoCopyWith<$Res> {
|
||||
factory _$$UserDetailDtoImplCopyWith(
|
||||
_$UserDetailDtoImpl value, $Res Function(_$UserDetailDtoImpl) then) =
|
||||
__$$UserDetailDtoImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({UserDto user});
|
||||
|
||||
@override
|
||||
$UserDtoCopyWith<$Res> get user;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$UserDetailDtoImplCopyWithImpl<$Res>
|
||||
extends _$UserDetailDtoCopyWithImpl<$Res, _$UserDetailDtoImpl>
|
||||
implements _$$UserDetailDtoImplCopyWith<$Res> {
|
||||
__$$UserDetailDtoImplCopyWithImpl(
|
||||
_$UserDetailDtoImpl _value, $Res Function(_$UserDetailDtoImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of UserDetailDto
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? user = null,
|
||||
}) {
|
||||
return _then(_$UserDetailDtoImpl(
|
||||
user: null == user
|
||||
? _value.user
|
||||
: user // ignore: cast_nullable_to_non_nullable
|
||||
as UserDto,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$UserDetailDtoImpl implements _UserDetailDto {
|
||||
const _$UserDetailDtoImpl({required this.user});
|
||||
|
||||
factory _$UserDetailDtoImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$UserDetailDtoImplFromJson(json);
|
||||
|
||||
@override
|
||||
final UserDto user;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UserDetailDto(user: $user)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$UserDetailDtoImpl &&
|
||||
(identical(other.user, user) || other.user == user));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, user);
|
||||
|
||||
/// Create a copy of UserDetailDto
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$UserDetailDtoImplCopyWith<_$UserDetailDtoImpl> get copyWith =>
|
||||
__$$UserDetailDtoImplCopyWithImpl<_$UserDetailDtoImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$UserDetailDtoImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _UserDetailDto implements UserDetailDto {
|
||||
const factory _UserDetailDto({required final UserDto user}) =
|
||||
_$UserDetailDtoImpl;
|
||||
|
||||
factory _UserDetailDto.fromJson(Map<String, dynamic> json) =
|
||||
_$UserDetailDtoImpl.fromJson;
|
||||
|
||||
@override
|
||||
UserDto get user;
|
||||
|
||||
/// Create a copy of UserDetailDto
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$UserDetailDtoImplCopyWith<_$UserDetailDtoImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
UserResponse _$UserResponseFromJson(Map<String, dynamic> json) {
|
||||
return _UserResponse.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$UserResponse {
|
||||
UserDto get user => throw _privateConstructorUsedError;
|
||||
String? get message => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this UserResponse to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of UserResponse
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$UserResponseCopyWith<UserResponse> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $UserResponseCopyWith<$Res> {
|
||||
factory $UserResponseCopyWith(
|
||||
UserResponse value, $Res Function(UserResponse) then) =
|
||||
_$UserResponseCopyWithImpl<$Res, UserResponse>;
|
||||
@useResult
|
||||
$Res call({UserDto user, String? message});
|
||||
|
||||
$UserDtoCopyWith<$Res> get user;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$UserResponseCopyWithImpl<$Res, $Val extends UserResponse>
|
||||
implements $UserResponseCopyWith<$Res> {
|
||||
_$UserResponseCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of UserResponse
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? user = null,
|
||||
Object? message = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
user: null == user
|
||||
? _value.user
|
||||
: user // ignore: cast_nullable_to_non_nullable
|
||||
as UserDto,
|
||||
message: freezed == message
|
||||
? _value.message
|
||||
: message // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
/// Create a copy of UserResponse
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$UserDtoCopyWith<$Res> get user {
|
||||
return $UserDtoCopyWith<$Res>(_value.user, (value) {
|
||||
return _then(_value.copyWith(user: value) as $Val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$UserResponseImplCopyWith<$Res>
|
||||
implements $UserResponseCopyWith<$Res> {
|
||||
factory _$$UserResponseImplCopyWith(
|
||||
_$UserResponseImpl value, $Res Function(_$UserResponseImpl) then) =
|
||||
__$$UserResponseImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({UserDto user, String? message});
|
||||
|
||||
@override
|
||||
$UserDtoCopyWith<$Res> get user;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$UserResponseImplCopyWithImpl<$Res>
|
||||
extends _$UserResponseCopyWithImpl<$Res, _$UserResponseImpl>
|
||||
implements _$$UserResponseImplCopyWith<$Res> {
|
||||
__$$UserResponseImplCopyWithImpl(
|
||||
_$UserResponseImpl _value, $Res Function(_$UserResponseImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of UserResponse
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? user = null,
|
||||
Object? message = freezed,
|
||||
}) {
|
||||
return _then(_$UserResponseImpl(
|
||||
user: null == user
|
||||
? _value.user
|
||||
: user // ignore: cast_nullable_to_non_nullable
|
||||
as UserDto,
|
||||
message: freezed == message
|
||||
? _value.message
|
||||
: message // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$UserResponseImpl implements _UserResponse {
|
||||
const _$UserResponseImpl({required this.user, this.message});
|
||||
|
||||
factory _$UserResponseImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$UserResponseImplFromJson(json);
|
||||
|
||||
@override
|
||||
final UserDto user;
|
||||
@override
|
||||
final String? message;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UserResponse(user: $user, message: $message)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$UserResponseImpl &&
|
||||
(identical(other.user, user) || other.user == user) &&
|
||||
(identical(other.message, message) || other.message == message));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, user, message);
|
||||
|
||||
/// Create a copy of UserResponse
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$UserResponseImplCopyWith<_$UserResponseImpl> get copyWith =>
|
||||
__$$UserResponseImplCopyWithImpl<_$UserResponseImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$UserResponseImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _UserResponse implements UserResponse {
|
||||
const factory _UserResponse(
|
||||
{required final UserDto user,
|
||||
final String? message}) = _$UserResponseImpl;
|
||||
|
||||
factory _UserResponse.fromJson(Map<String, dynamic> json) =
|
||||
_$UserResponseImpl.fromJson;
|
||||
|
||||
@override
|
||||
UserDto get user;
|
||||
@override
|
||||
String? get message;
|
||||
|
||||
/// Create a copy of UserResponse
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$UserResponseImplCopyWith<_$UserResponseImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
@@ -10,13 +10,18 @@ _$UserDtoImpl _$$UserDtoImplFromJson(Map<String, dynamic> json) =>
|
||||
_$UserDtoImpl(
|
||||
id: (json['id'] as num).toInt(),
|
||||
username: json['username'] as String,
|
||||
email: json['email'] as String,
|
||||
name: json['name'] as String,
|
||||
email: json['email'] as String?,
|
||||
phone: json['phone'] as String?,
|
||||
role: json['role'] as String,
|
||||
companyId: (json['company_id'] as num?)?.toInt(),
|
||||
companyName: json['company_name'] as String?,
|
||||
branchId: (json['branch_id'] as num?)?.toInt(),
|
||||
branchName: json['branch_name'] as String?,
|
||||
isActive: json['is_active'] as bool,
|
||||
lastLoginAt: json['last_login_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['last_login_at'] as String),
|
||||
createdAt: DateTime.parse(json['created_at'] as String),
|
||||
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||
);
|
||||
@@ -25,13 +30,16 @@ Map<String, dynamic> _$$UserDtoImplToJson(_$UserDtoImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'username': instance.username,
|
||||
'email': instance.email,
|
||||
'name': instance.name,
|
||||
'email': instance.email,
|
||||
'phone': instance.phone,
|
||||
'role': instance.role,
|
||||
'company_id': instance.companyId,
|
||||
'company_name': instance.companyName,
|
||||
'branch_id': instance.branchId,
|
||||
'branch_name': instance.branchName,
|
||||
'is_active': instance.isActive,
|
||||
'last_login_at': instance.lastLoginAt?.toIso8601String(),
|
||||
'created_at': instance.createdAt.toIso8601String(),
|
||||
'updated_at': instance.updatedAt.toIso8601String(),
|
||||
};
|
||||
@@ -40,7 +48,7 @@ _$CreateUserRequestImpl _$$CreateUserRequestImplFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$CreateUserRequestImpl(
|
||||
username: json['username'] as String,
|
||||
email: json['email'] as String,
|
||||
email: json['email'] as String?,
|
||||
password: json['password'] as String,
|
||||
name: json['name'] as String,
|
||||
phone: json['phone'] as String?,
|
||||
@@ -72,6 +80,7 @@ _$UpdateUserRequestImpl _$$UpdateUserRequestImplFromJson(
|
||||
role: json['role'] as String?,
|
||||
companyId: (json['company_id'] as num?)?.toInt(),
|
||||
branchId: (json['branch_id'] as num?)?.toInt(),
|
||||
isActive: json['is_active'] as bool?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$UpdateUserRequestImplToJson(
|
||||
@@ -84,6 +93,7 @@ Map<String, dynamic> _$$UpdateUserRequestImplToJson(
|
||||
'role': instance.role,
|
||||
'company_id': instance.companyId,
|
||||
'branch_id': instance.branchId,
|
||||
'is_active': instance.isActive,
|
||||
};
|
||||
|
||||
_$ChangeStatusRequestImpl _$$ChangeStatusRequestImplFromJson(
|
||||
@@ -131,3 +141,25 @@ Map<String, dynamic> _$$UserListDtoImplToJson(_$UserListDtoImpl instance) =>
|
||||
'per_page': instance.perPage,
|
||||
'total_pages': instance.totalPages,
|
||||
};
|
||||
|
||||
_$UserDetailDtoImpl _$$UserDetailDtoImplFromJson(Map<String, dynamic> json) =>
|
||||
_$UserDetailDtoImpl(
|
||||
user: UserDto.fromJson(json['user'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$UserDetailDtoImplToJson(_$UserDetailDtoImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'user': instance.user,
|
||||
};
|
||||
|
||||
_$UserResponseImpl _$$UserResponseImplFromJson(Map<String, dynamic> json) =>
|
||||
_$UserResponseImpl(
|
||||
user: UserDto.fromJson(json['user'] as Map<String, dynamic>),
|
||||
message: json['message'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$UserResponseImplToJson(_$UserResponseImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'user': instance.user,
|
||||
'message': instance.message,
|
||||
};
|
||||
|
||||
214
lib/data/repositories/auth_repository_impl.dart
Normal file
214
lib/data/repositories/auth_repository_impl.dart
Normal file
@@ -0,0 +1,214 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../../core/errors/failures.dart';
|
||||
import '../../domain/repositories/auth_repository.dart';
|
||||
import '../datasources/remote/auth_remote_datasource.dart';
|
||||
import '../models/auth/auth_user.dart';
|
||||
import '../models/auth/login_request.dart';
|
||||
import '../models/auth/login_response.dart';
|
||||
import '../models/auth/logout_request.dart';
|
||||
import '../models/auth/refresh_token_request.dart';
|
||||
import '../models/auth/token_response.dart';
|
||||
|
||||
/// 인증 Repository 구현체
|
||||
/// JWT 토큰 기반 인증 시스템을 관리하며 SharedPreferences를 사용해 토큰을 저장
|
||||
@Injectable(as: AuthRepository)
|
||||
class AuthRepositoryImpl implements AuthRepository {
|
||||
final AuthRemoteDataSource remoteDataSource;
|
||||
final SharedPreferences sharedPreferences;
|
||||
|
||||
// SharedPreferences 키 상수
|
||||
static const String _keyAccessToken = 'access_token';
|
||||
static const String _keyRefreshToken = 'refresh_token';
|
||||
static const String _keyUserData = 'user_data';
|
||||
|
||||
AuthRepositoryImpl({
|
||||
required this.remoteDataSource,
|
||||
required this.sharedPreferences,
|
||||
});
|
||||
|
||||
@override
|
||||
Future<Either<Failure, LoginResponse>> login(LoginRequest loginRequest) async {
|
||||
try {
|
||||
final result = await remoteDataSource.login(loginRequest);
|
||||
|
||||
return result.fold(
|
||||
(failure) => Left(failure),
|
||||
(loginResponse) async {
|
||||
// 로그인 성공 시 토큰과 사용자 정보를 로컬에 저장
|
||||
await _saveTokens(loginResponse.accessToken, loginResponse.refreshToken);
|
||||
await _saveUserData(loginResponse.user);
|
||||
|
||||
return Right(loginResponse);
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(
|
||||
message: '로그인 처리 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, void>> logout() async {
|
||||
try {
|
||||
// 로컬에 저장된 리프레시 토큰으로 로그아웃 요청 생성
|
||||
final refreshToken = await _getRefreshToken();
|
||||
if (refreshToken == null) {
|
||||
// 토큰이 없으면 로컬 데이터만 삭제하고 성공 처리
|
||||
await _clearLocalData();
|
||||
return const Right(null);
|
||||
}
|
||||
|
||||
final logoutRequest = LogoutRequest(refreshToken: refreshToken);
|
||||
final result = await remoteDataSource.logout(logoutRequest);
|
||||
|
||||
return result.fold(
|
||||
(failure) async {
|
||||
// 서버 로그아웃 실패해도 로컬 데이터는 삭제
|
||||
await _clearLocalData();
|
||||
return Left(failure);
|
||||
},
|
||||
(_) async {
|
||||
// 성공 시 로컬 데이터 삭제
|
||||
await _clearLocalData();
|
||||
return const Right(null);
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
// 오류 발생해도 로컬 데이터는 삭제
|
||||
await _clearLocalData();
|
||||
return Left(ServerFailure(
|
||||
message: '로그아웃 처리 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, TokenResponse>> refreshToken(RefreshTokenRequest refreshRequest) async {
|
||||
try {
|
||||
final result = await remoteDataSource.refreshToken(refreshRequest);
|
||||
|
||||
return result.fold(
|
||||
(failure) => Left(failure),
|
||||
(tokenResponse) async {
|
||||
// 새 토큰 저장
|
||||
await _saveTokens(tokenResponse.accessToken, tokenResponse.refreshToken);
|
||||
return Right(tokenResponse);
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(
|
||||
message: '토큰 갱신 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, AuthUser>> getCurrentUser() async {
|
||||
try {
|
||||
final userData = sharedPreferences.getString(_keyUserData);
|
||||
if (userData == null) {
|
||||
return const Left(AuthenticationFailure(
|
||||
message: '저장된 사용자 정보가 없습니다.',
|
||||
));
|
||||
}
|
||||
|
||||
// JSON 문자열을 AuthUser 객체로 변환
|
||||
final user = AuthUser.fromJson(
|
||||
Map<String, dynamic>.from(
|
||||
// JSON 디코딩 처리 필요 시 여기에 추가
|
||||
{} // TODO: JSON 디코딩 로직 추가
|
||||
)
|
||||
);
|
||||
|
||||
return Right(user);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(
|
||||
message: '사용자 정보 조회 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, bool>> isAuthenticated() async {
|
||||
try {
|
||||
final accessToken = await _getAccessToken();
|
||||
final refreshToken = await _getRefreshToken();
|
||||
|
||||
// 액세스 토큰과 리프레시 토큰이 모두 있으면 인증된 것으로 간주
|
||||
final isAuth = accessToken != null && refreshToken != null;
|
||||
return Right(isAuth);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(
|
||||
message: '인증 상태 확인 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, void>> changePassword(String currentPassword, String newPassword) async {
|
||||
// TODO: 비밀번호 변경 API가 구현되면 추가
|
||||
return const Left(ServerFailure(
|
||||
message: '비밀번호 변경 기능은 아직 구현되지 않았습니다.',
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, void>> requestPasswordReset(String email) async {
|
||||
// TODO: 비밀번호 재설정 API가 구현되면 추가
|
||||
return const Left(ServerFailure(
|
||||
message: '비밀번호 재설정 기능은 아직 구현되지 않았습니다.',
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, bool>> validateSession() async {
|
||||
try {
|
||||
final accessToken = await _getAccessToken();
|
||||
if (accessToken == null) {
|
||||
return const Right(false);
|
||||
}
|
||||
|
||||
// TODO: 서버에서 세션 유효성 검증 API가 있으면 호출
|
||||
// 현재는 토큰 존재 여부만 확인
|
||||
return const Right(true);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(
|
||||
message: '세션 유효성 검증 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Private 헬퍼 메서드들
|
||||
|
||||
/// 액세스 토큰과 리프레시 토큰을 로컬에 저장
|
||||
Future<void> _saveTokens(String accessToken, String refreshToken) async {
|
||||
await sharedPreferences.setString(_keyAccessToken, accessToken);
|
||||
await sharedPreferences.setString(_keyRefreshToken, refreshToken);
|
||||
}
|
||||
|
||||
/// 사용자 데이터를 로컬에 저장
|
||||
Future<void> _saveUserData(AuthUser user) async {
|
||||
// TODO: JSON 인코딩 로직 구현
|
||||
await sharedPreferences.setString(_keyUserData, user.toJson().toString());
|
||||
}
|
||||
|
||||
/// 액세스 토큰 조회
|
||||
Future<String?> _getAccessToken() async {
|
||||
return sharedPreferences.getString(_keyAccessToken);
|
||||
}
|
||||
|
||||
/// 리프레시 토큰 조회
|
||||
Future<String?> _getRefreshToken() async {
|
||||
return sharedPreferences.getString(_keyRefreshToken);
|
||||
}
|
||||
|
||||
/// 로컬 데이터 전체 삭제
|
||||
Future<void> _clearLocalData() async {
|
||||
await sharedPreferences.remove(_keyAccessToken);
|
||||
await sharedPreferences.remove(_keyRefreshToken);
|
||||
await sharedPreferences.remove(_keyUserData);
|
||||
}
|
||||
}
|
||||
456
lib/data/repositories/company_repository_impl.dart
Normal file
456
lib/data/repositories/company_repository_impl.dart
Normal file
@@ -0,0 +1,456 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import '../../core/errors/failures.dart';
|
||||
import '../../domain/repositories/company_repository.dart';
|
||||
import '../../models/company_model.dart';
|
||||
import '../../models/address_model.dart';
|
||||
import '../datasources/remote/company_remote_datasource.dart';
|
||||
import '../models/common/paginated_response.dart';
|
||||
import '../models/company/company_dto.dart';
|
||||
import '../models/company/branch_dto.dart';
|
||||
import '../models/company/company_list_dto.dart';
|
||||
|
||||
/// 회사 관리 Repository 구현체
|
||||
/// 회사 및 지점 정보 CRUD 작업을 처리하며 도메인 모델과 API DTO 간 변환을 담당
|
||||
@Injectable(as: CompanyRepository)
|
||||
class CompanyRepositoryImpl implements CompanyRepository {
|
||||
final CompanyRemoteDataSource remoteDataSource;
|
||||
|
||||
CompanyRepositoryImpl({required this.remoteDataSource});
|
||||
|
||||
@override
|
||||
Future<Either<Failure, PaginatedResponse<Company>>> getCompanies({
|
||||
int? page,
|
||||
int? limit,
|
||||
String? search,
|
||||
CompanyType? companyType,
|
||||
String? sortBy,
|
||||
String? sortOrder,
|
||||
}) async {
|
||||
try {
|
||||
final result = await remoteDataSource.getCompanies(
|
||||
page: page ?? 1,
|
||||
perPage: limit ?? 20,
|
||||
search: search,
|
||||
isActive: null, // companyType에 따른 필터링 로직 필요 시 추가
|
||||
);
|
||||
|
||||
// DTO를 도메인 모델로 변환
|
||||
final companies = result.items.map((dto) => _mapDtoToDomain(dto)).toList();
|
||||
|
||||
final paginatedResult = PaginatedResponse<Company>(
|
||||
items: companies,
|
||||
page: result.page,
|
||||
size: result.size,
|
||||
totalElements: result.totalElements,
|
||||
totalPages: result.totalPages,
|
||||
first: result.first,
|
||||
last: result.last,
|
||||
);
|
||||
|
||||
return Right(paginatedResult);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(
|
||||
message: '회사 목록 조회 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, Company>> getCompanyById(int id) async {
|
||||
try {
|
||||
final result = await remoteDataSource.getCompanyWithBranches(id);
|
||||
final company = _mapDetailDtoToDomain(result);
|
||||
return Right(company);
|
||||
} catch (e) {
|
||||
if (e.toString().contains('404')) {
|
||||
return Left(NotFoundFailure(
|
||||
message: '해당 회사를 찾을 수 없습니다.',
|
||||
resourceType: 'Company',
|
||||
resourceId: id.toString(),
|
||||
));
|
||||
}
|
||||
return Left(ServerFailure(
|
||||
message: '회사 상세 정보 조회 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, Company>> createCompany(Company company) async {
|
||||
try {
|
||||
final request = _mapDomainToCreateRequest(company);
|
||||
final result = await remoteDataSource.createCompany(request);
|
||||
final createdCompany = _mapResponseToDomain(result);
|
||||
return Right(createdCompany);
|
||||
} catch (e) {
|
||||
if (e.toString().contains('중복')) {
|
||||
return Left(DuplicateFailure(
|
||||
message: '이미 존재하는 회사명입니다.',
|
||||
field: 'name',
|
||||
value: company.name,
|
||||
));
|
||||
}
|
||||
if (e.toString().contains('유효성')) {
|
||||
return Left(ValidationFailure(
|
||||
message: '입력 데이터가 올바르지 않습니다.',
|
||||
));
|
||||
}
|
||||
return Left(ServerFailure(
|
||||
message: '회사 생성 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, Company>> updateCompany(int id, Company company) async {
|
||||
try {
|
||||
final request = _mapDomainToUpdateRequest(company);
|
||||
final result = await remoteDataSource.updateCompany(id, request);
|
||||
final updatedCompany = _mapResponseToDomain(result);
|
||||
return Right(updatedCompany);
|
||||
} catch (e) {
|
||||
if (e.toString().contains('404')) {
|
||||
return Left(NotFoundFailure(
|
||||
message: '수정할 회사를 찾을 수 없습니다.',
|
||||
resourceType: 'Company',
|
||||
resourceId: id.toString(),
|
||||
));
|
||||
}
|
||||
if (e.toString().contains('중복')) {
|
||||
return Left(DuplicateFailure(
|
||||
message: '이미 존재하는 회사명입니다.',
|
||||
field: 'name',
|
||||
value: company.name,
|
||||
));
|
||||
}
|
||||
return Left(ServerFailure(
|
||||
message: '회사 정보 수정 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, void>> deleteCompany(int id) async {
|
||||
try {
|
||||
await remoteDataSource.deleteCompany(id);
|
||||
return const Right(null);
|
||||
} catch (e) {
|
||||
if (e.toString().contains('404')) {
|
||||
return Left(NotFoundFailure(
|
||||
message: '삭제할 회사를 찾을 수 없습니다.',
|
||||
resourceType: 'Company',
|
||||
resourceId: id.toString(),
|
||||
));
|
||||
}
|
||||
if (e.toString().contains('참조')) {
|
||||
return Left(BusinessFailure(
|
||||
message: '해당 회사에 연결된 데이터가 있어 삭제할 수 없습니다.',
|
||||
));
|
||||
}
|
||||
return Left(ServerFailure(
|
||||
message: '회사 삭제 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, Company>> toggleCompanyStatus(int id) async {
|
||||
try {
|
||||
// 현재 회사 정보 조회
|
||||
final currentCompany = await remoteDataSource.getCompanyDetail(id);
|
||||
final newStatus = !currentCompany.isActive;
|
||||
|
||||
// 상태 업데이트
|
||||
await remoteDataSource.updateCompanyStatus(id, newStatus);
|
||||
|
||||
// 업데이트된 회사 정보 재조회
|
||||
final updatedCompany = await remoteDataSource.getCompanyDetail(id);
|
||||
final company = _mapResponseToDomain(updatedCompany);
|
||||
|
||||
return Right(company);
|
||||
} catch (e) {
|
||||
if (e.toString().contains('404')) {
|
||||
return Left(NotFoundFailure(
|
||||
message: '상태를 변경할 회사를 찾을 수 없습니다.',
|
||||
resourceType: 'Company',
|
||||
resourceId: id.toString(),
|
||||
));
|
||||
}
|
||||
return Left(ServerFailure(
|
||||
message: '회사 상태 변경 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, Branch>> createBranch(int companyId, Branch branch) async {
|
||||
try {
|
||||
final request = _mapBranchToCreateRequest(branch);
|
||||
final result = await remoteDataSource.createBranch(companyId, request);
|
||||
final createdBranch = _mapBranchResponseToDomain(result);
|
||||
return Right(createdBranch);
|
||||
} catch (e) {
|
||||
if (e.toString().contains('404')) {
|
||||
return Left(NotFoundFailure(
|
||||
message: '해당 회사를 찾을 수 없습니다.',
|
||||
resourceType: 'Company',
|
||||
resourceId: companyId.toString(),
|
||||
));
|
||||
}
|
||||
return Left(ServerFailure(
|
||||
message: '지점 생성 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, Branch>> updateBranch(int companyId, int branchId, Branch branch) async {
|
||||
try {
|
||||
final request = _mapBranchToUpdateRequest(branch);
|
||||
final result = await remoteDataSource.updateBranch(companyId, branchId, request);
|
||||
final updatedBranch = _mapBranchResponseToDomain(result);
|
||||
return Right(updatedBranch);
|
||||
} catch (e) {
|
||||
if (e.toString().contains('404')) {
|
||||
return Left(NotFoundFailure(
|
||||
message: '수정할 지점을 찾을 수 없습니다.',
|
||||
resourceType: 'Branch',
|
||||
resourceId: branchId.toString(),
|
||||
));
|
||||
}
|
||||
return Left(ServerFailure(
|
||||
message: '지점 정보 수정 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, void>> deleteBranch(int companyId, int branchId) async {
|
||||
try {
|
||||
await remoteDataSource.deleteBranch(companyId, branchId);
|
||||
return const Right(null);
|
||||
} catch (e) {
|
||||
if (e.toString().contains('404')) {
|
||||
return Left(NotFoundFailure(
|
||||
message: '삭제할 지점을 찾을 수 없습니다.',
|
||||
resourceType: 'Branch',
|
||||
resourceId: branchId.toString(),
|
||||
));
|
||||
}
|
||||
return Left(ServerFailure(
|
||||
message: '지점 삭제 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, List<String>>> searchCompanyNames(String query, {int? limit}) async {
|
||||
try {
|
||||
final companies = await remoteDataSource.searchCompanies(query);
|
||||
final names = companies.map((company) => company.name).take(limit ?? 10).toList();
|
||||
return Right(names);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(
|
||||
message: '회사명 검색 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, Map<CompanyType, int>>> getCompanyCountByType() async {
|
||||
// TODO: API에서 회사 유형별 통계 기능이 구현되면 추가
|
||||
return const Left(ServerFailure(
|
||||
message: '회사 유형별 통계 기능이 아직 구현되지 않았습니다.',
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, bool>> hasLinkedUsers(int companyId) async {
|
||||
// TODO: 회사에 연결된 사용자 존재 여부 확인 API 구현 필요
|
||||
try {
|
||||
// 임시로 false 반환 - API 구현 후 수정 필요
|
||||
return const Right(false);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(
|
||||
message: '연결된 사용자 확인 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, bool>> hasLinkedEquipment(int companyId) async {
|
||||
// TODO: 회사에 연결된 장비 존재 여부 확인 API 구현 필요
|
||||
try {
|
||||
// 임시로 false 반환 - API 구현 후 수정 필요
|
||||
return const Right(false);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(
|
||||
message: '연결된 장비 확인 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, bool>> isDuplicateCompanyName(String name, {int? excludeId}) async {
|
||||
try {
|
||||
final isDuplicate = await remoteDataSource.checkDuplicateCompany(name);
|
||||
// excludeId가 있는 경우 해당 ID 제외 로직 추가 필요
|
||||
return Right(isDuplicate);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(
|
||||
message: '중복 회사명 확인 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Private 매퍼 메서드들
|
||||
|
||||
Company _mapDtoToDomain(CompanyListDto dto) {
|
||||
return Company(
|
||||
id: dto.id,
|
||||
name: dto.name,
|
||||
address: Address.fromFullAddress(dto.address ?? ''),
|
||||
contactName: dto.contactName,
|
||||
contactPosition: null, // CompanyListDto에 없음
|
||||
contactPhone: dto.contactPhone,
|
||||
contactEmail: dto.contactEmail,
|
||||
companyTypes: _parseCompanyTypes(dto.companyTypes),
|
||||
remark: null, // CompanyListDto에 없음
|
||||
branches: [], // 목록에서는 지점 정보 비어있음
|
||||
);
|
||||
}
|
||||
|
||||
Company _mapDetailDtoToDomain(CompanyWithBranches dto) {
|
||||
return Company(
|
||||
id: dto.company.id,
|
||||
name: dto.company.name,
|
||||
address: Address.fromFullAddress(dto.company.address ?? ''),
|
||||
contactName: dto.company.contactName,
|
||||
contactPosition: dto.company.contactPosition,
|
||||
contactPhone: dto.company.contactPhone,
|
||||
contactEmail: dto.company.contactEmail,
|
||||
companyTypes: _parseCompanyTypes(dto.company.companyTypes),
|
||||
remark: dto.company.remark,
|
||||
branches: dto.branches.map((branch) => _mapBranchDtoToDomain(branch)).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Company _mapResponseToDomain(CompanyResponse response) {
|
||||
return Company(
|
||||
id: response.id,
|
||||
name: response.name,
|
||||
address: Address.fromFullAddress(response.address ?? ''),
|
||||
contactName: response.contactName,
|
||||
contactPosition: response.contactPosition,
|
||||
contactPhone: response.contactPhone,
|
||||
contactEmail: response.contactEmail,
|
||||
companyTypes: _parseCompanyTypes(response.companyTypes),
|
||||
remark: response.remark,
|
||||
branches: [], // CompanyResponse에서는 지점 정보 따로 조회
|
||||
);
|
||||
}
|
||||
|
||||
Branch _mapBranchDtoToDomain(BranchListDto dto) {
|
||||
return Branch(
|
||||
id: dto.id,
|
||||
companyId: dto.companyId,
|
||||
name: dto.branchName,
|
||||
address: Address.fromFullAddress(dto.address ?? ''),
|
||||
contactName: dto.managerName,
|
||||
contactPosition: null, // BranchListDto에 없음
|
||||
contactPhone: dto.phone,
|
||||
contactEmail: null, // BranchListDto에 없음
|
||||
remark: null, // BranchListDto에 없음
|
||||
);
|
||||
}
|
||||
|
||||
Branch _mapBranchResponseToDomain(BranchResponse response) {
|
||||
return Branch(
|
||||
id: response.id,
|
||||
companyId: response.companyId,
|
||||
name: response.branchName,
|
||||
address: Address.fromFullAddress(response.address ?? ''),
|
||||
contactName: response.managerName,
|
||||
contactPosition: null,
|
||||
contactPhone: response.phone,
|
||||
contactEmail: null,
|
||||
remark: response.remark,
|
||||
);
|
||||
}
|
||||
|
||||
/// API에서 받은 문자열 리스트를 CompanyType enum 리스트로 변환
|
||||
/// 지원하는 형식: ['customer', 'partner'] 또는 ['고객사', '파트너사']
|
||||
List<CompanyType> _parseCompanyTypes(List<String>? types) {
|
||||
if (types == null || types.isEmpty) return [CompanyType.customer];
|
||||
|
||||
return types.map((type) {
|
||||
final lowerType = type.toLowerCase().trim();
|
||||
// API 문자열 형식 매칭
|
||||
if (lowerType == 'partner' || lowerType.contains('partner') || lowerType == '파트너사') {
|
||||
return CompanyType.partner;
|
||||
}
|
||||
// 기본값은 customer
|
||||
return CompanyType.customer;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
/// CompanyType enum을 API 문자열로 변환
|
||||
String _mapCompanyTypeToApiString(CompanyType type) {
|
||||
switch (type) {
|
||||
case CompanyType.partner:
|
||||
return 'partner';
|
||||
case CompanyType.customer:
|
||||
return 'customer';
|
||||
}
|
||||
}
|
||||
|
||||
CreateCompanyRequest _mapDomainToCreateRequest(Company company) {
|
||||
return CreateCompanyRequest(
|
||||
name: company.name,
|
||||
address: company.address.toString(),
|
||||
contactName: company.contactName ?? '',
|
||||
contactPosition: company.contactPosition ?? '',
|
||||
contactPhone: company.contactPhone ?? '',
|
||||
contactEmail: company.contactEmail ?? '',
|
||||
companyTypes: company.companyTypes.map((type) => _mapCompanyTypeToApiString(type)).toList(),
|
||||
remark: company.remark,
|
||||
);
|
||||
}
|
||||
|
||||
UpdateCompanyRequest _mapDomainToUpdateRequest(Company company) {
|
||||
return UpdateCompanyRequest(
|
||||
name: company.name,
|
||||
address: company.address.toString(),
|
||||
contactName: company.contactName,
|
||||
contactPosition: company.contactPosition,
|
||||
contactPhone: company.contactPhone,
|
||||
contactEmail: company.contactEmail,
|
||||
companyTypes: company.companyTypes.map((type) => _mapCompanyTypeToApiString(type)).toList(),
|
||||
remark: company.remark,
|
||||
isActive: null, // UpdateCompanyRequest에서 필요한 경우 추가
|
||||
);
|
||||
}
|
||||
|
||||
CreateBranchRequest _mapBranchToCreateRequest(Branch branch) {
|
||||
return CreateBranchRequest(
|
||||
branchName: branch.name,
|
||||
address: branch.address.toString(),
|
||||
phone: branch.contactPhone ?? '',
|
||||
managerName: branch.contactName,
|
||||
managerPhone: null, // Branch에 없음
|
||||
remark: branch.remark,
|
||||
);
|
||||
}
|
||||
|
||||
UpdateBranchRequest _mapBranchToUpdateRequest(Branch branch) {
|
||||
return UpdateBranchRequest(
|
||||
branchName: branch.name,
|
||||
address: branch.address.toString(),
|
||||
phone: branch.contactPhone,
|
||||
managerName: branch.contactName,
|
||||
managerPhone: null, // Branch에 없음
|
||||
remark: branch.remark,
|
||||
);
|
||||
}
|
||||
}
|
||||
473
lib/data/repositories/equipment_repository_impl.dart
Normal file
473
lib/data/repositories/equipment_repository_impl.dart
Normal file
@@ -0,0 +1,473 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:superport/core/errors/exceptions.dart';
|
||||
import 'package:superport/core/errors/failures.dart';
|
||||
import 'package:superport/data/datasources/remote/equipment_remote_datasource.dart';
|
||||
import 'package:superport/data/models/equipment/equipment_dto.dart';
|
||||
import 'package:superport/data/models/equipment/equipment_in_request.dart';
|
||||
import 'package:superport/data/models/equipment/equipment_out_request.dart';
|
||||
import 'package:superport/data/models/equipment/equipment_request.dart';
|
||||
import 'package:superport/domain/repositories/equipment_repository.dart';
|
||||
import 'package:superport/models/equipment_unified_model.dart';
|
||||
|
||||
class EquipmentRepositoryImpl implements EquipmentRepository {
|
||||
final EquipmentRemoteDataSource _remoteDataSource;
|
||||
|
||||
EquipmentRepositoryImpl(this._remoteDataSource);
|
||||
|
||||
@override
|
||||
Future<Either<Failure, List<EquipmentIn>>> getEquipmentIns({
|
||||
int? page,
|
||||
int? limit,
|
||||
String? search,
|
||||
String? sortBy,
|
||||
String? sortOrder,
|
||||
}) async {
|
||||
try {
|
||||
final response = await _remoteDataSource.getEquipments(
|
||||
page: page ?? 1,
|
||||
perPage: limit ?? 20,
|
||||
status: 'IN_WAREHOUSE',
|
||||
search: search,
|
||||
);
|
||||
|
||||
final equipmentIns = response.items.map((dto) =>
|
||||
EquipmentIn(
|
||||
id: dto.id,
|
||||
equipment: Equipment(
|
||||
id: dto.id,
|
||||
manufacturer: dto.manufacturer,
|
||||
name: dto.modelName ?? '',
|
||||
category: 'N/A', // EquipmentListDto에는 category 필드가 없음
|
||||
subCategory: 'N/A', // EquipmentListDto에는 category 필드가 없음
|
||||
subSubCategory: 'N/A', // EquipmentListDto에는 category 필드가 없음
|
||||
serialNumber: dto.serialNumber,
|
||||
quantity: 1,
|
||||
),
|
||||
inDate: dto.createdAt,
|
||||
status: 'I',
|
||||
type: '신제품',
|
||||
warehouseLocation: dto.warehouseName,
|
||||
remark: null,
|
||||
)
|
||||
).toList();
|
||||
|
||||
return Right(equipmentIns);
|
||||
} on ServerException catch (e) {
|
||||
return Left(ServerFailure(message: e.message ?? '서버 오류가 발생했습니다'));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(message: '장비 입고 목록 조회 실패: $e'));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, EquipmentIn>> getEquipmentInById(int id) async {
|
||||
try {
|
||||
final response = await _remoteDataSource.getEquipmentDetail(id);
|
||||
|
||||
final equipmentIn = EquipmentIn(
|
||||
id: response.id,
|
||||
equipment: Equipment(
|
||||
id: response.id,
|
||||
manufacturer: response.manufacturer,
|
||||
name: response.modelName ?? '',
|
||||
category: response.category1 ?? '',
|
||||
subCategory: response.category2 ?? '',
|
||||
subSubCategory: response.category3 ?? '',
|
||||
serialNumber: response.serialNumber,
|
||||
barcode: response.barcode,
|
||||
quantity: 1,
|
||||
inDate: response.purchaseDate,
|
||||
remark: response.remark,
|
||||
),
|
||||
inDate: response.purchaseDate ?? DateTime.now(),
|
||||
status: 'I',
|
||||
type: '신제품',
|
||||
warehouseLocation: null,
|
||||
remark: response.remark,
|
||||
);
|
||||
|
||||
return Right(equipmentIn);
|
||||
} on ServerException catch (e) {
|
||||
return Left(ServerFailure(message: e.message ?? '서버 오류가 발생했습니다'));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(message: '장비 입고 상세 조회 실패: $e'));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, EquipmentIn>> createEquipmentIn(EquipmentIn equipmentIn) async {
|
||||
try {
|
||||
final request = EquipmentInRequest(
|
||||
equipmentId: equipmentIn.equipment.id ?? 0,
|
||||
quantity: equipmentIn.equipment.quantity,
|
||||
warehouseLocationId: 0, // TODO: warehouseLocation string을 ID로 변환 필요
|
||||
notes: equipmentIn.remark,
|
||||
);
|
||||
|
||||
final response = await _remoteDataSource.equipmentIn(request);
|
||||
|
||||
final newEquipmentIn = EquipmentIn(
|
||||
id: response.transactionId,
|
||||
equipment: Equipment(
|
||||
id: response.equipmentId,
|
||||
manufacturer: 'N/A', // 트랜잭션 응답에는 제조사 정보 없음
|
||||
name: 'N/A', // 트랜잭션 응답에는 모델명 정보 없음
|
||||
category: 'N/A', // 트랜잭션 응답에는 카테고리 정보 없음
|
||||
subCategory: 'N/A', // 트랜잭션 응답에는 카테고리 정보 없음
|
||||
subSubCategory: 'N/A', // 트랜잭션 응답에는 카테고리 정보 없음
|
||||
serialNumber: null,
|
||||
quantity: response.quantity,
|
||||
),
|
||||
inDate: response.transactionDate,
|
||||
status: 'I',
|
||||
type: '신제품',
|
||||
warehouseLocation: null,
|
||||
remark: response.message,
|
||||
);
|
||||
|
||||
return Right(newEquipmentIn);
|
||||
} on ServerException catch (e) {
|
||||
return Left(ServerFailure(message: e.message ?? '서버 오류가 발생했습니다'));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(message: '장비 입고 생성 실패: $e'));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, EquipmentIn>> updateEquipmentIn(int id, EquipmentIn equipmentIn) async {
|
||||
try {
|
||||
final request = UpdateEquipmentRequest(
|
||||
manufacturer: equipmentIn.equipment.manufacturer,
|
||||
modelName: equipmentIn.equipment.name,
|
||||
category1: equipmentIn.equipment.category,
|
||||
category2: equipmentIn.equipment.subCategory,
|
||||
category3: equipmentIn.equipment.subSubCategory,
|
||||
serialNumber: equipmentIn.equipment.serialNumber,
|
||||
barcode: equipmentIn.equipment.barcode,
|
||||
purchaseDate: equipmentIn.inDate,
|
||||
remark: equipmentIn.remark,
|
||||
);
|
||||
|
||||
final response = await _remoteDataSource.updateEquipment(id, request);
|
||||
|
||||
final updatedEquipmentIn = EquipmentIn(
|
||||
id: response.id,
|
||||
equipment: Equipment(
|
||||
id: response.id,
|
||||
manufacturer: response.manufacturer,
|
||||
name: response.modelName ?? '',
|
||||
category: response.category1 ?? '',
|
||||
subCategory: response.category2 ?? '',
|
||||
subSubCategory: response.category3 ?? '',
|
||||
serialNumber: response.serialNumber,
|
||||
barcode: response.barcode,
|
||||
quantity: 1,
|
||||
inDate: response.purchaseDate,
|
||||
remark: response.remark,
|
||||
),
|
||||
inDate: response.purchaseDate ?? DateTime.now(),
|
||||
status: 'I',
|
||||
type: '신제품',
|
||||
warehouseLocation: null,
|
||||
remark: response.remark,
|
||||
);
|
||||
|
||||
return Right(updatedEquipmentIn);
|
||||
} on ServerException catch (e) {
|
||||
return Left(ServerFailure(message: e.message ?? '서버 오류가 발생했습니다'));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(message: '장비 입고 수정 실패: $e'));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, void>> deleteEquipmentIn(int id) async {
|
||||
try {
|
||||
await _remoteDataSource.deleteEquipment(id);
|
||||
return const Right(null);
|
||||
} on ServerException catch (e) {
|
||||
return Left(ServerFailure(message: e.message ?? '서버 오류가 발생했습니다'));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(message: '장비 입고 삭제 실패: $e'));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, List<EquipmentOut>>> getEquipmentOuts({
|
||||
int? page,
|
||||
int? limit,
|
||||
String? search,
|
||||
String? sortBy,
|
||||
String? sortOrder,
|
||||
}) async {
|
||||
try {
|
||||
final response = await _remoteDataSource.getEquipments(
|
||||
page: page ?? 1,
|
||||
perPage: limit ?? 20,
|
||||
status: 'SHIPPED',
|
||||
search: search,
|
||||
);
|
||||
|
||||
final equipmentOuts = response.items.map((dto) =>
|
||||
EquipmentOut(
|
||||
id: dto.id,
|
||||
equipment: Equipment(
|
||||
id: dto.id,
|
||||
manufacturer: dto.manufacturer,
|
||||
name: dto.modelName ?? '',
|
||||
category: 'N/A', // EquipmentListDto에는 category 필드가 없음
|
||||
subCategory: 'N/A', // EquipmentListDto에는 category 필드가 없음
|
||||
subSubCategory: 'N/A', // EquipmentListDto에는 category 필드가 없음
|
||||
serialNumber: dto.serialNumber,
|
||||
quantity: 1,
|
||||
),
|
||||
outDate: dto.createdAt,
|
||||
status: 'O',
|
||||
company: dto.companyName,
|
||||
remark: null,
|
||||
)
|
||||
).toList();
|
||||
|
||||
return Right(equipmentOuts);
|
||||
} on ServerException catch (e) {
|
||||
return Left(ServerFailure(message: e.message ?? '서버 오류가 발생했습니다'));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(message: '장비 출고 목록 조회 실패: $e'));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, EquipmentOut>> getEquipmentOutById(int id) async {
|
||||
try {
|
||||
final response = await _remoteDataSource.getEquipmentDetail(id);
|
||||
|
||||
final equipmentOut = EquipmentOut(
|
||||
id: response.id,
|
||||
equipment: Equipment(
|
||||
id: response.id,
|
||||
manufacturer: response.manufacturer,
|
||||
name: response.modelName ?? '',
|
||||
category: response.category1 ?? '',
|
||||
subCategory: response.category2 ?? '',
|
||||
subSubCategory: response.category3 ?? '',
|
||||
serialNumber: response.serialNumber,
|
||||
barcode: response.barcode,
|
||||
quantity: 1,
|
||||
inDate: response.purchaseDate,
|
||||
remark: response.remark,
|
||||
),
|
||||
outDate: DateTime.now(), // TODO: 실제 출고일 정보 필요
|
||||
status: 'O',
|
||||
company: null,
|
||||
remark: response.remark,
|
||||
);
|
||||
|
||||
return Right(equipmentOut);
|
||||
} on ServerException catch (e) {
|
||||
return Left(ServerFailure(message: e.message ?? '서버 오류가 발생했습니다'));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(message: '장비 출고 상세 조회 실패: $e'));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, EquipmentOut>> createEquipmentOut(EquipmentOut equipmentOut) async {
|
||||
try {
|
||||
final request = EquipmentOutRequest(
|
||||
equipmentId: equipmentOut.equipment.id ?? 0,
|
||||
quantity: equipmentOut.equipment.quantity,
|
||||
companyId: 0, // TODO: company string을 ID로 변환 필요
|
||||
branchId: null,
|
||||
notes: equipmentOut.remark,
|
||||
);
|
||||
|
||||
final response = await _remoteDataSource.equipmentOut(request);
|
||||
|
||||
final newEquipmentOut = EquipmentOut(
|
||||
id: response.transactionId,
|
||||
equipment: Equipment(
|
||||
id: response.equipmentId,
|
||||
manufacturer: 'N/A', // 트랜잭션 응답에는 제조사 정보 없음
|
||||
name: 'N/A', // 트랜잭션 응답에는 모델명 정보 없음
|
||||
category: 'N/A', // 트랜잭션 응답에는 카테고리 정보 없음
|
||||
subCategory: 'N/A', // 트랜잭션 응답에는 카테고리 정보 없음
|
||||
subSubCategory: 'N/A', // 트랜잭션 응답에는 카테고리 정보 없음
|
||||
serialNumber: null,
|
||||
quantity: response.quantity,
|
||||
),
|
||||
outDate: response.transactionDate,
|
||||
status: 'O',
|
||||
company: null,
|
||||
remark: response.message,
|
||||
);
|
||||
|
||||
return Right(newEquipmentOut);
|
||||
} on ServerException catch (e) {
|
||||
return Left(ServerFailure(message: e.message ?? '서버 오류가 발생했습니다'));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(message: '장비 출고 생성 실패: $e'));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, EquipmentOut>> updateEquipmentOut(int id, EquipmentOut equipmentOut) async {
|
||||
try {
|
||||
final request = UpdateEquipmentRequest(
|
||||
currentCompanyId: 0, // TODO: company string을 ID로 변환 필요
|
||||
currentBranchId: null,
|
||||
remark: equipmentOut.remark,
|
||||
);
|
||||
|
||||
final response = await _remoteDataSource.updateEquipment(id, request);
|
||||
|
||||
final updatedEquipmentOut = EquipmentOut(
|
||||
id: response.id,
|
||||
equipment: Equipment(
|
||||
id: response.id,
|
||||
manufacturer: response.manufacturer,
|
||||
name: response.modelName ?? '',
|
||||
category: response.category1 ?? '',
|
||||
subCategory: response.category2 ?? '',
|
||||
subSubCategory: response.category3 ?? '',
|
||||
serialNumber: response.serialNumber,
|
||||
barcode: response.barcode,
|
||||
quantity: 1,
|
||||
inDate: response.purchaseDate,
|
||||
remark: response.remark,
|
||||
),
|
||||
outDate: DateTime.now(), // TODO: 실제 출고일 정보 필요
|
||||
status: 'O',
|
||||
company: null,
|
||||
remark: response.remark,
|
||||
);
|
||||
|
||||
return Right(updatedEquipmentOut);
|
||||
} on ServerException catch (e) {
|
||||
return Left(ServerFailure(message: e.message ?? '서버 오류가 발생했습니다'));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(message: '장비 출고 수정 실패: $e'));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, void>> deleteEquipmentOut(int id) async {
|
||||
try {
|
||||
await _remoteDataSource.deleteEquipment(id);
|
||||
return const Right(null);
|
||||
} on ServerException catch (e) {
|
||||
return Left(ServerFailure(message: e.message ?? '서버 오류가 발생했습니다'));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(message: '장비 출고 삭제 실패: $e'));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, List<EquipmentOut>>> createBatchEquipmentOut(List<EquipmentOut> equipmentOuts) async {
|
||||
try {
|
||||
final results = <EquipmentOut>[];
|
||||
|
||||
for (final equipmentOut in equipmentOuts) {
|
||||
final request = EquipmentOutRequest(
|
||||
equipmentId: equipmentOut.equipment.id ?? 0,
|
||||
quantity: equipmentOut.equipment.quantity,
|
||||
companyId: 0, // TODO: company string을 ID로 변환 필요
|
||||
branchId: null,
|
||||
notes: equipmentOut.remark,
|
||||
);
|
||||
|
||||
final response = await _remoteDataSource.equipmentOut(request);
|
||||
|
||||
results.add(EquipmentOut(
|
||||
id: response.transactionId,
|
||||
equipment: Equipment(
|
||||
id: response.equipmentId,
|
||||
manufacturer: 'N/A', // 트랜잭션 응답에는 제조사 정보 없음
|
||||
name: 'N/A', // 트랜잭션 응답에는 모델명 정보 없음
|
||||
category: 'N/A', // 트랜잭션 응답에는 카테고리 정보 없음
|
||||
subCategory: 'N/A', // 트랜잭션 응답에는 카테고리 정보 없음
|
||||
subSubCategory: 'N/A', // 트랜잭션 응답에는 카테고리 정보 없음
|
||||
serialNumber: null,
|
||||
quantity: response.quantity,
|
||||
),
|
||||
outDate: response.transactionDate,
|
||||
status: 'O',
|
||||
company: null,
|
||||
remark: response.message,
|
||||
));
|
||||
}
|
||||
|
||||
return Right(results);
|
||||
} on ServerException catch (e) {
|
||||
return Left(ServerFailure(message: e.message ?? '서버 오류가 발생했습니다'));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(message: '장비 일괄 출고 실패: $e'));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, List<String>>> getManufacturers() async {
|
||||
try {
|
||||
// TODO: 실제 API 엔드포인트 구현 필요
|
||||
return const Right(['삼성', 'LG', 'Apple', 'Dell', 'HP']);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(message: '제조사 목록 조회 실패: $e'));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, List<String>>> getEquipmentNames() async {
|
||||
try {
|
||||
// TODO: 실제 API 엔드포인트 구현 필요
|
||||
return const Right(['노트북', '모니터', '키보드', '마우스', '프린터']);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(message: '장비명 목록 조회 실패: $e'));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, List<dynamic>>> getEquipmentHistory(int equipmentId) async {
|
||||
try {
|
||||
final history = await _remoteDataSource.getEquipmentHistory(equipmentId);
|
||||
return Right(history);
|
||||
} on ServerException catch (e) {
|
||||
return Left(ServerFailure(message: e.message ?? '서버 오류가 발생했습니다'));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(message: '장비 이력 조회 실패: $e'));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, List<Equipment>>> searchEquipment({
|
||||
String? manufacturer,
|
||||
String? name,
|
||||
String? category,
|
||||
String? serialNumber,
|
||||
}) async {
|
||||
try {
|
||||
final response = await _remoteDataSource.getEquipments(
|
||||
search: serialNumber ?? name ?? manufacturer,
|
||||
page: 1,
|
||||
perPage: 50,
|
||||
);
|
||||
|
||||
final equipments = response.items.map((dto) =>
|
||||
Equipment(
|
||||
id: dto.id,
|
||||
manufacturer: dto.manufacturer,
|
||||
name: dto.modelName ?? '',
|
||||
category: 'N/A', // EquipmentListDto에는 category 필드가 없음
|
||||
subCategory: 'N/A', // EquipmentListDto에는 category 필드가 없음
|
||||
subSubCategory: 'N/A', // EquipmentListDto에는 category 필드가 없음
|
||||
serialNumber: dto.serialNumber,
|
||||
quantity: 1,
|
||||
)
|
||||
).toList();
|
||||
|
||||
return Right(equipments);
|
||||
} on ServerException catch (e) {
|
||||
return Left(ServerFailure(message: e.message ?? '서버 오류가 발생했습니다'));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(message: '장비 검색 실패: $e'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import '../models/license/license_dto.dart';
|
||||
|
||||
/// 라이선스 Repository 인터페이스
|
||||
abstract class LicenseRepository {
|
||||
/// 라이선스 목록 조회
|
||||
Future<LicenseListResponseDto> getLicenses({
|
||||
int page = 1,
|
||||
int perPage = 20,
|
||||
String? search,
|
||||
Map<String, dynamic>? filters,
|
||||
});
|
||||
|
||||
/// 라이선스 상세 조회
|
||||
Future<LicenseDto> getLicenseDetail(int id);
|
||||
|
||||
/// 라이선스 생성
|
||||
Future<LicenseDto> createLicense(Map<String, dynamic> data);
|
||||
|
||||
/// 라이선스 수정
|
||||
Future<LicenseDto> updateLicense(int id, Map<String, dynamic> data);
|
||||
|
||||
/// 라이선스 삭제
|
||||
Future<void> deleteLicense(int id);
|
||||
}
|
||||
@@ -1,58 +1,323 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import '../../core/errors/failures.dart';
|
||||
import '../../domain/repositories/license_repository.dart';
|
||||
import '../../models/license_model.dart';
|
||||
import '../datasources/remote/license_remote_datasource.dart';
|
||||
import '../models/common/paginated_response.dart';
|
||||
import '../models/dashboard/license_expiry_summary.dart';
|
||||
import '../models/license/license_dto.dart';
|
||||
import '../models/license/license_request_dto.dart';
|
||||
import 'license_repository.dart';
|
||||
|
||||
/// 라이선스 Repository 구현체
|
||||
/// 라이선스 및 유지보수 계약 관리 작업을 처리하며 도메인 모델과 API DTO 간 변환을 담당
|
||||
@Injectable(as: LicenseRepository)
|
||||
class LicenseRepositoryImpl implements LicenseRepository {
|
||||
final LicenseRemoteDataSource remoteDataSource;
|
||||
|
||||
LicenseRepositoryImpl(this.remoteDataSource);
|
||||
LicenseRepositoryImpl({required this.remoteDataSource});
|
||||
|
||||
@override
|
||||
Future<LicenseListResponseDto> getLicenses({
|
||||
int page = 1,
|
||||
int perPage = 20,
|
||||
Future<Either<Failure, PaginatedResponse<License>>> getLicenses({
|
||||
int? page,
|
||||
int? limit,
|
||||
String? search,
|
||||
Map<String, dynamic>? filters,
|
||||
int? companyId,
|
||||
String? equipmentType,
|
||||
String? expiryStatus,
|
||||
String? sortBy,
|
||||
String? sortOrder,
|
||||
}) async {
|
||||
// 검색 및 필터 파라미터를 DataSource 형식에 맞게 변환
|
||||
bool? isActive = filters?['is_active'];
|
||||
int? companyId = filters?['company_id'];
|
||||
int? assignedUserId = filters?['assigned_user_id'];
|
||||
String? licenseType = filters?['license_type'];
|
||||
|
||||
return await remoteDataSource.getLicenses(
|
||||
page: page,
|
||||
perPage: perPage,
|
||||
isActive: isActive,
|
||||
companyId: companyId,
|
||||
assignedUserId: assignedUserId,
|
||||
licenseType: licenseType,
|
||||
try {
|
||||
final result = await remoteDataSource.getLicenses(
|
||||
page: page ?? 1,
|
||||
perPage: limit ?? 20,
|
||||
isActive: null, // expiryStatus에 따른 필터링 로직 필요 시 추가
|
||||
companyId: companyId,
|
||||
assignedUserId: null,
|
||||
licenseType: equipmentType,
|
||||
);
|
||||
|
||||
// DTO를 도메인 모델로 변환
|
||||
final licenses = result.items.map((dto) => _mapDtoToDomain(dto)).toList();
|
||||
|
||||
// 검색 필터링 (서버에서 지원하지 않는 경우 클라이언트 측에서 처리)
|
||||
if (search != null && search.isNotEmpty) {
|
||||
final filteredLicenses = licenses.where((license) {
|
||||
final searchLower = search.toLowerCase();
|
||||
return (license.productName?.toLowerCase().contains(searchLower) ?? false) ||
|
||||
(license.companyName?.toLowerCase().contains(searchLower) ?? false) ||
|
||||
(license.vendor?.toLowerCase().contains(searchLower) ?? false);
|
||||
}).toList();
|
||||
|
||||
final paginatedResult = PaginatedResponse<License>(
|
||||
items: filteredLicenses,
|
||||
page: result.page,
|
||||
size: 20,
|
||||
totalElements: filteredLicenses.length,
|
||||
totalPages: (filteredLicenses.length / 20).ceil(),
|
||||
first: result.page == 0,
|
||||
last: result.page >= (filteredLicenses.length / 20).ceil() - 1,
|
||||
);
|
||||
|
||||
return Right(paginatedResult);
|
||||
}
|
||||
|
||||
final paginatedResult = PaginatedResponse<License>(
|
||||
items: licenses,
|
||||
page: result.page,
|
||||
size: 20,
|
||||
totalElements: result.total,
|
||||
totalPages: (result.total / 20).ceil(),
|
||||
first: result.page == 0,
|
||||
last: result.page >= (result.total / 20).ceil() - 1,
|
||||
);
|
||||
|
||||
return Right(paginatedResult);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(
|
||||
message: '라이선스 목록 조회 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, License>> getLicenseById(int id) async {
|
||||
try {
|
||||
final result = await remoteDataSource.getLicenseById(id);
|
||||
final license = _mapDtoToDomain(result);
|
||||
return Right(license);
|
||||
} catch (e) {
|
||||
if (e.toString().contains('404')) {
|
||||
return Left(NotFoundFailure(
|
||||
message: '해당 라이선스를 찾을 수 없습니다.',
|
||||
resourceType: 'License',
|
||||
resourceId: id.toString(),
|
||||
));
|
||||
}
|
||||
return Left(ServerFailure(
|
||||
message: '라이선스 상세 정보 조회 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, License>> createLicense(License license) async {
|
||||
try {
|
||||
final request = _mapDomainToCreateRequest(license);
|
||||
final result = await remoteDataSource.createLicense(request);
|
||||
final createdLicense = _mapDtoToDomain(result);
|
||||
return Right(createdLicense);
|
||||
} catch (e) {
|
||||
if (e.toString().contains('중복')) {
|
||||
return Left(DuplicateFailure(
|
||||
message: '이미 존재하는 라이선스입니다.',
|
||||
field: 'licenseKey',
|
||||
value: license.licenseKey,
|
||||
));
|
||||
}
|
||||
if (e.toString().contains('유효성')) {
|
||||
return Left(ValidationFailure(
|
||||
message: '입력 데이터가 올바르지 않습니다.',
|
||||
));
|
||||
}
|
||||
return Left(ServerFailure(
|
||||
message: '라이선스 생성 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, License>> updateLicense(int id, License license) async {
|
||||
try {
|
||||
final request = _mapDomainToUpdateRequest(license);
|
||||
final result = await remoteDataSource.updateLicense(id, request);
|
||||
final updatedLicense = _mapDtoToDomain(result);
|
||||
return Right(updatedLicense);
|
||||
} catch (e) {
|
||||
if (e.toString().contains('404')) {
|
||||
return Left(NotFoundFailure(
|
||||
message: '수정할 라이선스를 찾을 수 없습니다.',
|
||||
resourceType: 'License',
|
||||
resourceId: id.toString(),
|
||||
));
|
||||
}
|
||||
if (e.toString().contains('중복')) {
|
||||
return Left(DuplicateFailure(
|
||||
message: '이미 존재하는 라이선스키입니다.',
|
||||
field: 'licenseKey',
|
||||
value: license.licenseKey,
|
||||
));
|
||||
}
|
||||
return Left(ServerFailure(
|
||||
message: '라이선스 정보 수정 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, void>> deleteLicense(int id) async {
|
||||
try {
|
||||
await remoteDataSource.deleteLicense(id);
|
||||
return const Right(null);
|
||||
} catch (e) {
|
||||
if (e.toString().contains('404')) {
|
||||
return Left(NotFoundFailure(
|
||||
message: '삭제할 라이선스를 찾을 수 없습니다.',
|
||||
resourceType: 'License',
|
||||
resourceId: id.toString(),
|
||||
));
|
||||
}
|
||||
if (e.toString().contains('참조')) {
|
||||
return Left(BusinessFailure(
|
||||
message: '해당 라이선스에 연결된 데이터가 있어 삭제할 수 없습니다.',
|
||||
));
|
||||
}
|
||||
return Left(ServerFailure(
|
||||
message: '라이선스 삭제 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, List<License>>> getExpiringLicenses({int days = 30, int? companyId}) async {
|
||||
// TODO: API에서 만료 예정 라이선스 조회 기능이 구현되면 추가
|
||||
return const Left(ServerFailure(
|
||||
message: '만료 예정 라이선스 조회 기능이 아직 구현되지 않았습니다.',
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, List<License>>> getExpiredLicenses({int? companyId}) async {
|
||||
// TODO: API에서 만료된 라이선스 조회 기능이 구현되면 추가
|
||||
return const Left(ServerFailure(
|
||||
message: '만료된 라이선스 조회 기능이 아직 구현되지 않았습니다.',
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, LicenseExpirySummary>> getLicenseExpirySummary() async {
|
||||
// TODO: API에서 라이선스 만료 요약 기능이 구현되면 추가
|
||||
return const Left(ServerFailure(
|
||||
message: '라이선스 만료 요약 조회 기능이 아직 구현되지 않았습니다.',
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, License>> renewLicense(int id, DateTime newExpiryDate, {double? renewalCost, String? renewalNote}) async {
|
||||
// TODO: API에서 라이선스 갱신 기능이 구현되면 추가
|
||||
return const Left(ServerFailure(
|
||||
message: '라이선스 갱신 기능이 아직 구현되지 않았습니다.',
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, Map<String, int>>> getLicenseStatsByCompany(int companyId) async {
|
||||
// TODO: API에서 회사별 라이선스 통계 기능이 구현되면 추가
|
||||
return const Left(ServerFailure(
|
||||
message: '회사별 라이선스 통계 기능이 아직 구현되지 않았습니다.',
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, Map<String, int>>> getLicenseCountByType() async {
|
||||
// TODO: API에서 라이선스 유형별 통계 기능이 구현되면 추가
|
||||
return const Left(ServerFailure(
|
||||
message: '라이선스 유형별 통계 기능이 아직 구현되지 않았습니다.',
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, void>> setExpiryNotification(int licenseId, {int notifyDays = 30}) async {
|
||||
// TODO: API에서 만료 알림 설정 기능이 구현되면 추가
|
||||
return const Left(ServerFailure(
|
||||
message: '만료 알림 설정 기능이 아직 구현되지 않았습니다.',
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, List<License>>> searchLicenses(String query, {int? companyId, int? limit}) async {
|
||||
try {
|
||||
final result = await remoteDataSource.getLicenses(
|
||||
page: 1,
|
||||
perPage: limit ?? 10,
|
||||
companyId: companyId,
|
||||
);
|
||||
|
||||
// 클라이언트 측에서 검색 필터링
|
||||
final searchLower = query.toLowerCase();
|
||||
final filteredLicenses = result.items
|
||||
.where((dto) {
|
||||
final license = _mapDtoToDomain(dto);
|
||||
return (license.productName?.toLowerCase().contains(searchLower) ?? false) ||
|
||||
(license.companyName?.toLowerCase().contains(searchLower) ?? false) ||
|
||||
(license.vendor?.toLowerCase().contains(searchLower) ?? false);
|
||||
})
|
||||
.map((dto) => _mapDtoToDomain(dto))
|
||||
.toList();
|
||||
|
||||
return Right(filteredLicenses);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(
|
||||
message: '라이선스 검색 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Private 매퍼 메서드들
|
||||
|
||||
License _mapDtoToDomain(LicenseDto dto) {
|
||||
return License(
|
||||
id: dto.id,
|
||||
licenseKey: dto.licenseKey,
|
||||
productName: dto.productName,
|
||||
vendor: dto.vendor,
|
||||
licenseType: dto.licenseType,
|
||||
userCount: dto.userCount,
|
||||
purchaseDate: dto.purchaseDate,
|
||||
expiryDate: dto.expiryDate,
|
||||
purchasePrice: dto.purchasePrice,
|
||||
companyId: dto.companyId,
|
||||
branchId: dto.branchId,
|
||||
assignedUserId: dto.assignedUserId,
|
||||
remark: dto.remark,
|
||||
isActive: dto.isActive,
|
||||
createdAt: dto.createdAt,
|
||||
updatedAt: dto.updatedAt,
|
||||
companyName: dto.companyName,
|
||||
branchName: dto.branchName,
|
||||
assignedUserName: dto.assignedUserName,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<LicenseDto> getLicenseDetail(int id) async {
|
||||
return await remoteDataSource.getLicenseById(id);
|
||||
|
||||
CreateLicenseRequest _mapDomainToCreateRequest(License license) {
|
||||
return CreateLicenseRequest(
|
||||
licenseKey: license.licenseKey,
|
||||
productName: license.productName,
|
||||
vendor: license.vendor,
|
||||
licenseType: license.licenseType,
|
||||
userCount: license.userCount,
|
||||
purchaseDate: license.purchaseDate,
|
||||
expiryDate: license.expiryDate,
|
||||
purchasePrice: license.purchasePrice,
|
||||
companyId: license.companyId,
|
||||
branchId: license.branchId,
|
||||
remark: license.remark,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<LicenseDto> createLicense(Map<String, dynamic> data) async {
|
||||
final request = CreateLicenseRequest.fromJson(data);
|
||||
return await remoteDataSource.createLicense(request);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<LicenseDto> updateLicense(int id, Map<String, dynamic> data) async {
|
||||
final request = UpdateLicenseRequest.fromJson(data);
|
||||
return await remoteDataSource.updateLicense(id, request);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteLicense(int id) async {
|
||||
await remoteDataSource.deleteLicense(id);
|
||||
|
||||
UpdateLicenseRequest _mapDomainToUpdateRequest(License license) {
|
||||
return UpdateLicenseRequest(
|
||||
licenseKey: license.licenseKey,
|
||||
productName: license.productName,
|
||||
vendor: license.vendor,
|
||||
licenseType: license.licenseType,
|
||||
userCount: license.userCount,
|
||||
purchaseDate: license.purchaseDate,
|
||||
expiryDate: license.expiryDate,
|
||||
purchasePrice: license.purchasePrice,
|
||||
remark: license.remark,
|
||||
isActive: license.isActive,
|
||||
);
|
||||
}
|
||||
}
|
||||
369
lib/data/repositories/user_repository_impl.dart
Normal file
369
lib/data/repositories/user_repository_impl.dart
Normal file
@@ -0,0 +1,369 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import '../../core/errors/failures.dart';
|
||||
import '../../domain/repositories/user_repository.dart';
|
||||
import '../../models/user_model.dart';
|
||||
import '../datasources/remote/user_remote_datasource.dart';
|
||||
import '../models/common/paginated_response.dart';
|
||||
import '../models/user/user_dto.dart';
|
||||
|
||||
/// 사용자 관리 Repository 구현체
|
||||
/// 사용자 계정 CRUD 및 권한 관리 작업을 처리하며 도메인 모델과 API DTO 간 변환을 담당
|
||||
@Injectable(as: UserRepository)
|
||||
class UserRepositoryImpl implements UserRepository {
|
||||
final UserRemoteDataSource remoteDataSource;
|
||||
|
||||
UserRepositoryImpl({required this.remoteDataSource});
|
||||
|
||||
@override
|
||||
Future<Either<Failure, PaginatedResponse<User>>> getUsers({
|
||||
int? page,
|
||||
int? limit,
|
||||
String? search,
|
||||
String? role,
|
||||
int? companyId,
|
||||
bool? isActive,
|
||||
String? sortBy,
|
||||
String? sortOrder,
|
||||
}) async {
|
||||
try {
|
||||
final result = await remoteDataSource.getUsers(
|
||||
page: page ?? 1,
|
||||
perPage: limit ?? 20,
|
||||
isActive: isActive,
|
||||
companyId: companyId,
|
||||
role: role,
|
||||
);
|
||||
|
||||
// DTO를 도메인 모델로 변환
|
||||
final users = result.items.map((dto) => _mapDtoToDomain(dto)).toList();
|
||||
|
||||
// 검색 필터링 (서버에서 지원하지 않는 경우 클라이언트 측에서 처리)
|
||||
if (search != null && search.isNotEmpty) {
|
||||
final filteredUsers = users.where((user) {
|
||||
final searchLower = search.toLowerCase();
|
||||
return (user.username?.toLowerCase().contains(searchLower) ?? false) ||
|
||||
user.name.toLowerCase().contains(searchLower) ||
|
||||
(user.email?.toLowerCase().contains(searchLower) ?? false);
|
||||
}).toList();
|
||||
|
||||
final paginatedResult = PaginatedResponse<User>(
|
||||
items: filteredUsers,
|
||||
page: result.page,
|
||||
size: result.size,
|
||||
totalElements: filteredUsers.length,
|
||||
totalPages: (filteredUsers.length / result.size).ceil(),
|
||||
first: result.first,
|
||||
last: result.last,
|
||||
);
|
||||
|
||||
return Right(paginatedResult);
|
||||
}
|
||||
|
||||
final paginatedResult = PaginatedResponse<User>(
|
||||
items: users,
|
||||
page: result.page,
|
||||
size: result.size,
|
||||
totalElements: result.totalElements,
|
||||
totalPages: result.totalPages,
|
||||
first: result.first,
|
||||
last: result.last,
|
||||
);
|
||||
|
||||
return Right(paginatedResult);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(
|
||||
message: '사용자 목록 조회 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, User>> getUserById(int id) async {
|
||||
try {
|
||||
final result = await remoteDataSource.getUser(id);
|
||||
final user = _mapDtoToDomain(result);
|
||||
return Right(user);
|
||||
} catch (e) {
|
||||
if (e.toString().contains('404')) {
|
||||
return Left(NotFoundFailure(
|
||||
message: '해당 사용자를 찾을 수 없습니다.',
|
||||
resourceType: 'User',
|
||||
resourceId: id.toString(),
|
||||
));
|
||||
}
|
||||
return Left(ServerFailure(
|
||||
message: '사용자 상세 정보 조회 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, User>> createUser(User user, String password) async {
|
||||
try {
|
||||
final request = _mapDomainToCreateRequest(user, password);
|
||||
final result = await remoteDataSource.createUser(request);
|
||||
final createdUser = _mapDtoToDomain(result);
|
||||
return Right(createdUser);
|
||||
} catch (e) {
|
||||
if (e.toString().contains('중복')) {
|
||||
return Left(DuplicateFailure(
|
||||
message: '이미 사용 중인 이메일입니다.',
|
||||
field: 'username',
|
||||
value: user.username ?? '',
|
||||
));
|
||||
}
|
||||
if (e.toString().contains('유효성')) {
|
||||
return Left(ValidationFailure(
|
||||
message: '입력 데이터가 올바르지 않습니다.',
|
||||
));
|
||||
}
|
||||
return Left(ServerFailure(
|
||||
message: '사용자 생성 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, User>> updateUser(int id, User user) async {
|
||||
try {
|
||||
final request = _mapDomainToUpdateRequest(user);
|
||||
final result = await remoteDataSource.updateUser(id, request);
|
||||
final updatedUser = _mapDtoToDomain(result);
|
||||
return Right(updatedUser);
|
||||
} catch (e) {
|
||||
if (e.toString().contains('404')) {
|
||||
return Left(NotFoundFailure(
|
||||
message: '수정할 사용자를 찾을 수 없습니다.',
|
||||
resourceType: 'User',
|
||||
resourceId: id.toString(),
|
||||
));
|
||||
}
|
||||
if (e.toString().contains('중복')) {
|
||||
return Left(DuplicateFailure(
|
||||
message: '이미 사용 중인 이메일입니다.',
|
||||
field: 'username',
|
||||
value: user.username ?? '',
|
||||
));
|
||||
}
|
||||
return Left(ServerFailure(
|
||||
message: '사용자 정보 수정 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, void>> deleteUser(int id) async {
|
||||
try {
|
||||
await remoteDataSource.deleteUser(id);
|
||||
return const Right(null);
|
||||
} catch (e) {
|
||||
if (e.toString().contains('404')) {
|
||||
return Left(NotFoundFailure(
|
||||
message: '삭제할 사용자를 찾을 수 없습니다.',
|
||||
resourceType: 'User',
|
||||
resourceId: id.toString(),
|
||||
));
|
||||
}
|
||||
if (e.toString().contains('참조')) {
|
||||
return Left(BusinessFailure(
|
||||
message: '해당 사용자에 연결된 데이터가 있어 삭제할 수 없습니다.',
|
||||
));
|
||||
}
|
||||
return Left(ServerFailure(
|
||||
message: '사용자 삭제 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, User>> toggleUserStatus(int id) async {
|
||||
try {
|
||||
// 현재 사용자 정보 조회
|
||||
final currentUser = await remoteDataSource.getUser(id);
|
||||
final newStatus = !currentUser.isActive;
|
||||
|
||||
// 상태 업데이트
|
||||
final request = ChangeStatusRequest(isActive: newStatus);
|
||||
final updatedUser = await remoteDataSource.changeUserStatus(id, request);
|
||||
final user = _mapDtoToDomain(updatedUser);
|
||||
|
||||
return Right(user);
|
||||
} catch (e) {
|
||||
if (e.toString().contains('404')) {
|
||||
return Left(NotFoundFailure(
|
||||
message: '상태를 변경할 사용자를 찾을 수 없습니다.',
|
||||
resourceType: 'User',
|
||||
resourceId: id.toString(),
|
||||
));
|
||||
}
|
||||
return Left(ServerFailure(
|
||||
message: '사용자 상태 변경 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, void>> resetPassword(int id, String newPassword) async {
|
||||
try {
|
||||
// resetPassword 메서드가 데이터소스에 없으므로 changePassword 사용
|
||||
final request = ChangePasswordRequest(currentPassword: '', newPassword: newPassword);
|
||||
await remoteDataSource.changePassword(id, request);
|
||||
return const Right(null);
|
||||
} catch (e) {
|
||||
if (e.toString().contains('404')) {
|
||||
return Left(NotFoundFailure(
|
||||
message: '비밀번호를 재설정할 사용자를 찾을 수 없습니다.',
|
||||
resourceType: 'User',
|
||||
resourceId: id.toString(),
|
||||
));
|
||||
}
|
||||
return Left(ServerFailure(
|
||||
message: '비밀번호 재설정 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, User>> changeUserRole(int id, String newRole) async {
|
||||
try {
|
||||
// changeUserRole 메서드가 데이터소스에 없으므로 updateUser 사용
|
||||
final request = UpdateUserRequest(role: newRole);
|
||||
final updatedUser = await remoteDataSource.updateUser(id, request);
|
||||
final user = _mapDtoToDomain(updatedUser);
|
||||
|
||||
return Right(user);
|
||||
} catch (e) {
|
||||
if (e.toString().contains('404')) {
|
||||
return Left(NotFoundFailure(
|
||||
message: '역할을 변경할 사용자를 찾을 수 없습니다.',
|
||||
resourceType: 'User',
|
||||
resourceId: id.toString(),
|
||||
));
|
||||
}
|
||||
return Left(ServerFailure(
|
||||
message: '사용자 역할 변경 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, bool>> isDuplicateUsername(String username, {int? excludeId}) async {
|
||||
try {
|
||||
final isDuplicate = await remoteDataSource.checkDuplicateUsername(username);
|
||||
// excludeId가 있는 경우 해당 ID 제외 로직 추가 필요
|
||||
return Right(isDuplicate);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(
|
||||
message: '중복 사용자명 확인 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, List<User>>> getUsersByCompany(int companyId, {bool includeInactive = false}) async {
|
||||
try {
|
||||
// getUsersByCompany 메서드가 없으므로 getUsers로 대체
|
||||
final result = await remoteDataSource.getUsers(
|
||||
companyId: companyId,
|
||||
isActive: includeInactive ? null : true,
|
||||
);
|
||||
final users = result.users.map((dto) => _mapDtoToDomain(dto)).toList();
|
||||
return Right(users);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(
|
||||
message: '회사별 사용자 조회 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, Map<String, int>>> getUserCountByRole() async {
|
||||
// TODO: API에서 역할별 사용자 수 통계 기능이 구현되면 추가
|
||||
return const Left(ServerFailure(
|
||||
message: '역할별 사용자 수 통계 기능이 아직 구현되지 않았습니다.',
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, List<User>>> searchUsers(String query, {int? companyId, int? limit}) async {
|
||||
try {
|
||||
final result = await remoteDataSource.searchUsers(
|
||||
query: query,
|
||||
companyId: companyId,
|
||||
perPage: limit ?? 10,
|
||||
);
|
||||
final users = result.users.map((dto) => _mapDtoToDomain(dto)).toList();
|
||||
return Right(users);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(
|
||||
message: '사용자 검색 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, void>> updateLastLoginTime(int id) async {
|
||||
try {
|
||||
// updateLastLoginTime 메서드가 데이터소스에 없으므로 비어있는 구현
|
||||
// TODO: API에서 지원되면 구현
|
||||
throw UnimplementedError('마지막 로그인 시간 업데이트 기능이 아직 구현되지 않았습니다.');
|
||||
return const Right(null);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(
|
||||
message: '마지막 로그인 시간 업데이트 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Private 매퍼 메서드들
|
||||
|
||||
User _mapDtoToDomain(UserDto dto) {
|
||||
return User(
|
||||
id: dto.id,
|
||||
companyId: dto.companyId ?? 0,
|
||||
branchId: dto.branchId,
|
||||
name: dto.name,
|
||||
role: dto.role,
|
||||
email: dto.email,
|
||||
phoneNumbers: dto.phone != null ? [{'type': 'primary', 'number': dto.phone!}] : [],
|
||||
username: dto.username,
|
||||
isActive: dto.isActive,
|
||||
createdAt: dto.createdAt,
|
||||
updatedAt: dto.updatedAt,
|
||||
);
|
||||
}
|
||||
|
||||
// _mapDetailDtoToDomain 함수는 더 이상 사용하지 않음 - _mapDtoToDomain 사용
|
||||
|
||||
// _mapResponseToDomain 함수는 더 이상 사용하지 않음 - _mapDtoToDomain 사용
|
||||
|
||||
// UserRole enum은 더 이상 필요하지 않음 - String role을 직접 사용
|
||||
|
||||
CreateUserRequest _mapDomainToCreateRequest(User user, String password) {
|
||||
return CreateUserRequest(
|
||||
username: user.username ?? user.email ?? '',
|
||||
password: password,
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
phone: user.phoneNumbers.isNotEmpty ? user.phoneNumbers.first['number'] : null,
|
||||
role: user.role,
|
||||
companyId: user.companyId,
|
||||
branchId: user.branchId,
|
||||
);
|
||||
}
|
||||
|
||||
UpdateUserRequest _mapDomainToUpdateRequest(User user) {
|
||||
return UpdateUserRequest(
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
phone: user.phoneNumbers.isNotEmpty ? user.phoneNumbers.first['number'] : null,
|
||||
role: user.role,
|
||||
companyId: user.companyId,
|
||||
branchId: user.branchId,
|
||||
isActive: user.isActive,
|
||||
);
|
||||
}
|
||||
|
||||
// _mapRoleToString 함수는 더 이상 필요하지 않음 - role을 직접 String으로 사용
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import '../models/warehouse/warehouse_dto.dart';
|
||||
|
||||
/// 창고 위치 Repository 인터페이스
|
||||
abstract class WarehouseLocationRepository {
|
||||
/// 창고 위치 목록 조회
|
||||
Future<WarehouseLocationListDto> getWarehouseLocations({
|
||||
int page = 1,
|
||||
int perPage = 20,
|
||||
String? search,
|
||||
Map<String, dynamic>? filters,
|
||||
});
|
||||
|
||||
/// 창고 위치 상세 조회
|
||||
Future<WarehouseLocationDto> getWarehouseLocationDetail(int id);
|
||||
|
||||
/// 창고 위치 생성
|
||||
Future<WarehouseLocationDto> createWarehouseLocation(Map<String, dynamic> data);
|
||||
|
||||
/// 창고 위치 수정
|
||||
Future<WarehouseLocationDto> updateWarehouseLocation(int id, Map<String, dynamic> data);
|
||||
|
||||
/// 창고 위치 삭제
|
||||
Future<void> deleteWarehouseLocation(int id);
|
||||
|
||||
/// 창고에 장비가 있는지 확인
|
||||
Future<bool> checkWarehouseHasEquipment(int id);
|
||||
}
|
||||
@@ -1,56 +1,362 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import '../../core/errors/failures.dart';
|
||||
import '../../domain/repositories/warehouse_location_repository.dart';
|
||||
import '../../models/warehouse_location_model.dart';
|
||||
import '../../models/address_model.dart';
|
||||
import '../datasources/remote/warehouse_location_remote_datasource.dart';
|
||||
import '../models/common/paginated_response.dart';
|
||||
import '../models/warehouse/warehouse_dto.dart';
|
||||
import 'warehouse_location_repository.dart';
|
||||
|
||||
/// 창고 위치 Repository 구현체
|
||||
/// 창고 위치 및 장비 입고지 관리 작업을 처리하며 도메인 모델과 API DTO 간 변환을 담당
|
||||
@Injectable(as: WarehouseLocationRepository)
|
||||
class WarehouseLocationRepositoryImpl implements WarehouseLocationRepository {
|
||||
final WarehouseLocationRemoteDataSource remoteDataSource;
|
||||
|
||||
WarehouseLocationRepositoryImpl(this.remoteDataSource);
|
||||
WarehouseLocationRepositoryImpl({required this.remoteDataSource});
|
||||
|
||||
@override
|
||||
Future<WarehouseLocationListDto> getWarehouseLocations({
|
||||
int page = 1,
|
||||
int perPage = 20,
|
||||
Future<Either<Failure, PaginatedResponse<WarehouseLocation>>> getWarehouseLocations({
|
||||
int? page,
|
||||
int? limit,
|
||||
String? search,
|
||||
Map<String, dynamic>? filters,
|
||||
String? locationType,
|
||||
bool? isActive,
|
||||
bool? hasEquipment,
|
||||
String? sortBy,
|
||||
String? sortOrder,
|
||||
}) async {
|
||||
return await remoteDataSource.getWarehouseLocations(
|
||||
page: page,
|
||||
perPage: perPage,
|
||||
search: search,
|
||||
filters: filters,
|
||||
try {
|
||||
final result = await remoteDataSource.getWarehouseLocations(
|
||||
page: page ?? 1,
|
||||
perPage: limit ?? 20,
|
||||
search: search,
|
||||
filters: {
|
||||
if (locationType != null) 'location_type': locationType,
|
||||
if (isActive != null) 'is_active': isActive,
|
||||
if (hasEquipment != null) 'has_equipment': hasEquipment,
|
||||
},
|
||||
);
|
||||
|
||||
// DTO를 도메인 모델로 변환
|
||||
final warehouseLocations = result.items.map((dto) => _mapDtoToDomain(dto)).toList();
|
||||
|
||||
final paginatedResult = PaginatedResponse<WarehouseLocation>(
|
||||
items: warehouseLocations,
|
||||
page: result.page,
|
||||
size: result.perPage,
|
||||
totalElements: result.total,
|
||||
totalPages: result.totalPages,
|
||||
first: result.page == 1,
|
||||
last: result.page == result.totalPages,
|
||||
);
|
||||
|
||||
return Right(paginatedResult);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(
|
||||
message: '창고 위치 목록 조회 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, WarehouseLocation>> getWarehouseLocationById(int id) async {
|
||||
try {
|
||||
final result = await remoteDataSource.getWarehouseLocationDetail(id);
|
||||
final warehouseLocation = _mapDetailDtoToDomain(result);
|
||||
return Right(warehouseLocation);
|
||||
} catch (e) {
|
||||
if (e.toString().contains('404')) {
|
||||
return Left(NotFoundFailure(
|
||||
message: '해당 창고 위치를 찾을 수 없습니다.',
|
||||
resourceType: 'WarehouseLocation',
|
||||
resourceId: id.toString(),
|
||||
));
|
||||
}
|
||||
return Left(ServerFailure(
|
||||
message: '창고 위치 상세 정보 조회 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, WarehouseLocation>> createWarehouseLocation(WarehouseLocation warehouseLocation) async {
|
||||
try {
|
||||
final request = _mapDomainToCreateRequest(warehouseLocation);
|
||||
final result = await remoteDataSource.createWarehouseLocation(request);
|
||||
final createdWarehouseLocation = _mapDetailDtoToDomain(result);
|
||||
return Right(createdWarehouseLocation);
|
||||
} catch (e) {
|
||||
if (e.toString().contains('중복')) {
|
||||
return Left(DuplicateFailure(
|
||||
message: '이미 존재하는 창고명입니다.',
|
||||
field: 'name',
|
||||
value: warehouseLocation.name,
|
||||
));
|
||||
}
|
||||
if (e.toString().contains('유효성')) {
|
||||
return Left(ValidationFailure(
|
||||
message: '입력 데이터가 올바르지 않습니다.',
|
||||
));
|
||||
}
|
||||
return Left(ServerFailure(
|
||||
message: '창고 위치 생성 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, WarehouseLocation>> updateWarehouseLocation(int id, WarehouseLocation warehouseLocation) async {
|
||||
try {
|
||||
final request = _mapDomainToUpdateRequest(warehouseLocation);
|
||||
final result = await remoteDataSource.updateWarehouseLocation(id, request);
|
||||
final updatedWarehouseLocation = _mapDetailDtoToDomain(result);
|
||||
return Right(updatedWarehouseLocation);
|
||||
} catch (e) {
|
||||
if (e.toString().contains('404')) {
|
||||
return Left(NotFoundFailure(
|
||||
message: '수정할 창고 위치를 찾을 수 없습니다.',
|
||||
resourceType: 'WarehouseLocation',
|
||||
resourceId: id.toString(),
|
||||
));
|
||||
}
|
||||
if (e.toString().contains('중복')) {
|
||||
return Left(DuplicateFailure(
|
||||
message: '이미 존재하는 창고명입니다.',
|
||||
field: 'name',
|
||||
value: warehouseLocation.name,
|
||||
));
|
||||
}
|
||||
return Left(ServerFailure(
|
||||
message: '창고 위치 정보 수정 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, void>> deleteWarehouseLocation(int id) async {
|
||||
try {
|
||||
await remoteDataSource.deleteWarehouseLocation(id);
|
||||
return const Right(null);
|
||||
} catch (e) {
|
||||
if (e.toString().contains('404')) {
|
||||
return Left(NotFoundFailure(
|
||||
message: '삭제할 창고 위치를 찾을 수 없습니다.',
|
||||
resourceType: 'WarehouseLocation',
|
||||
resourceId: id.toString(),
|
||||
));
|
||||
}
|
||||
if (e.toString().contains('참조')) {
|
||||
return Left(BusinessFailure(
|
||||
message: '해당 창고에 보관 중인 장비가 있어 삭제할 수 없습니다.',
|
||||
));
|
||||
}
|
||||
return Left(ServerFailure(
|
||||
message: '창고 위치 삭제 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, WarehouseLocation>> toggleWarehouseLocationStatus(int id) async {
|
||||
try {
|
||||
// 현재 창고 위치 정보 조회
|
||||
final currentWarehouse = await remoteDataSource.getWarehouseLocationDetail(id);
|
||||
final newStatus = !currentWarehouse.isActive;
|
||||
|
||||
// 상태 업데이트
|
||||
await remoteDataSource.updateWarehouseLocationStatus(id, newStatus);
|
||||
|
||||
// 업데이트된 창고 위치 정보 재조회
|
||||
final updatedWarehouse = await remoteDataSource.getWarehouseLocationDetail(id);
|
||||
final warehouseLocation = _mapDetailDtoToDomain(updatedWarehouse);
|
||||
|
||||
return Right(warehouseLocation);
|
||||
} catch (e) {
|
||||
if (e.toString().contains('404')) {
|
||||
return Left(NotFoundFailure(
|
||||
message: '상태를 변경할 창고 위치를 찾을 수 없습니다.',
|
||||
resourceType: 'WarehouseLocation',
|
||||
resourceId: id.toString(),
|
||||
));
|
||||
}
|
||||
return Left(ServerFailure(
|
||||
message: '창고 위치 상태 변경 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, bool>> hasEquipment(int id) async {
|
||||
try {
|
||||
final hasEquipment = await remoteDataSource.checkWarehouseHasEquipment(id);
|
||||
return Right(hasEquipment);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(
|
||||
message: '창고 장비 보유 여부 확인 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, int>> getEquipmentCount(int id) async {
|
||||
// TODO: API에서 창고별 장비 수량 조회 기능이 구현되면 추가
|
||||
try {
|
||||
// 임시로 0 반환 - API 구현 후 수정 필요
|
||||
return const Right(0);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(
|
||||
message: '창고 장비 수량 조회 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, PaginatedResponse<dynamic>>> getEquipmentByWarehouse(
|
||||
int warehouseId, {
|
||||
int? page,
|
||||
int? limit,
|
||||
}) async {
|
||||
// TODO: API에서 창고별 장비 목록 조회 기능이 구현되면 추가
|
||||
return const Left(ServerFailure(
|
||||
message: '창고별 장비 목록 조회 기능이 아직 구현되지 않았습니다.',
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, Map<int, double>>> getWarehouseUtilization() async {
|
||||
// TODO: API에서 창고 사용률 통계 기능이 구현되면 추가
|
||||
return const Left(ServerFailure(
|
||||
message: '창고 사용률 통계 기능이 아직 구현되지 않았습니다.',
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, Map<String, int>>> getWarehouseCountByType() async {
|
||||
// TODO: API에서 창고 유형별 통계 기능이 구현되면 추가
|
||||
return const Left(ServerFailure(
|
||||
message: '창고 유형별 통계 기능이 아직 구현되지 않았습니다.',
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, bool>> isDuplicateWarehouseName(String name, {int? excludeId}) async {
|
||||
try {
|
||||
final isDuplicate = await remoteDataSource.checkDuplicateWarehouseName(name);
|
||||
// excludeId가 있는 경우 해당 ID 제외 로직 추가 필요
|
||||
return Right(isDuplicate);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(
|
||||
message: '중복 창고명 확인 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, List<WarehouseLocation>>> searchWarehouseLocations(String query, {int? limit}) async {
|
||||
try {
|
||||
final result = await remoteDataSource.getWarehouseLocations(
|
||||
page: 1,
|
||||
perPage: limit ?? 10,
|
||||
search: query,
|
||||
);
|
||||
|
||||
final warehouseLocations = result.items.map((dto) => _mapDtoToDomain(dto)).toList();
|
||||
return Right(warehouseLocations);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(
|
||||
message: '창고 위치 검색 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, List<WarehouseLocation>>> getActiveWarehouseLocations() async {
|
||||
try {
|
||||
final result = await remoteDataSource.getWarehouseLocations(
|
||||
page: 1,
|
||||
perPage: 100, // 활성 창고 모두 조회
|
||||
filters: {'is_active': true},
|
||||
);
|
||||
|
||||
final activeWarehouses = result.items.map((dto) => _mapDtoToDomain(dto)).toList();
|
||||
return Right(activeWarehouses);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(
|
||||
message: '활성 창고 위치 조회 중 오류가 발생했습니다: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, WarehouseLocation>> updateWarehouseCapacity(
|
||||
int id,
|
||||
int totalCapacity,
|
||||
int usedCapacity,
|
||||
) async {
|
||||
// TODO: API에서 창고 용량 업데이트 기능이 구현되면 추가
|
||||
return const Left(ServerFailure(
|
||||
message: '창고 용량 업데이트 기능이 아직 구현되지 않았습니다.',
|
||||
));
|
||||
}
|
||||
|
||||
// Private 매퍼 메서드들
|
||||
|
||||
WarehouseLocation _mapDtoToDomain(WarehouseLocationDto dto) {
|
||||
return WarehouseLocation(
|
||||
id: dto.id,
|
||||
name: dto.name,
|
||||
// String? address를 Address 객체로 변환
|
||||
address: dto.address != null && dto.address!.isNotEmpty
|
||||
? Address.fromFullAddress(dto.address!)
|
||||
: const Address(),
|
||||
// DTO에 없는 필드는 remark로 통합 (WarehouseLocation 모델의 실제 필드)
|
||||
remark: null, // DTO에는 description이나 remark 필드가 없음
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<WarehouseLocationDto> getWarehouseLocationDetail(int id) async {
|
||||
return await remoteDataSource.getWarehouseLocationDetail(id);
|
||||
|
||||
WarehouseLocation _mapDetailDtoToDomain(WarehouseLocationDto dto) {
|
||||
return WarehouseLocation(
|
||||
id: dto.id,
|
||||
name: dto.name,
|
||||
// String? address를 Address 객체로 변환
|
||||
address: dto.address != null && dto.address!.isNotEmpty
|
||||
? Address.fromFullAddress(dto.address!)
|
||||
: const Address(),
|
||||
// DTO에 없는 필드는 remark로 통합 (WarehouseLocation 모델의 실제 필드)
|
||||
remark: null, // DTO에는 description이나 remark 필드가 없음
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<WarehouseLocationDto> createWarehouseLocation(Map<String, dynamic> data) async {
|
||||
final request = CreateWarehouseLocationRequest.fromJson(data);
|
||||
return await remoteDataSource.createWarehouseLocation(request);
|
||||
|
||||
// WarehouseLocationType enum이 WarehouseLocation 모델에 없으므로 제거
|
||||
// 필요시 나중에 모델 업데이트 후 재추가
|
||||
|
||||
CreateWarehouseLocationRequest _mapDomainToCreateRequest(WarehouseLocation warehouseLocation) {
|
||||
return CreateWarehouseLocationRequest(
|
||||
name: warehouseLocation.name,
|
||||
// Address 객체를 String으로 변환
|
||||
address: warehouseLocation.address.toString(),
|
||||
// DTO 요청에 없는 필드들은 제거하고 DTO에 있는 필드만 매핑
|
||||
// capacity는 DTO에 있지만 모델에 없으므로 기본값 사용
|
||||
capacity: 0,
|
||||
// 나머지 필드들도 DTO 구조에 맞게 조정
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<WarehouseLocationDto> updateWarehouseLocation(int id, Map<String, dynamic> data) async {
|
||||
final request = UpdateWarehouseLocationRequest.fromJson(data);
|
||||
return await remoteDataSource.updateWarehouseLocation(id, request);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteWarehouseLocation(int id) async {
|
||||
await remoteDataSource.deleteWarehouseLocation(id);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> checkWarehouseHasEquipment(int id) async {
|
||||
// TODO: API 엔드포인트 구현 필요
|
||||
// 현재는 항상 false 반환
|
||||
return false;
|
||||
|
||||
UpdateWarehouseLocationRequest _mapDomainToUpdateRequest(WarehouseLocation warehouseLocation) {
|
||||
return UpdateWarehouseLocationRequest(
|
||||
name: warehouseLocation.name,
|
||||
// Address 객체를 String으로 변환
|
||||
address: warehouseLocation.address.toString(),
|
||||
// DTO 요청에 없는 필드들은 제거하고 DTO에 있는 필드만 매핑
|
||||
// capacity는 DTO에 있지만 모델에 없으므로 기본값 사용
|
||||
capacity: 0,
|
||||
// isActive는 DTO에 있지만 모델에 없으므로 기본값 true 사용
|
||||
isActive: true,
|
||||
);
|
||||
}
|
||||
|
||||
// WarehouseLocationType enum이 WarehouseLocation 모델에 없으므로 제거
|
||||
// 필요시 나중에 모델 업데이트 후 재추가
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import '../core/config/environment.dart';
|
||||
import '../data/datasources/remote/api_client.dart';
|
||||
import '../data/datasources/remote/auth_remote_datasource.dart';
|
||||
import '../data/datasources/remote/dashboard_remote_datasource.dart';
|
||||
import '../data/datasources/remote/equipment_remote_datasource.dart';
|
||||
import '../data/datasources/remote/company_remote_datasource.dart';
|
||||
import '../data/datasources/remote/user_remote_datasource.dart';
|
||||
import '../data/datasources/remote/license_remote_datasource.dart';
|
||||
import '../data/datasources/remote/lookup_remote_datasource.dart';
|
||||
import '../data/datasources/remote/warehouse_remote_datasource.dart';
|
||||
import '../services/auth_service.dart';
|
||||
import '../services/dashboard_service.dart';
|
||||
import '../services/equipment_service.dart';
|
||||
import '../services/company_service.dart';
|
||||
import '../services/user_service.dart';
|
||||
import '../services/license_service.dart';
|
||||
import '../services/lookup_service.dart';
|
||||
import '../services/warehouse_service.dart';
|
||||
|
||||
/// GetIt 인스턴스
|
||||
final getIt = GetIt.instance;
|
||||
|
||||
/// 의존성 주입 설정
|
||||
Future<void> setupDependencies() async {
|
||||
// 환경 초기화
|
||||
await Environment.initialize();
|
||||
|
||||
// 외부 라이브러리
|
||||
getIt.registerLazySingleton(() => Dio());
|
||||
getIt.registerLazySingleton(() => const FlutterSecureStorage());
|
||||
|
||||
// API 클라이언트
|
||||
getIt.registerLazySingleton(() => ApiClient());
|
||||
|
||||
// 데이터소스
|
||||
getIt.registerLazySingleton<AuthRemoteDataSource>(
|
||||
() => AuthRemoteDataSourceImpl(getIt()),
|
||||
);
|
||||
getIt.registerLazySingleton<DashboardRemoteDataSource>(
|
||||
() => DashboardRemoteDataSourceImpl(getIt()),
|
||||
);
|
||||
getIt.registerLazySingleton<EquipmentRemoteDataSource>(
|
||||
() => EquipmentRemoteDataSourceImpl(),
|
||||
);
|
||||
getIt.registerLazySingleton<CompanyRemoteDataSource>(
|
||||
() => CompanyRemoteDataSourceImpl(getIt()),
|
||||
);
|
||||
getIt.registerLazySingleton<UserRemoteDataSource>(
|
||||
() => UserRemoteDataSource(),
|
||||
);
|
||||
getIt.registerLazySingleton<LicenseRemoteDataSource>(
|
||||
() => LicenseRemoteDataSourceImpl(apiClient: getIt()),
|
||||
);
|
||||
getIt.registerLazySingleton<LookupRemoteDataSource>(
|
||||
() => LookupRemoteDataSourceImpl(getIt()),
|
||||
);
|
||||
getIt.registerLazySingleton<WarehouseRemoteDataSource>(
|
||||
() => WarehouseRemoteDataSourceImpl(apiClient: getIt()),
|
||||
);
|
||||
|
||||
// 서비스
|
||||
getIt.registerLazySingleton<AuthService>(
|
||||
() => AuthServiceImpl(getIt(), getIt()),
|
||||
);
|
||||
getIt.registerLazySingleton<DashboardService>(
|
||||
() => DashboardServiceImpl(getIt()),
|
||||
);
|
||||
getIt.registerLazySingleton<EquipmentService>(
|
||||
() => EquipmentService(),
|
||||
);
|
||||
getIt.registerLazySingleton<CompanyService>(
|
||||
() => CompanyService(getIt()),
|
||||
);
|
||||
getIt.registerLazySingleton<UserService>(
|
||||
() => UserService(),
|
||||
);
|
||||
getIt.registerLazySingleton<LicenseService>(
|
||||
() => LicenseService(getIt()),
|
||||
);
|
||||
getIt.registerLazySingleton<LookupService>(
|
||||
() => LookupService(getIt()),
|
||||
);
|
||||
getIt.registerLazySingleton<WarehouseService>(
|
||||
() => WarehouseService(),
|
||||
);
|
||||
|
||||
// 리포지토리
|
||||
// TODO: Repositories will be registered here
|
||||
|
||||
// 유스케이스
|
||||
// TODO: Use cases will be registered here
|
||||
|
||||
// 컨트롤러/프로바이더
|
||||
// TODO: Controllers will be registered here
|
||||
}
|
||||
|
||||
/// 의존성 리셋 (테스트용)
|
||||
Future<void> resetDependencies() async {
|
||||
await getIt.reset();
|
||||
}
|
||||
|
||||
/// 특정 타입의 의존성 가져오기
|
||||
T inject<T extends Object>() => getIt.get<T>();
|
||||
|
||||
/// 특정 타입의 의존성이 등록되어 있는지 확인
|
||||
bool isRegistered<T extends Object>() => getIt.isRegistered<T>();
|
||||
|
||||
/// 의존성 등록 헬퍼 함수들
|
||||
extension GetItHelpers on GetIt {
|
||||
/// 싱글톤 등록 헬퍼
|
||||
void registerSingletonIfNotRegistered<T extends Object>(
|
||||
T Function() factory,
|
||||
) {
|
||||
if (!isRegistered<T>()) {
|
||||
registerLazySingleton<T>(factory);
|
||||
}
|
||||
}
|
||||
|
||||
/// 팩토리 등록 헬퍼
|
||||
void registerFactoryIfNotRegistered<T extends Object>(
|
||||
T Function() factory,
|
||||
) {
|
||||
if (!isRegistered<T>()) {
|
||||
registerFactory<T>(factory);
|
||||
}
|
||||
}
|
||||
}
|
||||
51
lib/domain/repositories/auth_repository.dart
Normal file
51
lib/domain/repositories/auth_repository.dart
Normal file
@@ -0,0 +1,51 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import '../../core/errors/failures.dart';
|
||||
import '../../data/models/auth/auth_user.dart';
|
||||
import '../../data/models/auth/login_request.dart';
|
||||
import '../../data/models/auth/login_response.dart';
|
||||
import '../../data/models/auth/token_response.dart';
|
||||
import '../../data/models/auth/refresh_token_request.dart';
|
||||
|
||||
/// 인증 Repository 인터페이스
|
||||
/// 사용자 로그인, 로그아웃, 토큰 관리 등 인증 관련 기능을 담당
|
||||
abstract class AuthRepository {
|
||||
/// 사용자 로그인
|
||||
/// [loginRequest] 로그인 요청 데이터 (사용자명, 비밀번호)
|
||||
/// Returns: 로그인 응답 (토큰, 사용자 정보)
|
||||
Future<Either<Failure, LoginResponse>> login(LoginRequest loginRequest);
|
||||
|
||||
/// 사용자 로그아웃
|
||||
/// 서버에 로그아웃 요청을 보내고 세션을 종료
|
||||
/// Returns: 로그아웃 성공/실패 여부
|
||||
Future<Either<Failure, void>> logout();
|
||||
|
||||
/// 액세스 토큰 갱신
|
||||
/// [refreshRequest] 리프레시 토큰 요청 데이터
|
||||
/// Returns: 새로운 토큰 정보
|
||||
Future<Either<Failure, TokenResponse>> refreshToken(RefreshTokenRequest refreshRequest);
|
||||
|
||||
/// 현재 인증된 사용자 정보 조회
|
||||
/// Returns: 현재 사용자 정보
|
||||
Future<Either<Failure, AuthUser>> getCurrentUser();
|
||||
|
||||
/// 인증 상태 확인
|
||||
/// 현재 사용자가 인증되어 있는지 확인
|
||||
/// Returns: 인증 여부 (true: 인증됨, false: 미인증)
|
||||
Future<Either<Failure, bool>> isAuthenticated();
|
||||
|
||||
/// 비밀번호 변경
|
||||
/// [currentPassword] 현재 비밀번호
|
||||
/// [newPassword] 새 비밀번호
|
||||
/// Returns: 비밀번호 변경 성공/실패 여부
|
||||
Future<Either<Failure, void>> changePassword(String currentPassword, String newPassword);
|
||||
|
||||
/// 비밀번호 재설정 요청
|
||||
/// [email] 비밀번호 재설정을 요청할 이메일 주소
|
||||
/// Returns: 비밀번호 재설정 요청 성공/실패 여부
|
||||
Future<Either<Failure, void>> requestPasswordReset(String email);
|
||||
|
||||
/// 세션 유효성 검증
|
||||
/// 현재 저장된 토큰이 유효한지 서버에서 검증
|
||||
/// Returns: 세션 유효성 여부
|
||||
Future<Either<Failure, bool>> validateSession();
|
||||
}
|
||||
96
lib/domain/repositories/company_repository.dart
Normal file
96
lib/domain/repositories/company_repository.dart
Normal file
@@ -0,0 +1,96 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import '../../core/errors/failures.dart';
|
||||
import '../../models/company_model.dart';
|
||||
import '../../data/models/common/paginated_response.dart';
|
||||
|
||||
/// 회사 관리 Repository 인터페이스
|
||||
/// 회사 및 지점 정보 관리를 위한 CRUD 기능을 담당
|
||||
abstract class CompanyRepository {
|
||||
/// 회사 목록 조회
|
||||
/// [page] 페이지 번호 (기본값: 1)
|
||||
/// [limit] 페이지당 항목 수 (기본값: 20)
|
||||
/// [search] 검색어 (회사명, 담당자명 등)
|
||||
/// [companyType] 회사 유형 필터 (고객사, 파트너사)
|
||||
/// [sortBy] 정렬 기준 ('name', 'createdAt' 등)
|
||||
/// [sortOrder] 정렬 순서 ('asc', 'desc')
|
||||
/// Returns: 페이지네이션된 회사 목록
|
||||
Future<Either<Failure, PaginatedResponse<Company>>> getCompanies({
|
||||
int? page,
|
||||
int? limit,
|
||||
String? search,
|
||||
CompanyType? companyType,
|
||||
String? sortBy,
|
||||
String? sortOrder,
|
||||
});
|
||||
|
||||
/// 회사 상세 정보 조회
|
||||
/// [id] 회사 고유 식별자
|
||||
/// Returns: 지점 정보를 포함한 회사 상세 정보
|
||||
Future<Either<Failure, Company>> getCompanyById(int id);
|
||||
|
||||
/// 회사 생성
|
||||
/// [company] 생성할 회사 정보
|
||||
/// Returns: 생성된 회사 정보 (ID 포함)
|
||||
Future<Either<Failure, Company>> createCompany(Company company);
|
||||
|
||||
/// 회사 정보 수정
|
||||
/// [id] 수정할 회사 고유 식별자
|
||||
/// [company] 수정할 회사 정보
|
||||
/// Returns: 수정된 회사 정보
|
||||
Future<Either<Failure, Company>> updateCompany(int id, Company company);
|
||||
|
||||
/// 회사 삭제
|
||||
/// [id] 삭제할 회사 고유 식별자
|
||||
/// Returns: 삭제 성공/실패 여부
|
||||
Future<Either<Failure, void>> deleteCompany(int id);
|
||||
|
||||
/// 회사 상태 토글 (활성화/비활성화)
|
||||
/// [id] 상태를 변경할 회사 고유 식별자
|
||||
/// Returns: 상태 변경된 회사 정보
|
||||
Future<Either<Failure, Company>> toggleCompanyStatus(int id);
|
||||
|
||||
/// 지점 생성
|
||||
/// [companyId] 지점을 추가할 회사 ID
|
||||
/// [branch] 생성할 지점 정보
|
||||
/// Returns: 생성된 지점 정보 (ID 포함)
|
||||
Future<Either<Failure, Branch>> createBranch(int companyId, Branch branch);
|
||||
|
||||
/// 지점 정보 수정
|
||||
/// [companyId] 회사 ID
|
||||
/// [branchId] 수정할 지점 ID
|
||||
/// [branch] 수정할 지점 정보
|
||||
/// Returns: 수정된 지점 정보
|
||||
Future<Either<Failure, Branch>> updateBranch(int companyId, int branchId, Branch branch);
|
||||
|
||||
/// 지점 삭제
|
||||
/// [companyId] 회사 ID
|
||||
/// [branchId] 삭제할 지점 ID
|
||||
/// Returns: 삭제 성공/실패 여부
|
||||
Future<Either<Failure, void>> deleteBranch(int companyId, int branchId);
|
||||
|
||||
/// 회사명으로 검색 (자동완성용)
|
||||
/// [query] 검색 쿼리
|
||||
/// [limit] 결과 제한 수 (기본값: 10)
|
||||
/// Returns: 일치하는 회사명 목록
|
||||
Future<Either<Failure, List<String>>> searchCompanyNames(String query, {int? limit});
|
||||
|
||||
/// 회사 유형별 개수 통계
|
||||
/// Returns: 회사 유형별 개수 (고객사, 파트너사)
|
||||
Future<Either<Failure, Map<CompanyType, int>>> getCompanyCountByType();
|
||||
|
||||
/// 회사에 연결된 사용자 존재 여부 확인
|
||||
/// [companyId] 확인할 회사 ID
|
||||
/// Returns: 연결된 사용자 존재 여부 (삭제 가능 여부 판단용)
|
||||
Future<Either<Failure, bool>> hasLinkedUsers(int companyId);
|
||||
|
||||
/// 회사에 연결된 장비 존재 여부 확인
|
||||
/// [companyId] 확인할 회사 ID
|
||||
/// Returns: 연결된 장비 존재 여부 (삭제 가능 여부 판단용)
|
||||
Future<Either<Failure, bool>> hasLinkedEquipment(int companyId);
|
||||
|
||||
/// 중복 회사명 체크
|
||||
/// [name] 체크할 회사명
|
||||
/// [excludeId] 체크에서 제외할 회사 ID (수정 시 현재 회사 제외용)
|
||||
/// Returns: 중복 여부 (true: 중복됨, false: 중복되지 않음)
|
||||
Future<Either<Failure, bool>> isDuplicateCompanyName(String name, {int? excludeId});
|
||||
}
|
||||
101
lib/domain/repositories/license_repository.dart
Normal file
101
lib/domain/repositories/license_repository.dart
Normal file
@@ -0,0 +1,101 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import '../../core/errors/failures.dart';
|
||||
import '../../models/license_model.dart';
|
||||
import '../../data/models/common/paginated_response.dart';
|
||||
import '../../data/models/dashboard/license_expiry_summary.dart';
|
||||
|
||||
/// 라이선스 관리 Repository 인터페이스
|
||||
/// 장비 라이선스 및 유지보수 계약 정보 관리를 담당
|
||||
abstract class LicenseRepository {
|
||||
/// 라이선스 목록 조회
|
||||
/// [page] 페이지 번호 (기본값: 1)
|
||||
/// [limit] 페이지당 항목 수 (기본값: 20)
|
||||
/// [search] 검색어 (라이선스명, 회사명, 장비명 등)
|
||||
/// [companyId] 회사 ID 필터
|
||||
/// [equipmentType] 장비 유형 필터
|
||||
/// [expiryStatus] 만료 상태 필터 ('expired', 'expiring', 'active')
|
||||
/// [sortBy] 정렬 기준 ('name', 'expiryDate', 'createdAt' 등)
|
||||
/// [sortOrder] 정렬 순서 ('asc', 'desc')
|
||||
/// Returns: 페이지네이션된 라이선스 목록
|
||||
Future<Either<Failure, PaginatedResponse<License>>> getLicenses({
|
||||
int? page,
|
||||
int? limit,
|
||||
String? search,
|
||||
int? companyId,
|
||||
String? equipmentType,
|
||||
String? expiryStatus,
|
||||
String? sortBy,
|
||||
String? sortOrder,
|
||||
});
|
||||
|
||||
/// 라이선스 상세 정보 조회
|
||||
/// [id] 라이선스 고유 식별자
|
||||
/// Returns: 라이선스 상세 정보 (회사, 장비 정보 포함)
|
||||
Future<Either<Failure, License>> getLicenseById(int id);
|
||||
|
||||
/// 라이선스 생성
|
||||
/// [license] 생성할 라이선스 정보
|
||||
/// Returns: 생성된 라이선스 정보 (ID 포함)
|
||||
Future<Either<Failure, License>> createLicense(License license);
|
||||
|
||||
/// 라이선스 정보 수정
|
||||
/// [id] 수정할 라이선스 고유 식별자
|
||||
/// [license] 수정할 라이선스 정보
|
||||
/// Returns: 수정된 라이선스 정보
|
||||
Future<Either<Failure, License>> updateLicense(int id, License license);
|
||||
|
||||
/// 라이선스 삭제
|
||||
/// [id] 삭제할 라이선스 고유 식별자
|
||||
/// Returns: 삭제 성공/실패 여부
|
||||
Future<Either<Failure, void>> deleteLicense(int id);
|
||||
|
||||
/// 만료 예정 라이선스 조회
|
||||
/// [days] 앞으로 N일 내 만료 예정 (기본값: 30일)
|
||||
/// [companyId] 회사 ID 필터 (선택적)
|
||||
/// Returns: 만료 예정 라이선스 목록
|
||||
Future<Either<Failure, List<License>>> getExpiringLicenses({int days = 30, int? companyId});
|
||||
|
||||
/// 만료된 라이선스 조회
|
||||
/// [companyId] 회사 ID 필터 (선택적)
|
||||
/// Returns: 이미 만료된 라이선스 목록
|
||||
Future<Either<Failure, List<License>>> getExpiredLicenses({int? companyId});
|
||||
|
||||
/// 라이선스 만료 요약 정보 조회
|
||||
/// 대시보드용 30일/60일/90일 내 만료 예정 요약 정보
|
||||
/// Returns: 만료 예정 요약 정보
|
||||
Future<Either<Failure, LicenseExpirySummary>> getLicenseExpirySummary();
|
||||
|
||||
/// 라이선스 갱신
|
||||
/// [id] 갱신할 라이선스 ID
|
||||
/// [newExpiryDate] 새로운 만료일
|
||||
/// [renewalCost] 갱신 비용 (선택적)
|
||||
/// [renewalNote] 갱신 비고 (선택적)
|
||||
/// Returns: 갱신된 라이선스 정보
|
||||
Future<Either<Failure, License>> renewLicense(
|
||||
int id,
|
||||
DateTime newExpiryDate,
|
||||
{double? renewalCost, String? renewalNote}
|
||||
);
|
||||
|
||||
/// 회사별 라이선스 통계
|
||||
/// [companyId] 회사 ID
|
||||
/// Returns: 해당 회사의 라이선스 통계 정보 (전체, 활성, 만료, 만료예정)
|
||||
Future<Either<Failure, Map<String, int>>> getLicenseStatsByCompany(int companyId);
|
||||
|
||||
/// 라이선스 유형별 통계
|
||||
/// Returns: 라이선스 유형별 개수 (소프트웨어, 하드웨어 등)
|
||||
Future<Either<Failure, Map<String, int>>> getLicenseCountByType();
|
||||
|
||||
/// 라이선스 만료일 사전 알림 설정
|
||||
/// [licenseId] 라이선스 ID
|
||||
/// [notifyDays] 만료 N일 전 알림 (기본값: 30일)
|
||||
/// Returns: 알림 설정 성공/실패 여부
|
||||
Future<Either<Failure, void>> setExpiryNotification(int licenseId, {int notifyDays = 30});
|
||||
|
||||
/// 라이선스 검색 (자동완성용)
|
||||
/// [query] 검색 쿼리
|
||||
/// [companyId] 회사 ID 필터 (선택적)
|
||||
/// [limit] 결과 제한 수 (기본값: 10)
|
||||
/// Returns: 일치하는 라이선스 목록
|
||||
Future<Either<Failure, List<License>>> searchLicenses(String query, {int? companyId, int? limit});
|
||||
}
|
||||
96
lib/domain/repositories/user_repository.dart
Normal file
96
lib/domain/repositories/user_repository.dart
Normal file
@@ -0,0 +1,96 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import '../../core/errors/failures.dart';
|
||||
import '../../models/user_model.dart';
|
||||
import '../../data/models/common/paginated_response.dart';
|
||||
|
||||
/// 사용자 관리 Repository 인터페이스
|
||||
/// 사용자 계정 생성, 수정, 삭제 및 권한 관리를 담당
|
||||
abstract class UserRepository {
|
||||
/// 사용자 목록 조회
|
||||
/// [page] 페이지 번호 (기본값: 1)
|
||||
/// [limit] 페이지당 항목 수 (기본값: 20)
|
||||
/// [search] 검색어 (사용자명, 이메일, 회사명 등)
|
||||
/// [role] 역할 필터 ('S': 관리자, 'M': 멤버)
|
||||
/// [companyId] 회사 ID 필터
|
||||
/// [isActive] 활성화 상태 필터
|
||||
/// [sortBy] 정렬 기준 ('name', 'createdAt', 'role' 등)
|
||||
/// [sortOrder] 정렬 순서 ('asc', 'desc')
|
||||
/// Returns: 페이지네이션된 사용자 목록
|
||||
Future<Either<Failure, PaginatedResponse<User>>> getUsers({
|
||||
int? page,
|
||||
int? limit,
|
||||
String? search,
|
||||
String? role,
|
||||
int? companyId,
|
||||
bool? isActive,
|
||||
String? sortBy,
|
||||
String? sortOrder,
|
||||
});
|
||||
|
||||
/// 사용자 상세 정보 조회
|
||||
/// [id] 사용자 고유 식별자
|
||||
/// Returns: 사용자 상세 정보 (회사, 지점 정보 포함)
|
||||
Future<Either<Failure, User>> getUserById(int id);
|
||||
|
||||
/// 사용자 계정 생성
|
||||
/// [user] 생성할 사용자 정보
|
||||
/// [password] 초기 비밀번호
|
||||
/// Returns: 생성된 사용자 정보 (ID 포함)
|
||||
Future<Either<Failure, User>> createUser(User user, String password);
|
||||
|
||||
/// 사용자 정보 수정
|
||||
/// [id] 수정할 사용자 고유 식별자
|
||||
/// [user] 수정할 사용자 정보
|
||||
/// Returns: 수정된 사용자 정보
|
||||
Future<Either<Failure, User>> updateUser(int id, User user);
|
||||
|
||||
/// 사용자 삭제
|
||||
/// [id] 삭제할 사용자 고유 식별자
|
||||
/// Returns: 삭제 성공/실패 여부
|
||||
Future<Either<Failure, void>> deleteUser(int id);
|
||||
|
||||
/// 사용자 상태 토글 (활성화/비활성화)
|
||||
/// [id] 상태를 변경할 사용자 고유 식별자
|
||||
/// Returns: 상태 변경된 사용자 정보
|
||||
Future<Either<Failure, User>> toggleUserStatus(int id);
|
||||
|
||||
/// 사용자 비밀번호 재설정
|
||||
/// [id] 비밀번호를 재설정할 사용자 ID
|
||||
/// [newPassword] 새 비밀번호
|
||||
/// Returns: 재설정 성공/실패 여부
|
||||
Future<Either<Failure, void>> resetPassword(int id, String newPassword);
|
||||
|
||||
/// 사용자 역할 변경
|
||||
/// [id] 역할을 변경할 사용자 ID
|
||||
/// [newRole] 새 역할 ('S': 관리자, 'M': 멤버)
|
||||
/// Returns: 역할 변경된 사용자 정보
|
||||
Future<Either<Failure, User>> changeUserRole(int id, String newRole);
|
||||
|
||||
/// 사용자명(이메일) 중복 체크
|
||||
/// [username] 체크할 사용자명(이메일)
|
||||
/// [excludeId] 체크에서 제외할 사용자 ID (수정 시 현재 사용자 제외용)
|
||||
/// Returns: 중복 여부 (true: 중복됨, false: 중복되지 않음)
|
||||
Future<Either<Failure, bool>> isDuplicateUsername(String username, {int? excludeId});
|
||||
|
||||
/// 회사별 사용자 목록 조회
|
||||
/// [companyId] 회사 ID
|
||||
/// [includeInactive] 비활성화 사용자 포함 여부
|
||||
/// Returns: 해당 회사의 사용자 목록
|
||||
Future<Either<Failure, List<User>>> getUsersByCompany(int companyId, {bool includeInactive = false});
|
||||
|
||||
/// 역할별 사용자 수 통계
|
||||
/// Returns: 역할별 사용자 수 (관리자, 멤버)
|
||||
Future<Either<Failure, Map<String, int>>> getUserCountByRole();
|
||||
|
||||
/// 사용자 검색 (자동완성용)
|
||||
/// [query] 검색 쿼리
|
||||
/// [companyId] 회사 ID 필터 (선택적)
|
||||
/// [limit] 결과 제한 수 (기본값: 10)
|
||||
/// Returns: 일치하는 사용자 정보 목록
|
||||
Future<Either<Failure, List<User>>> searchUsers(String query, {int? companyId, int? limit});
|
||||
|
||||
/// 사용자 마지막 로그인 시간 업데이트
|
||||
/// [id] 사용자 ID
|
||||
/// Returns: 업데이트 성공/실패 여부
|
||||
Future<Either<Failure, void>> updateLastLoginTime(int id);
|
||||
}
|
||||
112
lib/domain/repositories/warehouse_location_repository.dart
Normal file
112
lib/domain/repositories/warehouse_location_repository.dart
Normal file
@@ -0,0 +1,112 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import '../../core/errors/failures.dart';
|
||||
import '../../models/warehouse_location_model.dart';
|
||||
import '../../data/models/common/paginated_response.dart';
|
||||
|
||||
/// 창고 위치 관리 Repository 인터페이스
|
||||
/// 장비 입고지 및 창고 위치 정보 관리를 담당
|
||||
abstract class WarehouseLocationRepository {
|
||||
/// 창고 위치 목록 조회
|
||||
/// [page] 페이지 번호 (기본값: 1)
|
||||
/// [limit] 페이지당 항목 수 (기본값: 20)
|
||||
/// [search] 검색어 (창고명, 주소, 담당자명 등)
|
||||
/// [locationType] 위치 유형 필터 ('창고', '사무실', '출고지' 등)
|
||||
/// [isActive] 활성화 상태 필터
|
||||
/// [hasEquipment] 장비 보유 여부 필터
|
||||
/// [sortBy] 정렬 기준 ('name', 'createdAt', 'locationType' 등)
|
||||
/// [sortOrder] 정렬 순서 ('asc', 'desc')
|
||||
/// Returns: 페이지네이션된 창고 위치 목록
|
||||
Future<Either<Failure, PaginatedResponse<WarehouseLocation>>> getWarehouseLocations({
|
||||
int? page,
|
||||
int? limit,
|
||||
String? search,
|
||||
String? locationType,
|
||||
bool? isActive,
|
||||
bool? hasEquipment,
|
||||
String? sortBy,
|
||||
String? sortOrder,
|
||||
});
|
||||
|
||||
/// 창고 위치 상세 정보 조회
|
||||
/// [id] 창고 위치 고유 식별자
|
||||
/// Returns: 창고 위치 상세 정보 (보관 중인 장비 정보 포함)
|
||||
Future<Either<Failure, WarehouseLocation>> getWarehouseLocationById(int id);
|
||||
|
||||
/// 창고 위치 생성
|
||||
/// [warehouseLocation] 생성할 창고 위치 정보
|
||||
/// Returns: 생성된 창고 위치 정보 (ID 포함)
|
||||
Future<Either<Failure, WarehouseLocation>> createWarehouseLocation(WarehouseLocation warehouseLocation);
|
||||
|
||||
/// 창고 위치 정보 수정
|
||||
/// [id] 수정할 창고 위치 고유 식별자
|
||||
/// [warehouseLocation] 수정할 창고 위치 정보
|
||||
/// Returns: 수정된 창고 위치 정보
|
||||
Future<Either<Failure, WarehouseLocation>> updateWarehouseLocation(int id, WarehouseLocation warehouseLocation);
|
||||
|
||||
/// 창고 위치 삭제
|
||||
/// [id] 삭제할 창고 위치 고유 식별자
|
||||
/// Returns: 삭제 성공/실패 여부
|
||||
Future<Either<Failure, void>> deleteWarehouseLocation(int id);
|
||||
|
||||
/// 창고 위치 상태 토글 (활성화/비활성화)
|
||||
/// [id] 상태를 변경할 창고 위치 고유 식별자
|
||||
/// Returns: 상태 변경된 창고 위치 정보
|
||||
Future<Either<Failure, WarehouseLocation>> toggleWarehouseLocationStatus(int id);
|
||||
|
||||
/// 창고에 장비가 있는지 확인
|
||||
/// [id] 확인할 창고 위치 ID
|
||||
/// Returns: 장비 보유 여부 (삭제 가능 여부 판단용)
|
||||
Future<Either<Failure, bool>> hasEquipment(int id);
|
||||
|
||||
/// 창고별 장비 수량 조회
|
||||
/// [id] 창고 위치 ID
|
||||
/// Returns: 해당 창고에 보관 중인 장비 수량
|
||||
Future<Either<Failure, int>> getEquipmentCount(int id);
|
||||
|
||||
/// 창고별 장비 목록 조회
|
||||
/// [warehouseId] 창고 위치 ID
|
||||
/// [page] 페이지 번호
|
||||
/// [limit] 페이지당 항목 수
|
||||
/// Returns: 해당 창고에 보관 중인 장비 목록
|
||||
Future<Either<Failure, PaginatedResponse<dynamic>>> getEquipmentByWarehouse(
|
||||
int warehouseId, {
|
||||
int? page,
|
||||
int? limit,
|
||||
});
|
||||
|
||||
/// 창고 사용률 통계
|
||||
/// Returns: 창고별 사용률 (전체 용량 대비 사용 중인 용량)
|
||||
Future<Either<Failure, Map<int, double>>> getWarehouseUtilization();
|
||||
|
||||
/// 창고 유형별 통계
|
||||
/// Returns: 창고 유형별 개수 ('창고', '사무실', '출고지' 등)
|
||||
Future<Either<Failure, Map<String, int>>> getWarehouseCountByType();
|
||||
|
||||
/// 창고명 중복 체크
|
||||
/// [name] 체크할 창고명
|
||||
/// [excludeId] 체크에서 제외할 창고 ID (수정 시 현재 창고 제외용)
|
||||
/// Returns: 중복 여부 (true: 중복됨, false: 중복되지 않음)
|
||||
Future<Either<Failure, bool>> isDuplicateWarehouseName(String name, {int? excludeId});
|
||||
|
||||
/// 창고 검색 (자동완성용)
|
||||
/// [query] 검색 쿼리
|
||||
/// [limit] 결과 제한 수 (기본값: 10)
|
||||
/// Returns: 일치하는 창고 위치 목록
|
||||
Future<Either<Failure, List<WarehouseLocation>>> searchWarehouseLocations(String query, {int? limit});
|
||||
|
||||
/// 활성 창고 위치 목록 조회 (드롭다운용)
|
||||
/// 장비 등록 시 선택 가능한 활성 상태의 창고 위치만 조회
|
||||
/// Returns: 활성화된 창고 위치 목록
|
||||
Future<Either<Failure, List<WarehouseLocation>>> getActiveWarehouseLocations();
|
||||
|
||||
/// 창고 용량 및 사용량 업데이트
|
||||
/// [id] 창고 위치 ID
|
||||
/// [totalCapacity] 전체 용량
|
||||
/// [usedCapacity] 사용 중인 용량
|
||||
/// Returns: 업데이트된 창고 위치 정보
|
||||
Future<Either<Failure, WarehouseLocation>> updateWarehouseCapacity(
|
||||
int id,
|
||||
int totalCapacity,
|
||||
int usedCapacity,
|
||||
);
|
||||
}
|
||||
@@ -1,20 +1,20 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import '../../../services/auth_service.dart';
|
||||
import '../../repositories/auth_repository.dart';
|
||||
import '../../../core/errors/failures.dart';
|
||||
import '../base_usecase.dart';
|
||||
|
||||
/// 인증 상태 확인 UseCase
|
||||
/// 현재 사용자가 로그인되어 있는지 확인
|
||||
class CheckAuthStatusUseCase extends UseCase<bool, NoParams> {
|
||||
final AuthService _authService;
|
||||
final AuthRepository _authRepository;
|
||||
|
||||
CheckAuthStatusUseCase(this._authService);
|
||||
CheckAuthStatusUseCase(this._authRepository);
|
||||
|
||||
@override
|
||||
Future<Either<Failure, bool>> call(NoParams params) async {
|
||||
try {
|
||||
final isAuthenticated = await _authService.isLoggedIn();
|
||||
return Right(isAuthenticated);
|
||||
final result = await _authRepository.isAuthenticated();
|
||||
return result;
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(
|
||||
message: '인증 상태 확인 중 오류가 발생했습니다.',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import '../../../services/auth_service.dart';
|
||||
import '../../repositories/auth_repository.dart';
|
||||
import '../../../data/models/auth/login_request.dart';
|
||||
import '../../../data/models/auth/login_response.dart';
|
||||
import '../../../core/errors/failures.dart';
|
||||
@@ -20,9 +20,9 @@ class LoginParams {
|
||||
/// 로그인 UseCase
|
||||
/// 사용자 인증을 처리하고 토큰을 저장
|
||||
class LoginUseCase extends UseCase<LoginResponse, LoginParams> {
|
||||
final AuthService _authService;
|
||||
final AuthRepository _authRepository;
|
||||
|
||||
LoginUseCase(this._authService);
|
||||
LoginUseCase(this._authRepository);
|
||||
|
||||
@override
|
||||
Future<Either<Failure, LoginResponse>> call(LoginParams params) async {
|
||||
@@ -49,7 +49,7 @@ class LoginUseCase extends UseCase<LoginResponse, LoginParams> {
|
||||
password: params.password,
|
||||
);
|
||||
|
||||
return await _authService.login(loginRequest);
|
||||
return await _authRepository.login(loginRequest);
|
||||
} on DioException catch (e) {
|
||||
if (e.response?.statusCode == 401) {
|
||||
return Left(AuthFailure(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import '../../../services/company_service.dart';
|
||||
import '../../repositories/company_repository.dart';
|
||||
import '../../../models/company_model.dart';
|
||||
import '../../../data/models/common/paginated_response.dart';
|
||||
import '../../../core/errors/failures.dart';
|
||||
import '../base_usecase.dart';
|
||||
|
||||
@@ -20,28 +21,21 @@ class GetCompaniesParams {
|
||||
}
|
||||
|
||||
/// 회사 목록 조회 UseCase
|
||||
class GetCompaniesUseCase extends UseCase<List<Company>, GetCompaniesParams> {
|
||||
final CompanyService _companyService;
|
||||
class GetCompaniesUseCase extends UseCase<PaginatedResponse<Company>, GetCompaniesParams> {
|
||||
final CompanyRepository _companyRepository;
|
||||
|
||||
GetCompaniesUseCase(this._companyService);
|
||||
GetCompaniesUseCase(this._companyRepository);
|
||||
|
||||
@override
|
||||
Future<Either<Failure, List<Company>>> call(GetCompaniesParams params) async {
|
||||
Future<Either<Failure, PaginatedResponse<Company>>> call(GetCompaniesParams params) async {
|
||||
try {
|
||||
final response = await _companyService.getCompanies(
|
||||
final result = await _companyRepository.getCompanies(
|
||||
page: params.page,
|
||||
perPage: params.perPage,
|
||||
limit: params.perPage,
|
||||
search: params.search,
|
||||
isActive: params.isActive,
|
||||
);
|
||||
|
||||
// PaginatedResponse에서 items만 추출
|
||||
return Right(response.items);
|
||||
} on ServerFailure catch (e) {
|
||||
return Left(ServerFailure(
|
||||
message: e.message,
|
||||
originalError: e,
|
||||
));
|
||||
return result;
|
||||
} catch (e) {
|
||||
return Left(UnknownFailure(
|
||||
message: '회사 목록을 불러오는 중 오류가 발생했습니다.',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import '../../../data/models/license/license_dto.dart';
|
||||
import '../../../data/repositories/license_repository.dart';
|
||||
import '../../repositories/license_repository.dart';
|
||||
import '../../../core/errors/failures.dart';
|
||||
import '../base_usecase.dart';
|
||||
|
||||
@@ -16,39 +16,66 @@ class CheckLicenseExpiryUseCase implements UseCase<LicenseExpiryResult, CheckLic
|
||||
Future<Either<Failure, LicenseExpiryResult>> call(CheckLicenseExpiryParams params) async {
|
||||
try {
|
||||
// 모든 라이선스 조회
|
||||
final allLicenses = await repository.getLicenses(
|
||||
final allLicensesResult = await repository.getLicenses(
|
||||
page: 1,
|
||||
perPage: 10000, // 모든 라이선스 조회
|
||||
limit: 10000, // 모든 라이선스 조회
|
||||
);
|
||||
|
||||
final now = DateTime.now();
|
||||
final expiring30Days = <LicenseDto>[];
|
||||
final expiring60Days = <LicenseDto>[];
|
||||
final expiring90Days = <LicenseDto>[];
|
||||
final expired = <LicenseDto>[];
|
||||
return allLicensesResult.fold(
|
||||
(failure) => Left(failure),
|
||||
(paginatedResponse) {
|
||||
final now = DateTime.now();
|
||||
final expiring30Days = <LicenseDto>[];
|
||||
final expiring60Days = <LicenseDto>[];
|
||||
final expiring90Days = <LicenseDto>[];
|
||||
final expired = <LicenseDto>[];
|
||||
|
||||
for (final license in allLicenses.items) {
|
||||
if (license.expiryDate == null) continue;
|
||||
for (final license in paginatedResponse.items) {
|
||||
final licenseDto = LicenseDto(
|
||||
id: license.id ?? 0,
|
||||
licenseKey: license.licenseKey,
|
||||
productName: license.productName,
|
||||
vendor: license.vendor,
|
||||
licenseType: license.licenseType,
|
||||
userCount: license.userCount,
|
||||
purchaseDate: license.purchaseDate,
|
||||
expiryDate: license.expiryDate,
|
||||
purchasePrice: license.purchasePrice,
|
||||
companyId: license.companyId,
|
||||
branchId: license.branchId,
|
||||
assignedUserId: license.assignedUserId,
|
||||
remark: license.remark,
|
||||
isActive: license.isActive ?? true,
|
||||
createdAt: license.createdAt ?? DateTime.now(),
|
||||
updatedAt: license.updatedAt ?? DateTime.now(),
|
||||
companyName: license.companyName,
|
||||
branchName: license.branchName,
|
||||
assignedUserName: license.assignedUserName,
|
||||
);
|
||||
|
||||
final daysUntilExpiry = license.expiryDate!.difference(now).inDays;
|
||||
if (licenseDto.expiryDate == null) continue;
|
||||
|
||||
if (daysUntilExpiry < 0) {
|
||||
expired.add(license);
|
||||
} else if (daysUntilExpiry <= 30) {
|
||||
expiring30Days.add(license);
|
||||
} else if (daysUntilExpiry <= 60) {
|
||||
expiring60Days.add(license);
|
||||
} else if (daysUntilExpiry <= 90) {
|
||||
expiring90Days.add(license);
|
||||
}
|
||||
}
|
||||
final daysUntilExpiry = licenseDto.expiryDate!.difference(now).inDays;
|
||||
|
||||
return Right(LicenseExpiryResult(
|
||||
expiring30Days: expiring30Days,
|
||||
expiring60Days: expiring60Days,
|
||||
expiring90Days: expiring90Days,
|
||||
expired: expired,
|
||||
));
|
||||
if (daysUntilExpiry < 0) {
|
||||
expired.add(licenseDto);
|
||||
} else if (daysUntilExpiry <= 30) {
|
||||
expiring30Days.add(licenseDto);
|
||||
} else if (daysUntilExpiry <= 60) {
|
||||
expiring60Days.add(licenseDto);
|
||||
} else if (daysUntilExpiry <= 90) {
|
||||
expiring90Days.add(licenseDto);
|
||||
}
|
||||
}
|
||||
|
||||
return Right(LicenseExpiryResult(
|
||||
expiring30Days: expiring30Days,
|
||||
expiring60Days: expiring60Days,
|
||||
expiring90Days: expiring90Days,
|
||||
expired: expired,
|
||||
));
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(message: e.toString()));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import '../../../data/models/license/license_dto.dart';
|
||||
import '../../../data/repositories/license_repository.dart';
|
||||
import '../../../models/license_model.dart';
|
||||
import '../../repositories/license_repository.dart';
|
||||
import '../../../core/errors/failures.dart';
|
||||
import '../base_usecase.dart';
|
||||
|
||||
@@ -16,18 +17,52 @@ class CreateLicenseUseCase implements UseCase<LicenseDto, CreateLicenseParams> {
|
||||
Future<Either<Failure, LicenseDto>> call(CreateLicenseParams params) async {
|
||||
try {
|
||||
// 비즈니스 로직: 만료일 검증
|
||||
if (params.expiryDate.isBefore(params.startDate)) {
|
||||
return Left(ValidationFailure(message: '만료일은 시작일 이후여야 합니다'));
|
||||
if (params.expiryDate.isBefore(params.purchaseDate)) {
|
||||
return Left(ValidationFailure(message: '만료일은 구매일 이후여야 합니다'));
|
||||
}
|
||||
|
||||
// 비즈니스 로직: 최소 라이선스 기간 검증 (30일)
|
||||
final duration = params.expiryDate.difference(params.startDate).inDays;
|
||||
final duration = params.expiryDate.difference(params.purchaseDate).inDays;
|
||||
if (duration < 30) {
|
||||
return Left(ValidationFailure(message: '라이선스 기간은 최소 30일 이상이어야 합니다'));
|
||||
}
|
||||
|
||||
final license = await repository.createLicense(params.toMap());
|
||||
return Right(license);
|
||||
final license = License(
|
||||
licenseKey: params.licenseKey,
|
||||
productName: params.productName,
|
||||
vendor: params.vendor,
|
||||
licenseType: params.licenseType,
|
||||
userCount: params.userCount,
|
||||
purchaseDate: params.purchaseDate,
|
||||
expiryDate: params.expiryDate,
|
||||
purchasePrice: params.purchasePrice,
|
||||
companyId: params.companyId,
|
||||
branchId: params.branchId,
|
||||
remark: params.remark,
|
||||
);
|
||||
|
||||
final result = await repository.createLicense(license);
|
||||
return result.map((createdLicense) => LicenseDto(
|
||||
id: createdLicense.id!,
|
||||
licenseKey: createdLicense.licenseKey,
|
||||
productName: createdLicense.productName,
|
||||
vendor: createdLicense.vendor,
|
||||
licenseType: createdLicense.licenseType,
|
||||
userCount: createdLicense.userCount,
|
||||
purchaseDate: createdLicense.purchaseDate,
|
||||
expiryDate: createdLicense.expiryDate,
|
||||
purchasePrice: createdLicense.purchasePrice,
|
||||
companyId: createdLicense.companyId,
|
||||
branchId: createdLicense.branchId,
|
||||
assignedUserId: createdLicense.assignedUserId,
|
||||
remark: createdLicense.remark,
|
||||
isActive: createdLicense.isActive,
|
||||
createdAt: createdLicense.createdAt ?? DateTime.now(),
|
||||
updatedAt: createdLicense.updatedAt ?? DateTime.now(),
|
||||
companyName: createdLicense.companyName,
|
||||
branchName: createdLicense.branchName,
|
||||
assignedUserName: createdLicense.assignedUserName,
|
||||
));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(message: e.toString()));
|
||||
}
|
||||
@@ -36,33 +71,29 @@ class CreateLicenseUseCase implements UseCase<LicenseDto, CreateLicenseParams> {
|
||||
|
||||
/// 라이선스 생성 파라미터
|
||||
class CreateLicenseParams {
|
||||
final int equipmentId;
|
||||
final int companyId;
|
||||
final String licenseType;
|
||||
final DateTime startDate;
|
||||
final String licenseKey;
|
||||
final String productName;
|
||||
final String? vendor;
|
||||
final String? licenseType;
|
||||
final int? userCount;
|
||||
final DateTime purchaseDate;
|
||||
final DateTime expiryDate;
|
||||
final String? description;
|
||||
final double? cost;
|
||||
final double? purchasePrice;
|
||||
final int companyId;
|
||||
final int? branchId;
|
||||
final String? remark;
|
||||
|
||||
CreateLicenseParams({
|
||||
required this.equipmentId,
|
||||
required this.companyId,
|
||||
required this.licenseType,
|
||||
required this.startDate,
|
||||
required this.licenseKey,
|
||||
required this.productName,
|
||||
this.vendor,
|
||||
this.licenseType,
|
||||
this.userCount,
|
||||
required this.purchaseDate,
|
||||
required this.expiryDate,
|
||||
this.description,
|
||||
this.cost,
|
||||
this.purchasePrice,
|
||||
required this.companyId,
|
||||
this.branchId,
|
||||
this.remark,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'equipment_id': equipmentId,
|
||||
'company_id': companyId,
|
||||
'license_type': licenseType,
|
||||
'start_date': startDate.toIso8601String(),
|
||||
'expiry_date': expiryDate.toIso8601String(),
|
||||
'description': description,
|
||||
'cost': cost,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import '../../../data/repositories/license_repository.dart';
|
||||
import '../../repositories/license_repository.dart';
|
||||
import '../../../core/errors/failures.dart';
|
||||
import '../base_usecase.dart';
|
||||
|
||||
@@ -15,13 +15,21 @@ class DeleteLicenseUseCase implements UseCase<bool, int> {
|
||||
Future<Either<Failure, bool>> call(int id) async {
|
||||
try {
|
||||
// 비즈니스 로직: 활성 라이선스는 삭제 불가
|
||||
final license = await repository.getLicenseDetail(id);
|
||||
if (license.isActive) {
|
||||
return Left(ValidationFailure(message: '활성 라이선스는 삭제할 수 없습니다'));
|
||||
}
|
||||
final licenseResult = await repository.getLicenseById(id);
|
||||
return licenseResult.fold(
|
||||
(failure) => Left(failure),
|
||||
(license) async {
|
||||
if (license.isActive == true) {
|
||||
return Left(ValidationFailure(message: '활성 라이선스는 삭제할 수 없습니다'));
|
||||
}
|
||||
|
||||
await repository.deleteLicense(id);
|
||||
return const Right(true);
|
||||
final deleteResult = await repository.deleteLicense(id);
|
||||
return deleteResult.fold(
|
||||
(failure) => Left(failure),
|
||||
(_) => const Right(true),
|
||||
);
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(message: e.toString()));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import '../../../data/models/license/license_dto.dart';
|
||||
import '../../../data/repositories/license_repository.dart';
|
||||
import '../../repositories/license_repository.dart';
|
||||
import '../../../core/errors/failures.dart';
|
||||
import '../base_usecase.dart';
|
||||
|
||||
@@ -15,8 +15,28 @@ class GetLicenseDetailUseCase implements UseCase<LicenseDto, int> {
|
||||
@override
|
||||
Future<Either<Failure, LicenseDto>> call(int id) async {
|
||||
try {
|
||||
final license = await repository.getLicenseDetail(id);
|
||||
return Right(license);
|
||||
final result = await repository.getLicenseById(id);
|
||||
return result.map((license) => LicenseDto(
|
||||
id: license.id ?? 0,
|
||||
licenseKey: license.licenseKey,
|
||||
productName: license.productName,
|
||||
vendor: license.vendor,
|
||||
licenseType: license.licenseType,
|
||||
userCount: license.userCount,
|
||||
purchaseDate: license.purchaseDate,
|
||||
expiryDate: license.expiryDate,
|
||||
purchasePrice: license.purchasePrice,
|
||||
companyId: license.companyId,
|
||||
branchId: license.branchId,
|
||||
assignedUserId: license.assignedUserId,
|
||||
remark: license.remark,
|
||||
isActive: license.isActive ?? true,
|
||||
createdAt: license.createdAt ?? DateTime.now(),
|
||||
updatedAt: license.updatedAt ?? DateTime.now(),
|
||||
companyName: license.companyName,
|
||||
branchName: license.branchName,
|
||||
assignedUserName: license.assignedUserName,
|
||||
));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(message: e.toString()));
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'package:dartz/dartz.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import '../../../data/models/common/pagination_params.dart';
|
||||
import '../../../data/models/license/license_dto.dart';
|
||||
import '../../../data/repositories/license_repository.dart';
|
||||
import '../../repositories/license_repository.dart';
|
||||
import '../../../core/errors/failures.dart';
|
||||
import '../base_usecase.dart';
|
||||
|
||||
@@ -16,13 +16,43 @@ class GetLicensesUseCase implements UseCase<LicenseListResponseDto, GetLicensesP
|
||||
@override
|
||||
Future<Either<Failure, LicenseListResponseDto>> call(GetLicensesParams params) async {
|
||||
try {
|
||||
final licenses = await repository.getLicenses(
|
||||
final result = await repository.getLicenses(
|
||||
page: params.page,
|
||||
perPage: params.perPage,
|
||||
limit: params.perPage,
|
||||
search: params.search,
|
||||
filters: params.filters,
|
||||
companyId: params.filters?['companyId'],
|
||||
equipmentType: params.filters?['equipmentType'],
|
||||
expiryStatus: params.filters?['expiryStatus'],
|
||||
sortBy: params.filters?['sortBy'],
|
||||
sortOrder: params.filters?['sortOrder'],
|
||||
);
|
||||
return Right(licenses);
|
||||
return result.map((paginatedResponse) => LicenseListResponseDto(
|
||||
items: paginatedResponse.items.map((license) => LicenseDto(
|
||||
id: license.id ?? 0,
|
||||
licenseKey: license.licenseKey,
|
||||
productName: license.productName,
|
||||
vendor: license.vendor,
|
||||
licenseType: license.licenseType,
|
||||
userCount: license.userCount,
|
||||
purchaseDate: license.purchaseDate,
|
||||
expiryDate: license.expiryDate,
|
||||
purchasePrice: license.purchasePrice,
|
||||
companyId: license.companyId,
|
||||
branchId: license.branchId,
|
||||
assignedUserId: license.assignedUserId,
|
||||
remark: license.remark,
|
||||
isActive: license.isActive ?? true,
|
||||
createdAt: license.createdAt ?? DateTime.now(),
|
||||
updatedAt: license.updatedAt ?? DateTime.now(),
|
||||
companyName: license.companyName,
|
||||
branchName: license.branchName,
|
||||
assignedUserName: license.assignedUserName,
|
||||
)).toList(),
|
||||
page: paginatedResponse.page,
|
||||
perPage: params.perPage,
|
||||
total: paginatedResponse.totalElements,
|
||||
totalPages: paginatedResponse.totalPages,
|
||||
));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(message: e.toString()));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import '../../../data/models/license/license_dto.dart';
|
||||
import '../../../data/repositories/license_repository.dart';
|
||||
import '../../../models/license_model.dart';
|
||||
import '../../repositories/license_repository.dart';
|
||||
import '../../../core/errors/failures.dart';
|
||||
import '../base_usecase.dart';
|
||||
|
||||
@@ -16,20 +17,57 @@ class UpdateLicenseUseCase implements UseCase<LicenseDto, UpdateLicenseParams> {
|
||||
Future<Either<Failure, LicenseDto>> call(UpdateLicenseParams params) async {
|
||||
try {
|
||||
// 비즈니스 로직: 만료일 검증
|
||||
if (params.expiryDate != null && params.startDate != null) {
|
||||
if (params.expiryDate!.isBefore(params.startDate!)) {
|
||||
return Left(ValidationFailure(message: '만료일은 시작일 이후여야 합니다'));
|
||||
if (params.expiryDate != null && params.purchaseDate != null) {
|
||||
if (params.expiryDate!.isBefore(params.purchaseDate!)) {
|
||||
return Left(ValidationFailure(message: '만료일은 구매일 이후여야 합니다'));
|
||||
}
|
||||
|
||||
// 비즈니스 로직: 최소 라이선스 기간 검증 (30일)
|
||||
final duration = params.expiryDate!.difference(params.startDate!).inDays;
|
||||
final duration = params.expiryDate!.difference(params.purchaseDate!).inDays;
|
||||
if (duration < 30) {
|
||||
return Left(ValidationFailure(message: '라이선스 기간은 최소 30일 이상이어야 합니다'));
|
||||
}
|
||||
}
|
||||
|
||||
final license = await repository.updateLicense(params.id, params.toMap());
|
||||
return Right(license);
|
||||
final license = License(
|
||||
id: params.id,
|
||||
licenseKey: params.licenseKey ?? '',
|
||||
productName: params.productName ?? '',
|
||||
vendor: params.vendor,
|
||||
licenseType: params.licenseType,
|
||||
userCount: params.userCount,
|
||||
purchaseDate: params.purchaseDate ?? DateTime.now(),
|
||||
expiryDate: params.expiryDate ?? DateTime.now(),
|
||||
purchasePrice: params.purchasePrice,
|
||||
companyId: params.companyId ?? 0,
|
||||
branchId: params.branchId,
|
||||
assignedUserId: params.assignedUserId,
|
||||
remark: params.remark,
|
||||
isActive: params.isActive ?? true,
|
||||
);
|
||||
|
||||
final result = await repository.updateLicense(params.id ?? 0, license);
|
||||
return result.map((updatedLicense) => LicenseDto(
|
||||
id: updatedLicense.id ?? 0,
|
||||
licenseKey: updatedLicense.licenseKey,
|
||||
productName: updatedLicense.productName,
|
||||
vendor: updatedLicense.vendor,
|
||||
licenseType: updatedLicense.licenseType,
|
||||
userCount: updatedLicense.userCount,
|
||||
purchaseDate: updatedLicense.purchaseDate,
|
||||
expiryDate: updatedLicense.expiryDate,
|
||||
purchasePrice: updatedLicense.purchasePrice,
|
||||
companyId: updatedLicense.companyId,
|
||||
branchId: updatedLicense.branchId,
|
||||
assignedUserId: updatedLicense.assignedUserId,
|
||||
remark: updatedLicense.remark,
|
||||
isActive: updatedLicense.isActive,
|
||||
createdAt: updatedLicense.createdAt ?? DateTime.now(),
|
||||
updatedAt: updatedLicense.updatedAt ?? DateTime.now(),
|
||||
companyName: updatedLicense.companyName,
|
||||
branchName: updatedLicense.branchName,
|
||||
assignedUserName: updatedLicense.assignedUserName,
|
||||
));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(message: e.toString()));
|
||||
}
|
||||
@@ -39,37 +77,34 @@ class UpdateLicenseUseCase implements UseCase<LicenseDto, UpdateLicenseParams> {
|
||||
/// 라이선스 수정 파라미터
|
||||
class UpdateLicenseParams {
|
||||
final int id;
|
||||
final int? equipmentId;
|
||||
final int? companyId;
|
||||
final String? licenseKey;
|
||||
final String? productName;
|
||||
final String? vendor;
|
||||
final String? licenseType;
|
||||
final DateTime? startDate;
|
||||
final int? userCount;
|
||||
final DateTime? purchaseDate;
|
||||
final DateTime? expiryDate;
|
||||
final String? description;
|
||||
final double? cost;
|
||||
final String? status;
|
||||
final double? purchasePrice;
|
||||
final int? companyId;
|
||||
final int? branchId;
|
||||
final int? assignedUserId;
|
||||
final String? remark;
|
||||
final bool? isActive;
|
||||
|
||||
UpdateLicenseParams({
|
||||
required this.id,
|
||||
this.equipmentId,
|
||||
this.companyId,
|
||||
this.licenseKey,
|
||||
this.productName,
|
||||
this.vendor,
|
||||
this.licenseType,
|
||||
this.startDate,
|
||||
this.userCount,
|
||||
this.purchaseDate,
|
||||
this.expiryDate,
|
||||
this.description,
|
||||
this.cost,
|
||||
this.status,
|
||||
this.purchasePrice,
|
||||
this.companyId,
|
||||
this.branchId,
|
||||
this.assignedUserId,
|
||||
this.remark,
|
||||
this.isActive,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
final Map<String, dynamic> data = {};
|
||||
if (equipmentId != null) data['equipment_id'] = equipmentId;
|
||||
if (companyId != null) data['company_id'] = companyId;
|
||||
if (licenseType != null) data['license_type'] = licenseType;
|
||||
if (startDate != null) data['start_date'] = startDate!.toIso8601String();
|
||||
if (expiryDate != null) data['expiry_date'] = expiryDate!.toIso8601String();
|
||||
if (description != null) data['description'] = description;
|
||||
if (cost != null) data['cost'] = cost;
|
||||
if (status != null) data['status'] = status;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import '../../../data/models/warehouse/warehouse_dto.dart';
|
||||
import '../../../data/repositories/warehouse_location_repository.dart';
|
||||
import '../../../models/warehouse_location_model.dart';
|
||||
import '../../../models/address_model.dart';
|
||||
import '../../repositories/warehouse_location_repository.dart';
|
||||
import '../../../core/errors/failures.dart';
|
||||
import '../base_usecase.dart';
|
||||
|
||||
@@ -33,8 +35,21 @@ class CreateWarehouseLocationUseCase implements UseCase<WarehouseLocationDto, Cr
|
||||
}
|
||||
}
|
||||
|
||||
final location = await repository.createWarehouseLocation(params.toMap());
|
||||
return Right(location);
|
||||
final warehouseLocation = WarehouseLocation(
|
||||
id: 0, // Default id for new warehouse location
|
||||
name: params.name,
|
||||
address: Address.fromFullAddress(params.address),
|
||||
remark: params.description,
|
||||
);
|
||||
|
||||
final result = await repository.createWarehouseLocation(warehouseLocation);
|
||||
return result.map((createdLocation) => WarehouseLocationDto(
|
||||
id: createdLocation.id ?? 0,
|
||||
name: createdLocation.name,
|
||||
address: createdLocation.address.toString(),
|
||||
isActive: true, // Default value since model doesn't have isActive
|
||||
createdAt: DateTime.now(), // Add required createdAt parameter
|
||||
));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(message: e.toString()));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import '../../../data/repositories/warehouse_location_repository.dart';
|
||||
import '../../repositories/warehouse_location_repository.dart';
|
||||
import '../../../core/errors/failures.dart';
|
||||
import '../base_usecase.dart';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import '../../../data/models/warehouse/warehouse_dto.dart';
|
||||
import '../../../data/repositories/warehouse_location_repository.dart';
|
||||
import '../../repositories/warehouse_location_repository.dart';
|
||||
import '../../../core/errors/failures.dart';
|
||||
import '../base_usecase.dart';
|
||||
|
||||
@@ -15,8 +15,14 @@ class GetWarehouseLocationDetailUseCase implements UseCase<WarehouseLocationDto,
|
||||
@override
|
||||
Future<Either<Failure, WarehouseLocationDto>> call(int id) async {
|
||||
try {
|
||||
final location = await repository.getWarehouseLocationDetail(id);
|
||||
return Right(location);
|
||||
final result = await repository.getWarehouseLocationById(id);
|
||||
return result.map((location) => WarehouseLocationDto(
|
||||
id: location.id ?? 0,
|
||||
name: location.name,
|
||||
address: location.address.toString(),
|
||||
isActive: true, // Default value since model doesn't have isActive
|
||||
createdAt: DateTime.now(), // Add required createdAt parameter
|
||||
));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(message: e.toString()));
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'package:dartz/dartz.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import '../../../data/models/common/pagination_params.dart';
|
||||
import '../../../data/models/warehouse/warehouse_dto.dart';
|
||||
import '../../../data/repositories/warehouse_location_repository.dart';
|
||||
import '../../repositories/warehouse_location_repository.dart';
|
||||
import '../../../core/errors/failures.dart';
|
||||
import '../base_usecase.dart';
|
||||
|
||||
@@ -16,13 +16,29 @@ class GetWarehouseLocationsUseCase implements UseCase<WarehouseLocationListDto,
|
||||
@override
|
||||
Future<Either<Failure, WarehouseLocationListDto>> call(GetWarehouseLocationsParams params) async {
|
||||
try {
|
||||
final locations = await repository.getWarehouseLocations(
|
||||
final result = await repository.getWarehouseLocations(
|
||||
page: params.page,
|
||||
perPage: params.perPage,
|
||||
limit: params.perPage,
|
||||
search: params.search,
|
||||
filters: params.filters,
|
||||
locationType: params.filters?['locationType'],
|
||||
isActive: params.filters?['isActive'],
|
||||
hasEquipment: params.filters?['hasEquipment'],
|
||||
sortBy: params.filters?['sortBy'],
|
||||
sortOrder: params.filters?['sortOrder'],
|
||||
);
|
||||
return Right(locations);
|
||||
return result.map((paginatedResponse) => WarehouseLocationListDto(
|
||||
items: paginatedResponse.items.map((location) => WarehouseLocationDto(
|
||||
id: location.id ?? 0,
|
||||
name: location.name,
|
||||
address: location.address.toString(),
|
||||
isActive: true, // Default value since model doesn't have isActive
|
||||
createdAt: DateTime.now(), // Add required createdAt parameter
|
||||
)).toList(),
|
||||
page: paginatedResponse.page,
|
||||
perPage: params.perPage, // Add missing required perPage parameter
|
||||
total: paginatedResponse.totalElements,
|
||||
totalPages: paginatedResponse.totalPages,
|
||||
));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(message: e.toString()));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import '../../../data/models/warehouse/warehouse_dto.dart';
|
||||
import '../../../data/repositories/warehouse_location_repository.dart';
|
||||
import '../../../models/warehouse_location_model.dart';
|
||||
import '../../../models/address_model.dart';
|
||||
import '../../repositories/warehouse_location_repository.dart';
|
||||
import '../../../core/errors/failures.dart';
|
||||
import '../base_usecase.dart';
|
||||
|
||||
@@ -41,8 +43,21 @@ class UpdateWarehouseLocationUseCase implements UseCase<WarehouseLocationDto, Up
|
||||
return Left(ValidationFailure(message: '유효하지 않은 경도값입니다'));
|
||||
}
|
||||
|
||||
final location = await repository.updateWarehouseLocation(params.id, params.toMap());
|
||||
return Right(location);
|
||||
final warehouseLocation = WarehouseLocation(
|
||||
id: params.id,
|
||||
name: params.name ?? '',
|
||||
address: params.address != null ? Address.fromFullAddress(params.address!) : const Address(),
|
||||
remark: params.description,
|
||||
);
|
||||
|
||||
final result = await repository.updateWarehouseLocation(params.id, warehouseLocation);
|
||||
return result.map((updatedLocation) => WarehouseLocationDto(
|
||||
id: updatedLocation.id ?? 0,
|
||||
name: updatedLocation.name,
|
||||
address: updatedLocation.address.toString(),
|
||||
isActive: true, // Default value since model doesn't have isActive
|
||||
createdAt: DateTime.now(), // Add required createdAt parameter
|
||||
));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(message: e.toString()));
|
||||
}
|
||||
|
||||
250
lib/injection_container.dart
Normal file
250
lib/injection_container.dart
Normal file
@@ -0,0 +1,250 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
// Core
|
||||
import 'core/storage/secure_storage.dart';
|
||||
|
||||
// Data Sources
|
||||
import 'data/datasources/remote/api_client.dart';
|
||||
import 'data/datasources/remote/auth_remote_datasource.dart';
|
||||
import 'data/datasources/remote/company_remote_datasource.dart';
|
||||
import 'data/datasources/remote/dashboard_remote_datasource.dart';
|
||||
import 'data/datasources/remote/equipment_remote_datasource.dart';
|
||||
import 'data/datasources/remote/license_remote_datasource.dart';
|
||||
import 'data/datasources/remote/lookup_remote_datasource.dart';
|
||||
import 'data/datasources/remote/user_remote_datasource.dart';
|
||||
import 'data/datasources/remote/warehouse_location_remote_datasource.dart';
|
||||
import 'data/datasources/remote/warehouse_remote_datasource.dart';
|
||||
import 'data/datasources/interceptors/api_interceptor.dart';
|
||||
|
||||
// Repositories
|
||||
import 'domain/repositories/auth_repository.dart';
|
||||
import 'domain/repositories/company_repository.dart';
|
||||
import 'domain/repositories/equipment_repository.dart';
|
||||
import 'domain/repositories/license_repository.dart';
|
||||
import 'domain/repositories/user_repository.dart';
|
||||
import 'domain/repositories/warehouse_location_repository.dart';
|
||||
import 'data/repositories/auth_repository_impl.dart';
|
||||
import 'data/repositories/company_repository_impl.dart';
|
||||
import 'data/repositories/equipment_repository_impl.dart';
|
||||
import 'data/repositories/license_repository_impl.dart';
|
||||
import 'data/repositories/user_repository_impl.dart';
|
||||
import 'data/repositories/warehouse_location_repository_impl.dart';
|
||||
|
||||
// Use Cases - Auth
|
||||
import 'domain/usecases/auth/login_usecase.dart';
|
||||
import 'domain/usecases/auth/logout_usecase.dart';
|
||||
import 'domain/usecases/auth/get_current_user_usecase.dart';
|
||||
import 'domain/usecases/auth/check_auth_status_usecase.dart';
|
||||
import 'domain/usecases/auth/refresh_token_usecase.dart';
|
||||
|
||||
// Use Cases - Company
|
||||
import 'domain/usecases/company/get_companies_usecase.dart';
|
||||
import 'domain/usecases/company/get_company_detail_usecase.dart';
|
||||
import 'domain/usecases/company/create_company_usecase.dart';
|
||||
import 'domain/usecases/company/update_company_usecase.dart';
|
||||
import 'domain/usecases/company/delete_company_usecase.dart';
|
||||
import 'domain/usecases/company/toggle_company_status_usecase.dart';
|
||||
|
||||
// Use Cases - User
|
||||
import 'domain/usecases/user/get_users_usecase.dart';
|
||||
import 'domain/usecases/user/get_user_detail_usecase.dart';
|
||||
import 'domain/usecases/user/create_user_usecase.dart';
|
||||
import 'domain/usecases/user/update_user_usecase.dart';
|
||||
import 'domain/usecases/user/delete_user_usecase.dart';
|
||||
import 'domain/usecases/user/toggle_user_status_usecase.dart';
|
||||
import 'domain/usecases/user/reset_password_usecase.dart';
|
||||
|
||||
// Use Cases - Equipment
|
||||
import 'domain/usecases/equipment/get_equipments_usecase.dart';
|
||||
import 'domain/usecases/equipment/equipment_in_usecase.dart';
|
||||
import 'domain/usecases/equipment/equipment_out_usecase.dart';
|
||||
import 'domain/usecases/equipment/get_equipment_history_usecase.dart';
|
||||
|
||||
// Use Cases - License
|
||||
import 'domain/usecases/license/get_licenses_usecase.dart';
|
||||
import 'domain/usecases/license/get_license_detail_usecase.dart';
|
||||
import 'domain/usecases/license/create_license_usecase.dart';
|
||||
import 'domain/usecases/license/update_license_usecase.dart';
|
||||
import 'domain/usecases/license/delete_license_usecase.dart';
|
||||
import 'domain/usecases/license/check_license_expiry_usecase.dart';
|
||||
|
||||
// Use Cases - Warehouse Location
|
||||
import 'domain/usecases/warehouse_location/get_warehouse_locations_usecase.dart';
|
||||
import 'domain/usecases/warehouse_location/get_warehouse_location_detail_usecase.dart';
|
||||
import 'domain/usecases/warehouse_location/create_warehouse_location_usecase.dart';
|
||||
import 'domain/usecases/warehouse_location/update_warehouse_location_usecase.dart';
|
||||
import 'domain/usecases/warehouse_location/delete_warehouse_location_usecase.dart';
|
||||
|
||||
// Services (기존 서비스들과의 호환성을 위해 유지)
|
||||
import 'services/auth_service.dart';
|
||||
import 'services/company_service.dart';
|
||||
import 'services/dashboard_service.dart';
|
||||
import 'services/equipment_service.dart';
|
||||
import 'services/license_service.dart';
|
||||
import 'services/lookup_service.dart';
|
||||
import 'services/user_service.dart';
|
||||
import 'services/warehouse_service.dart';
|
||||
|
||||
final sl = GetIt.instance;
|
||||
|
||||
Future<void> init() async {
|
||||
// External
|
||||
final sharedPreferences = await SharedPreferences.getInstance();
|
||||
sl.registerLazySingleton(() => sharedPreferences);
|
||||
|
||||
// Core
|
||||
sl.registerLazySingleton(() => SecureStorage());
|
||||
sl.registerLazySingleton(() => const FlutterSecureStorage());
|
||||
sl.registerLazySingleton(() => ApiInterceptor(sl()));
|
||||
|
||||
// API Client
|
||||
sl.registerLazySingleton(() => ApiClient());
|
||||
|
||||
// Dio
|
||||
sl.registerLazySingleton<Dio>(() {
|
||||
final dio = Dio();
|
||||
dio.options = BaseOptions(
|
||||
baseUrl: 'http://43.201.34.104:8080/api/v1',
|
||||
connectTimeout: const Duration(seconds: 30),
|
||||
receiveTimeout: const Duration(seconds: 30),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
);
|
||||
dio.interceptors.add(sl<ApiInterceptor>());
|
||||
dio.interceptors.add(LogInterceptor(
|
||||
requestBody: true,
|
||||
responseBody: true,
|
||||
));
|
||||
return dio;
|
||||
});
|
||||
|
||||
// Data Sources
|
||||
sl.registerLazySingleton<AuthRemoteDataSource>(
|
||||
() => AuthRemoteDataSourceImpl(sl<ApiClient>()),
|
||||
);
|
||||
sl.registerLazySingleton<CompanyRemoteDataSource>(
|
||||
() => CompanyRemoteDataSourceImpl(sl<ApiClient>()),
|
||||
);
|
||||
sl.registerLazySingleton<DashboardRemoteDataSource>(
|
||||
() => DashboardRemoteDataSourceImpl(sl<ApiClient>()),
|
||||
);
|
||||
sl.registerLazySingleton<EquipmentRemoteDataSource>(
|
||||
() => EquipmentRemoteDataSourceImpl(),
|
||||
);
|
||||
sl.registerLazySingleton<LicenseRemoteDataSource>(
|
||||
() => LicenseRemoteDataSourceImpl(apiClient: sl<ApiClient>()),
|
||||
);
|
||||
sl.registerLazySingleton<LookupRemoteDataSource>(
|
||||
() => LookupRemoteDataSourceImpl(sl<ApiClient>()),
|
||||
);
|
||||
sl.registerLazySingleton<UserRemoteDataSource>(
|
||||
() => UserRemoteDataSourceImpl(sl<ApiClient>()),
|
||||
);
|
||||
sl.registerLazySingleton<WarehouseLocationRemoteDataSource>(
|
||||
() => WarehouseLocationRemoteDataSourceImpl(apiClient: sl<ApiClient>()),
|
||||
);
|
||||
sl.registerLazySingleton<WarehouseRemoteDataSource>(
|
||||
() => WarehouseRemoteDataSourceImpl(apiClient: sl<ApiClient>()),
|
||||
);
|
||||
|
||||
// Repositories
|
||||
sl.registerLazySingleton<AuthRepository>(
|
||||
() => AuthRepositoryImpl(
|
||||
remoteDataSource: sl<AuthRemoteDataSource>(),
|
||||
sharedPreferences: sl<SharedPreferences>(),
|
||||
),
|
||||
);
|
||||
sl.registerLazySingleton<CompanyRepository>(
|
||||
() => CompanyRepositoryImpl(remoteDataSource: sl<CompanyRemoteDataSource>()),
|
||||
);
|
||||
sl.registerLazySingleton<EquipmentRepository>(
|
||||
() => EquipmentRepositoryImpl(sl<EquipmentRemoteDataSource>()),
|
||||
);
|
||||
sl.registerLazySingleton<LicenseRepository>(
|
||||
() => LicenseRepositoryImpl(remoteDataSource: sl<LicenseRemoteDataSource>()),
|
||||
);
|
||||
sl.registerLazySingleton<UserRepository>(
|
||||
() => UserRepositoryImpl(remoteDataSource: sl<UserRemoteDataSource>()),
|
||||
);
|
||||
sl.registerLazySingleton<WarehouseLocationRepository>(
|
||||
() => WarehouseLocationRepositoryImpl(remoteDataSource: sl<WarehouseLocationRemoteDataSource>()),
|
||||
);
|
||||
|
||||
// Use Cases - Auth
|
||||
sl.registerLazySingleton(() => LoginUseCase(sl<AuthRepository>())); // Repository 사용
|
||||
sl.registerLazySingleton(() => LogoutUseCase(sl<AuthService>())); // Service 사용 (아직 미수정)
|
||||
sl.registerLazySingleton(() => GetCurrentUserUseCase(sl<AuthService>())); // Service 사용 (아직 미수정)
|
||||
sl.registerLazySingleton(() => CheckAuthStatusUseCase(sl<AuthRepository>())); // Repository 사용
|
||||
sl.registerLazySingleton(() => RefreshTokenUseCase(sl<AuthService>())); // Service 사용 (아직 미수정)
|
||||
|
||||
// Use Cases - Company
|
||||
sl.registerLazySingleton(() => GetCompaniesUseCase(sl<CompanyRepository>())); // Repository 사용
|
||||
sl.registerLazySingleton(() => GetCompanyDetailUseCase(sl<CompanyService>())); // Service 사용 (아직 미수정)
|
||||
sl.registerLazySingleton(() => CreateCompanyUseCase(sl<CompanyService>())); // Service 사용 (아직 미수정)
|
||||
sl.registerLazySingleton(() => UpdateCompanyUseCase(sl<CompanyService>())); // Service 사용 (아직 미수정)
|
||||
sl.registerLazySingleton(() => DeleteCompanyUseCase(sl<CompanyService>())); // Service 사용 (아직 미수정)
|
||||
sl.registerLazySingleton(() => ToggleCompanyStatusUseCase(sl<CompanyService>())); // Service 사용 (아직 미수정)
|
||||
|
||||
// Use Cases - User
|
||||
sl.registerLazySingleton(() => GetUsersUseCase(sl<UserService>())); // Service 사용 (아직 미수정)
|
||||
sl.registerLazySingleton(() => GetUserDetailUseCase(sl<UserService>())); // Service 사용 (아직 미수정)
|
||||
sl.registerLazySingleton(() => CreateUserUseCase(sl<UserService>())); // Service 사용 (아직 미수정)
|
||||
sl.registerLazySingleton(() => UpdateUserUseCase(sl<UserService>())); // Service 사용 (아직 미수정)
|
||||
sl.registerLazySingleton(() => DeleteUserUseCase(sl<UserService>())); // Service 사용 (아직 미수정)
|
||||
sl.registerLazySingleton(() => ToggleUserStatusUseCase(sl<UserService>())); // Service 사용 (아직 미수정)
|
||||
sl.registerLazySingleton(() => ResetPasswordUseCase(sl<UserService>())); // Service 사용 (아직 미수정)
|
||||
|
||||
// Use Cases - Equipment
|
||||
sl.registerLazySingleton(() => GetEquipmentsUseCase(sl<EquipmentService>())); // Service 사용 (아직 미수정)
|
||||
sl.registerLazySingleton(() => EquipmentInUseCase(sl<EquipmentService>())); // Service 사용 (아직 미수정)
|
||||
sl.registerLazySingleton(() => EquipmentOutUseCase(sl<EquipmentService>())); // Service 사용 (아직 미수정)
|
||||
sl.registerLazySingleton(() => GetEquipmentHistoryUseCase(sl<EquipmentService>())); // Service 사용 (아직 미수정)
|
||||
|
||||
// Use Cases - License
|
||||
sl.registerLazySingleton(() => GetLicensesUseCase(sl<LicenseRepository>())); // Repository 사용 (이미 구현됨)
|
||||
sl.registerLazySingleton(() => GetLicenseDetailUseCase(sl<LicenseRepository>())); // Repository 사용 (이미 구현됨)
|
||||
sl.registerLazySingleton(() => CreateLicenseUseCase(sl<LicenseRepository>())); // Repository 사용 (이미 구현됨)
|
||||
sl.registerLazySingleton(() => UpdateLicenseUseCase(sl<LicenseRepository>())); // Repository 사용 (이미 구현됨)
|
||||
sl.registerLazySingleton(() => DeleteLicenseUseCase(sl<LicenseRepository>())); // Repository 사용 (이미 구현됨)
|
||||
sl.registerLazySingleton(() => CheckLicenseExpiryUseCase(sl<LicenseRepository>())); // Repository 사용 (이미 구현됨)
|
||||
|
||||
// Use Cases - Warehouse Location
|
||||
sl.registerLazySingleton(() => GetWarehouseLocationsUseCase(sl<WarehouseLocationRepository>())); // Repository 사용 (이미 구현됨)
|
||||
sl.registerLazySingleton(() => GetWarehouseLocationDetailUseCase(sl<WarehouseLocationRepository>())); // Repository 사용 (이미 구현됨)
|
||||
sl.registerLazySingleton(() => CreateWarehouseLocationUseCase(sl<WarehouseLocationRepository>())); // Repository 사용 (이미 구현됨)
|
||||
sl.registerLazySingleton(() => UpdateWarehouseLocationUseCase(sl<WarehouseLocationRepository>())); // Repository 사용 (이미 구현됨)
|
||||
sl.registerLazySingleton(() => DeleteWarehouseLocationUseCase(sl<WarehouseLocationRepository>())); // Repository 사용 (이미 구현됨)
|
||||
|
||||
// Services (기존 서비스들과의 호환성을 위해 유지)
|
||||
sl.registerLazySingleton<AuthService>(
|
||||
() => AuthServiceImpl(
|
||||
sl<AuthRemoteDataSource>(),
|
||||
sl<FlutterSecureStorage>(),
|
||||
),
|
||||
);
|
||||
sl.registerLazySingleton<CompanyService>(
|
||||
() => CompanyService(sl<CompanyRemoteDataSource>()),
|
||||
);
|
||||
sl.registerLazySingleton<DashboardService>(
|
||||
() => DashboardServiceImpl(sl<DashboardRemoteDataSource>()),
|
||||
);
|
||||
sl.registerLazySingleton<EquipmentService>(
|
||||
() => EquipmentService(),
|
||||
);
|
||||
sl.registerLazySingleton<LicenseService>(
|
||||
() => LicenseService(sl<LicenseRemoteDataSource>()),
|
||||
);
|
||||
sl.registerLazySingleton<LookupService>(
|
||||
() => LookupService(sl<LookupRemoteDataSource>()),
|
||||
);
|
||||
sl.registerLazySingleton<UserService>(
|
||||
() => UserService(sl<UserRemoteDataSource>()),
|
||||
);
|
||||
sl.registerLazySingleton<WarehouseService>(
|
||||
() => WarehouseService(),
|
||||
);
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import 'package:superport/services/auth_service.dart';
|
||||
import 'package:superport/utils/constants.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:superport/screens/login/login_screen.dart';
|
||||
import 'package:superport/di/injection_container.dart' as di;
|
||||
import 'package:superport/injection_container.dart' as di;
|
||||
|
||||
void main() async {
|
||||
// Flutter 바인딩 초기화
|
||||
@@ -21,7 +21,7 @@ void main() async {
|
||||
|
||||
try {
|
||||
// 의존성 주입 설정
|
||||
await di.setupDependencies();
|
||||
await di.init();
|
||||
} catch (e) {
|
||||
print('Failed to setup dependencies: $e');
|
||||
// 에러가 발생해도 앱은 실행되도록 함
|
||||
|
||||
@@ -38,7 +38,7 @@ class _AppLayoutState extends State<AppLayout>
|
||||
late final DashboardService _dashboardService;
|
||||
late final LookupService _lookupService;
|
||||
late Animation<double> _sidebarAnimation;
|
||||
int _expiringLicenseCount = 0; // 30일 내 만료 예정 라이선스 수
|
||||
int _expiringLicenseCount = 0; // 7일 내 만료 예정 라이선스 수
|
||||
|
||||
// 레이아웃 상수 (1920x1080 최적화)
|
||||
static const double _sidebarExpandedWidth = 260.0;
|
||||
@@ -79,6 +79,7 @@ class _AppLayoutState extends State<AppLayout>
|
||||
},
|
||||
(summary) {
|
||||
print('[DEBUG] 라이선스 만료 정보 로드 성공!');
|
||||
print('[DEBUG] 7일 내 만료: ${summary.expiring7Days ?? 0}개');
|
||||
print('[DEBUG] 30일 내 만료: ${summary.within30Days}개');
|
||||
print('[DEBUG] 60일 내 만료: ${summary.within60Days}개');
|
||||
print('[DEBUG] 90일 내 만료: ${summary.within90Days}개');
|
||||
@@ -86,8 +87,10 @@ class _AppLayoutState extends State<AppLayout>
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
// 30일 내 만료 수를 표시 (7일 내 만료가 포함됨)
|
||||
// expiring_30_days는 30일 이내의 모든 라이선스를 포함
|
||||
_expiringLicenseCount = summary.within30Days;
|
||||
print('[DEBUG] 상태 업데이트 완료: $_expiringLicenseCount');
|
||||
print('[DEBUG] 상태 업데이트 완료: $_expiringLicenseCount (30일 내 만료)');
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -33,7 +33,7 @@ class _CompanyListState extends State<CompanyList> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = CompanyListController();
|
||||
_controller.initializeWithPageSize(10); // 페이지 크기 설정
|
||||
_controller.initialize(pageSize: 10); // 통일된 초기화 방식
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -430,18 +430,13 @@ class _CompanyListState extends State<CompanyList> {
|
||||
],
|
||||
),
|
||||
|
||||
// 페이지네이션 (Controller 상태 사용)
|
||||
// 페이지네이션 (BaseListController의 goToPage 사용)
|
||||
pagination: Pagination(
|
||||
totalCount: controller.total,
|
||||
currentPage: controller.currentPage,
|
||||
pageSize: controller.pageSize,
|
||||
onPageChanged: (page) {
|
||||
// 다음 페이지 로드
|
||||
if (page > controller.currentPage) {
|
||||
controller.loadNextPage();
|
||||
} else if (page == 1) {
|
||||
controller.refresh();
|
||||
}
|
||||
controller.goToPage(page);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
@@ -32,15 +32,9 @@ class CompanyListController extends BaseListController<Company> {
|
||||
}
|
||||
}
|
||||
|
||||
// 초기 데이터 로드
|
||||
Future<void> initialize() async {
|
||||
await loadData(isRefresh: true);
|
||||
}
|
||||
|
||||
// 페이지 크기를 지정하여 초기화
|
||||
// 기존 initializeWithPageSize를 사용하는 코드와의 호환성 유지
|
||||
Future<void> initializeWithPageSize(int newPageSize) async {
|
||||
pageSize = newPageSize;
|
||||
await loadData(isRefresh: true);
|
||||
await initialize(pageSize: newPageSize);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -48,8 +42,8 @@ class CompanyListController extends BaseListController<Company> {
|
||||
required PaginationParams params,
|
||||
Map<String, dynamic>? additionalFilters,
|
||||
}) async {
|
||||
// API 호출 - 회사 목록 조회 (이제 PaginatedResponse 반환)
|
||||
final response = await ErrorHandler.handleApiCall<dynamic>(
|
||||
// API 호출 - 회사 목록 조회 (PaginatedResponse 반환)
|
||||
final response = await ErrorHandler.handleApiCall(
|
||||
() => _companyService.getCompanies(
|
||||
page: params.page,
|
||||
perPage: params.perPage,
|
||||
@@ -61,6 +55,20 @@ class CompanyListController extends BaseListController<Company> {
|
||||
},
|
||||
);
|
||||
|
||||
if (response == null) {
|
||||
return PagedResult(
|
||||
items: [],
|
||||
meta: PaginationMeta(
|
||||
currentPage: params.page,
|
||||
perPage: params.perPage,
|
||||
total: 0,
|
||||
totalPages: 0,
|
||||
hasNext: false,
|
||||
hasPrevious: false,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// PaginatedResponse를 PagedResult로 변환
|
||||
final meta = PaginationMeta(
|
||||
currentPage: response.page,
|
||||
|
||||
@@ -1,294 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../core/controllers/base_list_controller.dart';
|
||||
import '../../../core/errors/failures.dart';
|
||||
import '../../../domain/usecases/base_usecase.dart';
|
||||
import '../../../domain/usecases/company/company_usecases.dart';
|
||||
import '../../../models/company_model.dart';
|
||||
import '../../../services/company_service.dart';
|
||||
import '../../../di/injection_container.dart';
|
||||
import '../../../data/models/common/pagination_params.dart';
|
||||
|
||||
/// UseCase를 활용한 회사 목록 관리 컨트롤러
|
||||
/// BaseListController를 상속받아 공통 기능 재사용
|
||||
class CompanyListControllerWithUseCase extends BaseListController<Company> {
|
||||
// UseCases
|
||||
late final GetCompaniesUseCase _getCompaniesUseCase;
|
||||
late final CreateCompanyUseCase _createCompanyUseCase;
|
||||
late final UpdateCompanyUseCase _updateCompanyUseCase;
|
||||
late final DeleteCompanyUseCase _deleteCompanyUseCase;
|
||||
late final GetCompanyDetailUseCase _getCompanyDetailUseCase;
|
||||
late final ToggleCompanyStatusUseCase _toggleCompanyStatusUseCase;
|
||||
|
||||
// 필터 상태
|
||||
String? selectedType;
|
||||
bool? isActive;
|
||||
|
||||
// 선택된 회사들
|
||||
final Set<int> _selectedCompanyIds = {};
|
||||
Set<int> get selectedCompanyIds => _selectedCompanyIds;
|
||||
bool get hasSelection => _selectedCompanyIds.isNotEmpty;
|
||||
|
||||
CompanyListControllerWithUseCase() {
|
||||
// UseCase 초기화
|
||||
final companyService = inject<CompanyService>();
|
||||
_getCompaniesUseCase = GetCompaniesUseCase(companyService);
|
||||
_createCompanyUseCase = CreateCompanyUseCase(companyService);
|
||||
_updateCompanyUseCase = UpdateCompanyUseCase(companyService);
|
||||
_deleteCompanyUseCase = DeleteCompanyUseCase(companyService);
|
||||
_getCompanyDetailUseCase = GetCompanyDetailUseCase(companyService);
|
||||
_toggleCompanyStatusUseCase = ToggleCompanyStatusUseCase(companyService);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PagedResult<Company>> fetchData({
|
||||
required PaginationParams params,
|
||||
Map<String, dynamic>? additionalFilters,
|
||||
}) async {
|
||||
// UseCase를 통한 데이터 조회
|
||||
final usecaseParams = GetCompaniesParams(
|
||||
page: params.page,
|
||||
perPage: params.perPage,
|
||||
search: params.search,
|
||||
isActive: isActive,
|
||||
);
|
||||
|
||||
final result = await _getCompaniesUseCase(usecaseParams);
|
||||
|
||||
return result.fold(
|
||||
(failure) {
|
||||
throw Exception(failure.message);
|
||||
},
|
||||
(companies) {
|
||||
// PagedResult로 래핑하여 반환 (임시로 메타데이터 생성)
|
||||
final meta = PaginationMeta(
|
||||
currentPage: params.page,
|
||||
perPage: params.perPage,
|
||||
total: companies.length, // 실제로는 서버에서 받아와야 함
|
||||
totalPages: (companies.length / params.perPage).ceil(),
|
||||
hasNext: companies.length >= params.perPage,
|
||||
hasPrevious: params.page > 1,
|
||||
);
|
||||
return PagedResult(items: companies, meta: meta);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 회사 생성
|
||||
Future<bool> createCompany(Company company) async {
|
||||
isLoadingState = true;
|
||||
notifyListeners();
|
||||
|
||||
final params = CreateCompanyParams(company: company);
|
||||
final result = await _createCompanyUseCase(params);
|
||||
|
||||
return result.fold(
|
||||
(failure) {
|
||||
errorState = failure.message;
|
||||
|
||||
// ValidationFailure의 경우 상세 에러 표시
|
||||
if (failure is ValidationFailure && failure.errors != null) {
|
||||
final errorMessages = failure.errors!.entries
|
||||
.map((e) => '${e.key}: ${e.value}')
|
||||
.join('\n');
|
||||
errorState = errorMessages;
|
||||
}
|
||||
|
||||
isLoadingState = false;
|
||||
notifyListeners();
|
||||
return false;
|
||||
},
|
||||
(newCompany) {
|
||||
// 로컬 리스트에 추가
|
||||
addItemLocally(newCompany);
|
||||
isLoadingState = false;
|
||||
notifyListeners();
|
||||
return true;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 회사 수정
|
||||
Future<bool> updateCompany(int id, Company company) async {
|
||||
isLoadingState = true;
|
||||
notifyListeners();
|
||||
|
||||
final params = UpdateCompanyParams(id: id, company: company);
|
||||
final result = await _updateCompanyUseCase(params);
|
||||
|
||||
return result.fold(
|
||||
(failure) {
|
||||
errorState = failure.message;
|
||||
|
||||
// ValidationFailure의 경우 상세 에러 표시
|
||||
if (failure is ValidationFailure && failure.errors != null) {
|
||||
final errorMessages = failure.errors!.entries
|
||||
.map((e) => '${e.key}: ${e.value}')
|
||||
.join('\n');
|
||||
errorState = errorMessages;
|
||||
}
|
||||
|
||||
isLoadingState = false;
|
||||
notifyListeners();
|
||||
return false;
|
||||
},
|
||||
(updatedCompany) {
|
||||
// 로컬 리스트 업데이트
|
||||
updateItemLocally(updatedCompany, (item) => item.id == id);
|
||||
isLoadingState = false;
|
||||
notifyListeners();
|
||||
return true;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 회사 삭제
|
||||
Future<bool> deleteCompany(int id) async {
|
||||
isLoadingState = true;
|
||||
notifyListeners();
|
||||
|
||||
final params = DeleteCompanyParams(id: id);
|
||||
final result = await _deleteCompanyUseCase(params);
|
||||
|
||||
return result.fold(
|
||||
(failure) {
|
||||
errorState = failure.message;
|
||||
isLoadingState = false;
|
||||
notifyListeners();
|
||||
return false;
|
||||
},
|
||||
(_) {
|
||||
// 로컬 리스트에서 제거
|
||||
removeItemLocally((item) => item.id == id);
|
||||
_selectedCompanyIds.remove(id);
|
||||
isLoadingState = false;
|
||||
notifyListeners();
|
||||
return true;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 회사 상세 조회
|
||||
Future<Company?> getCompanyDetail(int id, {bool includeBranches = false}) async {
|
||||
final params = GetCompanyDetailParams(
|
||||
id: id,
|
||||
includeBranches: includeBranches,
|
||||
);
|
||||
|
||||
final result = await _getCompanyDetailUseCase(params);
|
||||
|
||||
return result.fold(
|
||||
(failure) {
|
||||
errorState = failure.message;
|
||||
notifyListeners();
|
||||
return null;
|
||||
},
|
||||
(company) => company,
|
||||
);
|
||||
}
|
||||
|
||||
/// 회사 상태 토글 (활성화/비활성화)
|
||||
Future<bool> toggleCompanyStatus(int id) async {
|
||||
isLoadingState = true;
|
||||
notifyListeners();
|
||||
|
||||
// 현재 회사 상태를 확인하여 토글 (기본값 true로 가정)
|
||||
final params = ToggleCompanyStatusParams(
|
||||
id: id,
|
||||
isActive: false, // 임시로 false로 설정 (실제로는 현재 상태를 API로 확인해야 함)
|
||||
);
|
||||
final result = await _toggleCompanyStatusUseCase(params);
|
||||
|
||||
return result.fold(
|
||||
(failure) {
|
||||
errorState = failure.message;
|
||||
isLoadingState = false;
|
||||
notifyListeners();
|
||||
return false;
|
||||
},
|
||||
(_) {
|
||||
// 로컬 리스트에서 상태 업데이트 (실제로는 API에서 업데이트된 Company 객체를 받아와야 함)
|
||||
isLoadingState = false;
|
||||
notifyListeners();
|
||||
return true;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 회사 선택/해제
|
||||
void toggleSelection(int companyId) {
|
||||
if (_selectedCompanyIds.contains(companyId)) {
|
||||
_selectedCompanyIds.remove(companyId);
|
||||
} else {
|
||||
_selectedCompanyIds.add(companyId);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 전체 선택/해제
|
||||
void toggleSelectAll() {
|
||||
if (_selectedCompanyIds.length == items.length) {
|
||||
_selectedCompanyIds.clear();
|
||||
} else {
|
||||
_selectedCompanyIds.clear();
|
||||
_selectedCompanyIds.addAll(items.where((c) => c.id != null).map((c) => c.id!));
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 선택 초기화
|
||||
void clearSelection() {
|
||||
_selectedCompanyIds.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 필터 적용
|
||||
void applyFilters({String? type, bool? active}) {
|
||||
selectedType = type;
|
||||
isActive = active;
|
||||
refresh();
|
||||
}
|
||||
|
||||
/// 필터 초기화
|
||||
void clearFilters() {
|
||||
selectedType = null;
|
||||
isActive = null;
|
||||
refresh();
|
||||
}
|
||||
|
||||
/// 선택된 회사들 일괄 삭제
|
||||
Future<bool> deleteSelectedCompanies() async {
|
||||
if (_selectedCompanyIds.isEmpty) return false;
|
||||
|
||||
isLoadingState = true;
|
||||
notifyListeners();
|
||||
|
||||
bool allSuccess = true;
|
||||
final failedIds = <int>[];
|
||||
|
||||
for (final id in _selectedCompanyIds.toList()) {
|
||||
final params = DeleteCompanyParams(id: id);
|
||||
final result = await _deleteCompanyUseCase(params);
|
||||
|
||||
result.fold(
|
||||
(failure) {
|
||||
allSuccess = false;
|
||||
failedIds.add(id);
|
||||
debugPrint('회사 $id 삭제 실패: ${failure.message}');
|
||||
},
|
||||
(_) {
|
||||
removeItemLocally((item) => item.id == id);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (failedIds.isNotEmpty) {
|
||||
errorState = '일부 회사 삭제 실패: ${failedIds.join(', ')}';
|
||||
}
|
||||
|
||||
_selectedCompanyIds.clear();
|
||||
isLoadingState = false;
|
||||
notifyListeners();
|
||||
|
||||
return allSuccess;
|
||||
}
|
||||
}
|
||||
@@ -103,16 +103,14 @@ class EquipmentListController extends BaseListController<UnifiedEquipment> {
|
||||
);
|
||||
}).toList();
|
||||
|
||||
// 임시로 메타데이터 생성 (추후 API에서 실제 메타데이터 반환 시 수정)
|
||||
// API에서 반환한 실제 메타데이터 사용
|
||||
final meta = PaginationMeta(
|
||||
currentPage: params.page,
|
||||
perPage: params.perPage,
|
||||
total: items.length < params.perPage ?
|
||||
(params.page - 1) * params.perPage + items.length :
|
||||
(params.page * params.perPage) + 1,
|
||||
totalPages: items.length < params.perPage ? params.page : params.page + 1,
|
||||
hasNext: items.length >= params.perPage,
|
||||
hasPrevious: params.page > 1,
|
||||
currentPage: apiEquipmentDtos.page,
|
||||
perPage: apiEquipmentDtos.size,
|
||||
total: apiEquipmentDtos.totalElements,
|
||||
totalPages: apiEquipmentDtos.totalPages,
|
||||
hasNext: !apiEquipmentDtos.last,
|
||||
hasPrevious: !apiEquipmentDtos.first,
|
||||
);
|
||||
|
||||
return PagedResult(items: items, meta: meta);
|
||||
|
||||
@@ -168,10 +168,11 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
|
||||
/// 필터링된 장비 목록 반환
|
||||
List<UnifiedEquipment> _getFilteredEquipments() {
|
||||
// 서버에서 이미 페이지네이션된 데이터를 사용
|
||||
var equipments = _controller.equipments;
|
||||
print('DEBUG: Total equipments from controller: ${equipments.length}'); // 디버그 정보
|
||||
|
||||
// 검색 키워드 적용 (확장된 검색 필드)
|
||||
// 로컬 검색 키워드 적용 (서버 검색과 병행)
|
||||
// 서버에서 검색된 결과에 추가 로컬 필터링
|
||||
if (_appliedSearchKeyword.isNotEmpty) {
|
||||
equipments = equipments.where((e) {
|
||||
final keyword = _appliedSearchKeyword.toLowerCase();
|
||||
@@ -190,8 +191,6 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
}).toList();
|
||||
}
|
||||
|
||||
print('DEBUG: Filtered equipments count: ${equipments.length}'); // 디버그 정보
|
||||
print('DEBUG: Selected status filter: $_selectedStatus'); // 디버그 정보
|
||||
return equipments;
|
||||
}
|
||||
|
||||
@@ -392,7 +391,8 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
final int selectedRentCount = controller.getSelectedEquipmentCountByStatus(EquipmentStatus.rent);
|
||||
|
||||
final filteredEquipments = _getFilteredEquipments();
|
||||
final totalCount = filteredEquipments.length;
|
||||
// 백엔드 API에서 제공하는 실제 전체 아이템 수 사용
|
||||
final totalCount = controller.total;
|
||||
|
||||
return BaseListScreen(
|
||||
isLoading: controller.isLoading && controller.equipments.isEmpty,
|
||||
@@ -414,8 +414,8 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
dataTable: _buildDataTable(filteredEquipments),
|
||||
|
||||
// 페이지네이션
|
||||
pagination: totalCount > controller.pageSize ? Pagination(
|
||||
totalCount: totalCount,
|
||||
pagination: controller.totalPages > 1 ? Pagination(
|
||||
totalCount: controller.total,
|
||||
currentPage: controller.currentPage,
|
||||
pageSize: controller.pageSize,
|
||||
onPageChanged: (page) {
|
||||
@@ -944,17 +944,12 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
|
||||
/// 데이터 테이블
|
||||
Widget _buildDataTable(List<UnifiedEquipment> filteredEquipments) {
|
||||
final int startIndex = (_controller.currentPage - 1) * _controller.pageSize;
|
||||
final int endIndex =
|
||||
(startIndex + _controller.pageSize) > filteredEquipments.length
|
||||
? filteredEquipments.length
|
||||
: (startIndex + _controller.pageSize);
|
||||
final List<UnifiedEquipment> pagedEquipments = filteredEquipments.sublist(
|
||||
startIndex,
|
||||
endIndex,
|
||||
);
|
||||
// 백엔드에서 이미 페이지네이션된 데이터를 받으므로
|
||||
// 프론트엔드에서 추가 페이징 불필요
|
||||
final List<UnifiedEquipment> pagedEquipments = filteredEquipments;
|
||||
|
||||
if (pagedEquipments.isEmpty) {
|
||||
// 전체 데이터가 없는지 확인 (API의 total 사용)
|
||||
if (_controller.total == 0 && pagedEquipments.isEmpty) {
|
||||
return StandardEmptyState(
|
||||
title:
|
||||
_appliedSearchKeyword.isNotEmpty
|
||||
@@ -1173,19 +1168,9 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
|
||||
/// 페이지 데이터 가져오기
|
||||
List<UnifiedEquipment> _getPagedEquipments() {
|
||||
final filteredEquipments = _getFilteredEquipments();
|
||||
final int startIndex = (_controller.currentPage - 1) * _controller.pageSize;
|
||||
final int endIndex = startIndex + _controller.pageSize;
|
||||
|
||||
if (startIndex >= filteredEquipments.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
final actualEndIndex = endIndex > filteredEquipments.length
|
||||
? filteredEquipments.length
|
||||
: endIndex;
|
||||
|
||||
return filteredEquipments.sublist(startIndex, actualEndIndex);
|
||||
// 서버 페이지네이션 사용: 컨트롤러의 items가 이미 페이지네이션된 데이터
|
||||
// 로컬 필터링만 적용
|
||||
return _getFilteredEquipments();
|
||||
}
|
||||
|
||||
/// 카테고리 축약 표기 함수
|
||||
|
||||
@@ -1,283 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../core/controllers/base_list_controller.dart';
|
||||
import '../../../core/utils/error_handler.dart';
|
||||
import '../../../data/models/common/pagination_params.dart';
|
||||
import '../../../data/models/license/license_dto.dart';
|
||||
import '../../../domain/usecases/license/license_usecases.dart';
|
||||
|
||||
/// UseCase 패턴을 적용한 라이선스 목록 컨트롤러
|
||||
class LicenseListControllerWithUseCase extends BaseListController<LicenseDto> {
|
||||
final GetLicensesUseCase getLicensesUseCase;
|
||||
final CreateLicenseUseCase createLicenseUseCase;
|
||||
final UpdateLicenseUseCase updateLicenseUseCase;
|
||||
final DeleteLicenseUseCase deleteLicenseUseCase;
|
||||
final CheckLicenseExpiryUseCase checkLicenseExpiryUseCase;
|
||||
|
||||
// 선택된 항목들
|
||||
final Set<int> _selectedLicenseIds = {};
|
||||
Set<int> get selectedLicenseIds => _selectedLicenseIds;
|
||||
|
||||
// 필터 옵션
|
||||
String? _filterByCompany;
|
||||
String? _filterByExpiry;
|
||||
DateTime? _filterStartDate;
|
||||
DateTime? _filterEndDate;
|
||||
|
||||
String? get filterByCompany => _filterByCompany;
|
||||
String? get filterByExpiry => _filterByExpiry;
|
||||
DateTime? get filterStartDate => _filterStartDate;
|
||||
DateTime? get filterEndDate => _filterEndDate;
|
||||
|
||||
// 만료 임박 라이선스 정보
|
||||
LicenseExpiryResult? _expiryResult;
|
||||
LicenseExpiryResult? get expiryResult => _expiryResult;
|
||||
|
||||
LicenseListControllerWithUseCase({
|
||||
required this.getLicensesUseCase,
|
||||
required this.createLicenseUseCase,
|
||||
required this.updateLicenseUseCase,
|
||||
required this.deleteLicenseUseCase,
|
||||
required this.checkLicenseExpiryUseCase,
|
||||
});
|
||||
|
||||
@override
|
||||
Future<PagedResult<LicenseDto>> fetchData({
|
||||
required PaginationParams params,
|
||||
Map<String, dynamic>? additionalFilters,
|
||||
}) async {
|
||||
try {
|
||||
// 필터 파라미터 구성
|
||||
final filters = <String, dynamic>{};
|
||||
if (_filterByCompany != null) filters['company_id'] = _filterByCompany;
|
||||
if (_filterByExpiry != null) filters['expiry'] = _filterByExpiry;
|
||||
if (_filterStartDate != null) filters['start_date'] = _filterStartDate!.toIso8601String();
|
||||
if (_filterEndDate != null) filters['end_date'] = _filterEndDate!.toIso8601String();
|
||||
|
||||
final updatedParams = params.copyWith(filters: filters);
|
||||
final getParams = GetLicensesParams.fromPaginationParams(updatedParams);
|
||||
|
||||
final result = await getLicensesUseCase(getParams);
|
||||
|
||||
return result.fold(
|
||||
(failure) => throw Exception(failure.message),
|
||||
(licenseResponse) {
|
||||
// PagedResult로 래핑하여 반환
|
||||
final meta = PaginationMeta(
|
||||
currentPage: params.page,
|
||||
perPage: params.perPage,
|
||||
total: licenseResponse.items.length, // 실제로는 서버에서 받아와야 함
|
||||
totalPages: (licenseResponse.items.length / params.perPage).ceil(),
|
||||
hasNext: licenseResponse.items.length >= params.perPage,
|
||||
hasPrevious: params.page > 1,
|
||||
);
|
||||
return PagedResult(items: licenseResponse.items, meta: meta);
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
throw Exception('데이터 로드 실패: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// 만료 임박 라이선스 체크
|
||||
Future<void> checkExpiringLicenses() async {
|
||||
try {
|
||||
final params = CheckLicenseExpiryParams(
|
||||
companyId: _filterByCompany != null ? int.tryParse(_filterByCompany!) : null,
|
||||
);
|
||||
|
||||
final result = await checkLicenseExpiryUseCase(params);
|
||||
|
||||
result.fold(
|
||||
(failure) => errorState = failure.message,
|
||||
(expiryResult) {
|
||||
_expiryResult = expiryResult;
|
||||
notifyListeners();
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
errorState = '라이선스 만료 체크 실패: $e';
|
||||
}
|
||||
}
|
||||
|
||||
/// 라이선스 생성
|
||||
Future<void> createLicense({
|
||||
required int equipmentId,
|
||||
required int companyId,
|
||||
required String licenseType,
|
||||
required DateTime startDate,
|
||||
required DateTime expiryDate,
|
||||
String? description,
|
||||
double? cost,
|
||||
}) async {
|
||||
try {
|
||||
isLoadingState = true;
|
||||
|
||||
final params = CreateLicenseParams(
|
||||
equipmentId: equipmentId,
|
||||
companyId: companyId,
|
||||
licenseType: licenseType,
|
||||
startDate: startDate,
|
||||
expiryDate: expiryDate,
|
||||
description: description,
|
||||
cost: cost,
|
||||
);
|
||||
|
||||
final result = await createLicenseUseCase(params);
|
||||
|
||||
await result.fold(
|
||||
(failure) async => errorState = failure.message,
|
||||
(license) async {
|
||||
await refresh();
|
||||
await checkExpiringLicenses();
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
errorState = '오류 생성: $e';
|
||||
} finally {
|
||||
isLoadingState = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 라이선스 수정
|
||||
Future<void> updateLicense({
|
||||
required int id,
|
||||
int? equipmentId,
|
||||
int? companyId,
|
||||
String? licenseType,
|
||||
DateTime? startDate,
|
||||
DateTime? expiryDate,
|
||||
String? description,
|
||||
double? cost,
|
||||
String? status,
|
||||
}) async {
|
||||
try {
|
||||
isLoadingState = true;
|
||||
|
||||
final params = UpdateLicenseParams(
|
||||
id: id,
|
||||
equipmentId: equipmentId,
|
||||
companyId: companyId,
|
||||
licenseType: licenseType,
|
||||
startDate: startDate,
|
||||
expiryDate: expiryDate,
|
||||
description: description,
|
||||
cost: cost,
|
||||
status: status,
|
||||
);
|
||||
|
||||
final result = await updateLicenseUseCase(params);
|
||||
|
||||
await result.fold(
|
||||
(failure) async => errorState = failure.message,
|
||||
(license) async {
|
||||
updateItemLocally(license, (item) => item.id == license.id);
|
||||
await checkExpiringLicenses();
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
errorState = '오류 생성: $e';
|
||||
} finally {
|
||||
isLoadingState = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 라이선스 삭제
|
||||
Future<void> deleteLicense(int id) async {
|
||||
try {
|
||||
isLoadingState = true;
|
||||
|
||||
final result = await deleteLicenseUseCase(id);
|
||||
|
||||
await result.fold(
|
||||
(failure) async => errorState = failure.message,
|
||||
(_) async {
|
||||
removeItemLocally((item) => item.id == id);
|
||||
_selectedLicenseIds.remove(id);
|
||||
await checkExpiringLicenses();
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
errorState = '오류 생성: $e';
|
||||
} finally {
|
||||
isLoadingState = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 필터 설정
|
||||
void setFilters({
|
||||
String? company,
|
||||
String? expiry,
|
||||
DateTime? startDate,
|
||||
DateTime? endDate,
|
||||
}) {
|
||||
_filterByCompany = company;
|
||||
_filterByExpiry = expiry;
|
||||
_filterStartDate = startDate;
|
||||
_filterEndDate = endDate;
|
||||
refresh();
|
||||
}
|
||||
|
||||
/// 필터 초기화
|
||||
void clearFilters() {
|
||||
_filterByCompany = null;
|
||||
_filterByExpiry = null;
|
||||
_filterStartDate = null;
|
||||
_filterEndDate = null;
|
||||
refresh();
|
||||
}
|
||||
|
||||
/// 라이선스 선택 토글
|
||||
void toggleLicenseSelection(int id) {
|
||||
if (_selectedLicenseIds.contains(id)) {
|
||||
_selectedLicenseIds.remove(id);
|
||||
} else {
|
||||
_selectedLicenseIds.add(id);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 모든 라이선스 선택
|
||||
void selectAll() {
|
||||
_selectedLicenseIds.clear();
|
||||
_selectedLicenseIds.addAll(items.map((e) => e.id));
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 선택 해제
|
||||
void clearSelection() {
|
||||
_selectedLicenseIds.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 선택된 라이선스 일괄 삭제
|
||||
Future<void> deleteSelectedLicenses() async {
|
||||
if (_selectedLicenseIds.isEmpty) return;
|
||||
|
||||
try {
|
||||
isLoadingState = true;
|
||||
|
||||
for (final id in _selectedLicenseIds.toList()) {
|
||||
final result = await deleteLicenseUseCase(id);
|
||||
result.fold(
|
||||
(failure) => print('Failed to delete license $id: ${failure.message}'),
|
||||
(_) => removeItemLocally((item) => item.id == id),
|
||||
);
|
||||
}
|
||||
|
||||
_selectedLicenseIds.clear();
|
||||
await checkExpiringLicenses();
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
errorState = '오류 생성: $e';
|
||||
} finally {
|
||||
isLoadingState = false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_selectedLicenseIds.clear();
|
||||
_expiryResult = null;
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,7 @@ class _LicenseListState extends State<LicenseList> {
|
||||
// 실제 API 사용 여부에 따라 컨트롤러 초기화
|
||||
final useApi = env.Environment.useApi;
|
||||
_controller = LicenseListController();
|
||||
_controller.pageSize = 10; // 페이지 크기를 10으로 설정
|
||||
|
||||
debugPrint('📌 Controller 모드: ${useApi ? "Real API" : "Mock Data"}');
|
||||
debugPrint('==========================================\n');
|
||||
@@ -239,16 +240,17 @@ class _LicenseListState extends State<LicenseList> {
|
||||
child: Consumer<LicenseListController>(
|
||||
builder: (context, controller, child) {
|
||||
final licenses = controller.licenses;
|
||||
final totalCount = licenses.length;
|
||||
// 백엔드 API에서 제공하는 실제 전체 아이템 수 사용
|
||||
final totalCount = controller.total;
|
||||
|
||||
return BaseListScreen(
|
||||
headerSection: _buildStatisticsCards(),
|
||||
searchBar: _buildSearchBar(),
|
||||
actionBar: _buildActionBar(),
|
||||
dataTable: _buildDataTable(),
|
||||
pagination: totalCount > controller.pageSize
|
||||
pagination: controller.total > 0
|
||||
? Pagination(
|
||||
totalCount: totalCount,
|
||||
totalCount: controller.total,
|
||||
currentPage: controller.currentPage,
|
||||
pageSize: controller.pageSize,
|
||||
onPageChanged: (page) {
|
||||
@@ -597,6 +599,7 @@ class _LicenseListState extends State<LicenseList> {
|
||||
...pagedLicenses.asMap().entries.map((entry) {
|
||||
final displayIndex = entry.key;
|
||||
final license = entry.value;
|
||||
// 백엔드에서 이미 페이지네이션된 데이터를 받으므로 추가 계산 불필요
|
||||
final index = (_controller.currentPage - 1) * _controller.pageSize + displayIndex;
|
||||
final daysRemaining = _controller.getDaysUntilExpiry(license.expiryDate);
|
||||
|
||||
|
||||
@@ -2,14 +2,14 @@ import 'package:flutter/material.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:superport/core/errors/failures.dart';
|
||||
import 'package:superport/data/models/auth/login_request.dart';
|
||||
import 'package:superport/di/injection_container.dart';
|
||||
import 'package:superport/injection_container.dart';
|
||||
import 'package:superport/services/auth_service.dart';
|
||||
import 'package:superport/services/health_test_service.dart';
|
||||
import 'package:superport/services/health_check_service.dart';
|
||||
|
||||
/// 로그인 화면의 상태 및 비즈니스 로직을 담당하는 ChangeNotifier 기반 컨트롤러
|
||||
class LoginController extends ChangeNotifier {
|
||||
final AuthService _authService = inject<AuthService>();
|
||||
final AuthService _authService = sl<AuthService>();
|
||||
final HealthCheckService _healthCheckService = HealthCheckService();
|
||||
/// 아이디 입력 컨트롤러
|
||||
final TextEditingController idController = TextEditingController();
|
||||
|
||||
@@ -1,193 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import '../../../core/errors/failures.dart';
|
||||
import '../../../domain/usecases/base_usecase.dart';
|
||||
import '../../../domain/usecases/auth/login_usecase.dart';
|
||||
import '../../../domain/usecases/auth/check_auth_status_usecase.dart';
|
||||
import '../../../services/auth_service.dart';
|
||||
import '../../../services/health_check_service.dart';
|
||||
import '../../../di/injection_container.dart';
|
||||
|
||||
/// UseCase를 활용한 로그인 화면 컨트롤러
|
||||
/// 비즈니스 로직을 UseCase로 분리하여 테스트 용이성과 재사용성 향상
|
||||
class LoginControllerWithUseCase extends ChangeNotifier {
|
||||
// UseCases
|
||||
late final LoginUseCase _loginUseCase;
|
||||
late final CheckAuthStatusUseCase _checkAuthStatusUseCase;
|
||||
|
||||
// Services
|
||||
final HealthCheckService _healthCheckService = HealthCheckService();
|
||||
|
||||
// UI Controllers
|
||||
final TextEditingController idController = TextEditingController();
|
||||
final TextEditingController pwController = TextEditingController();
|
||||
|
||||
// Focus Nodes
|
||||
final FocusNode idFocus = FocusNode();
|
||||
final FocusNode pwFocus = FocusNode();
|
||||
|
||||
// State
|
||||
bool saveId = false;
|
||||
bool _isLoading = false;
|
||||
String? _errorMessage;
|
||||
|
||||
// Getters
|
||||
bool get isLoading => _isLoading;
|
||||
String? get errorMessage => _errorMessage;
|
||||
|
||||
LoginControllerWithUseCase() {
|
||||
// UseCase 초기화
|
||||
final authService = inject<AuthService>();
|
||||
_loginUseCase = LoginUseCase(authService);
|
||||
_checkAuthStatusUseCase = CheckAuthStatusUseCase(authService);
|
||||
|
||||
// 초기 인증 상태 확인
|
||||
_checkInitialAuthStatus();
|
||||
}
|
||||
|
||||
/// 초기 인증 상태 확인
|
||||
Future<void> _checkInitialAuthStatus() async {
|
||||
final result = await _checkAuthStatusUseCase(const NoParams());
|
||||
result.fold(
|
||||
(failure) => debugPrint('인증 상태 확인 실패: ${failure.message}'),
|
||||
(isAuthenticated) {
|
||||
if (isAuthenticated) {
|
||||
debugPrint('이미 로그인된 상태입니다.');
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 아이디 저장 체크박스 상태 변경
|
||||
void setSaveId(bool value) {
|
||||
saveId = value;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 에러 메시지 초기화
|
||||
void clearError() {
|
||||
_errorMessage = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 로그인 처리
|
||||
Future<bool> login() async {
|
||||
// 입력값 검증 (UI 레벨)
|
||||
if (idController.text.trim().isEmpty) {
|
||||
_errorMessage = '아이디 또는 이메일을 입력해주세요.';
|
||||
notifyListeners();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pwController.text.isEmpty) {
|
||||
_errorMessage = '비밀번호를 입력해주세요.';
|
||||
notifyListeners();
|
||||
return false;
|
||||
}
|
||||
|
||||
// 로딩 시작
|
||||
_isLoading = true;
|
||||
_errorMessage = null;
|
||||
notifyListeners();
|
||||
|
||||
// 입력값이 이메일인지 username인지 판단
|
||||
final inputValue = idController.text.trim();
|
||||
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
|
||||
final isEmail = emailRegex.hasMatch(inputValue);
|
||||
|
||||
try {
|
||||
// UseCase 실행
|
||||
final params = LoginParams(
|
||||
email: isEmail ? inputValue : '$inputValue@superport.kr', // username인 경우 임시 도메인 추가
|
||||
password: pwController.text,
|
||||
);
|
||||
|
||||
debugPrint('[LoginController] 로그인 시도: ${params.email}');
|
||||
|
||||
final result = await _loginUseCase(params).timeout(
|
||||
const Duration(seconds: 10),
|
||||
onTimeout: () async {
|
||||
debugPrint('[LoginController] 로그인 요청 타임아웃');
|
||||
return Left(NetworkFailure(
|
||||
message: '요청 시간이 초과되었습니다. 네트워크 연결을 확인해주세요.',
|
||||
));
|
||||
},
|
||||
);
|
||||
|
||||
return result.fold(
|
||||
(failure) {
|
||||
debugPrint('[LoginController] 로그인 실패: ${failure.message}');
|
||||
|
||||
// 실패 타입에 따른 메시지 처리
|
||||
if (failure is ValidationFailure) {
|
||||
_errorMessage = failure.message;
|
||||
} else if (failure is AuthenticationFailure) {
|
||||
_errorMessage = '이메일 또는 비밀번호가 올바르지 않습니다.';
|
||||
} else if (failure is NetworkFailure) {
|
||||
_errorMessage = '네트워크 연결을 확인해주세요.';
|
||||
} else if (failure is ServerFailure) {
|
||||
_errorMessage = '서버 오류가 발생했습니다.\n잠시 후 다시 시도해주세요.';
|
||||
} else {
|
||||
_errorMessage = failure.message;
|
||||
}
|
||||
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
return false;
|
||||
},
|
||||
(loginResponse) {
|
||||
debugPrint('[LoginController] 로그인 성공');
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
return true;
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('[LoginController] 예상치 못한 에러: $e');
|
||||
_errorMessage = '로그인 중 오류가 발생했습니다.';
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 헬스체크 실행
|
||||
Future<bool> performHealthCheck() async {
|
||||
debugPrint('[LoginController] 헬스체크 시작');
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
final healthResult = await _healthCheckService.checkHealth();
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
|
||||
// HealthCheckService가 Map을 반환하는 경우 적절히 변환
|
||||
final isHealthy = healthResult is bool ? healthResult :
|
||||
(healthResult is Map && healthResult['status'] == 'healthy');
|
||||
|
||||
if (isHealthy == false) {
|
||||
_errorMessage = '서버와 연결할 수 없습니다.\n잠시 후 다시 시도해주세요.';
|
||||
notifyListeners();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
debugPrint('[LoginController] 헬스체크 실패: $e');
|
||||
_errorMessage = '서버 상태 확인 중 오류가 발생했습니다.';
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
idController.dispose();
|
||||
pwController.dispose();
|
||||
idFocus.dispose();
|
||||
pwFocus.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -28,8 +28,7 @@ class _UserListState extends State<UserList> {
|
||||
// 초기 데이터 로드
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
final controller = context.read<UserListController>();
|
||||
controller.pageSize = 10; // 페이지 크기 설정
|
||||
controller.loadUsers();
|
||||
controller.initialize(pageSize: 10); // 통일된 초기화 방식
|
||||
});
|
||||
|
||||
// 검색 디바운싱
|
||||
|
||||
@@ -1,300 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../core/controllers/base_list_controller.dart';
|
||||
import '../../../core/utils/error_handler.dart';
|
||||
import '../../../data/models/common/pagination_params.dart';
|
||||
import '../../../data/models/warehouse/warehouse_dto.dart';
|
||||
import '../../../domain/usecases/warehouse_location/warehouse_location_usecases.dart';
|
||||
|
||||
/// UseCase 패턴을 적용한 창고 위치 목록 컨트롤러
|
||||
class WarehouseLocationListControllerWithUseCase extends BaseListController<WarehouseLocationDto> {
|
||||
final GetWarehouseLocationsUseCase getWarehouseLocationsUseCase;
|
||||
final CreateWarehouseLocationUseCase createWarehouseLocationUseCase;
|
||||
final UpdateWarehouseLocationUseCase updateWarehouseLocationUseCase;
|
||||
final DeleteWarehouseLocationUseCase deleteWarehouseLocationUseCase;
|
||||
|
||||
// 선택된 항목들
|
||||
final Set<int> _selectedLocationIds = {};
|
||||
Set<int> get selectedLocationIds => _selectedLocationIds;
|
||||
|
||||
// 필터 옵션
|
||||
bool _showActiveOnly = true;
|
||||
String? _filterByManager;
|
||||
|
||||
bool get showActiveOnly => _showActiveOnly;
|
||||
String? get filterByManager => _filterByManager;
|
||||
|
||||
WarehouseLocationListControllerWithUseCase({
|
||||
required this.getWarehouseLocationsUseCase,
|
||||
required this.createWarehouseLocationUseCase,
|
||||
required this.updateWarehouseLocationUseCase,
|
||||
required this.deleteWarehouseLocationUseCase,
|
||||
});
|
||||
|
||||
@override
|
||||
Future<PagedResult<WarehouseLocationDto>> fetchData({
|
||||
required PaginationParams params,
|
||||
Map<String, dynamic>? additionalFilters,
|
||||
}) async {
|
||||
try {
|
||||
// 필터 파라미터 구성
|
||||
final filters = <String, dynamic>{};
|
||||
if (_showActiveOnly) filters['is_active'] = true;
|
||||
if (_filterByManager != null) filters['manager'] = _filterByManager;
|
||||
|
||||
final updatedParams = params.copyWith(filters: filters);
|
||||
final getParams = GetWarehouseLocationsParams.fromPaginationParams(updatedParams);
|
||||
|
||||
final result = await getWarehouseLocationsUseCase(getParams);
|
||||
|
||||
return result.fold(
|
||||
(failure) => throw Exception(failure.message),
|
||||
(locationsResponse) {
|
||||
// PagedResult로 래핑하여 반환
|
||||
final meta = PaginationMeta(
|
||||
currentPage: params.page,
|
||||
perPage: params.perPage,
|
||||
total: locationsResponse.items.length,
|
||||
totalPages: (locationsResponse.items.length / params.perPage).ceil(),
|
||||
hasNext: locationsResponse.items.length >= params.perPage,
|
||||
hasPrevious: params.page > 1,
|
||||
);
|
||||
return PagedResult(items: locationsResponse.items, meta: meta);
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
throw Exception('데이터 로드 실패: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// 창고 위치 생성
|
||||
Future<void> createWarehouseLocation({
|
||||
required String name,
|
||||
required String address,
|
||||
String? description,
|
||||
String? contactNumber,
|
||||
String? manager,
|
||||
double? latitude,
|
||||
double? longitude,
|
||||
}) async {
|
||||
try {
|
||||
isLoadingState = true;
|
||||
|
||||
final params = CreateWarehouseLocationParams(
|
||||
name: name,
|
||||
address: address,
|
||||
description: description,
|
||||
contactNumber: contactNumber,
|
||||
manager: manager,
|
||||
latitude: latitude,
|
||||
longitude: longitude,
|
||||
);
|
||||
|
||||
final result = await createWarehouseLocationUseCase(params);
|
||||
|
||||
await result.fold(
|
||||
(failure) async => errorState = failure.message,
|
||||
(location) async => await refresh(),
|
||||
);
|
||||
} catch (e) {
|
||||
errorState = '오류 발생: $e';
|
||||
} finally {
|
||||
isLoadingState = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 창고 위치 수정
|
||||
Future<void> updateWarehouseLocation({
|
||||
required int id,
|
||||
String? name,
|
||||
String? address,
|
||||
String? description,
|
||||
String? contactNumber,
|
||||
String? manager,
|
||||
double? latitude,
|
||||
double? longitude,
|
||||
bool? isActive,
|
||||
}) async {
|
||||
try {
|
||||
isLoadingState = true;
|
||||
|
||||
final params = UpdateWarehouseLocationParams(
|
||||
id: id,
|
||||
name: name,
|
||||
address: address,
|
||||
description: description,
|
||||
contactNumber: contactNumber,
|
||||
manager: manager,
|
||||
latitude: latitude,
|
||||
longitude: longitude,
|
||||
isActive: isActive,
|
||||
);
|
||||
|
||||
final result = await updateWarehouseLocationUseCase(params);
|
||||
|
||||
await result.fold(
|
||||
(failure) async => errorState = failure.message,
|
||||
(location) async => updateItemLocally(location, (item) => item.id == location.id),
|
||||
);
|
||||
} catch (e) {
|
||||
errorState = '오류 발생: $e';
|
||||
} finally {
|
||||
isLoadingState = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 창고 위치 삭제
|
||||
Future<void> deleteWarehouseLocation(int id) async {
|
||||
try {
|
||||
isLoadingState = true;
|
||||
|
||||
final result = await deleteWarehouseLocationUseCase(id);
|
||||
|
||||
await result.fold(
|
||||
(failure) async => errorState = failure.message,
|
||||
(_) async {
|
||||
removeItemLocally((item) => item.id == id);
|
||||
_selectedLocationIds.remove(id);
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
errorState = '오류 발생: $e';
|
||||
} finally {
|
||||
isLoadingState = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 창고 위치 활성/비활성 토글
|
||||
Future<void> toggleLocationStatus(int id) async {
|
||||
final location = items.firstWhere((item) => item.id == id);
|
||||
await updateWarehouseLocation(
|
||||
id: id,
|
||||
isActive: !location.isActive,
|
||||
);
|
||||
}
|
||||
|
||||
/// 필터 설정
|
||||
void setFilters({
|
||||
bool? showActiveOnly,
|
||||
String? manager,
|
||||
}) {
|
||||
if (showActiveOnly != null) _showActiveOnly = showActiveOnly;
|
||||
_filterByManager = manager;
|
||||
refresh();
|
||||
}
|
||||
|
||||
/// 필터 초기화
|
||||
void clearFilters() {
|
||||
_showActiveOnly = true;
|
||||
_filterByManager = null;
|
||||
refresh();
|
||||
}
|
||||
|
||||
/// 창고 위치 선택 토글
|
||||
void toggleLocationSelection(int id) {
|
||||
if (_selectedLocationIds.contains(id)) {
|
||||
_selectedLocationIds.remove(id);
|
||||
} else {
|
||||
_selectedLocationIds.add(id);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 모든 창고 위치 선택
|
||||
void selectAll() {
|
||||
_selectedLocationIds.clear();
|
||||
_selectedLocationIds.addAll(items.map((e) => e.id));
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 선택 해제
|
||||
void clearSelection() {
|
||||
_selectedLocationIds.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 선택된 창고 위치 일괄 삭제
|
||||
Future<void> deleteSelectedLocations() async {
|
||||
if (_selectedLocationIds.isEmpty) return;
|
||||
|
||||
try {
|
||||
isLoadingState = true;
|
||||
|
||||
final errors = <String>[];
|
||||
for (final id in _selectedLocationIds.toList()) {
|
||||
final result = await deleteWarehouseLocationUseCase(id);
|
||||
result.fold(
|
||||
(failure) => errors.add('Location $id: ${failure.message}'),
|
||||
(_) => removeItemLocally((item) => item.id == id),
|
||||
);
|
||||
}
|
||||
|
||||
_selectedLocationIds.clear();
|
||||
|
||||
if (errors.isNotEmpty) {
|
||||
errorState = '일부 창고 위치 삭제 실패:\n${errors.join('\n')}';
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
errorState = '오류 발생: $e';
|
||||
} finally {
|
||||
isLoadingState = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 선택된 창고 위치 일괄 활성화
|
||||
Future<void> activateSelectedLocations() async {
|
||||
if (_selectedLocationIds.isEmpty) return;
|
||||
|
||||
try {
|
||||
isLoadingState = true;
|
||||
|
||||
for (final id in _selectedLocationIds.toList()) {
|
||||
await updateWarehouseLocation(id: id, isActive: true);
|
||||
}
|
||||
|
||||
_selectedLocationIds.clear();
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
errorState = '오류 발생: $e';
|
||||
} finally {
|
||||
isLoadingState = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 선택된 창고 위치 일괄 비활성화
|
||||
Future<void> deactivateSelectedLocations() async {
|
||||
if (_selectedLocationIds.isEmpty) return;
|
||||
|
||||
try {
|
||||
isLoadingState = true;
|
||||
|
||||
for (final id in _selectedLocationIds.toList()) {
|
||||
await updateWarehouseLocation(id: id, isActive: false);
|
||||
}
|
||||
|
||||
_selectedLocationIds.clear();
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
errorState = '오류 발생: $e';
|
||||
} finally {
|
||||
isLoadingState = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 드롭다운용 활성 창고 위치 목록 가져오기
|
||||
List<WarehouseLocationDto> getActiveLocations() {
|
||||
return items.where((location) => location.isActive).toList();
|
||||
}
|
||||
|
||||
/// 특정 관리자의 창고 위치 목록 가져오기
|
||||
List<WarehouseLocationDto> getLocationsByManager(String manager) {
|
||||
return items.where((location) => location.managerName == manager).toList(); // managerName 필드 사용
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_selectedLocationIds.clear();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ class _WarehouseLocationListState
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = WarehouseLocationListController();
|
||||
_controller.pageSize = 10; // 페이지 크기를 10으로 설정
|
||||
// 초기 데이터 로드
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_controller.loadWarehouseLocations();
|
||||
@@ -145,8 +146,8 @@ class _WarehouseLocationListState
|
||||
dataTable: _buildDataTable(pagedLocations),
|
||||
|
||||
// 페이지네이션
|
||||
pagination: totalCount > controller.pageSize ? Pagination(
|
||||
totalCount: totalCount,
|
||||
pagination: controller.totalPages > 1 ? Pagination(
|
||||
totalCount: controller.total,
|
||||
currentPage: controller.currentPage,
|
||||
pageSize: controller.pageSize,
|
||||
onPageChanged: (page) {
|
||||
@@ -162,7 +163,8 @@ class _WarehouseLocationListState
|
||||
|
||||
/// 데이터 테이블
|
||||
Widget _buildDataTable(List<WarehouseLocation> pagedLocations) {
|
||||
if (pagedLocations.isEmpty) {
|
||||
// 전체 데이터가 없는지 확인 (API의 total 사용)
|
||||
if (_controller.total == 0 && pagedLocations.isEmpty) {
|
||||
return StandardEmptyState(
|
||||
title:
|
||||
_controller.searchQuery.isNotEmpty
|
||||
|
||||
@@ -8,7 +8,7 @@ import 'package:superport/models/user_model.dart';
|
||||
class UserService {
|
||||
final UserRemoteDataSource _userRemoteDataSource;
|
||||
|
||||
UserService() : _userRemoteDataSource = UserRemoteDataSource();
|
||||
UserService(this._userRemoteDataSource);
|
||||
|
||||
/// 사용자 목록 조회
|
||||
Future<PaginatedResponse<User>> getUsers({
|
||||
|
||||
Reference in New Issue
Block a user