- 전체 371개 파일 중 82개 미사용 파일 식별 - Phase 1: 33개 파일 삭제 예정 (100% 안전) - Phase 2: 30개 파일 삭제 검토 예정 - Phase 3: 19개 파일 수동 검토 예정 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
468 lines
14 KiB
Dart
468 lines
14 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:injectable/injectable.dart';
|
|
import '../../../data/models/equipment/equipment_dto.dart';
|
|
import '../../../data/models/company/company_dto.dart';
|
|
import '../../../data/models/model/model_dto.dart';
|
|
import '../../../data/models/vendor_dto.dart';
|
|
import '../../../domain/usecases/equipment/create_equipment_usecase.dart';
|
|
import '../../../domain/usecases/equipment/update_equipment_usecase.dart';
|
|
import '../../../domain/usecases/equipment/get_equipment_detail_usecase.dart';
|
|
import '../../../domain/usecases/company/get_companies_usecase.dart';
|
|
import '../../../domain/usecases/models/get_models_usecase.dart';
|
|
import '../../../domain/usecases/vendor_usecase.dart';
|
|
import '../../../core/errors/failures.dart';
|
|
|
|
/// 장비 폼 컨트롤러 (생성/수정)
|
|
/// Phase 8 - Clean Architecture 패턴, 복합 FK 지원
|
|
@injectable
|
|
class EquipmentFormController extends ChangeNotifier {
|
|
final CreateEquipmentUseCase _createEquipmentUseCase;
|
|
final UpdateEquipmentUseCase _updateEquipmentUseCase;
|
|
final GetEquipmentDetailUseCase _getEquipmentDetailUseCase;
|
|
final GetCompaniesUseCase _getCompaniesUseCase;
|
|
final GetModelsUseCase _getModelsUseCase;
|
|
final VendorUseCase _vendorUseCase;
|
|
|
|
EquipmentFormController(
|
|
this._createEquipmentUseCase,
|
|
this._updateEquipmentUseCase,
|
|
this._getEquipmentDetailUseCase,
|
|
this._getCompaniesUseCase,
|
|
this._getModelsUseCase,
|
|
this._vendorUseCase,
|
|
);
|
|
|
|
// 상태 관리
|
|
bool _isLoading = false;
|
|
bool _isLoadingCompanies = false;
|
|
bool _isLoadingVendors = false;
|
|
bool _isLoadingModels = false;
|
|
bool _isSaving = false;
|
|
String? _error;
|
|
|
|
// 폼 데이터
|
|
EquipmentDto? _currentEquipment;
|
|
int? _equipmentId; // 수정 모드일 때 사용
|
|
|
|
// 드롭다운 데이터
|
|
List<CompanyDto> _companies = [];
|
|
List<VendorDto> _vendors = [];
|
|
List<ModelDto> _models = [];
|
|
List<ModelDto> _filteredModels = [];
|
|
|
|
// 선택된 값
|
|
int? _selectedCompanyId;
|
|
int? _selectedVendorId;
|
|
int? _selectedModelId;
|
|
|
|
// 폼 컨트롤러들
|
|
final TextEditingController serialNumberController = TextEditingController();
|
|
final TextEditingController barcodeController = TextEditingController();
|
|
final TextEditingController purchasePriceController = TextEditingController();
|
|
final TextEditingController warrantyNumberController = TextEditingController();
|
|
final TextEditingController remarkController = TextEditingController();
|
|
|
|
// 날짜 필드들
|
|
DateTime? _purchasedAt;
|
|
DateTime _warrantyStartedAt = DateTime.now().toUtc();
|
|
DateTime _warrantyEndedAt = DateTime.now().toUtc().add(const Duration(days: 365));
|
|
|
|
// Getters
|
|
bool get isLoading => _isLoading;
|
|
bool get isLoadingCompanies => _isLoadingCompanies;
|
|
bool get isLoadingVendors => _isLoadingVendors;
|
|
bool get isLoadingModels => _isLoadingModels;
|
|
bool get isSaving => _isSaving;
|
|
String? get error => _error;
|
|
EquipmentDto? get currentEquipment => _currentEquipment;
|
|
bool get isEditMode => _equipmentId != null;
|
|
|
|
List<CompanyDto> get companies => _companies;
|
|
List<VendorDto> get vendors => _vendors;
|
|
List<ModelDto> get filteredModels => _filteredModels;
|
|
|
|
int? get selectedCompanyId => _selectedCompanyId;
|
|
int? get selectedVendorId => _selectedVendorId;
|
|
int? get selectedModelId => _selectedModelId;
|
|
|
|
DateTime? get purchasedAt => _purchasedAt;
|
|
DateTime get warrantyStartedAt => _warrantyStartedAt;
|
|
DateTime get warrantyEndedAt => _warrantyEndedAt;
|
|
|
|
/// 초기화 (생성 모드)
|
|
Future<void> initializeForCreate() async {
|
|
_equipmentId = null;
|
|
_currentEquipment = null;
|
|
_clearForm();
|
|
await _loadInitialData();
|
|
}
|
|
|
|
/// 초기화 (수정 모드)
|
|
Future<void> initializeForEdit(int equipmentId) async {
|
|
_equipmentId = equipmentId;
|
|
_isLoading = true;
|
|
_error = null;
|
|
notifyListeners();
|
|
|
|
try {
|
|
await _loadInitialData();
|
|
await _loadEquipmentDetail(equipmentId);
|
|
} catch (e) {
|
|
_error = '장비 정보를 불러오는데 실패했습니다: $e';
|
|
} finally {
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
/// 초기 데이터 로드 (회사, 제조사, 모델)
|
|
Future<void> _loadInitialData() async {
|
|
await Future.wait([
|
|
_loadCompanies(),
|
|
_loadVendors(),
|
|
_loadModels(),
|
|
]);
|
|
}
|
|
|
|
/// 회사 목록 로드
|
|
Future<void> _loadCompanies() async {
|
|
_isLoadingCompanies = true;
|
|
notifyListeners();
|
|
|
|
try {
|
|
final params = GetCompaniesParams(page: 1, perPage: 1000); // 모든 회사 가져오기
|
|
final result = await _getCompaniesUseCase(params);
|
|
|
|
result.fold(
|
|
(failure) {
|
|
_error = _getErrorMessage(failure);
|
|
},
|
|
(paginatedResponse) {
|
|
_companies = paginatedResponse.items
|
|
.cast<CompanyDto>() // 타입 캐스팅 추가
|
|
.where((company) => company.isActive)
|
|
.toList()
|
|
..sort((a, b) => a.name.compareTo(b.name));
|
|
},
|
|
);
|
|
} catch (e) {
|
|
_error = '회사 목록을 불러오는데 실패했습니다: $e';
|
|
} finally {
|
|
_isLoadingCompanies = false;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
/// 제조사 목록 로드
|
|
Future<void> _loadVendors() async {
|
|
_isLoadingVendors = true;
|
|
notifyListeners();
|
|
|
|
try {
|
|
final vendorResponse = await _vendorUseCase.getVendors(limit: 1000); // 모든 제조사 가져오기
|
|
_vendors = (vendorResponse.items as List)
|
|
.whereType<VendorDto>()
|
|
.where((vendor) => vendor.isActive)
|
|
.toList()
|
|
..sort((a, b) => a.name.compareTo(b.name));
|
|
} catch (e) {
|
|
_error = '제조사 목록을 불러오는데 실패했습니다: $e';
|
|
} finally {
|
|
_isLoadingVendors = false;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
/// 모델 목록 로드
|
|
Future<void> _loadModels() async {
|
|
_isLoadingModels = true;
|
|
notifyListeners();
|
|
|
|
try {
|
|
const params = GetModelsParams(page: 1, perPage: 1000);
|
|
final result = await _getModelsUseCase(params);
|
|
result.fold(
|
|
(failure) => throw Exception(failure.message),
|
|
(modelResponse) {
|
|
_models = modelResponse.items;
|
|
_filteredModels = _models;
|
|
},
|
|
);
|
|
} catch (e) {
|
|
_error = '모델 목록을 불러오는데 실패했습니다: $e';
|
|
} finally {
|
|
_isLoadingModels = false;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
/// 장비 상세 정보 로드 (수정 모드)
|
|
Future<void> _loadEquipmentDetail(int equipmentId) async {
|
|
final result = await _getEquipmentDetailUseCase(equipmentId);
|
|
|
|
result.fold(
|
|
(failure) {
|
|
_error = _getErrorMessage(failure);
|
|
},
|
|
(equipment) {
|
|
_currentEquipment = equipment;
|
|
_populateForm(equipment);
|
|
},
|
|
);
|
|
}
|
|
|
|
/// 폼 필드에 데이터 채우기 (수정 모드)
|
|
void _populateForm(EquipmentDto equipment) {
|
|
_selectedCompanyId = equipment.companiesId;
|
|
_selectedModelId = equipment.modelsId;
|
|
|
|
serialNumberController.text = equipment.serialNumber;
|
|
barcodeController.text = equipment.barcode ?? '';
|
|
purchasePriceController.text = equipment.purchasePrice.toString();
|
|
warrantyNumberController.text = equipment.warrantyNumber;
|
|
remarkController.text = equipment.remark ?? '';
|
|
|
|
_purchasedAt = equipment.purchasedAt?.toUtc(); // ✅ UTC 타임존으로 변환
|
|
_warrantyStartedAt = equipment.warrantyStartedAt.toUtc(); // ✅ UTC 타임존으로 변환
|
|
_warrantyEndedAt = equipment.warrantyEndedAt.toUtc(); // ✅ UTC 타임존으로 변환
|
|
|
|
// 선택된 회사에 따라 모델 필터링
|
|
_filterModelsByCompany(_selectedCompanyId);
|
|
|
|
notifyListeners();
|
|
}
|
|
|
|
/// 회사 선택
|
|
void selectCompany(int? companyId) {
|
|
_selectedCompanyId = companyId;
|
|
notifyListeners();
|
|
}
|
|
|
|
/// 제조사 선택 (제조사별 모델 필터링 활성화)
|
|
void selectVendor(int? vendorId) {
|
|
_selectedVendorId = vendorId;
|
|
_selectedModelId = null; // 모델 선택 초기화
|
|
_filterModelsByVendor(vendorId); // 실제 제조사별 필터링 실행
|
|
notifyListeners();
|
|
}
|
|
|
|
/// 모델 선택
|
|
void selectModel(int? modelId) {
|
|
_selectedModelId = modelId;
|
|
notifyListeners();
|
|
}
|
|
|
|
/// 제조사별 모델 필터링 (실제 구현)
|
|
void _filterModelsByVendor(int? vendorId) {
|
|
if (vendorId == null) {
|
|
_filteredModels = _models;
|
|
} else {
|
|
// vendorsId 기준으로 실제 필터링 구현
|
|
_filteredModels = _models.where((model) => model.vendorsId == vendorId).toList();
|
|
}
|
|
notifyListeners();
|
|
}
|
|
|
|
/// 레거시 호환성을 위한 Company 기반 필터링 (현재는 전체 모델 표시)
|
|
void _filterModelsByCompany(int? companyId) {
|
|
if (companyId == null) {
|
|
_filteredModels = _models;
|
|
} else {
|
|
// 회사별 모델 필터링은 현재 구조에서는 불가능 (모든 모델 표시)
|
|
_filteredModels = _models;
|
|
}
|
|
notifyListeners();
|
|
}
|
|
|
|
/// 구매일 선택
|
|
void setPurchasedAt(DateTime? date) {
|
|
_purchasedAt = date?.toUtc(); // ✅ UTC 타임존으로 변환
|
|
notifyListeners();
|
|
}
|
|
|
|
/// 워런티 시작일 선택
|
|
void setWarrantyStartedAt(DateTime date) {
|
|
_warrantyStartedAt = date.toUtc(); // ✅ UTC 타임존으로 변환
|
|
// 시작일이 종료일보다 늦으면 종료일을 1년 후로 설정
|
|
if (_warrantyStartedAt.isAfter(_warrantyEndedAt)) {
|
|
_warrantyEndedAt = _warrantyStartedAt.add(const Duration(days: 365));
|
|
}
|
|
notifyListeners();
|
|
}
|
|
|
|
/// 워런티 종료일 선택
|
|
void setWarrantyEndedAt(DateTime date) {
|
|
_warrantyEndedAt = date.toUtc(); // ✅ UTC 타임존으로 변환
|
|
notifyListeners();
|
|
}
|
|
|
|
/// 폼 유효성 검증
|
|
String? validateForm() {
|
|
if (_selectedCompanyId == null) {
|
|
return '회사를 선택해주세요.';
|
|
}
|
|
|
|
if (_selectedModelId == null) {
|
|
return '모델을 선택해주세요.';
|
|
}
|
|
|
|
if (serialNumberController.text.trim().isEmpty) {
|
|
return '시리얼 번호를 입력해주세요.';
|
|
}
|
|
|
|
if (warrantyNumberController.text.trim().isEmpty) {
|
|
return '워런티 번호를 입력해주세요.';
|
|
}
|
|
|
|
if (_warrantyStartedAt.isAfter(_warrantyEndedAt)) {
|
|
return '워런티 시작일이 종료일보다 늦을 수 없습니다.';
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// 장비 저장 (생성 또는 수정)
|
|
Future<bool> saveEquipment() async {
|
|
final validationError = validateForm();
|
|
if (validationError != null) {
|
|
_error = validationError;
|
|
notifyListeners();
|
|
return false;
|
|
}
|
|
|
|
_isSaving = true;
|
|
_error = null;
|
|
notifyListeners();
|
|
|
|
try {
|
|
if (isEditMode) {
|
|
return await _updateEquipment();
|
|
} else {
|
|
return await _createEquipment();
|
|
}
|
|
} catch (e) {
|
|
_error = '저장 중 오류가 발생했습니다: $e';
|
|
return false;
|
|
} finally {
|
|
_isSaving = false;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
/// 장비 생성
|
|
Future<bool> _createEquipment() async {
|
|
final request = EquipmentRequestDto(
|
|
companiesId: _selectedCompanyId, // 백엔드: Option<i32> - null 허용
|
|
modelsId: _selectedModelId, // 백엔드: Option<i32> - null 허용
|
|
serialNumber: serialNumberController.text.trim(),
|
|
barcode: barcodeController.text.trim().isNotEmpty
|
|
? barcodeController.text.trim()
|
|
: null,
|
|
purchasedAt: (_purchasedAt ?? DateTime.now()).toUtc(), // 백엔드: 필수 필드 - 기본값 제공
|
|
purchasePrice: int.tryParse(purchasePriceController.text) ?? 0,
|
|
warrantyNumber: warrantyNumberController.text.trim(),
|
|
warrantyStartedAt: _warrantyStartedAt.toUtc(), // ✅ UTC 타임존으로 변환
|
|
warrantyEndedAt: _warrantyEndedAt.toUtc(), // ✅ UTC 타임존으로 변환
|
|
remark: remarkController.text.trim().isNotEmpty
|
|
? remarkController.text.trim()
|
|
: null,
|
|
);
|
|
|
|
final result = await _createEquipmentUseCase(request);
|
|
|
|
return result.fold(
|
|
(failure) {
|
|
_error = _getErrorMessage(failure);
|
|
return false;
|
|
},
|
|
(equipment) {
|
|
_currentEquipment = equipment;
|
|
return true;
|
|
},
|
|
);
|
|
}
|
|
|
|
/// 장비 수정
|
|
Future<bool> _updateEquipment() async {
|
|
final request = EquipmentUpdateRequestDto(
|
|
companiesId: _selectedCompanyId!,
|
|
modelsId: _selectedModelId!,
|
|
serialNumber: serialNumberController.text.trim(),
|
|
barcode: barcodeController.text.trim().isNotEmpty
|
|
? barcodeController.text.trim()
|
|
: null,
|
|
purchasedAt: _purchasedAt?.toUtc(), // ✅ UTC 타임존으로 변환
|
|
purchasePrice: int.tryParse(purchasePriceController.text) ?? 0,
|
|
warrantyNumber: warrantyNumberController.text.trim(),
|
|
warrantyStartedAt: _warrantyStartedAt.toUtc(), // ✅ UTC 타임존으로 변환
|
|
warrantyEndedAt: _warrantyEndedAt.toUtc(), // ✅ UTC 타임존으로 변환
|
|
remark: remarkController.text.trim().isNotEmpty
|
|
? remarkController.text.trim()
|
|
: null,
|
|
);
|
|
|
|
final result = await _updateEquipmentUseCase(UpdateEquipmentParams(
|
|
id: _equipmentId!,
|
|
request: request,
|
|
));
|
|
|
|
return result.fold(
|
|
(failure) {
|
|
_error = _getErrorMessage(failure);
|
|
return false;
|
|
},
|
|
(equipment) {
|
|
_currentEquipment = equipment;
|
|
return true;
|
|
},
|
|
);
|
|
}
|
|
|
|
/// 폼 초기화
|
|
void _clearForm() {
|
|
_selectedCompanyId = null;
|
|
_selectedModelId = null;
|
|
|
|
serialNumberController.clear();
|
|
barcodeController.clear();
|
|
purchasePriceController.text = '0';
|
|
warrantyNumberController.clear();
|
|
remarkController.clear();
|
|
|
|
_purchasedAt = null;
|
|
_warrantyStartedAt = DateTime.now().toUtc(); // ✅ UTC 타임존으로 변환
|
|
_warrantyEndedAt = DateTime.now().toUtc().add(const Duration(days: 365)); // ✅ UTC 타임존으로 변환
|
|
|
|
_error = null;
|
|
}
|
|
|
|
/// 에러 메시지 변환
|
|
String _getErrorMessage(Failure failure) {
|
|
switch (failure.runtimeType) {
|
|
case ServerFailure:
|
|
return (failure as ServerFailure).message;
|
|
case NetworkFailure:
|
|
return '네트워크 연결을 확인해주세요.';
|
|
case ValidationFailure:
|
|
return (failure as ValidationFailure).message;
|
|
default:
|
|
return '알 수 없는 오류가 발생했습니다.';
|
|
}
|
|
}
|
|
|
|
/// 에러 초기화
|
|
void clearError() {
|
|
_error = null;
|
|
notifyListeners();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
serialNumberController.dispose();
|
|
barcodeController.dispose();
|
|
purchasePriceController.dispose();
|
|
warrantyNumberController.dispose();
|
|
remarkController.dispose();
|
|
super.dispose();
|
|
}
|
|
} |