import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import 'package:injectable/injectable.dart'; import 'package:superport/core/constants/api_endpoints.dart'; import 'package:superport/core/errors/exceptions.dart'; import 'package:superport/data/datasources/remote/api_client.dart'; import 'package:superport/data/models/common/api_response.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'; import 'package:superport/data/models/company/branch_dto.dart'; import 'package:superport/data/models/company/company_branch_flat_dto.dart'; abstract class CompanyRemoteDataSource { Future> getCompanies({ int page = 1, int perPage = 20, String? search, bool? isActive, }); Future createCompany(CompanyRequestDto request); Future getCompanyDetail(int id); Future getCompanyWithChildren(int id); Future updateCompany(int id, CompanyUpdateRequestDto request); Future deleteCompany(int id); Future> getCompanyNames(); Future> getCompaniesWithBranches(); Future checkDuplicateCompany(String name); Future> searchCompanies(String query); Future updateCompanyStatus(int id, bool isActive); // Branch related methods Future createBranch(int companyId, CreateBranchRequest request); Future getBranchDetail(int companyId, int branchId); Future updateBranch(int companyId, int branchId, UpdateBranchRequest request); Future deleteBranch(int companyId, int branchId); Future> getCompanyBranches(int companyId); Future> getCompanyBranchesFlat(); // Headquarters methods Future> getHeadquartersWithPagination(); Future> getHeadquarters(); Future> getAllHeadquarters(); } @LazySingleton(as: CompanyRemoteDataSource) class CompanyRemoteDataSourceImpl implements CompanyRemoteDataSource { final ApiClient _apiClient; CompanyRemoteDataSourceImpl(this._apiClient); @override Future> getCompanies({ int page = 1, int perPage = 20, String? search, bool? isActive, }) async { try { final queryParams = { 'page': page, 'per_page': perPage, if (search != null) 'search': search, if (isActive != null) 'is_active': isActive, }; final response = await _apiClient.get( ApiEndpoints.companies, queryParameters: queryParams, ); if (response.statusCode == 200) { // 백엔드 실제 응답 구조: {"data": [...], "total": 120, "page": 1, "page_size": 20, "total_pages": 6} final responseData = response.data; if (responseData != null && responseData['data'] != null) { final List dataList = responseData['data']; // CompanyListDto로 변환 final items = dataList.map((item) => CompanyListDto.fromJson(item as Map)).toList(); // PaginatedResponse 생성 (백엔드 실제 응답 구조 기준) return PaginatedResponse( items: items, page: responseData['page'] ?? page, size: responseData['page_size'] ?? perPage, totalElements: responseData['total'] ?? 0, totalPages: responseData['total_pages'] ?? 1, first: (responseData['page'] ?? page) == 1, last: (responseData['page'] ?? page) >= (responseData['total_pages'] ?? 1), ); } else { throw ApiException( message: 'Invalid response format', statusCode: response.statusCode, ); } } else { throw ApiException( message: 'Failed to load companies', statusCode: response.statusCode, ); } } catch (e) { if (e is ApiException) rethrow; throw ApiException(message: e.toString()); } } @override Future createCompany(CompanyRequestDto request) async { try { debugPrint('[CompanyRemoteDataSource] Sending POST request to ${ApiEndpoints.companies}'); debugPrint('[CompanyRemoteDataSource] Request data: ${request.toJson()}'); final response = await _apiClient.post( ApiEndpoints.companies, data: request.toJson(), ); debugPrint('[CompanyRemoteDataSource] Response status: ${response.statusCode}'); debugPrint('[CompanyRemoteDataSource] Response data: ${response.data}'); if (response.statusCode == 201 || response.statusCode == 200) { // API 응답 구조 확인 final responseData = response.data; if (responseData != null && responseData['success'] == true && responseData['data'] != null) { // 직접 파싱 return CompanyDto.fromJson(responseData['data'] as Map); } else { // ApiResponse 형식으로 파싱 시도 final apiResponse = ApiResponse.fromJson( response.data, (json) => CompanyDto.fromJson(json as Map), ); return apiResponse.data!; } } else { throw ApiException( message: 'Failed to create company - Status: ${response.statusCode}', statusCode: response.statusCode, ); } } catch (e, stackTrace) { debugPrint('[CompanyRemoteDataSource] Error creating company: $e'); debugPrint('[CompanyRemoteDataSource] Stack trace: $stackTrace'); if (e is ApiException) rethrow; throw ApiException(message: 'Error creating company: $e'); } } @override Future getCompanyDetail(int id) async { try { final response = await _apiClient.dio.get( '${ApiEndpoints.companies}/$id', ); return CompanyDto.fromJson(response.data['data']); } on DioException catch (e) { throw ServerException( message: e.response?.data['message'] ?? 'Failed to fetch company detail', statusCode: e.response?.statusCode, ); } } @override Future getCompanyWithChildren(int id) async { try { final response = await _apiClient.dio.get( '${ApiEndpoints.companies}/$id/with-branches', ); return CompanyWithChildren.fromJson(response.data['data']); } on DioException catch (e) { throw ServerException( message: e.response?.data['message'] ?? 'Failed to fetch company with branches', statusCode: e.response?.statusCode, ); } } @override Future updateCompany(int id, CompanyUpdateRequestDto request) async { try { final response = await _apiClient.dio.put( '${ApiEndpoints.companies}/$id', data: request.toJson(), ); return CompanyDto.fromJson(response.data['data']); } on DioException catch (e) { throw ServerException( message: e.response?.data['message'] ?? 'Failed to update company', statusCode: e.response?.statusCode, ); } } @override Future deleteCompany(int id) async { try { await _apiClient.dio.delete( '${ApiEndpoints.companies}/$id', ); } on DioException catch (e) { throw ServerException( message: e.response?.data['message'] ?? 'Failed to delete company', statusCode: e.response?.statusCode, ); } } @override Future> getCompanyNames() async { try { final response = await _apiClient.dio.get( '${ApiEndpoints.companies}/names', ); final List data = response.data['data']; return data.map((json) => CompanyNameDto.fromJson(json)).toList(); } on DioException catch (e) { throw ServerException( message: e.response?.data['message'] ?? 'Failed to fetch company names', statusCode: e.response?.statusCode, ); } } // Branch methods @override Future createBranch(int companyId, CreateBranchRequest request) async { try { final response = await _apiClient.dio.post( '${ApiEndpoints.companies}/$companyId/branches', data: request.toJson(), ); return BranchResponse.fromJson(response.data['data']); } on DioException catch (e) { throw ServerException( message: e.response?.data['message'] ?? 'Failed to create branch', statusCode: e.response?.statusCode, ); } } @override Future getBranchDetail(int companyId, int branchId) async { try { final response = await _apiClient.dio.get( '${ApiEndpoints.companies}/$companyId/branches/$branchId', ); return BranchResponse.fromJson(response.data['data']); } on DioException catch (e) { throw ServerException( message: e.response?.data['message'] ?? 'Failed to fetch branch detail', statusCode: e.response?.statusCode, ); } } @override Future updateBranch(int companyId, int branchId, UpdateBranchRequest request) async { try { final response = await _apiClient.dio.put( '${ApiEndpoints.companies}/$companyId/branches/$branchId', data: request.toJson(), ); return BranchResponse.fromJson(response.data['data']); } on DioException catch (e) { throw ServerException( message: e.response?.data['message'] ?? 'Failed to update branch', statusCode: e.response?.statusCode, ); } } @override Future deleteBranch(int companyId, int branchId) async { try { await _apiClient.dio.delete( '${ApiEndpoints.companies}/$companyId/branches/$branchId', ); } on DioException catch (e) { throw ServerException( message: e.response?.data['message'] ?? 'Failed to delete branch', statusCode: e.response?.statusCode, ); } } @override Future> getCompanyBranches(int companyId) async { try { final response = await _apiClient.dio.get( '${ApiEndpoints.companies}/$companyId/branches', ); final List data = response.data['data']; return data.map((json) => BranchListDto.fromJson(json)).toList(); } on DioException catch (e) { throw ServerException( message: e.response?.data['message'] ?? 'Failed to fetch company branches', statusCode: e.response?.statusCode, ); } } @override Future> getCompaniesWithBranches() async { try { final response = await _apiClient.get('${ApiEndpoints.companies}/branches'); if (response.statusCode == 200) { final apiResponse = ApiResponse>.fromJson( response.data, (json) => (json as List) .map((item) => CompanyWithChildren.fromJson(item)) .toList(), ); return apiResponse.data!; } else { throw ApiException( message: 'Failed to load companies with branches', statusCode: response.statusCode, ); } } catch (e) { if (e is ApiException) rethrow; throw ApiException(message: e.toString()); } } @override Future> getCompanyBranchesFlat() async { try { final response = await _apiClient.get('${ApiEndpoints.companies}/branches'); if (response.statusCode == 200) { final responseData = response.data; if (responseData != null && responseData['success'] == true && responseData['data'] != null) { final List dataList = responseData['data']; return dataList.map((item) => CompanyBranchFlatDto.fromJson(item as Map) ).toList(); } else { throw ApiException( message: responseData?['error']?['message'] ?? 'Failed to load company branches', statusCode: response.statusCode, ); } } else { throw ApiException( message: 'Failed to load company branches', statusCode: response.statusCode, ); } } catch (e) { if (e is ApiException) rethrow; throw ApiException(message: e.toString()); } } @override Future checkDuplicateCompany(String name) async { try { final response = await _apiClient.get( '${ApiEndpoints.companies}/check-duplicate', queryParameters: {'name': name}, ); if (response.statusCode == 200) { final apiResponse = ApiResponse>.fromJson( response.data, (json) => json as Map, ); return apiResponse.data?['exists'] ?? false; } else { throw ApiException( message: 'Failed to check duplicate', statusCode: response.statusCode, ); } } catch (e) { if (e is ApiException) rethrow; throw ApiException(message: e.toString()); } } @override Future> searchCompanies(String query) async { try { final response = await _apiClient.get( '${ApiEndpoints.companies}/search', queryParameters: {'q': query}, ); if (response.statusCode == 200) { final apiResponse = ApiResponse>.fromJson( response.data, (json) => (json as List) .map((item) => CompanyListDto.fromJson(item)) .toList(), ); return apiResponse.data!; } else { throw ApiException( message: 'Failed to search companies', statusCode: response.statusCode, ); } } catch (e) { if (e is ApiException) rethrow; throw ApiException(message: e.toString()); } } @override Future updateCompanyStatus(int id, bool isActive) async { try { final response = await _apiClient.patch( '${ApiEndpoints.companies}/$id/status', data: {'is_active': isActive}, ); if (response.statusCode != 200) { throw ApiException( message: 'Failed to update company status', statusCode: response.statusCode, ); } } catch (e) { if (e is ApiException) rethrow; throw ApiException(message: e.toString()); } } @override Future> getHeadquartersWithPagination() async { try { // /headquarters 엔드포인트가 백엔드에 없으므로 /companies API에서 본사만 필터링 final response = await _apiClient.get( ApiEndpoints.companies, queryParameters: { 'page': 1, 'per_page': 1000, // 충분히 큰 값으로 모든 본사 조회 'is_active': true, }, ); if (response.statusCode == 200) { final responseData = response.data; if (responseData != null && responseData['data'] != null) { final List dataList = responseData['data']; // parentCompanyId가 null인 본사만 필터링 final headquartersData = dataList.where((item) => item['parent_company_id'] == null).toList(); // CompanyListDto로 변환 final items = headquartersData.map((item) => CompanyListDto.fromJson(item as Map) ).toList(); // PaginatedResponse 생성 return PaginatedResponse( items: items, page: 1, size: items.length, totalElements: items.length, // 본사 개수 totalPages: 1, first: true, last: true, ); } else { throw ApiException( message: 'Invalid response format', statusCode: response.statusCode, ); } } else { throw ApiException( message: 'Failed to load headquarters', statusCode: response.statusCode, ); } } catch (e) { if (e is ApiException) rethrow; throw ApiException(message: e.toString()); } } @override Future> getHeadquarters() async { try { // /headquarters 엔드포인트가 백엔드에 없으므로 /companies API에서 본사만 필터링 final response = await _apiClient.get( ApiEndpoints.companies, queryParameters: { 'page': 1, 'per_page': 1000, // 충분히 큰 값으로 모든 본사 조회 'is_active': true, }, ); if (response.statusCode == 200) { final responseData = response.data; if (responseData != null && responseData['data'] != null) { final List dataList = responseData['data']; // parentCompanyId가 null인 본사만 필터링 final headquartersData = dataList.where((item) => item['parent_company_id'] == null).toList(); return headquartersData.map((item) => CompanyListDto.fromJson(item as Map) ).toList(); } else { throw ApiException( message: 'Invalid response format', statusCode: response.statusCode, ); } } else { throw ApiException( message: 'Failed to load headquarters', statusCode: response.statusCode, ); } } catch (e) { if (e is ApiException) rethrow; throw ApiException(message: e.toString()); } } @override Future> getAllHeadquarters() async { try { // /headquarters 엔드포인트가 백엔드에 없으므로 /companies API에서 본사만 필터링 final response = await _apiClient.get( ApiEndpoints.companies, queryParameters: { 'per_page': 1000, // Large enough to get all headquarters 'page': 1, 'is_active': true, }, ); if (response.statusCode == 200) { final responseData = response.data; if (responseData != null && responseData['data'] != null) { final List dataList = responseData['data']; // parentCompanyId가 null인 본사만 필터링 final headquartersData = dataList.where((item) => item['parent_company_id'] == null).toList(); return headquartersData.map((item) => CompanyListDto.fromJson(item as Map) ).toList(); } else { throw ApiException( message: 'Invalid response format', statusCode: response.statusCode, ); } } else { throw ApiException( message: 'Failed to load all headquarters', statusCode: response.statusCode, ); } } catch (e) { if (e is ApiException) rethrow; throw ApiException(message: e.toString()); } } }