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:
@@ -3,7 +3,6 @@ import 'package:get_it/get_it.dart';
|
||||
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';
|
||||
@@ -14,7 +13,6 @@ import 'package:superport/core/utils/debug_logger.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>();
|
||||
@@ -24,7 +22,7 @@ class EquipmentInFormController extends ChangeNotifier {
|
||||
bool _isLoading = false;
|
||||
String? _error;
|
||||
bool _isSaving = false;
|
||||
bool _useApi = true; // Feature flag
|
||||
// API만 사용
|
||||
|
||||
// Getters
|
||||
bool get isLoading => _isLoading;
|
||||
@@ -76,7 +74,7 @@ class EquipmentInFormController extends ChangeNotifier {
|
||||
|
||||
final TextEditingController remarkController = TextEditingController();
|
||||
|
||||
EquipmentInFormController({required this.dataService, this.equipmentInId}) {
|
||||
EquipmentInFormController({this.equipmentInId}) {
|
||||
isEditMode = equipmentInId != null;
|
||||
_loadManufacturers();
|
||||
_loadEquipmentNames();
|
||||
@@ -95,91 +93,71 @@ class EquipmentInFormController extends ChangeNotifier {
|
||||
await _loadEquipmentIn();
|
||||
}
|
||||
|
||||
// 제조사 목록 로드
|
||||
// 자동완성 데이터는 API를 통해 로드해야 하지만, 현재는 빈 목록으로 설정
|
||||
void _loadManufacturers() {
|
||||
manufacturers = dataService.getAllManufacturers();
|
||||
// TODO: API를 통해 제조사 목록 로드
|
||||
manufacturers = [];
|
||||
}
|
||||
|
||||
// 장비명 목록 로드
|
||||
void _loadEquipmentNames() {
|
||||
equipmentNames = dataService.getAllEquipmentNames();
|
||||
// TODO: API를 통해 장비명 목록 로드
|
||||
equipmentNames = [];
|
||||
}
|
||||
|
||||
// 카테고리 목록 로드
|
||||
void _loadCategories() {
|
||||
categories = dataService.getAllCategories();
|
||||
// TODO: API를 통해 카테고리 목록 로드
|
||||
categories = [];
|
||||
}
|
||||
|
||||
// 서브카테고리 목록 로드
|
||||
void _loadSubCategories() {
|
||||
subCategories = dataService.getAllSubCategories();
|
||||
// TODO: API를 통해 서브카테고리 목록 로드
|
||||
subCategories = [];
|
||||
}
|
||||
|
||||
// 서브서브카테고리 목록 로드
|
||||
void _loadSubSubCategories() {
|
||||
subSubCategories = dataService.getAllSubSubCategories();
|
||||
// TODO: API를 통해 서브서브카테고리 목록 로드
|
||||
subSubCategories = [];
|
||||
}
|
||||
|
||||
// 입고지 목록 로드
|
||||
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};
|
||||
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);
|
||||
// API 실패 시 빈 목록
|
||||
warehouseLocations = [];
|
||||
warehouseLocationMap = {};
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// 파트너사 목록 로드
|
||||
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();
|
||||
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);
|
||||
// API 실패 시 빈 목록
|
||||
partnerCompanies = [];
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,12 +176,11 @@ class EquipmentInFormController extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
if (_useApi) {
|
||||
// equipmentInId는 실제로 장비 ID임 (입고 ID가 아님)
|
||||
actualEquipmentId = equipmentInId;
|
||||
|
||||
try {
|
||||
// API에서 장비 정보 가져오기
|
||||
// equipmentInId는 실제로 장빔 ID임 (입고 ID가 아님)
|
||||
actualEquipmentId = equipmentInId;
|
||||
|
||||
try {
|
||||
// API에서 장비 정보 가져오기
|
||||
DebugLogger.log('장비 정보 로드 시작', tag: 'EQUIPMENT_IN', data: {
|
||||
'equipmentId': actualEquipmentId,
|
||||
});
|
||||
@@ -238,25 +215,8 @@ class EquipmentInFormController extends ChangeNotifier {
|
||||
|
||||
} catch (e) {
|
||||
DebugLogger.logError('장비 정보 로드 실패', error: e);
|
||||
// API 실패 시 Mock 데이터 시도
|
||||
final equipmentIn = dataService.getEquipmentInById(equipmentInId!);
|
||||
if (equipmentIn != null) {
|
||||
actualEquipmentId = equipmentIn.equipment.id;
|
||||
_loadFromMockData(equipmentIn);
|
||||
} else {
|
||||
throw ServerFailure(message: '장비 정보를 찾을 수 없습니다.');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Mock 데이터 사용
|
||||
final equipmentIn = dataService.getEquipmentInById(equipmentInId!);
|
||||
if (equipmentIn != null) {
|
||||
actualEquipmentId = equipmentIn.equipment.id;
|
||||
_loadFromMockData(equipmentIn);
|
||||
} else {
|
||||
throw ServerFailure(message: '장비 정보를 찾을 수 없습니다.');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
_error = '장비 정보를 불러오는데 실패했습니다: $e';
|
||||
DebugLogger.logError('장비 로드 실패', error: e);
|
||||
@@ -266,28 +226,6 @@ class EquipmentInFormController extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
void _loadFromMockData(EquipmentIn equipmentIn) {
|
||||
manufacturer = equipmentIn.equipment.manufacturer;
|
||||
name = equipmentIn.equipment.name;
|
||||
category = equipmentIn.equipment.category;
|
||||
subCategory = equipmentIn.equipment.subCategory;
|
||||
subSubCategory = equipmentIn.equipment.subSubCategory;
|
||||
serialNumber = equipmentIn.equipment.serialNumber ?? '';
|
||||
barcode = equipmentIn.equipment.barcode ?? '';
|
||||
quantity = equipmentIn.equipment.quantity;
|
||||
inDate = equipmentIn.inDate;
|
||||
hasSerialNumber = serialNumber.isNotEmpty;
|
||||
equipmentType = equipmentIn.type;
|
||||
warehouseLocation = equipmentIn.warehouseLocation;
|
||||
partnerCompany = equipmentIn.partnerCompany;
|
||||
remarkController.text = equipmentIn.remark ?? '';
|
||||
|
||||
// 워런티 정보 로드
|
||||
warrantyLicense = equipmentIn.partnerCompany;
|
||||
warrantyStartDate = equipmentIn.inDate;
|
||||
warrantyEndDate = equipmentIn.inDate.add(const Duration(days: 365));
|
||||
warrantyCode = null;
|
||||
}
|
||||
|
||||
// 워런티 기간 계산
|
||||
String getWarrantyPeriodSummary() {
|
||||
@@ -374,9 +312,8 @@ class EquipmentInFormController extends ChangeNotifier {
|
||||
// 워런티 코드 저장 필요시 여기에 추가
|
||||
);
|
||||
|
||||
if (_useApi) {
|
||||
// API 호출
|
||||
if (isEditMode) {
|
||||
// API 호출
|
||||
if (isEditMode) {
|
||||
// 수정 모드 - API로 장비 정보 업데이트
|
||||
if (actualEquipmentId == null) {
|
||||
throw ServerFailure(message: '장비 ID가 없습니다.');
|
||||
@@ -437,35 +374,6 @@ class EquipmentInFormController extends ChangeNotifier {
|
||||
throw e; // 에러를 상위로 전파하여 적절한 에러 메시지 표시
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Mock 데이터 사용
|
||||
if (isEditMode) {
|
||||
final equipmentIn = dataService.getEquipmentInById(equipmentInId!);
|
||||
if (equipmentIn != null) {
|
||||
final updatedEquipmentIn = EquipmentIn(
|
||||
id: equipmentIn.id,
|
||||
equipment: equipment,
|
||||
inDate: inDate,
|
||||
status: equipmentIn.status,
|
||||
type: equipmentType,
|
||||
warehouseLocation: warehouseLocation,
|
||||
partnerCompany: partnerCompany,
|
||||
remark: remarkController.text.trim(),
|
||||
);
|
||||
dataService.updateEquipmentIn(updatedEquipmentIn);
|
||||
}
|
||||
} else {
|
||||
final newEquipmentIn = EquipmentIn(
|
||||
equipment: equipment,
|
||||
inDate: inDate,
|
||||
type: equipmentType,
|
||||
warehouseLocation: warehouseLocation,
|
||||
partnerCompany: partnerCompany,
|
||||
remark: remarkController.text.trim(),
|
||||
);
|
||||
dataService.addEquipmentIn(newEquipmentIn);
|
||||
}
|
||||
}
|
||||
|
||||
// 저장 후 리스트 재로딩 (중복 방지 및 최신화)
|
||||
_loadManufacturers();
|
||||
@@ -498,11 +406,7 @@ class EquipmentInFormController extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// API 사용 여부 토글 (테스트용)
|
||||
void toggleApiUsage() {
|
||||
_useApi = !_useApi;
|
||||
notifyListeners();
|
||||
}
|
||||
// API만 사용하므로 토글 기능 제거
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:superport/models/equipment_unified_model.dart';
|
||||
import 'package:superport/services/equipment_service.dart';
|
||||
import 'package:superport/utils/constants.dart';
|
||||
import 'package:superport/core/errors/failures.dart';
|
||||
import 'package:superport/models/equipment_unified_model.dart' as legacy;
|
||||
import 'package:superport/core/utils/debug_logger.dart';
|
||||
|
||||
// companyTypeToString 함수 import
|
||||
import 'package:superport/utils/constants.dart'
|
||||
show companyTypeToString, CompanyType;
|
||||
import 'package:superport/models/company_model.dart';
|
||||
import 'package:superport/models/address_model.dart';
|
||||
import 'package:superport/core/utils/equipment_status_converter.dart';
|
||||
|
||||
// 장비 목록 화면의 상태 및 비즈니스 로직을 담당하는 컨트롤러
|
||||
class EquipmentListController extends ChangeNotifier {
|
||||
final EquipmentService _equipmentService = GetIt.instance<EquipmentService>();
|
||||
|
||||
List<UnifiedEquipment> equipments = [];
|
||||
String? selectedStatusFilter;
|
||||
String searchKeyword = ''; // 검색어 추가
|
||||
final Set<String> selectedEquipmentIds = {}; // 'id:status' 형식
|
||||
|
||||
bool _isLoading = false;
|
||||
String? _error;
|
||||
// API만 사용
|
||||
|
||||
// 페이지네이션
|
||||
int _currentPage = 1;
|
||||
final int _perPage = 20;
|
||||
bool _hasMore = true;
|
||||
|
||||
// Getters
|
||||
bool get isLoading => _isLoading;
|
||||
String? get error => _error;
|
||||
bool get hasMore => _hasMore;
|
||||
int get currentPage => _currentPage;
|
||||
|
||||
EquipmentListController();
|
||||
|
||||
// 데이터 로드 및 상태 필터 적용
|
||||
Future<void> loadData({bool isRefresh = false, String? search}) async {
|
||||
if (_isLoading) return;
|
||||
|
||||
_isLoading = true;
|
||||
_error = null;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
// API 호출 - 전체 데이터 로드
|
||||
print('╔══════════════════════════════════════════════════════════');
|
||||
print('║ 📦 장비 목록 API 호출 시작');
|
||||
print('║ • 상태 필터: ${selectedStatusFilter ?? "전체"}');
|
||||
print('║ • 검색어: ${search ?? searchKeyword}');
|
||||
print('╚══════════════════════════════════════════════════════════');
|
||||
|
||||
// 전체 데이터를 가져오기 위해 큰 perPage 값 사용
|
||||
final apiEquipmentDtos = await _equipmentService.getEquipmentsWithStatus(
|
||||
page: 1,
|
||||
perPage: 1000, // 충분히 큰 값으로 전체 데이터 로드
|
||||
status: selectedStatusFilter != null ? EquipmentStatusConverter.clientToServer(selectedStatusFilter) : null,
|
||||
search: search ?? searchKeyword,
|
||||
);
|
||||
|
||||
print('╔══════════════════════════════════════════════════════════');
|
||||
print('║ 📊 장비 목록 로드 완료');
|
||||
print('║ ▶ 총 장비 수: ${apiEquipmentDtos.length}개');
|
||||
print('╟──────────────────────────────────────────────────────────');
|
||||
|
||||
// 상태별 통계
|
||||
Map<String, int> statusCount = {};
|
||||
for (final dto in apiEquipmentDtos) {
|
||||
final clientStatus = EquipmentStatusConverter.serverToClient(dto.status);
|
||||
statusCount[clientStatus] = (statusCount[clientStatus] ?? 0) + 1;
|
||||
}
|
||||
|
||||
statusCount.forEach((status, count) {
|
||||
print('║ • $status: $count개');
|
||||
});
|
||||
|
||||
print('╟──────────────────────────────────────────────────────────');
|
||||
print('║ 📑 전체 데이터 로드 완료');
|
||||
print('║ • View에서 페이지네이션 처리 예정');
|
||||
print('╚══════════════════════════════════════════════════════════');
|
||||
|
||||
// DTO를 UnifiedEquipment로 변환 (status 정보 포함)
|
||||
final List<UnifiedEquipment> unifiedEquipments = apiEquipmentDtos.map((dto) {
|
||||
final equipment = Equipment(
|
||||
id: dto.id,
|
||||
manufacturer: dto.manufacturer,
|
||||
name: dto.modelName ?? dto.equipmentNumber,
|
||||
category: '', // 세부 정보는 상세 조회에서 가져와야 함
|
||||
subCategory: '',
|
||||
subSubCategory: '',
|
||||
serialNumber: dto.serialNumber,
|
||||
quantity: 1,
|
||||
inDate: dto.createdAt,
|
||||
);
|
||||
|
||||
return UnifiedEquipment(
|
||||
id: dto.id,
|
||||
equipment: equipment,
|
||||
date: dto.createdAt,
|
||||
status: EquipmentStatusConverter.serverToClient(dto.status), // 서버 status를 클라이언트 status로 변환
|
||||
);
|
||||
}).toList();
|
||||
|
||||
equipments = unifiedEquipments;
|
||||
_hasMore = false; // 전체 데이터를 로드했으므로 더 이상 로드할 필요 없음
|
||||
|
||||
selectedEquipmentIds.clear();
|
||||
} on Failure catch (e) {
|
||||
_error = e.message;
|
||||
} catch (e) {
|
||||
_error = 'An unexpected error occurred: $e';
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// 상태 필터 변경
|
||||
Future<void> changeStatusFilter(String? status) async {
|
||||
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) {
|
||||
if (id == null || isSelected == null) return;
|
||||
final key = '$id:$status';
|
||||
if (isSelected) {
|
||||
selectedEquipmentIds.add(key);
|
||||
} else {
|
||||
selectedEquipmentIds.remove(key);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// 선택된 입고 장비 수 반환
|
||||
int getSelectedInStockCount() {
|
||||
int count = 0;
|
||||
for (final idStatusPair in selectedEquipmentIds) {
|
||||
final parts = idStatusPair.split(':');
|
||||
if (parts.length == 2 && parts[1] == EquipmentStatus.in_) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// 선택된 전체 장비 수 반환
|
||||
int getSelectedEquipmentCount() {
|
||||
return selectedEquipmentIds.length;
|
||||
}
|
||||
|
||||
// 선택된 특정 상태의 장비 수 반환
|
||||
int getSelectedEquipmentCountByStatus(String status) {
|
||||
int count = 0;
|
||||
for (final idStatusPair in selectedEquipmentIds) {
|
||||
final parts = idStatusPair.split(':');
|
||||
if (parts.length == 2 && parts[1] == status) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// 선택된 장비들의 UnifiedEquipment 객체 목록 반환
|
||||
List<UnifiedEquipment> getSelectedEquipments() {
|
||||
List<UnifiedEquipment> selected = [];
|
||||
for (final idStatusPair in selectedEquipmentIds) {
|
||||
final parts = idStatusPair.split(':');
|
||||
if (parts.length == 2) {
|
||||
final id = int.tryParse(parts[0]);
|
||||
if (id != null) {
|
||||
final equipment = equipments.firstWhere(
|
||||
(e) => e.id == id && e.status == parts[1],
|
||||
orElse: () => null as UnifiedEquipment,
|
||||
);
|
||||
if (equipment != null) {
|
||||
selected.add(equipment);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
// 선택된 특정 상태의 장비들의 UnifiedEquipment 객체 목록 반환
|
||||
List<UnifiedEquipment> getSelectedEquipmentsByStatus(String status) {
|
||||
List<UnifiedEquipment> selected = [];
|
||||
for (final idStatusPair in selectedEquipmentIds) {
|
||||
final parts = idStatusPair.split(':');
|
||||
if (parts.length == 2 && parts[1] == status) {
|
||||
final id = int.tryParse(parts[0]);
|
||||
if (id != null) {
|
||||
final equipment = equipments.firstWhere(
|
||||
(e) => e.id == id && e.status == status,
|
||||
orElse: () => null as UnifiedEquipment,
|
||||
);
|
||||
if (equipment != null) {
|
||||
selected.add(equipment);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
// 선택된 장비들의 요약 정보를 Map 형태로 반환 (출고/대여/폐기 폼에서 사용)
|
||||
List<Map<String, dynamic>> getSelectedEquipmentsSummary() {
|
||||
List<Map<String, dynamic>> summaryList = [];
|
||||
List<UnifiedEquipment> selectedEquipmentsInStock =
|
||||
getSelectedEquipmentsByStatus(EquipmentStatus.in_);
|
||||
|
||||
for (final equipment in selectedEquipmentsInStock) {
|
||||
summaryList.add({
|
||||
'equipment': equipment.equipment,
|
||||
'equipmentInId': equipment.id,
|
||||
'status': equipment.status,
|
||||
});
|
||||
}
|
||||
|
||||
return summaryList;
|
||||
}
|
||||
|
||||
// 출고 정보(회사, 담당자, 라이센스 등) 반환
|
||||
// 출고 정보는 API를 통해 번별로 조회해야 하므로 별도 서비스로 분리 예정
|
||||
String getOutEquipmentInfo(int equipmentId, String infoType) {
|
||||
// TODO: API로 출고 정보 조회 구현
|
||||
return '-';
|
||||
}
|
||||
|
||||
// 장비 삭제
|
||||
Future<bool> deleteEquipment(UnifiedEquipment equipment) async {
|
||||
try {
|
||||
// API를 통한 삭제
|
||||
if (equipment.equipment.id != null) {
|
||||
await _equipmentService.deleteEquipment(equipment.equipment.id!);
|
||||
} else {
|
||||
throw Exception('Equipment ID is null');
|
||||
}
|
||||
|
||||
// 로컬 리스트에서도 제거
|
||||
equipments.removeWhere((e) => e.id == equipment.id && e.status == equipment.status);
|
||||
notifyListeners();
|
||||
|
||||
return true;
|
||||
} on Failure catch (e) {
|
||||
_error = e.message;
|
||||
notifyListeners();
|
||||
return false;
|
||||
} catch (e) {
|
||||
_error = 'Failed to delete equipment: $e';
|
||||
notifyListeners();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// API만 사용하므로 토글 기능 제거
|
||||
|
||||
// 에러 처리
|
||||
void clearError() {
|
||||
_error = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,338 +1,315 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:superport/models/equipment_unified_model.dart';
|
||||
import 'package:superport/services/equipment_service.dart';
|
||||
import 'package:superport/services/mock_data_service.dart';
|
||||
import 'package:superport/utils/constants.dart';
|
||||
import 'package:superport/core/errors/failures.dart';
|
||||
import 'package:superport/models/equipment_unified_model.dart' as legacy;
|
||||
import 'package:superport/core/utils/debug_logger.dart';
|
||||
|
||||
// companyTypeToString 함수 import
|
||||
import 'package:superport/utils/constants.dart'
|
||||
show companyTypeToString, CompanyType;
|
||||
import 'package:superport/core/utils/error_handler.dart';
|
||||
import 'package:superport/core/controllers/base_list_controller.dart';
|
||||
import 'package:superport/core/utils/equipment_status_converter.dart';
|
||||
import 'package:superport/models/company_model.dart';
|
||||
import 'package:superport/models/address_model.dart';
|
||||
import 'package:superport/core/utils/equipment_status_converter.dart';
|
||||
import 'package:superport/data/models/common/pagination_params.dart';
|
||||
|
||||
// 장비 목록 화면의 상태 및 비즈니스 로직을 담당하는 컨트롤러
|
||||
class EquipmentListController extends ChangeNotifier {
|
||||
final MockDataService dataService;
|
||||
final EquipmentService _equipmentService = GetIt.instance<EquipmentService>();
|
||||
/// 장비 목록 화면의 상태 및 비즈니스 로직을 담당하는 컨트롤러 (리팩토링 버전)
|
||||
/// BaseListController를 상속받아 공통 기능을 재사용
|
||||
class EquipmentListController extends BaseListController<UnifiedEquipment> {
|
||||
late final EquipmentService _equipmentService;
|
||||
|
||||
List<UnifiedEquipment> equipments = [];
|
||||
String? selectedStatusFilter;
|
||||
String searchKeyword = ''; // 검색어 추가
|
||||
// 추가 상태 관리
|
||||
final Set<String> selectedEquipmentIds = {}; // 'id:status' 형식
|
||||
|
||||
bool _isLoading = false;
|
||||
String? _error;
|
||||
bool _useApi = true; // Feature flag for API usage
|
||||
|
||||
// 페이지네이션
|
||||
int _currentPage = 1;
|
||||
final int _perPage = 20;
|
||||
bool _hasMore = true;
|
||||
// 필터
|
||||
String? _statusFilter;
|
||||
String? _categoryFilter;
|
||||
int? _companyIdFilter;
|
||||
String? _selectedStatusFilter;
|
||||
|
||||
// Getters
|
||||
bool get isLoading => _isLoading;
|
||||
String? get error => _error;
|
||||
bool get hasMore => _hasMore;
|
||||
int get currentPage => _currentPage;
|
||||
|
||||
EquipmentListController({required this.dataService});
|
||||
|
||||
// 데이터 로드 및 상태 필터 적용
|
||||
Future<void> loadData({bool isRefresh = false, String? search}) async {
|
||||
if (_isLoading) return;
|
||||
|
||||
_isLoading = true;
|
||||
_error = null;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
if (_useApi) {
|
||||
// API 호출 - 전체 데이터 로드
|
||||
print('╔══════════════════════════════════════════════════════════');
|
||||
print('║ 📦 장비 목록 API 호출 시작');
|
||||
print('║ • 상태 필터: ${selectedStatusFilter ?? "전체"}');
|
||||
print('║ • 검색어: ${search ?? searchKeyword}');
|
||||
print('╚══════════════════════════════════════════════════════════');
|
||||
|
||||
// 전체 데이터를 가져오기 위해 큰 perPage 값 사용
|
||||
final apiEquipmentDtos = await _equipmentService.getEquipmentsWithStatus(
|
||||
page: 1,
|
||||
perPage: 1000, // 충분히 큰 값으로 전체 데이터 로드
|
||||
status: selectedStatusFilter != null ? EquipmentStatusConverter.clientToServer(selectedStatusFilter) : null,
|
||||
search: search ?? searchKeyword,
|
||||
);
|
||||
|
||||
print('╔══════════════════════════════════════════════════════════');
|
||||
print('║ 📊 장비 목록 로드 완료');
|
||||
print('║ ▶ 총 장비 수: ${apiEquipmentDtos.length}개');
|
||||
print('╟──────────────────────────────────────────────────────────');
|
||||
|
||||
// 상태별 통계
|
||||
Map<String, int> statusCount = {};
|
||||
for (final dto in apiEquipmentDtos) {
|
||||
final clientStatus = EquipmentStatusConverter.serverToClient(dto.status);
|
||||
statusCount[clientStatus] = (statusCount[clientStatus] ?? 0) + 1;
|
||||
}
|
||||
|
||||
statusCount.forEach((status, count) {
|
||||
print('║ • $status: $count개');
|
||||
});
|
||||
|
||||
print('╟──────────────────────────────────────────────────────────');
|
||||
print('║ 📑 전체 데이터 로드 완료');
|
||||
print('║ • View에서 페이지네이션 처리 예정');
|
||||
print('╚══════════════════════════════════════════════════════════');
|
||||
|
||||
// DTO를 UnifiedEquipment로 변환 (status 정보 포함)
|
||||
final List<UnifiedEquipment> unifiedEquipments = apiEquipmentDtos.map((dto) {
|
||||
final equipment = Equipment(
|
||||
id: dto.id,
|
||||
manufacturer: dto.manufacturer,
|
||||
name: dto.modelName ?? dto.equipmentNumber,
|
||||
category: '', // 세부 정보는 상세 조회에서 가져와야 함
|
||||
subCategory: '',
|
||||
subSubCategory: '',
|
||||
serialNumber: dto.serialNumber,
|
||||
quantity: 1,
|
||||
inDate: dto.createdAt,
|
||||
);
|
||||
|
||||
return UnifiedEquipment(
|
||||
id: dto.id,
|
||||
equipment: equipment,
|
||||
date: dto.createdAt,
|
||||
status: EquipmentStatusConverter.serverToClient(dto.status), // 서버 status를 클라이언트 status로 변환
|
||||
);
|
||||
}).toList();
|
||||
|
||||
equipments = unifiedEquipments;
|
||||
_hasMore = false; // 전체 데이터를 로드했으므로 더 이상 로드할 필요 없음
|
||||
} else {
|
||||
// Mock 데이터 사용
|
||||
equipments = dataService.getAllEquipments();
|
||||
if (selectedStatusFilter != null) {
|
||||
equipments =
|
||||
equipments.where((e) => e.status == selectedStatusFilter).toList();
|
||||
}
|
||||
_hasMore = false;
|
||||
}
|
||||
|
||||
selectedEquipmentIds.clear();
|
||||
} on Failure catch (e) {
|
||||
_error = e.message;
|
||||
} catch (e) {
|
||||
_error = 'An unexpected error occurred: $e';
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// 상태 필터 변경
|
||||
Future<void> changeStatusFilter(String? status) async {
|
||||
selectedStatusFilter = status;
|
||||
await loadData(isRefresh: true);
|
||||
}
|
||||
List<UnifiedEquipment> get equipments => items;
|
||||
String? get statusFilter => _statusFilter;
|
||||
String? get categoryFilter => _categoryFilter;
|
||||
int? get companyIdFilter => _companyIdFilter;
|
||||
String? get selectedStatusFilter => _selectedStatusFilter;
|
||||
|
||||
// 검색어 변경
|
||||
Future<void> updateSearchKeyword(String keyword) async {
|
||||
searchKeyword = keyword;
|
||||
await loadData(isRefresh: true, search: keyword);
|
||||
// Setters
|
||||
set selectedStatusFilter(String? value) {
|
||||
_selectedStatusFilter = value;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// 장비 선택/해제 (모든 상태 지원)
|
||||
void selectEquipment(int? id, String status, bool? isSelected) {
|
||||
if (id == null || isSelected == null) return;
|
||||
final key = '$id:$status';
|
||||
if (isSelected) {
|
||||
selectedEquipmentIds.add(key);
|
||||
EquipmentListController() {
|
||||
if (GetIt.instance.isRegistered<EquipmentService>()) {
|
||||
_equipmentService = GetIt.instance<EquipmentService>();
|
||||
} else {
|
||||
selectedEquipmentIds.remove(key);
|
||||
throw Exception('EquipmentService not registered in GetIt');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PagedResult<UnifiedEquipment>> fetchData({
|
||||
required PaginationParams params,
|
||||
Map<String, dynamic>? additionalFilters,
|
||||
}) async {
|
||||
// API 호출
|
||||
final apiEquipmentDtos = await ErrorHandler.handleApiCall(
|
||||
() => _equipmentService.getEquipmentsWithStatus(
|
||||
page: params.page,
|
||||
perPage: params.perPage,
|
||||
status: _statusFilter != null ?
|
||||
EquipmentStatusConverter.clientToServer(_statusFilter) : null,
|
||||
search: params.search,
|
||||
companyId: _companyIdFilter,
|
||||
),
|
||||
onError: (failure) {
|
||||
throw failure;
|
||||
},
|
||||
);
|
||||
|
||||
if (apiEquipmentDtos == null) {
|
||||
return PagedResult(
|
||||
items: [],
|
||||
meta: PaginationMeta(
|
||||
currentPage: params.page,
|
||||
perPage: params.perPage,
|
||||
total: 0,
|
||||
totalPages: 0,
|
||||
hasNext: false,
|
||||
hasPrevious: false,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// DTO를 UnifiedEquipment로 변환
|
||||
final items = apiEquipmentDtos.map((dto) {
|
||||
final equipment = Equipment(
|
||||
id: dto.id,
|
||||
manufacturer: dto.manufacturer ?? 'Unknown',
|
||||
name: dto.modelName ?? dto.equipmentNumber ?? 'Unknown',
|
||||
category: 'Equipment', // 임시 카테고리
|
||||
subCategory: 'General', // 임시 서브카테고리
|
||||
subSubCategory: 'Standard', // 임시 서브서브카테고리
|
||||
serialNumber: dto.serialNumber,
|
||||
quantity: 1, // 기본 수량
|
||||
);
|
||||
|
||||
// 간단한 Company 정보 생성 (사용하지 않으므로 제거)
|
||||
// final company = dto.companyName != null ? ... : null;
|
||||
|
||||
return UnifiedEquipment(
|
||||
id: dto.id,
|
||||
equipment: equipment,
|
||||
date: dto.createdAt ?? DateTime.now(),
|
||||
status: EquipmentStatusConverter.serverToClient(dto.status),
|
||||
notes: null, // EquipmentListDto에 remark 필드 없음
|
||||
);
|
||||
}).toList();
|
||||
|
||||
// 임시로 메타데이터 생성 (추후 API에서 실제 메타데이터 반환 시 수정)
|
||||
final meta = PaginationMeta(
|
||||
currentPage: params.page,
|
||||
perPage: params.perPage,
|
||||
total: items.length < params.perPage ?
|
||||
(params.page - 1) * params.perPage + items.length :
|
||||
params.page * params.perPage + 1,
|
||||
totalPages: items.length < params.perPage ? params.page : params.page + 1,
|
||||
hasNext: items.length >= params.perPage,
|
||||
hasPrevious: params.page > 1,
|
||||
);
|
||||
|
||||
return PagedResult(items: items, meta: meta);
|
||||
}
|
||||
|
||||
@override
|
||||
bool filterItem(UnifiedEquipment item, String query) {
|
||||
final q = query.toLowerCase();
|
||||
return (item.equipment.name.toLowerCase().contains(q)) ||
|
||||
(item.equipment.serialNumber?.toLowerCase().contains(q) ?? false) ||
|
||||
(item.equipment.manufacturer.toLowerCase().contains(q)) ||
|
||||
(item.notes?.toLowerCase().contains(q) ?? false) ||
|
||||
(item.status.toLowerCase().contains(q));
|
||||
}
|
||||
|
||||
/// 장비 선택/선택 해제
|
||||
void toggleSelection(UnifiedEquipment equipment) {
|
||||
final equipmentKey = '${equipment.equipment.id}:${equipment.status}';
|
||||
if (selectedEquipmentIds.contains(equipmentKey)) {
|
||||
selectedEquipmentIds.remove(equipmentKey);
|
||||
} else {
|
||||
selectedEquipmentIds.add(equipmentKey);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// 선택된 입고 장비 수 반환
|
||||
int getSelectedInStockCount() {
|
||||
int count = 0;
|
||||
for (final idStatusPair in selectedEquipmentIds) {
|
||||
final parts = idStatusPair.split(':');
|
||||
if (parts.length == 2 && parts[1] == EquipmentStatus.in_) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
/// 모든 선택 해제
|
||||
void clearSelection() {
|
||||
selectedEquipmentIds.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// 선택된 전체 장비 수 반환
|
||||
/// 선택된 장비 정보 가져오기
|
||||
Map<String, List<UnifiedEquipment>> getSelectedEquipmentsByStatus() {
|
||||
final Map<String, List<UnifiedEquipment>> groupedEquipments = {};
|
||||
|
||||
for (final equipment in items) {
|
||||
final equipmentKey = '${equipment.equipment.id}:${equipment.status}';
|
||||
if (selectedEquipmentIds.contains(equipmentKey)) {
|
||||
if (!groupedEquipments.containsKey(equipment.status)) {
|
||||
groupedEquipments[equipment.status] = [];
|
||||
}
|
||||
groupedEquipments[equipment.status]!.add(equipment);
|
||||
}
|
||||
}
|
||||
|
||||
return groupedEquipments;
|
||||
}
|
||||
|
||||
/// 필터 설정
|
||||
void setFilters({
|
||||
String? status,
|
||||
String? category,
|
||||
int? companyId,
|
||||
}) {
|
||||
_statusFilter = status;
|
||||
_categoryFilter = category;
|
||||
_companyIdFilter = companyId;
|
||||
loadData(isRefresh: true);
|
||||
}
|
||||
|
||||
/// 상태 필터 변경
|
||||
void filterByStatus(String? status) {
|
||||
_statusFilter = status;
|
||||
loadData(isRefresh: true);
|
||||
}
|
||||
|
||||
/// 카테고리 필터 변경
|
||||
void filterByCategory(String? category) {
|
||||
_categoryFilter = category;
|
||||
loadData(isRefresh: true);
|
||||
}
|
||||
|
||||
/// 회사 필터 변경
|
||||
void filterByCompany(int? companyId) {
|
||||
_companyIdFilter = companyId;
|
||||
loadData(isRefresh: true);
|
||||
}
|
||||
|
||||
/// 필터 초기화
|
||||
void clearFilters() {
|
||||
_statusFilter = null;
|
||||
_categoryFilter = null;
|
||||
_companyIdFilter = null;
|
||||
search('');
|
||||
loadData(isRefresh: true);
|
||||
}
|
||||
|
||||
/// 장비 삭제
|
||||
Future<void> deleteEquipment(int id, String status) async {
|
||||
await ErrorHandler.handleApiCall<void>(
|
||||
() => _equipmentService.deleteEquipment(id),
|
||||
);
|
||||
|
||||
removeItemLocally((e) => e.equipment.id == id && e.status == status);
|
||||
|
||||
// 선택 목록에서도 제거
|
||||
final equipmentKey = '$id:$status';
|
||||
selectedEquipmentIds.remove(equipmentKey);
|
||||
}
|
||||
|
||||
/// 선택된 장비 일괄 삭제
|
||||
Future<void> deleteSelectedEquipments() async {
|
||||
final selectedGroups = getSelectedEquipmentsByStatus();
|
||||
|
||||
for (final entry in selectedGroups.entries) {
|
||||
for (final equipment in entry.value) {
|
||||
if (equipment.equipment.id != null) {
|
||||
await deleteEquipment(equipment.equipment.id!, equipment.status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clearSelection();
|
||||
}
|
||||
|
||||
/// 장비 상태 변경 (임시 구현 - API가 지원하지 않음)
|
||||
Future<void> updateEquipmentStatus(int id, String currentStatus, String newStatus) async {
|
||||
debugPrint('장비 상태 변경: $id, $currentStatus -> $newStatus');
|
||||
// TODO: 실제 API가 장비 상태 변경을 지원할 때 구현
|
||||
// 현재는 새로고침만 수행
|
||||
await refresh();
|
||||
}
|
||||
|
||||
/// 장비 정보 수정
|
||||
Future<void> updateEquipment(int id, UnifiedEquipment equipment) async {
|
||||
await ErrorHandler.handleApiCall<void>(
|
||||
() => _equipmentService.updateEquipment(id, equipment.equipment),
|
||||
onError: (failure) {
|
||||
throw failure;
|
||||
},
|
||||
);
|
||||
|
||||
updateItemLocally(equipment, (e) =>
|
||||
e.equipment.id == equipment.equipment.id &&
|
||||
e.status == equipment.status
|
||||
);
|
||||
}
|
||||
|
||||
/// 상태 필터 변경
|
||||
void changeStatusFilter(String? status) {
|
||||
_selectedStatusFilter = status;
|
||||
_statusFilter = status;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 검색 키워드 업데이트
|
||||
void updateSearchKeyword(String keyword) {
|
||||
search(keyword); // BaseListController의 search 메서드 사용
|
||||
}
|
||||
|
||||
/// 장비 선택 (토글 선택을 위한 별칭)
|
||||
void selectEquipment(UnifiedEquipment equipment) {
|
||||
toggleSelection(equipment);
|
||||
}
|
||||
|
||||
/// 선택된 입고 상태 장비 개수
|
||||
int getSelectedInStockCount() {
|
||||
return selectedEquipmentIds
|
||||
.where((key) => key.endsWith(':입고'))
|
||||
.length;
|
||||
}
|
||||
|
||||
/// 선택된 장비들 가져오기
|
||||
List<UnifiedEquipment> getSelectedEquipments() {
|
||||
return items.where((equipment) {
|
||||
final equipmentKey = '${equipment.equipment.id}:${equipment.status}';
|
||||
return selectedEquipmentIds.contains(equipmentKey);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
/// 선택된 장비들 요약 정보
|
||||
String getSelectedEquipmentsSummary() {
|
||||
final selectedEquipments = getSelectedEquipments();
|
||||
if (selectedEquipments.isEmpty) return '선택된 장비가 없습니다';
|
||||
|
||||
final Map<String, int> statusCounts = {};
|
||||
for (final equipment in selectedEquipments) {
|
||||
statusCounts[equipment.status] = (statusCounts[equipment.status] ?? 0) + 1;
|
||||
}
|
||||
|
||||
final summaryParts = statusCounts.entries
|
||||
.map((entry) => '${entry.key}: ${entry.value}개')
|
||||
.toList();
|
||||
|
||||
return summaryParts.join(', ');
|
||||
}
|
||||
|
||||
/// 선택된 장비 총 개수
|
||||
int getSelectedEquipmentCount() {
|
||||
return selectedEquipmentIds.length;
|
||||
}
|
||||
|
||||
// 선택된 특정 상태의 장비 수 반환
|
||||
/// 특정 상태의 선택된 장비 개수
|
||||
int getSelectedEquipmentCountByStatus(String status) {
|
||||
int count = 0;
|
||||
for (final idStatusPair in selectedEquipmentIds) {
|
||||
final parts = idStatusPair.split(':');
|
||||
if (parts.length == 2 && parts[1] == status) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
return selectedEquipmentIds
|
||||
.where((key) => key.endsWith(':$status'))
|
||||
.length;
|
||||
}
|
||||
|
||||
// 선택된 장비들의 UnifiedEquipment 객체 목록 반환
|
||||
List<UnifiedEquipment> getSelectedEquipments() {
|
||||
List<UnifiedEquipment> selected = [];
|
||||
for (final idStatusPair in selectedEquipmentIds) {
|
||||
final parts = idStatusPair.split(':');
|
||||
if (parts.length == 2) {
|
||||
final id = int.tryParse(parts[0]);
|
||||
if (id != null) {
|
||||
final equipment = equipments.firstWhere(
|
||||
(e) => e.id == id && e.status == parts[1],
|
||||
orElse: () => null as UnifiedEquipment,
|
||||
);
|
||||
if (equipment != null) {
|
||||
selected.add(equipment);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
// 선택된 특정 상태의 장비들의 UnifiedEquipment 객체 목록 반환
|
||||
List<UnifiedEquipment> getSelectedEquipmentsByStatus(String status) {
|
||||
List<UnifiedEquipment> selected = [];
|
||||
for (final idStatusPair in selectedEquipmentIds) {
|
||||
final parts = idStatusPair.split(':');
|
||||
if (parts.length == 2 && parts[1] == status) {
|
||||
final id = int.tryParse(parts[0]);
|
||||
if (id != null) {
|
||||
final equipment = equipments.firstWhere(
|
||||
(e) => e.id == id && e.status == status,
|
||||
orElse: () => null as UnifiedEquipment,
|
||||
);
|
||||
if (equipment != null) {
|
||||
selected.add(equipment);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
// 선택된 장비들의 요약 정보를 Map 형태로 반환 (출고/대여/폐기 폼에서 사용)
|
||||
List<Map<String, dynamic>> getSelectedEquipmentsSummary() {
|
||||
List<Map<String, dynamic>> summaryList = [];
|
||||
List<UnifiedEquipment> selectedEquipmentsInStock =
|
||||
getSelectedEquipmentsByStatus(EquipmentStatus.in_);
|
||||
|
||||
for (final equipment in selectedEquipmentsInStock) {
|
||||
summaryList.add({
|
||||
'equipment': equipment.equipment,
|
||||
'equipmentInId': equipment.id,
|
||||
'status': equipment.status,
|
||||
});
|
||||
}
|
||||
|
||||
return summaryList;
|
||||
}
|
||||
|
||||
// 출고 정보(회사, 담당자, 라이센스 등) 반환
|
||||
String getOutEquipmentInfo(int equipmentId, String infoType) {
|
||||
final equipmentOut = dataService.getEquipmentOutById(equipmentId);
|
||||
if (equipmentOut != null) {
|
||||
switch (infoType) {
|
||||
case 'company':
|
||||
final company = equipmentOut.company ?? '-';
|
||||
if (company != '-') {
|
||||
final companyObj = dataService.getAllCompanies().firstWhere(
|
||||
(c) => c.name == company,
|
||||
orElse:
|
||||
() => Company(
|
||||
name: company,
|
||||
address: Address(),
|
||||
companyTypes: [CompanyType.customer], // 기본값 고객사
|
||||
),
|
||||
);
|
||||
// 여러 유형 중 첫 번째만 표시 (대표 유형)
|
||||
final typeText =
|
||||
companyObj.companyTypes.isNotEmpty
|
||||
? companyTypeToString(companyObj.companyTypes.first)
|
||||
: '-';
|
||||
return '$company (${typeText})';
|
||||
}
|
||||
return company;
|
||||
case 'manager':
|
||||
return equipmentOut.manager ?? '-';
|
||||
case 'license':
|
||||
return equipmentOut.license ?? '-';
|
||||
default:
|
||||
return '-';
|
||||
}
|
||||
}
|
||||
return '-';
|
||||
}
|
||||
|
||||
// 장비 삭제
|
||||
Future<bool> deleteEquipment(UnifiedEquipment equipment) async {
|
||||
try {
|
||||
if (_useApi) {
|
||||
// API를 통한 삭제
|
||||
if (equipment.equipment.id != null) {
|
||||
await _equipmentService.deleteEquipment(equipment.equipment.id!);
|
||||
} else {
|
||||
throw Exception('Equipment ID is null');
|
||||
}
|
||||
} else {
|
||||
// Mock 데이터 삭제
|
||||
if (equipment.status == EquipmentStatus.in_) {
|
||||
dataService.deleteEquipmentIn(equipment.id!);
|
||||
} else if (equipment.status == EquipmentStatus.out) {
|
||||
dataService.deleteEquipmentOut(equipment.id!);
|
||||
} else if (equipment.status == EquipmentStatus.rent) {
|
||||
// TODO: 대여 상태 삭제 구현
|
||||
throw UnimplementedError('Rent status deletion not implemented');
|
||||
}
|
||||
}
|
||||
|
||||
// 로컬 리스트에서도 제거
|
||||
equipments.removeWhere((e) => e.id == equipment.id && e.status == equipment.status);
|
||||
notifyListeners();
|
||||
|
||||
return true;
|
||||
} on Failure catch (e) {
|
||||
_error = e.message;
|
||||
notifyListeners();
|
||||
return false;
|
||||
} catch (e) {
|
||||
_error = 'Failed to delete equipment: $e';
|
||||
notifyListeners();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// API 사용 여부 토글 (테스트용)
|
||||
void toggleApiUsage() {
|
||||
_useApi = !_useApi;
|
||||
loadData(isRefresh: true);
|
||||
}
|
||||
|
||||
// 에러 처리
|
||||
void clearError() {
|
||||
_error = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import 'package:superport/models/equipment_unified_model.dart';
|
||||
import 'package:superport/models/company_model.dart';
|
||||
import 'package:superport/models/company_branch_info.dart';
|
||||
import 'package:superport/models/address_model.dart';
|
||||
import 'package:superport/services/mock_data_service.dart';
|
||||
import 'package:superport/services/equipment_service.dart';
|
||||
import 'package:superport/services/company_service.dart';
|
||||
import 'package:superport/utils/constants.dart';
|
||||
@@ -14,7 +13,8 @@ import 'package:superport/utils/constants.dart';
|
||||
///
|
||||
/// 폼의 전체 상태, 유효성, 저장, 데이터 로딩 등 비즈니스 로직을 담당한다.
|
||||
class EquipmentOutFormController extends ChangeNotifier {
|
||||
final MockDataService dataService;
|
||||
final EquipmentService _equipmentService = GetIt.instance<EquipmentService>();
|
||||
final CompanyService _companyService = GetIt.instance<CompanyService>();
|
||||
int? equipmentOutId;
|
||||
|
||||
// 편집 모드 여부
|
||||
@@ -62,7 +62,6 @@ class EquipmentOutFormController extends ChangeNotifier {
|
||||
final TextEditingController remarkController = TextEditingController();
|
||||
|
||||
EquipmentOutFormController({
|
||||
required this.dataService,
|
||||
this.equipmentOutId,
|
||||
}) {
|
||||
isEditMode = equipmentOutId != null;
|
||||
@@ -77,22 +76,32 @@ class EquipmentOutFormController extends ChangeNotifier {
|
||||
}
|
||||
|
||||
// 드롭다운 데이터 로드
|
||||
void loadDropdownData() {
|
||||
// 회사 목록 로드 (출고처 가능한 회사만)
|
||||
companies = dataService.getAllCompanies()
|
||||
.where((c) => c.companyTypes.contains(CompanyType.customer))
|
||||
.map((c) => CompanyBranchInfo(
|
||||
id: c.id,
|
||||
name: c.name,
|
||||
originalName: c.name,
|
||||
isMainCompany: true,
|
||||
companyId: c.id,
|
||||
Future<void> loadDropdownData() async {
|
||||
try {
|
||||
// API를 통해 회사 목록 로드
|
||||
final allCompanies = await _companyService.getCompanies();
|
||||
companies = allCompanies
|
||||
.where((c) => c.companyTypes.contains(CompanyType.customer))
|
||||
.map((c) => CompanyBranchInfo(
|
||||
id: c.id,
|
||||
name: c.name,
|
||||
originalName: c.name,
|
||||
isMainCompany: true,
|
||||
companyId: c.id,
|
||||
branchId: null,
|
||||
))
|
||||
.toList();
|
||||
|
||||
// 라이선스 목록 로드
|
||||
licenses = dataService.getAllLicenses().map((l) => l.name).toList();
|
||||
|
||||
// TODO: 라이선스 목록도 API로 로드
|
||||
licenses = []; // 임시로 빈 목록
|
||||
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
debugPrint('드롭다운 데이터 로드 실패: $e');
|
||||
companies = [];
|
||||
licenses = [];
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// 선택된 장비로 초기화
|
||||
@@ -109,23 +118,10 @@ class EquipmentOutFormController extends ChangeNotifier {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mock 데이터에서 회사별 담당자 목록 가져오기
|
||||
final company = dataService.getAllCompanies().firstWhere(
|
||||
(c) => c.name == selectedCompanies[index],
|
||||
orElse: () => Company(
|
||||
name: '',
|
||||
companyTypes: [],
|
||||
),
|
||||
);
|
||||
|
||||
if (company.name.isNotEmpty && company.contactName != null && company.contactName!.isNotEmpty) {
|
||||
// 회사의 담당자 정보
|
||||
hasManagersPerCompany[index] = true;
|
||||
filteredManagersPerCompany[index] = [company.contactName!];
|
||||
} else {
|
||||
hasManagersPerCompany[index] = false;
|
||||
filteredManagersPerCompany[index] = ['없음'];
|
||||
}
|
||||
// TODO: API를 통해 회사별 담당자 목록 로드
|
||||
// 현재는 임시로 빈 목록 사용
|
||||
hasManagersPerCompany[index] = false;
|
||||
filteredManagersPerCompany[index] = [];
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user