refactor: 프로젝트 구조 개선 및 테스트 시스템 강화
Some checks failed
Flutter Test & Quality Check / Test on macos-latest (push) Has been cancelled
Flutter Test & Quality Check / Test on ubuntu-latest (push) Has been cancelled
Flutter Test & Quality Check / Build APK (push) Has been cancelled

주요 변경사항:
- CLAUDE.md: 프로젝트 규칙 v2.0으로 업데이트, 아키텍처 명확화
- 불필요한 문서 제거: NEXT_TASKS.md, TEST_PROGRESS.md, test_results 파일들
- 테스트 시스템 개선: 실제 API 테스트 스위트 추가 (15개 새 테스트 파일)
- License 관리: DTO 모델 개선, API 응답 처리 최적화
- 에러 처리: Interceptor 로직 강화, 상세 로깅 추가
- Company/User/Warehouse 테스트: 자동화 테스트 안정성 향상
- Phone Utils: 전화번호 포맷팅 로직 개선
- Overview Controller: 대시보드 데이터 로딩 최적화
- Analysis Options: Flutter 린트 규칙 추가

테스트 개선:
- company_real_api_test.dart: 실제 API 회사 관리 테스트
- equipment_in/out_real_api_test.dart: 장비 입출고 API 테스트
- license_real_api_test.dart: 라이선스 관리 API 테스트
- user_real_api_test.dart: 사용자 관리 API 테스트
- warehouse_location_real_api_test.dart: 창고 위치 API 테스트
- filter_sort_test.dart: 필터링/정렬 기능 테스트
- pagination_test.dart: 페이지네이션 테스트
- interactive_search_test.dart: 검색 기능 테스트
- overview_dashboard_test.dart: 대시보드 통합 테스트

코드 품질:
- 모든 서비스에 에러 처리 강화
- DTO 모델 null safety 개선
- 테스트 커버리지 확대
- 불필요한 로그 파일 제거로 리포지토리 정리

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
JiWoong Sul
2025-08-07 17:16:30 +09:00
parent fe05094392
commit c8dd1ff815
79 changed files with 12558 additions and 9761 deletions

View File

@@ -4,8 +4,11 @@ import 'package:superport/models/equipment_unified_model.dart';
import 'package:superport/models/company_model.dart';
import 'package:superport/services/equipment_service.dart';
import 'package:superport/services/mock_data_service.dart';
import 'package:superport/services/warehouse_service.dart';
import 'package:superport/services/company_service.dart';
import 'package:superport/utils/constants.dart';
import 'package:superport/core/errors/failures.dart';
import 'package:superport/core/utils/debug_logger.dart';
/// 장비 입고 폼 컨트롤러
///
@@ -13,6 +16,8 @@ import 'package:superport/core/errors/failures.dart';
class EquipmentInFormController extends ChangeNotifier {
final MockDataService dataService;
final EquipmentService _equipmentService = GetIt.instance<EquipmentService>();
final WarehouseService _warehouseService = GetIt.instance<WarehouseService>();
final CompanyService _companyService = GetIt.instance<CompanyService>();
final int? equipmentInId;
bool _isLoading = false;
@@ -55,6 +60,9 @@ class EquipmentInFormController extends ChangeNotifier {
List<String> categories = [];
List<String> subCategories = [];
List<String> subSubCategories = [];
// 창고 위치 전체 데이터 (이름-ID 매핑용)
Map<String, int> warehouseLocationMap = {};
// 편집 모드 여부
bool isEditMode = false;
@@ -108,19 +116,66 @@ class EquipmentInFormController extends ChangeNotifier {
}
// 입고지 목록 로드
void _loadWarehouseLocations() {
warehouseLocations =
dataService.getAllWarehouseLocations().map((e) => e.name).toList();
void _loadWarehouseLocations() async {
if (_useApi) {
try {
DebugLogger.log('입고지 목록 API 로드 시작', tag: 'EQUIPMENT_IN');
final locations = await _warehouseService.getWarehouseLocations();
warehouseLocations = locations.map((e) => e.name).toList();
// 이름-ID 매핑 저장
warehouseLocationMap = {for (var loc in locations) loc.name: loc.id};
DebugLogger.log('입고지 목록 로드 성공', tag: 'EQUIPMENT_IN', data: {
'count': warehouseLocations.length,
'locations': warehouseLocations,
'locationMap': warehouseLocationMap,
});
notifyListeners();
} catch (e) {
DebugLogger.logError('입고지 목록 로드 실패', error: e);
// 실패 시 Mock 데이터 사용
final mockLocations = dataService.getAllWarehouseLocations();
warehouseLocations = mockLocations.map((e) => e.name).toList();
warehouseLocationMap = {for (var loc in mockLocations) loc.name: loc.id};
notifyListeners();
}
} else {
final mockLocations = dataService.getAllWarehouseLocations();
warehouseLocations = mockLocations.map((e) => e.name).toList();
warehouseLocationMap = {for (var loc in mockLocations) loc.name: loc.id};
}
}
// 파트너사 목록 로드
void _loadPartnerCompanies() {
partnerCompanies =
dataService
.getAllCompanies()
.where((c) => c.companyTypes.contains(CompanyType.partner))
.map((c) => c.name)
.toList();
void _loadPartnerCompanies() async {
if (_useApi) {
try {
DebugLogger.log('파트너사 목록 API 로드 시작', tag: 'EQUIPMENT_IN');
final companies = await _companyService.getCompanies();
partnerCompanies = companies.map((c) => c.name).toList();
DebugLogger.log('파트너사 목록 로드 성공', tag: 'EQUIPMENT_IN', data: {
'count': partnerCompanies.length,
'companies': partnerCompanies,
});
notifyListeners();
} catch (e) {
DebugLogger.logError('파트너사 목록 로드 실패', error: e);
// 실패 시 Mock 데이터 사용
partnerCompanies =
dataService
.getAllCompanies()
.where((c) => c.companyTypes.contains(CompanyType.partner))
.map((c) => c.name)
.toList();
notifyListeners();
}
} else {
partnerCompanies =
dataService
.getAllCompanies()
.where((c) => c.companyTypes.contains(CompanyType.partner))
.map((c) => c.name)
.toList();
}
}
// 워런티 라이센스 목록 로드
@@ -304,30 +359,50 @@ class EquipmentInFormController extends ChangeNotifier {
await _equipmentService.updateEquipment(equipmentInId!, equipment);
} else {
// 생성 모드
// 1. 먼저 장비 생성
final createdEquipment = await _equipmentService.createEquipment(equipment);
// 2. 입고 처리 (warehouse location ID 필요)
int? warehouseLocationId;
if (warehouseLocation != null) {
// TODO: 창고 위치 ID 가져오기 - 현재는 목 데이터에서 찾기
try {
final warehouse = dataService.getAllWarehouseLocations().firstWhere(
(w) => w.name == warehouseLocation,
);
warehouseLocationId = warehouse.id;
} catch (e) {
// 창고를 찾을 수 없는 경우
warehouseLocationId = null;
try {
// 1. 먼저 장비 생성
DebugLogger.log('장비 생성 시작', tag: 'EQUIPMENT_IN', data: {
'manufacturer': manufacturer,
'name': name,
'serialNumber': serialNumber,
});
final createdEquipment = await _equipmentService.createEquipment(equipment);
DebugLogger.log('장비 생성 성공', tag: 'EQUIPMENT_IN', data: {
'equipmentId': createdEquipment.id,
});
// 2. 입고 처리 (warehouse location ID 필요)
int? warehouseLocationId;
if (warehouseLocation != null) {
// 저장된 매핑에서 ID 가져오기
warehouseLocationId = warehouseLocationMap[warehouseLocation];
if (warehouseLocationId == null) {
DebugLogger.logError('창고 위치 ID를 찾을 수 없음', error: 'Warehouse: $warehouseLocation');
}
}
DebugLogger.log('입고 처리 시작', tag: 'EQUIPMENT_IN', data: {
'equipmentId': createdEquipment.id,
'quantity': quantity,
'warehouseLocationId': warehouseLocationId,
});
await _equipmentService.equipmentIn(
equipmentId: createdEquipment.id!,
quantity: quantity,
warehouseLocationId: warehouseLocationId,
notes: remarkController.text.trim(),
);
DebugLogger.log('입고 처리 성공', tag: 'EQUIPMENT_IN');
} catch (e) {
DebugLogger.logError('장비 입고 처리 실패', error: e);
throw e; // 에러를 상위로 전파하여 적절한 에러 메시지 표시
}
await _equipmentService.equipmentIn(
equipmentId: createdEquipment.id!,
quantity: quantity,
warehouseLocationId: warehouseLocationId,
notes: remarkController.text.trim(),
);
}
} else {
// Mock 데이터 사용

View File

@@ -22,6 +22,7 @@ class EquipmentListController extends ChangeNotifier {
List<UnifiedEquipment> equipments = [];
String? selectedStatusFilter;
String searchKeyword = ''; // 검색어 추가
final Set<String> selectedEquipmentIds = {}; // 'id:status' 형식
bool _isLoading = false;
@@ -42,7 +43,7 @@ class EquipmentListController extends ChangeNotifier {
EquipmentListController({required this.dataService});
// 데이터 로드 및 상태 필터 적용
Future<void> loadData({bool isRefresh = false}) async {
Future<void> loadData({bool isRefresh = false, String? search}) async {
if (isRefresh) {
_currentPage = 1;
_hasMore = true;
@@ -69,6 +70,7 @@ class EquipmentListController extends ChangeNotifier {
page: _currentPage,
perPage: _perPage,
status: selectedStatusFilter != null ? EquipmentStatusConverter.clientToServer(selectedStatusFilter) : null,
search: search ?? searchKeyword,
);
DebugLogger.log('장비 목록 API 응답', tag: 'EQUIPMENT', data: {
@@ -137,6 +139,12 @@ class EquipmentListController extends ChangeNotifier {
selectedStatusFilter = status;
await loadData(isRefresh: true);
}
// 검색어 변경
Future<void> updateSearchKeyword(String keyword) async {
searchKeyword = keyword;
await loadData(isRefresh: true, search: keyword);
}
// 장비 선택/해제 (모든 상태 지원)
void selectEquipment(int? id, String status, bool? isSelected) {

View File

@@ -116,11 +116,12 @@ class _EquipmentListRedesignState extends State<EquipmentListRedesign> {
}
/// 검색 실행
void _onSearch() {
void _onSearch() async {
setState(() {
_appliedSearchKeyword = _searchController.text;
_currentPage = 1;
});
await _controller.updateSearchKeyword(_searchController.text);
}
/// 장비 선택/해제