refactor: Clean Architecture 적용 및 코드베이스 전면 리팩토링
## 주요 변경사항 ### 아키텍처 개선 - Clean Architecture 패턴 적용 (Domain, Data, Presentation 레이어 분리) - Use Case 패턴 도입으로 비즈니스 로직 캡슐화 - Repository 패턴으로 데이터 접근 추상화 - 의존성 주입 구조 개선 ### 상태 관리 최적화 - 모든 Controller에서 불필요한 상태 관리 로직 제거 - 페이지네이션 로직 통일 및 간소화 - 에러 처리 로직 개선 (에러 메시지 한글화) - 로딩 상태 관리 최적화 ### Mock 서비스 제거 - MockDataService 완전 제거 - 모든 화면을 실제 API 전용으로 전환 - 불필요한 Mock 관련 코드 정리 ### UI/UX 개선 - Overview 화면 대시보드 기능 강화 - 라이선스 만료 알림 위젯 추가 - 사이드바 네비게이션 개선 - 일관된 UI 컴포넌트 사용 ### 코드 품질 - 중복 코드 제거 및 함수 추출 - 파일별 책임 분리 명확화 - 테스트 코드 업데이트 ## 영향 범위 - 모든 화면의 Controller 리팩토링 - API 통신 레이어 구조 개선 - 에러 처리 및 로깅 시스템 개선 ## 향후 계획 - 단위 테스트 커버리지 확대 - 통합 테스트 시나리오 추가 - 성능 모니터링 도구 통합
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import '../../../data/models/warehouse/warehouse_dto.dart';
|
||||
import '../../../data/repositories/warehouse_location_repository.dart';
|
||||
import '../../../core/errors/failures.dart';
|
||||
import '../base_usecase.dart';
|
||||
|
||||
/// 창고 위치 생성 UseCase
|
||||
@injectable
|
||||
class CreateWarehouseLocationUseCase implements UseCase<WarehouseLocationDto, CreateWarehouseLocationParams> {
|
||||
final WarehouseLocationRepository repository;
|
||||
|
||||
CreateWarehouseLocationUseCase(this.repository);
|
||||
|
||||
@override
|
||||
Future<Either<Failure, WarehouseLocationDto>> call(CreateWarehouseLocationParams params) async {
|
||||
try {
|
||||
// 비즈니스 로직: 이름 중복 체크
|
||||
if (params.name.isEmpty) {
|
||||
return Left(ValidationFailure(message: '창고 위치 이름은 필수입니다'));
|
||||
}
|
||||
|
||||
// 비즈니스 로직: 주소 유효성 검증
|
||||
if (params.address.isEmpty) {
|
||||
return Left(ValidationFailure(message: '창고 주소는 필수입니다'));
|
||||
}
|
||||
|
||||
// 비즈니스 로직: 연락처 형식 검증
|
||||
if (params.contactNumber != null && params.contactNumber!.isNotEmpty) {
|
||||
final phoneRegex = RegExp(r'^[\d\-\+\(\)\s]+$');
|
||||
if (!phoneRegex.hasMatch(params.contactNumber!)) {
|
||||
return Left(ValidationFailure(message: '올바른 연락처 형식이 아닙니다'));
|
||||
}
|
||||
}
|
||||
|
||||
final location = await repository.createWarehouseLocation(params.toMap());
|
||||
return Right(location);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(message: e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 창고 위치 생성 파라미터
|
||||
class CreateWarehouseLocationParams {
|
||||
final String name;
|
||||
final String address;
|
||||
final String? description;
|
||||
final String? contactNumber;
|
||||
final String? manager;
|
||||
final double? latitude;
|
||||
final double? longitude;
|
||||
|
||||
CreateWarehouseLocationParams({
|
||||
required this.name,
|
||||
required this.address,
|
||||
this.description,
|
||||
this.contactNumber,
|
||||
this.manager,
|
||||
this.latitude,
|
||||
this.longitude,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'name': name,
|
||||
'address': address,
|
||||
'description': description,
|
||||
'contact_number': contactNumber,
|
||||
'manager': manager,
|
||||
'latitude': latitude,
|
||||
'longitude': longitude,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import '../../../data/repositories/warehouse_location_repository.dart';
|
||||
import '../../../core/errors/failures.dart';
|
||||
import '../base_usecase.dart';
|
||||
|
||||
/// 창고 위치 삭제 UseCase
|
||||
@injectable
|
||||
class DeleteWarehouseLocationUseCase implements UseCase<bool, int> {
|
||||
final WarehouseLocationRepository repository;
|
||||
|
||||
DeleteWarehouseLocationUseCase(this.repository);
|
||||
|
||||
@override
|
||||
Future<Either<Failure, bool>> call(int id) async {
|
||||
try {
|
||||
// 비즈니스 로직: 사용 중인 창고는 삭제 불가
|
||||
// TODO: 창고에 장비가 있는지 확인하는 로직 추가 필요
|
||||
// final hasEquipment = await repository.checkWarehouseHasEquipment(id);
|
||||
// if (hasEquipment) {
|
||||
// return Left(ValidationFailure(message: '장비가 있는 창고는 삭제할 수 없습니다'));
|
||||
// }
|
||||
|
||||
await repository.deleteWarehouseLocation(id);
|
||||
return const Right(true);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(message: e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import '../../../data/models/warehouse/warehouse_dto.dart';
|
||||
import '../../../data/repositories/warehouse_location_repository.dart';
|
||||
import '../../../core/errors/failures.dart';
|
||||
import '../base_usecase.dart';
|
||||
|
||||
/// 창고 위치 상세 조회 UseCase
|
||||
@injectable
|
||||
class GetWarehouseLocationDetailUseCase implements UseCase<WarehouseLocationDto, int> {
|
||||
final WarehouseLocationRepository repository;
|
||||
|
||||
GetWarehouseLocationDetailUseCase(this.repository);
|
||||
|
||||
@override
|
||||
Future<Either<Failure, WarehouseLocationDto>> call(int id) async {
|
||||
try {
|
||||
final location = await repository.getWarehouseLocationDetail(id);
|
||||
return Right(location);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(message: e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import '../../../data/models/common/pagination_params.dart';
|
||||
import '../../../data/models/warehouse/warehouse_dto.dart';
|
||||
import '../../../data/repositories/warehouse_location_repository.dart';
|
||||
import '../../../core/errors/failures.dart';
|
||||
import '../base_usecase.dart';
|
||||
|
||||
/// 창고 위치 목록 조회 UseCase
|
||||
@injectable
|
||||
class GetWarehouseLocationsUseCase implements UseCase<WarehouseLocationListDto, GetWarehouseLocationsParams> {
|
||||
final WarehouseLocationRepository repository;
|
||||
|
||||
GetWarehouseLocationsUseCase(this.repository);
|
||||
|
||||
@override
|
||||
Future<Either<Failure, WarehouseLocationListDto>> call(GetWarehouseLocationsParams params) async {
|
||||
try {
|
||||
final locations = await repository.getWarehouseLocations(
|
||||
page: params.page,
|
||||
perPage: params.perPage,
|
||||
search: params.search,
|
||||
filters: params.filters,
|
||||
);
|
||||
return Right(locations);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(message: e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 창고 위치 목록 조회 파라미터
|
||||
class GetWarehouseLocationsParams {
|
||||
final int page;
|
||||
final int perPage;
|
||||
final String? search;
|
||||
final Map<String, dynamic>? filters;
|
||||
|
||||
GetWarehouseLocationsParams({
|
||||
this.page = 1,
|
||||
this.perPage = 20,
|
||||
this.search,
|
||||
this.filters,
|
||||
});
|
||||
|
||||
/// PaginationParams로부터 변환
|
||||
factory GetWarehouseLocationsParams.fromPaginationParams(PaginationParams params) {
|
||||
return GetWarehouseLocationsParams(
|
||||
page: params.page,
|
||||
perPage: params.perPage,
|
||||
search: params.search,
|
||||
filters: params.filters,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import '../../../data/models/warehouse/warehouse_dto.dart';
|
||||
import '../../../data/repositories/warehouse_location_repository.dart';
|
||||
import '../../../core/errors/failures.dart';
|
||||
import '../base_usecase.dart';
|
||||
|
||||
/// 창고 위치 수정 UseCase
|
||||
@injectable
|
||||
class UpdateWarehouseLocationUseCase implements UseCase<WarehouseLocationDto, UpdateWarehouseLocationParams> {
|
||||
final WarehouseLocationRepository repository;
|
||||
|
||||
UpdateWarehouseLocationUseCase(this.repository);
|
||||
|
||||
@override
|
||||
Future<Either<Failure, WarehouseLocationDto>> call(UpdateWarehouseLocationParams params) async {
|
||||
try {
|
||||
// 비즈니스 로직: 이름 유효성 검증
|
||||
if (params.name != null && params.name!.isEmpty) {
|
||||
return Left(ValidationFailure(message: '창고 위치 이름은 비어있을 수 없습니다'));
|
||||
}
|
||||
|
||||
// 비즈니스 로직: 주소 유효성 검증
|
||||
if (params.address != null && params.address!.isEmpty) {
|
||||
return Left(ValidationFailure(message: '창고 주소는 비어있을 수 없습니다'));
|
||||
}
|
||||
|
||||
// 비즈니스 로직: 연락처 형식 검증
|
||||
if (params.contactNumber != null && params.contactNumber!.isNotEmpty) {
|
||||
final phoneRegex = RegExp(r'^[\d\-\+\(\)\s]+$');
|
||||
if (!phoneRegex.hasMatch(params.contactNumber!)) {
|
||||
return Left(ValidationFailure(message: '올바른 연락처 형식이 아닙니다'));
|
||||
}
|
||||
}
|
||||
|
||||
// 비즈니스 로직: 좌표 유효성 검증
|
||||
if (params.latitude != null && (params.latitude! < -90 || params.latitude! > 90)) {
|
||||
return Left(ValidationFailure(message: '유효하지 않은 위도값입니다'));
|
||||
}
|
||||
if (params.longitude != null && (params.longitude! < -180 || params.longitude! > 180)) {
|
||||
return Left(ValidationFailure(message: '유효하지 않은 경도값입니다'));
|
||||
}
|
||||
|
||||
final location = await repository.updateWarehouseLocation(params.id, params.toMap());
|
||||
return Right(location);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(message: e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 창고 위치 수정 파라미터
|
||||
class UpdateWarehouseLocationParams {
|
||||
final int id;
|
||||
final String? name;
|
||||
final String? address;
|
||||
final String? description;
|
||||
final String? contactNumber;
|
||||
final String? manager;
|
||||
final double? latitude;
|
||||
final double? longitude;
|
||||
final bool? isActive;
|
||||
|
||||
UpdateWarehouseLocationParams({
|
||||
required this.id,
|
||||
this.name,
|
||||
this.address,
|
||||
this.description,
|
||||
this.contactNumber,
|
||||
this.manager,
|
||||
this.latitude,
|
||||
this.longitude,
|
||||
this.isActive,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
final Map<String, dynamic> data = {};
|
||||
if (name != null) data['name'] = name;
|
||||
if (address != null) data['address'] = address;
|
||||
if (description != null) data['description'] = description;
|
||||
if (contactNumber != null) data['contact_number'] = contactNumber;
|
||||
if (manager != null) data['manager'] = manager;
|
||||
if (latitude != null) data['latitude'] = latitude;
|
||||
if (longitude != null) data['longitude'] = longitude;
|
||||
if (isActive != null) data['is_active'] = isActive;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// WarehouseLocation UseCase barrel file
|
||||
export 'get_warehouse_locations_usecase.dart';
|
||||
export 'get_warehouse_location_detail_usecase.dart';
|
||||
export 'create_warehouse_location_usecase.dart';
|
||||
export 'update_warehouse_location_usecase.dart';
|
||||
export 'delete_warehouse_location_usecase.dart';
|
||||
Reference in New Issue
Block a user