import 'package:flutter/foundation.dart'; import 'package:injectable/injectable.dart'; import 'package:dartz/dartz.dart'; import 'package:superport/core/constants/app_constants.dart'; import 'package:superport/core/errors/exceptions.dart'; import 'package:superport/core/errors/failures.dart'; import 'package:superport/data/datasources/remote/company_remote_datasource.dart'; import 'package:superport/data/models/common/paginated_response.dart'; import 'package:superport/data/models/company/company_dto.dart'; import 'package:superport/data/models/company/company_list_dto.dart'; // Branch DTO는 더 이상 사용하지 않음 (계층형 Company 구조로 변경) import 'package:superport/models/company_model.dart'; import 'package:superport/models/company_item_model.dart'; import 'package:superport/models/address_model.dart'; @lazySingleton class CompanyService { final CompanyRemoteDataSource _remoteDataSource; CompanyService(this._remoteDataSource); // 회사 목록 조회 Future> getCompanies({ int page = 1, int perPage = AppConstants.companyPageSize, String? search, bool? isActive, bool includeInactive = false, }) async { try { // 🔧 백엔드 버그 우회: 검색 시에는 is_active 파라미터 제거 // search + is_active 조합에서 빈 결과 반환 버그 존재 final effectiveIsActive = search != null && search.isNotEmpty ? null // 검색 시에는 is_active 필터 제거 : (isActive ?? !includeInactive); // 일반 목록 조회는 기존 로직 유지 final response = await _remoteDataSource.getCompanies( page: page, perPage: perPage, search: search, isActive: effectiveIsActive, ); return PaginatedResponse( items: response.items.map((dto) => _convertListDtoToCompany(dto)).toList(), page: response.page, size: response.size, totalElements: response.totalElements, totalPages: response.totalPages, first: response.first, last: response.last, ); } on ApiException catch (e) { debugPrint('[CompanyService] ApiException: ${e.message}'); throw ServerFailure(message: e.message); } catch (e, stackTrace) { debugPrint('[CompanyService] Error loading companies: $e'); debugPrint('[CompanyService] Stack trace: $stackTrace'); throw ServerFailure(message: 'Failed to fetch company list: $e'); } } // 회사 생성 Future createCompany(Company company) async { try { final request = CompanyRequestDto( name: company.name, address: company.address.toString(), contactName: company.contactName ?? '', contactPhone: company.contactPhone ?? '', contactEmail: company.contactEmail ?? '', isPartner: company.isPartner, isCustomer: company.isCustomer, parentCompanyId: company.parentCompanyId, remark: company.remark, ); debugPrint('[CompanyService] Creating company with request: ${request.toJson()}'); final response = await _remoteDataSource.createCompany(request); debugPrint('[CompanyService] Company created with ID: ${response.id}'); return _convertResponseToCompany(response); } on ApiException catch (e) { debugPrint('[CompanyService] ApiException during company creation: ${e.message}'); throw ServerFailure(message: e.message); } catch (e, stackTrace) { debugPrint('[CompanyService] Unexpected error during company creation: $e'); debugPrint('[CompanyService] Stack trace: $stackTrace'); throw ServerFailure(message: 'Failed to create company: $e'); } } // 회사 상세 조회 Future getCompanyDetail(int id) async { try { final response = await _remoteDataSource.getCompanyDetail(id); return _convertResponseToCompany(response); } on ApiException catch (e) { throw ServerFailure(message: e.message); } catch (e) { throw ServerFailure(message: 'Failed to fetch company detail: $e'); } } // 회사와 자회사 정보 함께 조회 (계층형 구조) Future getCompanyWithChildren(int id) async { try { final response = await _remoteDataSource.getCompanyWithChildren(id); final company = _convertResponseToCompany(response.company); // TODO: children 정보는 필요시 별도 처리 return company; } on ApiException catch (e) { throw ServerFailure(message: e.message); } catch (e) { throw ServerFailure(message: 'Failed to fetch company with children: $e'); } } // 회사 수정 Future updateCompany(int id, Company company) async { try { final request = CompanyUpdateRequestDto( name: company.name, address: company.address.toString(), contactName: company.contactName, contactPhone: company.contactPhone, contactEmail: company.contactEmail, isPartner: company.isPartner, isCustomer: company.isCustomer, parentCompanyId: company.parentCompanyId, remark: company.remark, ); final response = await _remoteDataSource.updateCompany(id, request); return _convertResponseToCompany(response); } on ApiException catch (e) { throw ServerFailure(message: e.message); } catch (e) { throw ServerFailure(message: 'Failed to update company: $e'); } } // 회사 삭제 Future deleteCompany(int id) async { try { await _remoteDataSource.deleteCompany(id); } on ApiException catch (e) { throw ServerFailure(message: e.message); } catch (e) { throw ServerFailure(message: 'Failed to delete company: $e'); } } // 회사명 목록 조회 (드롭다운용) Future> getCompanyNames() async { try { return await _remoteDataSource.getCompanyNames(); } on ApiException catch (e) { throw ServerFailure(message: e.message); } catch (e) { throw ServerFailure(message: 'Failed to fetch company names: $e'); } } // DEPRECATED: 지점 관련 메서드들 (계층형 Company 구조로 대체) @Deprecated('계층형 Company 구조로 대체되었습니다. createCompany를 사용하세요.') Future createBranch(int companyId, Company childCompany) async { // TODO: parentCompanyId를 설정하여 자회사로 생성 final companyWithParent = childCompany.copyWith(parentCompanyId: companyId); return createCompany(companyWithParent); } // DEPRECATED: 더 이상 사용하지 않는 Branch 관련 메서드들 /* Future getBranchDetail(int companyId, int branchId) async { try { final response = await _remoteDataSource.getBranchDetail(companyId, branchId); return _convertBranchResponseToBranch(response); } on ApiException catch (e) { throw ServerFailure(message: e.message); } catch (e) { throw ServerFailure(message: 'Failed to fetch branch detail: $e'); } } Future updateBranch(int companyId, int branchId, Branch branch) async { try { final request = UpdateBranchRequest( branchName: branch.name, address: branch.address.toString(), phone: branch.contactPhone, managerName: branch.contactName, managerPhone: branch.contactPhone, remark: branch.remark, ); final response = await _remoteDataSource.updateBranch(companyId, branchId, request); return _convertBranchResponseToBranch(response); } on ApiException catch (e) { throw ServerFailure(message: e.message); } catch (e) { throw ServerFailure(message: 'Failed to update branch: $e'); } } Future deleteBranch(int companyId, int branchId) async { try { await _remoteDataSource.deleteBranch(companyId, branchId); } on ApiException catch (e) { throw ServerFailure(message: e.message); } catch (e) { throw ServerFailure(message: 'Failed to delete branch: $e'); } } Future> getCompanyBranches(int companyId) async { try { final dtoList = await _remoteDataSource.getCompanyBranches(companyId); return dtoList.map((dto) => _convertBranchDtoToBranch(dto)).toList(); } on ApiException catch (e) { throw ServerFailure(message: e.message); } catch (e) { throw ServerFailure(message: 'Failed to fetch company branches: $e'); } } */ // 회사-지점 전체 정보 조회 Future> getCompaniesWithBranches() async { try { return await _remoteDataSource.getCompaniesWithBranches(); } on ApiException catch (e) { throw ServerFailure(message: e.message); } catch (e) { throw ServerFailure(message: 'Failed to fetch companies with branches: $e'); } } // 회사명 중복 확인 Future checkDuplicateCompany(String name) async { try { return await _remoteDataSource.checkDuplicateCompany(name); } on ApiException catch (e) { throw ServerFailure(message: e.message); } catch (e) { throw ServerFailure(message: 'Failed to check duplicate: $e'); } } // 회사 검색 Future> searchCompanies(String query) async { try { final dtoList = await _remoteDataSource.searchCompanies(query); return dtoList.map((dto) => _convertListDtoToCompany(dto)).toList(); } on ApiException catch (e) { throw ServerFailure(message: e.message); } catch (e) { throw ServerFailure(message: 'Failed to search companies: $e'); } } // 회사 활성 상태 변경 Future updateCompanyStatus(int id, bool isActive) async { try { await _remoteDataSource.updateCompanyStatus(id, isActive); } on ApiException catch (e) { throw ServerFailure(message: e.message); } catch (e) { throw ServerFailure(message: 'Failed to update company status: $e'); } } // 회사 목록과 지점 정보를 함께 조회 (플랫 데이터를 그룹화) Future> getCompaniesWithBranchesFlat() async { try { final flatData = await _remoteDataSource.getCompanyBranchesFlat(); // 회사별로 데이터 그룹화 final Map> companyBranchesMap = {}; final Map companyNamesMap = {}; final Map> companyTypesMap = {}; final Map companyIsPartnerMap = {}; final Map companyIsCustomerMap = {}; for (final item in flatData) { companyNamesMap[item.companyId] = item.companyName; // 첫 번째 아이템에서만 회사 타입 정보 저장 if (!companyTypesMap.containsKey(item.companyId)) { List types = []; // 1. company_types 필드가 있으면 우선 사용 (하위 호환성) if (item.companyTypes != null && item.companyTypes!.isNotEmpty) { types = item.companyTypes!.map((typeStr) { if (typeStr.toLowerCase().contains('partner')) return CompanyType.partner; return CompanyType.customer; }).toSet().toList(); // 중복 제거 } // 2. company_types가 없으면 is_partner, is_customer 사용 else { if (item.isCustomer) types.add(CompanyType.customer); if (item.isPartner) types.add(CompanyType.partner); } companyTypesMap[item.companyId] = types; companyIsPartnerMap[item.companyId] = item.isPartner; companyIsCustomerMap[item.companyId] = item.isCustomer; } if (item.branchId != null && item.branchName != null) { if (!companyBranchesMap.containsKey(item.companyId)) { companyBranchesMap[item.companyId] = []; } companyBranchesMap[item.companyId]!.add( Branch( id: item.branchId, companyId: item.companyId, name: item.branchName!, address: Address.fromFullAddress(''), // 주소 정보가 없으므로 빈 값 ), ); } } // Company 객체 생성 final List companies = []; for (final entry in companyNamesMap.entries) { companies.add( Company( id: entry.key, name: entry.value, address: Address.fromFullAddress(''), // 주소 정보가 없으므로 빈 값 companyTypes: companyTypesMap[entry.key] ?? [], // 빈 리스트 가능 branches: companyBranchesMap[entry.key], // null 허용 ), ); } return companies; } on ApiException catch (e) { debugPrint('[CompanyService] ApiException: ${e.message}'); throw ServerFailure(message: e.message); } catch (e, stackTrace) { debugPrint('[CompanyService] Error loading companies with branches: $e'); debugPrint('[CompanyService] Stack trace: $stackTrace'); throw ServerFailure(message: 'Failed to fetch companies with branches: $e'); } } // 변환 헬퍼 메서드들 Company _convertListDtoToCompany(CompanyListDto dto) { List companyTypes = []; // 1. company_types 필드가 있으면 우선 사용 (하위 호환성) if (dto.companyTypes != null && dto.companyTypes!.isNotEmpty) { companyTypes = dto.companyTypes!.map((typeStr) { if (typeStr.toLowerCase().contains('partner')) return CompanyType.partner; return CompanyType.customer; }).toSet().toList(); // 중복 제거 } // 2. company_types가 없으면 is_partner, is_customer 사용 else { if (dto.isCustomer) companyTypes.add(CompanyType.customer); if (dto.isPartner) companyTypes.add(CompanyType.partner); } // 3. 둘 다 없으면 빈 리스트 유지 (기본값 제거) return Company( id: dto.id, name: dto.name, address: Address.fromFullAddress(dto.address ?? ''), contactName: dto.contactName, contactPosition: null, // CompanyListDto에는 position이 없음 contactPhone: dto.contactPhone, contactEmail: dto.contactEmail, companyTypes: companyTypes, remark: null, // CompanyListDto에는 remark이 없음 isActive: dto.isActive, isPartner: dto.isPartner, isCustomer: dto.isCustomer, parentCompanyId: dto.parentCompanyId, createdAt: dto.registeredAt, // CompanyListDto.registeredAt → createdAt updatedAt: null, // CompanyListDto에는 updatedAt이 없음 branches: [], // branches는 빈 배열로 초기화 ); } Company _convertResponseToCompany(CompanyDto dto) { List companyTypes = []; // CompanyDto에는 companyTypes 필드가 없으므로 is_partner, is_customer 사용 if (dto.isCustomer) companyTypes.add(CompanyType.customer); if (dto.isPartner) companyTypes.add(CompanyType.partner); // 3. 둘 다 없으면 빈 리스트 유지 return Company( id: dto.id, name: dto.name, address: dto.address != null ? Address.fromFullAddress(dto.address) : const Address(), contactName: dto.contactName, contactPosition: null, // CompanyDto에 contactPosition 필드 없음 contactPhone: dto.contactPhone, contactEmail: dto.contactEmail, companyTypes: companyTypes, remark: dto.remark, isActive: dto.isActive, isPartner: dto.isPartner, isCustomer: dto.isCustomer, parentCompanyId: dto.parentCompanyId, createdAt: dto.registeredAt, // createdAt → registeredAt updatedAt: dto.updatedAt, branches: [], // branches는 빈 배열로 초기화 ); } // CompanyListDto를 CompanyItem으로 변환 (본사 목록용) CompanyItem _convertListDtoToCompanyItem(CompanyListDto dto) { final company = _convertListDtoToCompany(dto); return CompanyItem(company: company); } // DEPRECATED: Branch 변환 메서드들 (계층형 Company 구조로 대체) /* Branch _convertBranchDtoToBranch(BranchListDto dto) { return Branch( id: dto.id, companyId: dto.companyId, name: dto.branchName, address: Address.fromFullAddress(dto.address), contactName: dto.managerName, contactPhone: dto.phone, ); } Branch _convertBranchResponseToBranch(BranchResponse dto) { return Branch( id: dto.id, companyId: dto.companyId, name: dto.branchName, address: Address.fromFullAddress(dto.address), contactName: dto.managerName, contactPhone: dto.phone, remark: dto.remark, ); } */ // 본사 목록 조회 (페이지네이션 포함) - 개수 확인용 // 백엔드 /companies API에서 parentCompanyId == null 필터링 Future>> getHeadquartersWithPagination() async { try { final response = await _remoteDataSource.getHeadquartersWithPagination(); return Right(PaginatedResponse( items: response.items.map((dto) => _convertListDtoToCompany(dto)).toList(), page: response.page, size: response.size, totalElements: response.totalElements, totalPages: response.totalPages, first: response.first, last: response.last, )); } on ApiException catch (e) { debugPrint('[CompanyService] ApiException in getHeadquartersWithPagination: ${e.message}'); return Left(ServerFailure(message: e.message)); } catch (e, stackTrace) { debugPrint('[CompanyService] Error loading headquarters with pagination: $e'); debugPrint('[CompanyService] Stack trace: $stackTrace'); return Left(ServerFailure(message: 'Failed to load headquarters')); } } // 본사 목록 조회 (지점 추가 시 사용) Future>> getHeadquarters() async { try { final response = await _remoteDataSource.getHeadquarters(); final companyItems = response.map((dto) => _convertListDtoToCompanyItem(dto)).toList(); return Right(companyItems); } on ApiException catch (e) { debugPrint('[CompanyService] ApiException in getHeadquarters: ${e.message}'); return Left(ServerFailure(message: e.message)); } catch (e, stackTrace) { debugPrint('[CompanyService] Error loading headquarters: $e'); debugPrint('[CompanyService] Stack trace: $stackTrace'); return Left(ServerFailure(message: 'Failed to fetch headquarters list: $e')); } } // 모든 본사 목록 조회 (지점 추가 드롭다운용 - 전체 목록 한번에 로드) Future>> getAllHeadquarters() async { try { final response = await _remoteDataSource.getAllHeadquarters(); final companyItems = response.map((dto) => _convertListDtoToCompanyItem(dto)).toList(); return Right(companyItems); } on ApiException catch (e) { debugPrint('[CompanyService] ApiException in getAllHeadquarters: ${e.message}'); return Left(ServerFailure(message: e.message)); } catch (e, stackTrace) { debugPrint('[CompanyService] Error loading all headquarters: $e'); debugPrint('[CompanyService] Stack trace: $stackTrace'); return Left(ServerFailure(message: 'Failed to fetch all headquarters list: $e')); } } } // Company 모델에 copyWith 메서드가 없다면 extension으로 추가 extension CompanyExtension on Company { Company copyWith({ int? id, String? name, Address? address, String? contactName, String? contactPosition, String? contactPhone, String? contactEmail, List? branches, List? companyTypes, String? remark, }) { return Company( id: id ?? this.id, name: name ?? this.name, address: address ?? this.address, contactName: contactName ?? this.contactName, contactPosition: contactPosition ?? this.contactPosition, contactPhone: contactPhone ?? this.contactPhone, contactEmail: contactEmail ?? this.contactEmail, branches: branches ?? this.branches, companyTypes: companyTypes ?? this.companyTypes, remark: remark ?? this.remark, ); } }