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>> 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( 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> getCompanyById(int id) async { try { final result = await remoteDataSource.getCompanyWithChildren(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> 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> 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> 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> 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> 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> 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> 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>> 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>> getCompanyCountByType() async { // TODO: API에서 회사 유형별 통계 기능이 구현되면 추가 return const Left(ServerFailure( message: '회사 유형별 통계 기능이 아직 구현되지 않았습니다.', )); } @override Future> hasLinkedUsers(int companyId) async { // TODO: 회사에 연결된 사용자 존재 여부 확인 API 구현 필요 try { // 임시로 false 반환 - API 구현 후 수정 필요 return const Right(false); } catch (e) { return Left(ServerFailure( message: '연결된 사용자 확인 중 오류가 발생했습니다: ${e.toString()}', )); } } @override Future> hasLinkedEquipment(int companyId) async { // TODO: 회사에 연결된 장비 존재 여부 확인 API 구현 필요 try { // 임시로 false 반환 - API 구현 후 수정 필요 return const Right(false); } catch (e) { return Left(ServerFailure( message: '연결된 장비 확인 중 오류가 발생했습니다: ${e.toString()}', )); } } @override Future> 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(CompanyWithChildren dto) { return Company( id: dto.company.id, name: dto.company.name, address: Address.fromFullAddress(dto.company.address ?? ''), contactName: dto.company.contactName, contactPosition: null, // 백엔드에서 미지원 contactPhone: dto.company.contactPhone, contactEmail: dto.company.contactEmail, companyTypes: [], // 백엔드에서 미지원 remark: dto.company.remark, branches: [], // TODO: 계층형 구조로 변경됨. children은 자회사를 의미하므로 branches는 빈 리스트로 설정 ); } Company _mapResponseToDomain(CompanyDto response) { return Company( id: response.id, name: response.name, address: Address.fromFullAddress(response.address ?? ''), contactName: response.contactName, contactPosition: null, // 백엔드에서 미지원 contactPhone: response.contactPhone, contactEmail: response.contactEmail, companyTypes: [], // 백엔드에서 미지원 remark: response.remark, branches: [], // CompanyDto에서는 지점 정보 따로 조회 ); } 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 _parseCompanyTypes(List? 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(); } CompanyRequestDto _mapDomainToCreateRequest(Company company) { return CompanyRequestDto( name: company.name, address: company.address.toString(), contactName: company.contactName ?? '', contactPhone: company.contactPhone ?? '', contactEmail: company.contactEmail ?? '', remark: company.remark, ); } CompanyUpdateRequestDto _mapDomainToUpdateRequest(Company company) { return CompanyUpdateRequestDto( name: company.name, address: company.address.toString(), contactName: company.contactName, contactPhone: company.contactPhone, contactEmail: company.contactEmail, remark: company.remark, isActive: null, // CompanyUpdateRequestDto에서 필요한 경우 추가 ); } 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, ); } // 계층 구조 관련 메서드 구현 @override Future>> getCompanyHierarchy({ bool includeInactive = false, }) async { try { // 모든 회사 조회 final result = await remoteDataSource.getCompanies( page: 1, perPage: 1000, // 전체 회사 조회를 위한 큰 수 isActive: includeInactive ? null : true, ); // DTO를 도메인 모델로 변환 final companies = result.items.map((dto) => _mapDtoToDomain(dto)).toList(); // 계층 구조로 재구성 (클라이언트에서 처리) // 실제 API가 계층 구조를 반환하면 이 부분 수정 필요 return Right(companies); } catch (e) { return Left(ServerFailure( message: '회사 계층 구조 조회 중 오류가 발생했습니다: ${e.toString()}', )); } } @override Future>> getChildrenCompanies( int companyId, { bool recursive = false, }) async { try { // API에서 자식 회사 조회 // 현재는 getCompanyWithChildren 사용 final result = await remoteDataSource.getCompanyWithChildren(companyId); final children = []; for (final childDto in result.children) { final child = _mapResponseToDomain(childDto); children.add(child); // 재귀적으로 모든 자손 포함 if (recursive && childDto.id != null && childDto.id != companyId) { final grandChildrenResult = await getChildrenCompanies(childDto.id!, recursive: true); grandChildrenResult.fold( (failure) {}, // 에러 무시하고 진행 (grandChildren) => children.addAll(grandChildren), ); } } return Right(children); } catch (e) { return Left(ServerFailure( message: '자식 회사 조회 중 오류가 발생했습니다: ${e.toString()}', )); } } @override Future>> getAncestorPath(int companyId) async { try { final path = []; int? currentId = companyId; while (currentId != null) { final companyResult = await getCompanyById(currentId); final company = companyResult.fold( (failure) => null, (company) => company, ); if (company == null) break; path.insert(0, company); // 루트부터 시작하도록 앞에 삽입 // parent_company_id를 찾기 위해 API 호출 필요 // 현재 CompanyDto에 parentCompanyId가 있으므로 사용 final detailResult = await remoteDataSource.getCompanyWithChildren(currentId); currentId = detailResult.company.parentCompanyId; } return Right(path); } catch (e) { return Left(ServerFailure( message: '부모 경로 조회 중 오류가 발생했습니다: ${e.toString()}', )); } } @override Future> updateParentCompany( int companyId, int? newParentId, ) async { try { // 먼저 현재 회사 정보 조회 final currentResult = await getCompanyById(companyId); return currentResult.fold( (failure) => Left(failure), (company) async { // 부모 회사 ID만 업데이트 final updateRequest = CompanyUpdateRequestDto( parentCompanyId: newParentId, ); final result = await remoteDataSource.updateCompany(companyId, updateRequest); final updatedCompany = _mapResponseToDomain(result); return Right(updatedCompany); }, ); } catch (e) { return Left(ServerFailure( message: '부모 회사 변경 중 오류가 발생했습니다: ${e.toString()}', )); } } @override Future> hasChildrenCompanies(int companyId) async { try { final childrenResult = await getChildrenCompanies(companyId, recursive: false); return childrenResult.fold( (failure) => Left(failure), (children) => Right(children.isNotEmpty), ); } catch (e) { return Left(ServerFailure( message: '자식 회사 존재 여부 확인 중 오류가 발생했습니다: ${e.toString()}', )); } } @override Future> validateHierarchyChange( int companyId, int? newParentId, ) async { try { if (newParentId == null) { // 루트로 변경하는 경우는 항상 유효 return const Right(true); } // 자기 자신을 부모로 설정하려는 경우 if (companyId == newParentId) { return const Right(false); } // 자손을 부모로 설정하려는 경우 검증 final descendantsResult = await getChildrenCompanies(companyId, recursive: true); return descendantsResult.fold( (failure) => Left(failure), (descendants) { final descendantIds = descendants.map((c) => c.id).toList(); if (descendantIds.contains(newParentId)) { return const Right(false); } return const Right(true); }, ); } catch (e) { return Left(ServerFailure( message: '계층 구조 유효성 검증 중 오류가 발생했습니다: ${e.toString()}', )); } } }