사용하지 않는 파일 정리 전 백업 (Phase 10 완료 후 상태)
This commit is contained in:
187
lib/data/repositories/administrator_repository_impl.dart
Normal file
187
lib/data/repositories/administrator_repository_impl.dart
Normal 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 ?? '알 수 없는 오류가 발생했습니다.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
249
lib/data/repositories/equipment_history_repository.dart
Normal file
249
lib/data/repositories/equipment_history_repository.dart
Normal 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('네트워크 오류가 발생했습니다.');
|
||||
}
|
||||
}
|
||||
@@ -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'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
214
lib/data/repositories/maintenance_repository.dart
Normal file
214
lib/data/repositories/maintenance_repository.dart
Normal 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 '알 수 없는 오류가 발생했습니다.';
|
||||
}
|
||||
}
|
||||
171
lib/data/repositories/model_repository.dart
Normal file
171
lib/data/repositories/model_repository.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
219
lib/data/repositories/rent_repository.dart
Normal file
219
lib/data/repositories/rent_repository.dart
Normal 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}';
|
||||
}
|
||||
}
|
||||
}
|
||||
185
lib/data/repositories/rent_repository_impl.dart
Normal file
185
lib/data/repositories/rent_repository_impl.dart
Normal 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 '알 수 없는 오류가 발생했습니다.';
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
166
lib/data/repositories/vendor_repository.dart
Normal file
166
lib/data/repositories/vendor_repository.dart
Normal 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('알 수 없는 오류가 발생했습니다.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
182
lib/data/repositories/zipcode_repository.dart
Normal file
182
lib/data/repositories/zipcode_repository.dart
Normal 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('알 수 없는 오류가 발생했습니다.');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user