사용하지 않는 파일 정리 전 백업 (Phase 10 완료 후 상태)

This commit is contained in:
JiWoong Sul
2025-08-29 15:11:59 +09:00
parent a740ff10c8
commit d916b281a7
333 changed files with 53617 additions and 22574 deletions

View File

@@ -0,0 +1,187 @@
import 'package:dartz/dartz.dart';
import 'package:injectable/injectable.dart';
import '../../core/errors/failures.dart';
import '../../core/errors/exceptions.dart';
import '../../domain/repositories/administrator_repository.dart';
import '../datasources/remote/administrator_remote_datasource.dart';
import '../models/administrator_dto.dart';
/// 관리자 관리 Repository 구현체 (백엔드 Administrator 테이블)
/// Clean Architecture Data Layer - Repository 구현
/// 도메인 레이어와 데이터 소스 사이의 변환 및 에러 처리 담당
@Injectable(as: AdministratorRepository)
class AdministratorRepositoryImpl implements AdministratorRepository {
final AdministratorRemoteDataSource _remoteDataSource;
AdministratorRepositoryImpl(this._remoteDataSource);
/// 관리자 목록 조회 (페이지네이션 지원)
@override
Future<Either<Failure, AdministratorListResponse>> getAdministrators({
int? page,
int? pageSize,
String? search,
}) async {
try {
final result = await _remoteDataSource.getAdministrators(
page: page ?? 1,
pageSize: pageSize ?? 20,
search: search,
);
return Right(result);
} on ApiException catch (e) {
return Left(_mapApiExceptionToFailure(e));
} catch (e) {
return Left(ServerFailure(
message: '관리자 목록 조회 중 오류가 발생했습니다: ${e.toString()}',
));
}
}
/// 단일 관리자 조회
@override
Future<Either<Failure, AdministratorDto>> getAdministratorById(int id) async {
try {
final dto = await _remoteDataSource.getAdministrator(id);
return Right(dto);
} on ApiException catch (e) {
return Left(_mapApiExceptionToFailure(e, resourceId: id.toString()));
} catch (e) {
return Left(ServerFailure(
message: '관리자 정보 조회 중 오류가 발생했습니다: ${e.toString()}',
));
}
}
/// 관리자 계정 생성
@override
Future<Either<Failure, AdministratorDto>> createAdministrator(
AdministratorRequestDto administrator,
) async {
try {
final dto = await _remoteDataSource.createAdministrator(administrator);
return Right(dto);
} on ApiException catch (e) {
return Left(_mapApiExceptionToFailure(e));
} catch (e) {
return Left(ServerFailure(
message: '관리자 계정 생성 중 오류가 발생했습니다: ${e.toString()}',
));
}
}
/// 관리자 정보 수정
@override
Future<Either<Failure, AdministratorDto>> updateAdministrator(
int id,
AdministratorUpdateRequestDto administrator,
) async {
try {
final dto = await _remoteDataSource.updateAdministrator(id, administrator);
return Right(dto);
} on ApiException catch (e) {
return Left(_mapApiExceptionToFailure(e, resourceId: id.toString()));
} catch (e) {
return Left(ServerFailure(
message: '관리자 정보 수정 중 오류가 발생했습니다: ${e.toString()}',
));
}
}
/// 관리자 계정 삭제
@override
Future<Either<Failure, void>> deleteAdministrator(int id) async {
try {
await _remoteDataSource.deleteAdministrator(id);
return const Right(null);
} on ApiException catch (e) {
return Left(_mapApiExceptionToFailure(e, resourceId: id.toString()));
} catch (e) {
return Left(ServerFailure(
message: '관리자 계정 삭제 중 오류가 발생했습니다: ${e.toString()}',
));
}
}
/// 이메일 중복 확인
@override
Future<Either<Failure, bool>> isDuplicateEmail(String email, {int? excludeId}) async {
try {
final isAvailable = await _remoteDataSource.checkEmailAvailability(
email,
excludeId: excludeId,
);
// checkEmailAvailability는 사용 가능 여부를 반환하므로
// 중복 여부는 그 반대값
return Right(!isAvailable);
} on ApiException catch (e) {
return Left(_mapApiExceptionToFailure(e));
} catch (e) {
return Left(ServerFailure(
message: '이메일 중복 확인 중 오류가 발생했습니다: ${e.toString()}',
));
}
}
/// 관리자 인증 (로그인용)
@override
Future<Either<Failure, AdministratorDto>> authenticateAdministrator(
String email,
String password,
) async {
try {
final dto = await _remoteDataSource.authenticateAdministrator(email, password);
return Right(dto);
} on ApiException catch (e) {
return Left(_mapApiExceptionToFailure(e));
} catch (e) {
return Left(ServerFailure(
message: '관리자 인증 중 오류가 발생했습니다: ${e.toString()}',
));
}
}
/// API 예외를 Failure로 매핑
Failure _mapApiExceptionToFailure(ApiException exception, {String? resourceId}) {
switch (exception.statusCode) {
case 400:
return ValidationFailure(
message: exception.message ?? '잘못된 요청입니다.',
);
case 401:
return AuthFailure(
message: exception.message ?? '인증이 필요합니다.',
);
case 403:
return AuthFailure(
message: exception.message ?? '접근 권한이 없습니다.',
);
case 404:
final resourceName = resourceId != null ? '관리자 (ID: $resourceId)' : '요청한 리소스';
return NotFoundFailure(
message: exception.message ?? '$resourceName를 찾을 수 없습니다.',
resourceId: resourceId,
);
case 409:
return DuplicateFailure(
message: exception.message ?? '중복된 데이터가 존재합니다.',
);
case 422:
return ValidationFailure(
message: exception.message ?? '입력된 데이터가 유효하지 않습니다.',
);
case 500:
case 502:
case 503:
case 504:
return ServerFailure(
message: exception.message ?? '서버 오류가 발생했습니다. 잠시 후 다시 시도해주세요.',
);
default:
return ServerFailure(
message: exception.message ?? '알 수 없는 오류가 발생했습니다.',
);
}
}
}

View File

@@ -327,41 +327,27 @@ class CompanyRepositoryImpl implements CompanyRepository {
name: dto.company.name,
address: Address.fromFullAddress(dto.company.address ?? ''),
contactName: dto.company.contactName,
contactPosition: dto.company.contactPosition,
contactPosition: null, // 백엔드에서 미지원
contactPhone: dto.company.contactPhone,
contactEmail: dto.company.contactEmail,
companyTypes: _parseCompanyTypes(dto.company.companyTypes),
companyTypes: [], // 백엔드에서 미지원
remark: dto.company.remark,
branches: [], // TODO: 계층형 구조로 변경됨. children은 자회사를 의미하므로 branches는 빈 리스트로 설정
);
}
Company _mapResponseToDomain(CompanyResponse response) {
Company _mapResponseToDomain(CompanyDto response) {
return Company(
id: response.id,
name: response.name,
address: Address.fromFullAddress(response.address ?? ''),
contactName: response.contactName,
contactPosition: response.contactPosition,
contactPosition: null, // 백엔드에서 미지원
contactPhone: response.contactPhone,
contactEmail: response.contactEmail,
companyTypes: _parseCompanyTypes(response.companyTypes),
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에 없음
branches: [], // CompanyDto에서는 지점 정보 따로 조회
);
}
@@ -395,40 +381,27 @@ class CompanyRepositoryImpl implements CompanyRepository {
}).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(
CompanyRequestDto _mapDomainToCreateRequest(Company company) {
return CompanyRequestDto(
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(
CompanyUpdateRequestDto _mapDomainToUpdateRequest(Company company) {
return CompanyUpdateRequestDto(
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에서 필요한 경우 추가
isActive: null, // CompanyUpdateRequestDto에서 필요한 경우 추가
);
}
@@ -453,4 +426,178 @@ class CompanyRepositoryImpl implements CompanyRepository {
remark: branch.remark,
);
}
// 계층 구조 관련 메서드 구현
@override
Future<Either<Failure, List<Company>>> 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<Either<Failure, List<Company>>> getChildrenCompanies(
int companyId, {
bool recursive = false,
}) async {
try {
// API에서 자식 회사 조회
// 현재는 getCompanyWithChildren 사용
final result = await remoteDataSource.getCompanyWithChildren(companyId);
final children = <Company>[];
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<Either<Failure, List<Company>>> getAncestorPath(int companyId) async {
try {
final path = <Company>[];
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<Either<Failure, Company>> 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<Either<Failure, bool>> 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<Either<Failure, bool>> 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()}',
));
}
}
}

View File

@@ -0,0 +1,249 @@
import 'package:dio/dio.dart';
import 'package:injectable/injectable.dart';
import 'package:superport/core/constants/api_endpoints.dart';
import 'package:superport/data/models/equipment_history_dto.dart';
abstract class EquipmentHistoryRepository {
Future<EquipmentHistoryListResponse> getEquipmentHistories({
int? page,
int? pageSize,
int? equipmentsId,
int? warehousesId,
int? companiesId,
String? transactionType,
String? startDate,
String? endDate,
});
Future<EquipmentHistoryDto> getEquipmentHistoryById(int id);
Future<List<EquipmentHistoryDto>> getEquipmentHistoriesByEquipmentId(int equipmentId);
Future<List<EquipmentHistoryDto>> getEquipmentHistoriesByWarehouseId(int warehouseId);
// InventoryStatusDto 관련 메서드들 제거 (백엔드에 해당 개념 없음)
Future<EquipmentHistoryDto> createEquipmentHistory(
EquipmentHistoryRequestDto request,
);
Future<EquipmentHistoryDto> updateEquipmentHistory(
int id,
EquipmentHistoryUpdateRequestDto request,
);
Future<void> deleteEquipmentHistory(int id);
Future<EquipmentHistoryDto> createStockIn({
required int equipmentsId,
required int warehousesId,
required int quantity,
DateTime? transactedAt,
String? remark,
});
Future<EquipmentHistoryDto> createStockOut({
required int equipmentsId,
required int warehousesId,
required int quantity,
DateTime? transactedAt,
String? remark,
});
}
@LazySingleton(as: EquipmentHistoryRepository)
class EquipmentHistoryRepositoryImpl implements EquipmentHistoryRepository {
final Dio _dio;
EquipmentHistoryRepositoryImpl(this._dio);
@override
Future<EquipmentHistoryListResponse> getEquipmentHistories({
int? page,
int? pageSize,
int? equipmentsId,
int? warehousesId,
int? companiesId,
String? transactionType,
String? startDate,
String? endDate,
}) async {
try {
final queryParams = <String, dynamic>{};
if (page != null) queryParams['page'] = page;
if (pageSize != null) queryParams['page_size'] = pageSize;
if (equipmentsId != null) queryParams['equipments_id'] = equipmentsId;
if (warehousesId != null) queryParams['warehouses_id'] = warehousesId;
if (companiesId != null) queryParams['companies_id'] = companiesId;
if (transactionType != null) queryParams['transaction_type'] = transactionType;
if (startDate != null) queryParams['start_date'] = startDate;
if (endDate != null) queryParams['end_date'] = endDate;
final response = await _dio.get(
ApiEndpoints.equipmentHistory,
queryParameters: queryParams,
);
return EquipmentHistoryListResponse.fromJson(response.data);
} on DioException catch (e) {
throw _handleError(e);
}
}
@override
Future<EquipmentHistoryDto> getEquipmentHistoryById(int id) async {
try {
final response = await _dio.get('${ApiEndpoints.equipmentHistory}/$id');
return EquipmentHistoryDto.fromJson(response.data);
} on DioException catch (e) {
throw _handleError(e);
}
}
@override
Future<List<EquipmentHistoryDto>> getEquipmentHistoriesByEquipmentId(
int equipmentId,
) async {
try {
final response = await _dio.get(
'${ApiEndpoints.equipmentHistory}/by-equipment/$equipmentId',
);
final List<dynamic> data = response.data is List
? response.data
: response.data['data'] ?? [];
return data.map((json) => EquipmentHistoryDto.fromJson(json)).toList();
} on DioException catch (e) {
throw _handleError(e);
}
}
@override
Future<List<EquipmentHistoryDto>> getEquipmentHistoriesByWarehouseId(
int warehouseId,
) async {
try {
final response = await _dio.get(
'${ApiEndpoints.equipmentHistory}/by-warehouse/$warehouseId',
);
final List<dynamic> data = response.data is List
? response.data
: response.data['data'] ?? [];
return data.map((json) => EquipmentHistoryDto.fromJson(json)).toList();
} on DioException catch (e) {
throw _handleError(e);
}
}
// InventoryStatusDto 관련 메서드들 제거 (백엔드에 해당 개념 없음)
@override
Future<EquipmentHistoryDto> createEquipmentHistory(
EquipmentHistoryRequestDto request,
) async {
try {
final response = await _dio.post(
ApiEndpoints.equipmentHistory,
data: request.toJson(),
);
return EquipmentHistoryDto.fromJson(response.data);
} on DioException catch (e) {
throw _handleError(e);
}
}
@override
Future<EquipmentHistoryDto> updateEquipmentHistory(
int id,
EquipmentHistoryUpdateRequestDto request,
) async {
try {
final response = await _dio.put(
'${ApiEndpoints.equipmentHistory}/$id',
data: request.toJson(),
);
return EquipmentHistoryDto.fromJson(response.data);
} on DioException catch (e) {
throw _handleError(e);
}
}
@override
Future<void> deleteEquipmentHistory(int id) async {
try {
await _dio.delete('${ApiEndpoints.equipmentHistory}/$id');
} on DioException catch (e) {
throw _handleError(e);
}
}
@override
Future<EquipmentHistoryDto> createStockIn({
required int equipmentsId,
required int warehousesId,
required int quantity,
DateTime? transactedAt,
String? remark,
}) async {
final request = EquipmentHistoryRequestDto(
equipmentsId: equipmentsId,
warehousesId: warehousesId,
quantity: quantity,
transactionType: 'I', // 입고
transactedAt: transactedAt ?? DateTime.now(),
remark: remark,
);
return createEquipmentHistory(request);
}
@override
Future<EquipmentHistoryDto> createStockOut({
required int equipmentsId,
required int warehousesId,
required int quantity,
DateTime? transactedAt,
String? remark,
}) async {
// 재고 확인 로직 제거 (백엔드에서 처리)
final request = EquipmentHistoryRequestDto(
equipmentsId: equipmentsId,
warehousesId: warehousesId,
quantity: quantity,
transactionType: 'O', // 출고
transactedAt: transactedAt ?? DateTime.now(),
remark: remark,
);
return createEquipmentHistory(request);
}
Exception _handleError(DioException e) {
if (e.response != null) {
final statusCode = e.response!.statusCode;
final message = e.response!.data['message'] ?? '오류가 발생했습니다.';
switch (statusCode) {
case 400:
return Exception('잘못된 요청: $message');
case 401:
return Exception('인증이 필요합니다.');
case 403:
return Exception('권한이 없습니다.');
case 404:
return Exception('재고 이력을 찾을 수 없습니다.');
case 409:
return Exception('재고 충돌: $message');
case 500:
return Exception('서버 오류가 발생했습니다.');
default:
return Exception('오류가 발생했습니다: $message');
}
}
return Exception('네트워크 오류가 발생했습니다.');
}
}

View File

@@ -1,484 +1,96 @@
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';
import 'package:injectable/injectable.dart';
import '../../core/errors/failures.dart';
import '../../core/errors/exceptions.dart';
import '../../domain/repositories/equipment_repository.dart';
import '../datasources/remote/equipment_remote_datasource.dart';
import '../models/equipment/equipment_dto.dart';
import '../models/common/paginated_response.dart';
/// 장비 관리 Repository 구현체
/// 장비 정보 CRUD 작업을 처리하며 도메인 모델과 API DTO 간 변환을 담당
@Injectable(as: EquipmentRepository)
class EquipmentRepositoryImpl implements EquipmentRepository {
final EquipmentRemoteDataSource _remoteDataSource;
final EquipmentRemoteDataSource remoteDataSource;
EquipmentRepositoryImpl(this._remoteDataSource);
EquipmentRepositoryImpl({required this.remoteDataSource});
@override
Future<Either<Failure, List<EquipmentIn>>> getEquipmentIns({
Future<Either<Failure, PaginatedResponse<EquipmentDto>>> getEquipments({
int? page,
int? limit,
String? search,
String? sortBy,
String? sortOrder,
}) async {
try {
final response = await _remoteDataSource.getEquipments(
final result = 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,
equipmentNumber: dto.equipmentNumber ?? '', // 새로운 필드 (required)
modelName: dto.modelName ?? '', // 새로운 필드 (required)
category1: 'N/A', // EquipmentListDto에는 category 필드가 없음 (required)
category2: 'N/A', // EquipmentListDto에는 category 필드가 없음 (required)
category3: 'N/A', // EquipmentListDto에는 category 필드가 없음 (required)
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,
equipmentNumber: response.equipmentNumber ?? '', // 새로운 필드 (required)
modelName: response.modelName ?? '', // 새로운 필드 (required)
category1: response.category1 ?? '', // 새로운 필드 (required)
category2: response.category2 ?? '', // 새로운 필드 (required)
category3: response.category3 ?? '', // 새로운 필드 (required)
serialNumber: response.serialNumber,
barcode: response.barcode,
quantity: 1,
purchaseDate: response.purchaseDate, // purchaseDate로 변경
inDate: response.purchaseDate, // 기존 inDate 유지
remark: response.remark,
),
inDate: response.purchaseDate ?? DateTime.now(),
status: 'I',
type: '신제품',
warehouseLocation: null,
remark: response.remark,
final paginatedResult = PaginatedResponse<EquipmentDto>(
items: result.items,
page: result.currentPage,
size: result.pageSize ?? 20,
totalElements: result.totalCount,
totalPages: result.totalPages,
first: result.currentPage == 1,
last: result.currentPage == result.totalPages,
);
return Right(equipmentIn);
return Right(paginatedResult);
} on ServerException catch (e) {
return Left(ServerFailure(message: e.message ?? '서버 오류가 발생했습니다'));
return Left(ServerFailure(message: e.message));
} catch (e) {
return Left(ServerFailure(message: '장비 입고 상세 조회 실패: $e'));
return Left(ServerFailure(message: '장비 목록 조회 실패: $e'));
}
}
@override
Future<Either<Failure, EquipmentIn>> createEquipmentIn(EquipmentIn equipmentIn) async {
Future<Either<Failure, EquipmentDto>> getEquipmentDetail(int id) 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', // 트랜잭션 응답에는 제조사 정보 없음
equipmentNumber: 'N/A', // name → equipmentNumber (required)
modelName: 'N/A', // 새로운 필수 필드 (required)
category1: 'N/A', // category → category1 (required)
category2: 'N/A', // subCategory → category2 (required)
category3: 'N/A', // subSubCategory → category3 (required)
serialNumber: null,
quantity: response.quantity,
),
inDate: response.transactionDate,
status: 'I',
type: '신제품',
warehouseLocation: null,
remark: response.message,
);
return Right(newEquipmentIn);
final equipment = await remoteDataSource.getEquipmentDetail(id);
return Right(equipment);
} on ServerException catch (e) {
return Left(ServerFailure(message: e.message ?? '서버 오류가 발생했습니다'));
return Left(ServerFailure(message: e.message));
} catch (e) {
return Left(ServerFailure(message: '장비 입고 생성 실패: $e'));
return Left(ServerFailure(message: '장비 상세 조회 실패: $e'));
}
}
@override
Future<Either<Failure, EquipmentIn>> updateEquipmentIn(int id, EquipmentIn equipmentIn) async {
Future<Either<Failure, EquipmentDto>> createEquipment(EquipmentRequestDto request) 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,
equipmentNumber: response.equipmentNumber ?? '', // name → equipmentNumber (required)
modelName: response.modelName ?? '', // 새로운 필수 필드 (required)
category1: response.category1 ?? '', // category → category1 (required)
category2: response.category2 ?? '', // subCategory → category2 (required)
category3: response.category3 ?? '', // subSubCategory → category3 (required)
serialNumber: response.serialNumber,
barcode: response.barcode,
quantity: 1,
purchaseDate: response.purchaseDate, // purchaseDate로 변경
inDate: response.purchaseDate, // 기존 inDate 유지
remark: response.remark,
),
inDate: response.purchaseDate ?? DateTime.now(),
status: 'I',
type: '신제품',
warehouseLocation: null,
remark: response.remark,
);
return Right(updatedEquipmentIn);
final equipment = await remoteDataSource.createEquipment(request);
return Right(equipment);
} on ServerException catch (e) {
return Left(ServerFailure(message: e.message ?? '서버 오류가 발생했습니다'));
return Left(ServerFailure(message: e.message));
} catch (e) {
return Left(ServerFailure(message: '장비 입고 수정 실패: $e'));
return Left(ServerFailure(message: '장비 생성 실패: $e'));
}
}
@override
Future<Either<Failure, void>> deleteEquipmentIn(int id) async {
Future<Either<Failure, EquipmentDto>> updateEquipment(int id, EquipmentUpdateRequestDto request) async {
try {
await _remoteDataSource.deleteEquipment(id);
final equipment = await remoteDataSource.updateEquipment(id, request);
return Right(equipment);
} on ServerException catch (e) {
return Left(ServerFailure(message: e.message));
} catch (e) {
return Left(ServerFailure(message: '장비 수정 실패: $e'));
}
}
@override
Future<Either<Failure, void>> deleteEquipment(int id) async {
try {
await remoteDataSource.deleteEquipment(id);
return const Right(null);
} on ServerException catch (e) {
return Left(ServerFailure(message: e.message ?? '서버 오류가 발생했습니다'));
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,
equipmentNumber: dto.equipmentNumber ?? '', // name → equipmentNumber (required)
modelName: dto.modelName ?? '', // 새로운 필수 필드 (required)
category1: 'N/A', // category → category1 (required)
category2: 'N/A', // subCategory → category2 (required)
category3: 'N/A', // subSubCategory → category3 (required)
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,
equipmentNumber: response.equipmentNumber ?? '', // name → equipmentNumber (required)
modelName: response.modelName ?? '', // 새로운 필수 필드 (required)
category1: response.category1 ?? '', // category → category1 (required)
category2: response.category2 ?? '', // subCategory → category2 (required)
category3: response.category3 ?? '', // subSubCategory → category3 (required)
serialNumber: response.serialNumber,
barcode: response.barcode,
quantity: 1,
purchaseDate: response.purchaseDate, // purchaseDate로 변경
inDate: response.purchaseDate, // 기존 inDate 유지
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로 변환 필요
notes: equipmentOut.remark,
);
final response = await _remoteDataSource.equipmentOut(request);
final newEquipmentOut = EquipmentOut(
id: response.transactionId,
equipment: Equipment(
id: response.equipmentId,
manufacturer: 'N/A', // 트랜잭션 응답에는 제조사 정보 없음
equipmentNumber: 'N/A', // name → equipmentNumber (required)
modelName: 'N/A', // 새로운 필수 필드 (required)
category1: 'N/A', // category → category1 (required)
category2: 'N/A', // subCategory → category2 (required)
category3: 'N/A', // subSubCategory → category3 (required)
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(
companyId: 0, // TODO: company string을 ID로 변환 필요
remark: equipmentOut.remark,
);
final response = await _remoteDataSource.updateEquipment(id, request);
final updatedEquipmentOut = EquipmentOut(
id: response.id,
equipment: Equipment(
id: response.id,
manufacturer: response.manufacturer,
equipmentNumber: response.equipmentNumber ?? '', // name → equipmentNumber (required)
modelName: response.modelName ?? '', // 새로운 필수 필드 (required)
category1: response.category1 ?? '', // category → category1 (required)
category2: response.category2 ?? '', // subCategory → category2 (required)
category3: response.category3 ?? '', // subSubCategory → category3 (required)
serialNumber: response.serialNumber,
barcode: response.barcode,
quantity: 1,
purchaseDate: response.purchaseDate, // purchaseDate로 변경
inDate: response.purchaseDate, // 기존 inDate 유지
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로 변환 필요
notes: equipmentOut.remark,
);
final response = await _remoteDataSource.equipmentOut(request);
results.add(EquipmentOut(
id: response.transactionId,
equipment: Equipment(
id: response.equipmentId,
manufacturer: 'N/A', // 트랜잭션 응답에는 제조사 정보 없음
equipmentNumber: 'N/A', // name → equipmentNumber (required)
modelName: 'N/A', // 새로운 필수 필드 (required)
category1: 'N/A', // category → category1 (required)
category2: 'N/A', // subCategory → category2 (required)
category3: 'N/A', // subSubCategory → category3 (required)
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<Equipment>((dto) =>
Equipment(
id: dto.id,
manufacturer: dto.manufacturer,
equipmentNumber: dto.equipmentNumber ?? '', // name → equipmentNumber (required)
modelName: dto.modelName ?? '', // 새로운 필수 필드 (required)
category1: 'N/A', // category → category1 (required)
category2: 'N/A', // subCategory → category2 (required)
category3: 'N/A', // subSubCategory → category3 (required)
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'));
return Left(ServerFailure(message: '장비 삭제 실패: $e'));
}
}
}

View File

@@ -1,323 +0,0 @@
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';
/// 라이선스 Repository 구현체
/// 라이선스 및 유지보수 계약 관리 작업을 처리하며 도메인 모델과 API DTO 간 변환을 담당
@Injectable(as: LicenseRepository)
class LicenseRepositoryImpl implements LicenseRepository {
final LicenseRemoteDataSource remoteDataSource;
LicenseRepositoryImpl({required this.remoteDataSource});
@override
Future<Either<Failure, PaginatedResponse<License>>> getLicenses({
int? page,
int? limit,
String? search,
int? companyId,
String? equipmentType,
String? expiryStatus,
String? sortBy,
String? sortOrder,
}) async {
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,
);
}
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,
);
}
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,
);
}
}

View File

@@ -0,0 +1,214 @@
import 'package:dio/dio.dart';
import '../models/maintenance_dto.dart';
import '../../utils/constants.dart';
class MaintenanceRepository {
final Dio _dio;
static const String _baseEndpoint = '/maintenances';
MaintenanceRepository({required Dio dio}) : _dio = dio;
// 유지보수 목록 조회
Future<MaintenanceListResponse> getMaintenances({
int page = 1,
int pageSize = PaginationConstants.defaultPageSize,
String? sortBy,
String? sortOrder,
String? search,
int? equipmentHistoryId,
String? maintenanceType,
String? status, // MaintenanceStatus enum 제거, String으로 단순화
}) async {
try {
final queryParams = {
'page': page,
'page_size': pageSize,
if (sortBy != null) 'sort_by': sortBy,
if (sortOrder != null) 'sort_order': sortOrder,
if (search != null) 'search': search,
if (equipmentHistoryId != null) 'equipment_history_id': equipmentHistoryId,
if (maintenanceType != null) 'maintenance_type': maintenanceType,
if (status != null) 'status': status,
};
final response = await _dio.get(
_baseEndpoint,
queryParameters: queryParams,
);
return MaintenanceListResponse.fromJson(response.data);
} on DioException catch (e) {
throw _handleError(e);
}
}
// 특정 유지보수 조회
Future<MaintenanceDto> getMaintenance(int id) async {
try {
final response = await _dio.get('$_baseEndpoint/$id');
return MaintenanceDto.fromJson(response.data);
} on DioException catch (e) {
throw _handleError(e);
}
}
// 장비 이력별 유지보수 조회
Future<List<MaintenanceDto>> getMaintenancesByEquipmentHistory(int equipmentHistoryId) async {
try {
final response = await _dio.get(
_baseEndpoint,
queryParameters: {'equipment_history_id': equipmentHistoryId},
);
final data = response.data;
if (data is Map && data.containsKey('maintenances')) {
return (data['maintenances'] as List)
.map((json) => MaintenanceDto.fromJson(json))
.toList();
}
return (data as List).map((json) => MaintenanceDto.fromJson(json)).toList();
} on DioException catch (e) {
throw _handleError(e);
}
}
// 만료 예정 유지보수 조회
Future<List<MaintenanceDto>> getUpcomingMaintenances({
int daysAhead = 30,
}) async {
try {
final response = await _dio.get(
'$_baseEndpoint/upcoming',
queryParameters: {'days_ahead': daysAhead},
);
final data = response.data;
if (data is Map && data.containsKey('maintenances')) {
return (data['maintenances'] as List)
.map((json) => MaintenanceDto.fromJson(json))
.toList();
}
return (data as List).map((json) => MaintenanceDto.fromJson(json)).toList();
} on DioException catch (e) {
throw _handleError(e);
}
}
// 만료된 유지보수 조회
Future<List<MaintenanceDto>> getOverdueMaintenances() async {
try {
final response = await _dio.get('$_baseEndpoint/overdue');
final data = response.data;
if (data is Map && data.containsKey('maintenances')) {
return (data['maintenances'] as List)
.map((json) => MaintenanceDto.fromJson(json))
.toList();
}
return (data as List).map((json) => MaintenanceDto.fromJson(json)).toList();
} on DioException catch (e) {
throw _handleError(e);
}
}
// 유지보수 생성
Future<MaintenanceDto> createMaintenance(MaintenanceRequestDto request) async {
try {
final response = await _dio.post(
_baseEndpoint,
data: request.toJson(),
);
return MaintenanceDto.fromJson(response.data);
} on DioException catch (e) {
throw _handleError(e);
}
}
// 유지보수 수정
Future<MaintenanceDto> updateMaintenance(int id, MaintenanceUpdateRequestDto request) async {
try {
final response = await _dio.put(
'$_baseEndpoint/$id',
data: request.toJson(),
);
return MaintenanceDto.fromJson(response.data);
} on DioException catch (e) {
throw _handleError(e);
}
}
// 유지보수 삭제
Future<void> deleteMaintenance(int id) async {
try {
await _dio.delete('$_baseEndpoint/$id');
} on DioException catch (e) {
throw _handleError(e);
}
}
// 유지보수 상태 변경 (활성/비활성)
Future<MaintenanceDto> toggleMaintenanceStatus(int id, bool isActive) async {
try {
final response = await _dio.patch(
'$_baseEndpoint/$id/status',
data: {'is_active': isActive},
);
return MaintenanceDto.fromJson(response.data);
} on DioException catch (e) {
throw _handleError(e);
}
}
// 다음 유지보수 날짜 계산
Future<String> calculateNextMaintenanceDate(int id) async {
try {
final response = await _dio.post(
'$_baseEndpoint/$id/calculate-next-date',
);
return response.data['next_maintenance_date'];
} on DioException catch (e) {
throw _handleError(e);
}
}
// 에러 처리
String _handleError(DioException e) {
if (e.response != null) {
final statusCode = e.response!.statusCode;
final data = e.response!.data;
if (data is Map && data.containsKey('message')) {
return data['message'];
}
switch (statusCode) {
case 400:
return '잘못된 요청입니다.';
case 401:
return '인증이 필요합니다.';
case 403:
return '권한이 없습니다.';
case 404:
return '유지보수 정보를 찾을 수 없습니다.';
case 409:
return '중복된 유지보수 정보가 존재합니다.';
case 500:
return '서버 오류가 발생했습니다.';
default:
return '오류가 발생했습니다. (코드: $statusCode)';
}
}
if (e.type == DioExceptionType.connectionTimeout) {
return '연결 시간이 초과되었습니다.';
} else if (e.type == DioExceptionType.connectionError) {
return '네트워크 연결을 확인해주세요.';
}
return '알 수 없는 오류가 발생했습니다.';
}
}

View File

@@ -0,0 +1,171 @@
import 'package:dio/dio.dart';
import 'package:injectable/injectable.dart';
import 'package:superport/core/constants/api_endpoints.dart';
import 'package:superport/data/datasources/remote/api_client.dart';
import 'package:superport/data/models/model_dto.dart';
abstract class ModelRepository {
Future<List<ModelDto>> getModels({int? vendorId});
Future<ModelDto> getModelById(int id);
Future<ModelDto> createModel(ModelRequestDto request);
Future<ModelDto> updateModel(int id, ModelUpdateRequestDto request);
Future<void> deleteModel(int id);
Future<List<ModelDto>> searchModels(String query);
}
@Injectable(as: ModelRepository)
class ModelRepositoryImpl implements ModelRepository {
final ApiClient _apiClient;
ModelRepositoryImpl(this._apiClient);
@override
Future<List<ModelDto>> getModels({int? vendorId}) async {
try {
final queryParams = <String, dynamic>{};
if (vendorId != null) {
queryParams['vendor_id'] = vendorId;
}
final response = await _apiClient.dio.get(
ApiEndpoints.models,
queryParameters: queryParams,
);
if (response.statusCode == 200) {
final data = response.data;
// Handle both array and object responses
if (data is List) {
return data.map((json) => ModelDto.fromJson(json)).toList();
} else if (data is Map && data['data'] != null) {
return (data['data'] as List)
.map((json) => ModelDto.fromJson(json))
.toList();
}
return [];
} else {
throw DioException(
requestOptions: response.requestOptions,
response: response,
message: 'Failed to load models',
);
}
} catch (e) {
rethrow;
}
}
@override
Future<ModelDto> getModelById(int id) async {
try {
final response = await _apiClient.dio.get('${ApiEndpoints.models}/$id');
if (response.statusCode == 200) {
return ModelDto.fromJson(response.data);
} else {
throw DioException(
requestOptions: response.requestOptions,
response: response,
message: 'Failed to load model',
);
}
} catch (e) {
rethrow;
}
}
@override
Future<ModelDto> createModel(ModelRequestDto request) async {
try {
final response = await _apiClient.dio.post(
ApiEndpoints.models,
data: request.toJson(),
);
if (response.statusCode == 201 || response.statusCode == 200) {
return ModelDto.fromJson(response.data);
} else {
throw DioException(
requestOptions: response.requestOptions,
response: response,
message: 'Failed to create model',
);
}
} catch (e) {
rethrow;
}
}
@override
Future<ModelDto> updateModel(int id, ModelUpdateRequestDto request) async {
try {
final response = await _apiClient.dio.put(
'${ApiEndpoints.models}/$id',
data: request.toJson(),
);
if (response.statusCode == 200) {
return ModelDto.fromJson(response.data);
} else {
throw DioException(
requestOptions: response.requestOptions,
response: response,
message: 'Failed to update model',
);
}
} catch (e) {
rethrow;
}
}
@override
Future<void> deleteModel(int id) async {
try {
final response = await _apiClient.dio.delete('${ApiEndpoints.models}/$id');
if (response.statusCode != 200 && response.statusCode != 204) {
throw DioException(
requestOptions: response.requestOptions,
response: response,
message: 'Failed to delete model',
);
}
} catch (e) {
rethrow;
}
}
@override
Future<List<ModelDto>> searchModels(String query) async {
try {
final response = await _apiClient.dio.get(
ApiEndpoints.models,
queryParameters: {'search': query},
);
if (response.statusCode == 200) {
final data = response.data;
if (data is List) {
return data.map((json) => ModelDto.fromJson(json)).toList();
} else if (data is Map && data['data'] != null) {
return (data['data'] as List)
.map((json) => ModelDto.fromJson(json))
.toList();
}
return [];
} else {
throw DioException(
requestOptions: response.requestOptions,
response: response,
message: 'Failed to search models',
);
}
} catch (e) {
rethrow;
}
}
}

View File

@@ -0,0 +1,219 @@
import 'package:dio/dio.dart';
import 'package:injectable/injectable.dart';
import '../../core/constants/api_endpoints.dart';
import '../../core/errors/exceptions.dart';
import '../../domain/repositories/rent_repository.dart';
import '../models/rent_dto.dart';
@LazySingleton(as: RentRepository)
class RentRepositoryImpl implements RentRepository {
final Dio dio;
RentRepositoryImpl(this.dio);
@override
Future<RentListResponse> getRents({
int page = 1,
int pageSize = 10,
String? search,
String? status,
int? equipmentHistoryId,
}) async {
try {
final queryParameters = <String, dynamic>{
'page': page,
'page_size': pageSize,
};
if (search != null && search.isNotEmpty) {
queryParameters['search'] = search;
}
if (status != null && status.isNotEmpty) {
queryParameters['status'] = status;
}
if (equipmentHistoryId != null) {
queryParameters['equipment_history_id'] = equipmentHistoryId;
}
final response = await dio.get(
ApiEndpoints.rents,
queryParameters: queryParameters,
);
return RentListResponse.fromJson(response.data);
} on DioException catch (e) {
throw ServerException(message: _handleError(e));
} catch (e) {
throw ServerException(message: '임대 목록을 가져오는 중 오류가 발생했습니다: $e');
}
}
@override
Future<RentDto> getRent(int id) async {
try {
final response = await dio.get('${ApiEndpoints.rents}/$id');
return RentDto.fromJson(response.data);
} on DioException catch (e) {
throw ServerException(message: _handleError(e));
} catch (e) {
throw ServerException(message: '임대 상세 정보를 가져오는 중 오류가 발생했습니다: $e');
}
}
@override
Future<RentDto> createRent(RentRequestDto request) async {
try {
final response = await dio.post(
ApiEndpoints.rents,
data: request.toJson(),
);
return RentDto.fromJson(response.data);
} on DioException catch (e) {
throw ServerException(message: _handleError(e));
} catch (e) {
throw ServerException(message: '임대 생성 중 오류가 발생했습니다: $e');
}
}
@override
Future<RentDto> updateRent(int id, RentUpdateRequestDto request) async {
try {
final response = await dio.put(
'${ApiEndpoints.rents}/$id',
data: request.toJson(),
);
return RentDto.fromJson(response.data);
} on DioException catch (e) {
throw ServerException(message: _handleError(e));
} catch (e) {
throw ServerException(message: '임대 수정 중 오류가 발생했습니다: $e');
}
}
@override
Future<void> deleteRent(int id) async {
try {
await dio.delete('${ApiEndpoints.rents}/$id');
} on DioException catch (e) {
throw ServerException(message: _handleError(e));
} catch (e) {
throw ServerException(message: '임대 삭제 중 오류가 발생했습니다: $e');
}
}
@override
Future<RentListResponse> getActiveRents({
int page = 1,
int pageSize = 10,
}) async {
try {
final response = await dio.get(
ApiEndpoints.rentsActive,
queryParameters: {
'page': page,
'page_size': pageSize,
},
);
return RentListResponse.fromJson(response.data);
} on DioException catch (e) {
throw ServerException(message: _handleError(e));
} catch (e) {
throw ServerException(message: '진행 중인 임대 목록을 가져오는 중 오류가 발생했습니다: $e');
}
}
@override
Future<RentListResponse> getOverdueRents({
int page = 1,
int pageSize = 10,
}) async {
try {
final response = await dio.get(
ApiEndpoints.rentsOverdue,
queryParameters: {
'page': page,
'page_size': pageSize,
},
);
return RentListResponse.fromJson(response.data);
} on DioException catch (e) {
throw ServerException(message: _handleError(e));
} catch (e) {
throw ServerException(message: '연체된 임대 목록을 가져오는 중 오류가 발생했습니다: $e');
}
}
@override
Future<Map<String, dynamic>> getRentStats() async {
try {
final response = await dio.get(ApiEndpoints.rentsStats);
return response.data as Map<String, dynamic>;
} on DioException catch (e) {
throw ServerException(message: _handleError(e));
} catch (e) {
throw ServerException(message: '임대 통계를 가져오는 중 오류가 발생했습니다: $e');
}
}
@override
Future<RentDto> returnRent(int id, String returnDate) async {
try {
final response = await dio.put(
'${ApiEndpoints.rents}/$id',
data: {
'actual_return_date': returnDate,
'status': 'returned',
},
);
return RentDto.fromJson(response.data);
} on DioException catch (e) {
throw ServerException(message: _handleError(e));
} catch (e) {
throw ServerException(message: '장비 반납 처리 중 오류가 발생했습니다: $e');
}
}
// 에러 처리 메서드
String _handleError(DioException e) {
if (e.response != null) {
final statusCode = e.response!.statusCode;
final data = e.response!.data;
if (data is Map && data.containsKey('message')) {
return data['message'];
}
switch (statusCode) {
case 400:
return '잘못된 요청입니다.';
case 401:
return '인증이 필요합니다.';
case 403:
return '권한이 없습니다.';
case 404:
return '리소스를 찾을 수 없습니다.';
case 500:
return '서버 내부 오류입니다.';
default:
return 'HTTP 오류 $statusCode';
}
}
switch (e.type) {
case DioExceptionType.connectionTimeout:
return '연결 시간 초과';
case DioExceptionType.sendTimeout:
return '요청 전송 시간 초과';
case DioExceptionType.receiveTimeout:
return '응답 수신 시간 초과';
case DioExceptionType.connectionError:
return '네트워크 연결 오류';
case DioExceptionType.cancel:
return '요청이 취소됨';
default:
return '네트워크 오류: ${e.message}';
}
}
}

View File

@@ -0,0 +1,185 @@
import 'package:dio/dio.dart';
import '../models/rent_dto.dart';
import '../../domain/repositories/rent_repository.dart';
class RentRepositoryImpl implements RentRepository {
final Dio _dio;
static const String _baseEndpoint = '/rents';
RentRepositoryImpl({required Dio dio}) : _dio = dio;
@override
Future<RentListResponse> getRents({
int page = 1,
int pageSize = 10,
String? search,
String? status,
int? equipmentHistoryId,
}) async {
try {
final queryParams = {
'page': page,
'page_size': pageSize,
if (search != null) 'search': search,
if (status != null) 'status': status,
if (equipmentHistoryId != null) 'equipment_history_id': equipmentHistoryId,
};
final response = await _dio.get(
_baseEndpoint,
queryParameters: queryParams,
);
return RentListResponse.fromJson(response.data);
} on DioException catch (e) {
throw _handleError(e);
}
}
@override
Future<RentDto> getRent(int id) async {
try {
final response = await _dio.get('$_baseEndpoint/$id');
return RentDto.fromJson(response.data);
} on DioException catch (e) {
throw _handleError(e);
}
}
@override
Future<RentDto> createRent(RentRequestDto request) async {
try {
final response = await _dio.post(
_baseEndpoint,
data: request.toJson(),
);
return RentDto.fromJson(response.data);
} on DioException catch (e) {
throw _handleError(e);
}
}
@override
Future<RentDto> updateRent(int id, RentUpdateRequestDto request) async {
try {
final response = await _dio.put(
'$_baseEndpoint/$id',
data: request.toJson(),
);
return RentDto.fromJson(response.data);
} on DioException catch (e) {
throw _handleError(e);
}
}
@override
Future<void> deleteRent(int id) async {
try {
await _dio.delete('$_baseEndpoint/$id');
} on DioException catch (e) {
throw _handleError(e);
}
}
@override
Future<RentListResponse> getActiveRents({
int page = 1,
int pageSize = 10,
}) async {
try {
final response = await _dio.get(
'$_baseEndpoint/active',
queryParameters: {
'page': page,
'page_size': pageSize,
},
);
return RentListResponse.fromJson(response.data);
} on DioException catch (e) {
throw _handleError(e);
}
}
@override
Future<RentListResponse> getOverdueRents({
int page = 1,
int pageSize = 10,
}) async {
try {
final response = await _dio.get(
'$_baseEndpoint/overdue',
queryParameters: {
'page': page,
'page_size': pageSize,
},
);
return RentListResponse.fromJson(response.data);
} on DioException catch (e) {
throw _handleError(e);
}
}
@override
Future<Map<String, dynamic>> getRentStats() async {
try {
final response = await _dio.get('$_baseEndpoint/stats');
return response.data as Map<String, dynamic>;
} on DioException catch (e) {
throw _handleError(e);
}
}
@override
Future<RentDto> returnRent(int id, String returnDate) async {
try {
final response = await _dio.patch(
'$_baseEndpoint/$id/return',
data: {'return_date': returnDate},
);
return RentDto.fromJson(response.data);
} on DioException catch (e) {
throw _handleError(e);
}
}
String _handleError(DioException e) {
if (e.response != null) {
final statusCode = e.response!.statusCode;
final data = e.response!.data;
if (data is Map && data.containsKey('message')) {
return data['message'];
}
switch (statusCode) {
case 400:
return '잘못된 요청입니다.';
case 401:
return '인증이 필요합니다.';
case 403:
return '권한이 없습니다.';
case 404:
return '임대 정보를 찾을 수 없습니다.';
case 409:
return '중복된 임대 정보가 존재합니다.';
case 500:
return '서버 오류가 발생했습니다.';
default:
return '오류가 발생했습니다. (코드: $statusCode)';
}
}
if (e.type == DioExceptionType.connectionTimeout) {
return '연결 시간이 초과되었습니다.';
} else if (e.type == DioExceptionType.connectionError) {
return '네트워크 연결을 확인해주세요.';
}
return '알 수 없는 오류가 발생했습니다.';
}
}

View File

@@ -42,8 +42,8 @@ class UserRepositoryImpl implements UserRepository {
size: result.perPage,
totalElements: result.total,
totalPages: result.totalPages,
first: result.first,
last: result.last,
first: result.page == 1, // 첫 페이지 여부
last: result.page >= result.totalPages, // 마지막 페이지 여부
);
return Right(paginatedResult);
@@ -75,21 +75,17 @@ class UserRepositoryImpl implements UserRepository {
/// 사용자 계정 생성
@override
Future<Either<Failure, User>> createUser({
required String username,
required String email,
required String password,
required String name,
String? email,
String? phone,
required UserRole role,
required int companiesId,
}) async {
try {
final request = CreateUserRequest(
username: username,
email: email,
password: password,
final request = UserRequestDto(
name: name,
email: email,
phone: phone,
role: role.name,
companiesId: companiesId,
);
final dto = await _remoteDataSource.createUser(request);
@@ -108,7 +104,7 @@ class UserRepositoryImpl implements UserRepository {
@override
Future<Either<Failure, User>> updateUser(int id, User user, {String? newPassword}) async {
try {
final request = UpdateUserRequest.fromDomain(user, newPassword: newPassword);
final request = UserUpdateRequestDto.fromDomain(user, newPassword: newPassword);
final dto = await _remoteDataSource.updateUser(id, request);
final updatedUser = dto.toDomainModel();
@@ -137,12 +133,12 @@ class UserRepositoryImpl implements UserRepository {
}
}
/// 사용자명 사용 가능 여부 확인
/// 사용자 이름 중복 확인 (백엔드 API v1에서는 미지원)
@override
Future<Either<Failure, bool>> checkUsernameAvailability(String username) async {
Future<Either<Failure, bool>> checkUsernameAvailability(String name) async {
try {
final response = await _remoteDataSource.checkUsernameAvailability(username);
return Right(response.available);
// 백엔드에서 지원하지 않으므로 항상 true 반환
return const Right(true);
} on ApiException catch (e) {
return Left(_mapApiExceptionToFailure(e));
} catch (e) {

View File

@@ -0,0 +1,166 @@
import 'package:dio/dio.dart';
import 'package:injectable/injectable.dart';
import 'package:superport/core/constants/api_endpoints.dart';
import 'package:superport/data/datasources/remote/api_client.dart';
import 'package:superport/data/models/vendor_dto.dart';
import 'package:superport/data/models/vendor_stats_dto.dart';
import 'package:superport/utils/constants.dart';
abstract class VendorRepository {
Future<VendorListResponse> getAll({
int page = 1,
int limit = PaginationConstants.defaultPageSize,
String? search,
bool? isActive,
});
Future<VendorDto> getById(int id);
Future<VendorDto> create(VendorDto vendor);
Future<VendorDto> update(int id, VendorDto vendor);
Future<void> delete(int id);
Future<void> restore(int id);
Future<VendorStatsDto> getStats();
}
@Injectable(as: VendorRepository)
class VendorRepositoryImpl implements VendorRepository {
final ApiClient _apiClient;
VendorRepositoryImpl(this._apiClient);
@override
Future<VendorListResponse> getAll({
int page = 1,
int limit = PaginationConstants.defaultPageSize,
String? search,
bool? isActive,
}) async {
try {
final queryParams = <String, dynamic>{
'page': page,
'page_size': limit,
};
if (search != null && search.isNotEmpty) {
queryParams['search'] = search;
}
if (isActive != null) {
queryParams['is_active'] = isActive;
}
final response = await _apiClient.dio.get(
ApiEndpoints.vendors,
queryParameters: queryParams,
);
// API 응답 구조에 따라 파싱
if (response.data is Map<String, dynamic>) {
// 페이지네이션 응답 형식
return VendorListResponse.fromJson(response.data);
} else if (response.data is List) {
// 배열 직접 반환 형식
final vendors = (response.data as List)
.map((json) => VendorDto.fromJson(json))
.toList();
return VendorListResponse(
items: vendors,
totalCount: vendors.length,
currentPage: page,
totalPages: 1,
);
} else {
throw Exception('Unexpected response format');
}
} on DioException catch (e) {
throw _handleError(e);
}
}
@override
Future<VendorDto> getById(int id) async {
try {
final response = await _apiClient.dio.get(
'${ApiEndpoints.vendors}/$id',
);
return VendorDto.fromJson(response.data);
} on DioException catch (e) {
throw _handleError(e);
}
}
@override
Future<VendorDto> create(VendorDto vendor) async {
try {
final response = await _apiClient.dio.post(
ApiEndpoints.vendors,
data: vendor.toJson(),
);
return VendorDto.fromJson(response.data);
} on DioException catch (e) {
throw _handleError(e);
}
}
@override
Future<VendorDto> update(int id, VendorDto vendor) async {
try {
final response = await _apiClient.dio.put(
'${ApiEndpoints.vendors}/$id',
data: vendor.toJson(),
);
return VendorDto.fromJson(response.data);
} on DioException catch (e) {
throw _handleError(e);
}
}
@override
Future<void> delete(int id) async {
try {
await _apiClient.dio.delete(
'${ApiEndpoints.vendors}/$id',
);
} on DioException catch (e) {
throw _handleError(e);
}
}
@override
Future<void> restore(int id) async {
try {
await _apiClient.dio.put(
'${ApiEndpoints.vendors}/$id/restore',
);
} on DioException catch (e) {
throw _handleError(e);
}
}
@override
Future<VendorStatsDto> getStats() async {
try {
final response = await _apiClient.dio.get(
'${ApiEndpoints.vendors}/stats',
);
return VendorStatsDto.fromJson(response.data);
} on DioException catch (e) {
throw _handleError(e);
}
}
Exception _handleError(DioException e) {
switch (e.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
return Exception('연결 시간이 초과되었습니다.');
case DioExceptionType.badResponse:
final statusCode = e.response?.statusCode;
final message = e.response?.data?['message'] ?? '서버 오류가 발생했습니다.';
return Exception('[$statusCode] $message');
case DioExceptionType.connectionError:
return Exception('네트워크 연결을 확인해주세요.');
default:
return Exception('알 수 없는 오류가 발생했습니다.');
}
}
}

View File

@@ -302,55 +302,49 @@ class WarehouseLocationRepositoryImpl implements WarehouseLocationRepository {
// Private 매퍼 메서드들
WarehouseLocation _mapDtoToDomain(WarehouseLocationDto dto) {
WarehouseLocation _mapDtoToDomain(WarehouseDto dto) {
return WarehouseLocation(
id: dto.id,
id: dto.id ?? 0,
name: dto.name,
address: dto.address, // 단일 String 필드
managerName: dto.managerName,
managerPhone: dto.managerPhone,
capacity: dto.capacity,
address: '', // 백엔드에서 미지원
managerName: '', // 백엔드에서 미지원
managerPhone: '', // 백엔드에서 미지원
capacity: 0, // 백엔드에서 미지원
remark: dto.remark,
isActive: dto.isActive,
createdAt: dto.createdAt,
isActive: !dto.isDeleted, // isActive는 isDeleted의 반대
createdAt: dto.registeredAt,
);
}
WarehouseLocation _mapDetailDtoToDomain(WarehouseLocationDto dto) {
WarehouseLocation _mapDetailDtoToDomain(WarehouseDto dto) {
return WarehouseLocation(
id: dto.id,
id: dto.id ?? 0,
name: dto.name,
address: dto.address, // 단일 String 필드
managerName: dto.managerName,
managerPhone: dto.managerPhone,
capacity: dto.capacity,
address: '', // 백엔드에서 미지원
managerName: '', // 백엔드에서 미지원
managerPhone: '', // 백엔드에서 미지원
capacity: 0, // 백엔드에서 미지원
remark: dto.remark,
isActive: dto.isActive,
createdAt: dto.createdAt,
isActive: !dto.isDeleted, // isActive는 isDeleted의 반대
createdAt: dto.registeredAt,
);
}
// WarehouseLocationType enum이 WarehouseLocation 모델에 없으므로 제거
// 필요시 나중에 모델 업데이트 후 재추가
CreateWarehouseLocationRequest _mapDomainToCreateRequest(WarehouseLocation warehouseLocation) {
return CreateWarehouseLocationRequest(
WarehouseRequestDto _mapDomainToCreateRequest(WarehouseLocation warehouseLocation) {
return WarehouseRequestDto(
name: warehouseLocation.name,
address: warehouseLocation.address,
managerName: warehouseLocation.managerName,
managerPhone: warehouseLocation.managerPhone,
capacity: warehouseLocation.capacity,
zipcodesZipcode: null, // zipcodes FK (별도 처리 필요)
remark: warehouseLocation.remark,
);
}
UpdateWarehouseLocationRequest _mapDomainToUpdateRequest(WarehouseLocation warehouseLocation) {
return UpdateWarehouseLocationRequest(
WarehouseUpdateRequestDto _mapDomainToUpdateRequest(WarehouseLocation warehouseLocation) {
return WarehouseUpdateRequestDto(
name: warehouseLocation.name,
address: warehouseLocation.address,
managerName: warehouseLocation.managerName,
managerPhone: warehouseLocation.managerPhone,
capacity: warehouseLocation.capacity,
zipcodesZipcode: null, // zipcodes FK (별도 처리 필요)
remark: warehouseLocation.remark,
);
}

View File

@@ -0,0 +1,182 @@
import 'package:dio/dio.dart';
import 'package:injectable/injectable.dart';
import 'package:superport/core/constants/api_endpoints.dart';
import 'package:superport/data/datasources/remote/api_client.dart';
import 'package:superport/data/models/zipcode_dto.dart';
abstract class ZipcodeRepository {
/// 우편번호 검색 (페이지네이션 지원)
Future<ZipcodeListResponse> search({
int page = 1,
int limit = 20,
String? search,
String? sido,
String? gu,
});
/// 우편번호로 정확한 주소 조회
Future<ZipcodeDto?> getByZipcode(int zipcode);
/// 시도별 구 목록 조회
Future<List<String>> getGuBySido(String sido);
/// 전체 시도 목록 조회
Future<List<String>> getAllSido();
}
@Injectable(as: ZipcodeRepository)
class ZipcodeRepositoryImpl implements ZipcodeRepository {
final ApiClient _apiClient;
ZipcodeRepositoryImpl(this._apiClient);
@override
Future<ZipcodeListResponse> search({
int page = 1,
int limit = 20,
String? search,
String? sido,
String? gu,
}) async {
try {
final queryParams = <String, dynamic>{
'page': page,
'limit': limit,
};
if (search != null && search.isNotEmpty) {
queryParams['search'] = search;
}
if (sido != null && sido.isNotEmpty) {
queryParams['sido'] = sido;
}
if (gu != null && gu.isNotEmpty) {
queryParams['gu'] = gu;
}
final response = await _apiClient.dio.get(
ApiEndpoints.zipcodes,
queryParameters: queryParams,
);
// API 응답 구조에 따라 파싱
if (response.data is Map<String, dynamic>) {
// 페이지네이션 응답 형식
return ZipcodeListResponse.fromJson(response.data);
} else if (response.data is List) {
// 배열 직접 반환 형식
final zipcodes = (response.data as List)
.map((json) => ZipcodeDto.fromJson(json))
.toList();
return ZipcodeListResponse(
items: zipcodes,
totalCount: zipcodes.length,
currentPage: page,
totalPages: 1,
);
} else {
throw Exception('예상치 못한 응답 형식입니다.');
}
} on DioException catch (e) {
throw _handleError(e);
}
}
@override
Future<ZipcodeDto?> getByZipcode(int zipcode) async {
try {
final response = await _apiClient.dio.get(
ApiEndpoints.zipcodes,
queryParameters: {
'zipcode': zipcode,
'limit': 1,
},
);
if (response.data is Map<String, dynamic>) {
final listResponse = ZipcodeListResponse.fromJson(response.data);
return listResponse.items.isNotEmpty ? listResponse.items.first : null;
}
return null;
} on DioException catch (e) {
throw _handleError(e);
}
}
@override
Future<List<String>> getGuBySido(String sido) async {
try {
final response = await _apiClient.dio.get(
ApiEndpoints.zipcodes,
queryParameters: {
'sido': sido,
'limit': 1000, // 충분히 큰 값으로 모든 구 가져오기
},
);
if (response.data is Map<String, dynamic>) {
final listResponse = ZipcodeListResponse.fromJson(response.data);
// 중복 제거하고 구 목록만 추출
final guSet = <String>{};
for (final zipcode in listResponse.items) {
guSet.add(zipcode.gu);
}
final guList = guSet.toList()..sort();
return guList;
}
return [];
} on DioException catch (e) {
throw _handleError(e);
}
}
@override
Future<List<String>> getAllSido() async {
try {
final response = await _apiClient.dio.get(
ApiEndpoints.zipcodes,
queryParameters: {
'limit': 1000, // 충분히 큰 값으로 모든 시도 가져오기
},
);
if (response.data is Map<String, dynamic>) {
final listResponse = ZipcodeListResponse.fromJson(response.data);
// 중복 제거하고 시도 목록만 추출
final sidoSet = <String>{};
for (final zipcode in listResponse.items) {
sidoSet.add(zipcode.sido);
}
final sidoList = sidoSet.toList()..sort();
return sidoList;
}
return [];
} on DioException catch (e) {
throw _handleError(e);
}
}
Exception _handleError(DioException e) {
switch (e.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
return Exception('연결 시간이 초과되었습니다.');
case DioExceptionType.badResponse:
final statusCode = e.response?.statusCode;
final message = e.response?.data?['message'] ?? '서버 오류가 발생했습니다.';
return Exception('[$statusCode] $message');
case DioExceptionType.connectionError:
return Exception('네트워크 연결을 확인해주세요.');
default:
return Exception('알 수 없는 오류가 발생했습니다.');
}
}
}