backup: 사용하지 않는 파일 삭제 전 복구 지점

- 전체 371개 파일 중 82개 미사용 파일 식별
- Phase 1: 33개 파일 삭제 예정 (100% 안전)
- Phase 2: 30개 파일 삭제 검토 예정
- Phase 3: 19개 파일 수동 검토 예정

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
JiWoong Sul
2025-09-02 19:51:40 +09:00
parent 650cd4be55
commit c419f8f458
149 changed files with 12934 additions and 3644 deletions

View File

@@ -0,0 +1,280 @@
import 'package:flutter/material.dart';
import 'package:injectable/injectable.dart';
import '../../../data/models/equipment/equipment_dto.dart';
import '../../../domain/usecases/equipment/get_equipments_usecase.dart';
import '../../../domain/usecases/equipment/get_equipment_detail_usecase.dart';
import '../../../domain/usecases/equipment/create_equipment_usecase.dart';
import '../../../domain/usecases/equipment/update_equipment_usecase.dart';
import '../../../domain/usecases/equipment/delete_equipment_usecase.dart';
import '../../../domain/usecases/equipment/restore_equipment_usecase.dart';
import '../../../core/constants/app_constants.dart';
@injectable
class EquipmentController with ChangeNotifier {
final GetEquipmentsUseCase _getEquipmentsUseCase;
final GetEquipmentDetailUseCase _getEquipmentDetailUseCase;
final CreateEquipmentUseCase _createEquipmentUseCase;
final UpdateEquipmentUseCase _updateEquipmentUseCase;
final DeleteEquipmentUseCase _deleteEquipmentUseCase;
final RestoreEquipmentUseCase _restoreEquipmentUseCase;
// 상태 관리
bool _isLoading = false;
String? _error;
List<EquipmentDto> _equipments = [];
EquipmentDto? _selectedEquipment;
// 페이지네이션
int _currentPage = 1;
int _totalPages = 0;
int _totalItems = 0;
final int _pageSize = AppConstants.equipmentPageSize;
// 필터
String? _searchQuery;
bool _includeDeleted = false;
EquipmentController(
this._getEquipmentsUseCase,
this._getEquipmentDetailUseCase,
this._createEquipmentUseCase,
this._updateEquipmentUseCase,
this._deleteEquipmentUseCase,
this._restoreEquipmentUseCase,
);
// Getters
bool get isLoading => _isLoading;
String? get error => _error;
bool get hasError => _error != null;
List<EquipmentDto> get equipments => _equipments;
EquipmentDto? get selectedEquipment => _selectedEquipment;
int get currentPage => _currentPage;
int get totalPages => _totalPages;
int get totalItems => _totalItems;
int get pageSize => _pageSize;
String? get searchQuery => _searchQuery;
bool get includeDeleted => _includeDeleted;
void _setLoading(bool loading) {
_isLoading = loading;
notifyListeners();
}
void _setError(String? error) {
_error = error;
notifyListeners();
}
void clearError() {
_error = null;
notifyListeners();
}
/// 장비 목록 조회
Future<void> loadEquipments({
int page = 1,
int perPage = AppConstants.equipmentPageSize,
String? search,
bool refresh = false,
}) async {
try {
if (refresh) {
_equipments.clear();
_currentPage = 1;
notifyListeners();
}
_setLoading(true);
final params = GetEquipmentsParams(
page: page,
perPage: perPage,
search: search,
);
final response = await _getEquipmentsUseCase(params);
response.fold(
(failure) => _setError('장비 목록을 불러오는데 실패했습니다: ${failure.message}'),
(data) {
_equipments = data.items;
_currentPage = page;
_totalPages = data.totalPages;
_totalItems = data.totalElements;
_searchQuery = search;
clearError();
},
);
} catch (e) {
_setError('장비 목록을 불러오는데 실패했습니다: $e');
} finally {
_setLoading(false);
}
}
/// 장비 상세 조회
Future<void> loadEquipment(int id) async {
try {
_setLoading(true);
final response = await _getEquipmentDetailUseCase(id);
response.fold(
(failure) => _setError('장비 정보를 불러오는데 실패했습니다: ${failure.message}'),
(equipment) {
_selectedEquipment = equipment;
clearError();
},
);
} catch (e) {
_setError('장비 정보를 불러오는데 실패했습니다: $e');
} finally {
_setLoading(false);
}
}
/// 장비 생성
Future<bool> createEquipment(EquipmentRequestDto request) async {
try {
_setLoading(true);
final response = await _createEquipmentUseCase(request);
return response.fold(
(failure) {
_setError('장비 생성에 실패했습니다: ${failure.message}');
return false;
},
(equipment) {
// 목록 새로고침
loadEquipments(refresh: true);
clearError();
return true;
},
);
} catch (e) {
_setError('장비 생성에 실패했습니다: $e');
return false;
} finally {
_setLoading(false);
}
}
/// 장비 수정
Future<bool> updateEquipment(int id, EquipmentUpdateRequestDto request) async {
try {
_setLoading(true);
final params = UpdateEquipmentParams(id: id, request: request);
final response = await _updateEquipmentUseCase(params);
return response.fold(
(failure) {
_setError('장비 수정에 실패했습니다: ${failure.message}');
return false;
},
(equipment) {
// 목록 새로고침
loadEquipments(refresh: true);
clearError();
return true;
},
);
} catch (e) {
_setError('장비 수정에 실패했습니다: $e');
return false;
} finally {
_setLoading(false);
}
}
/// 장비 삭제 (Soft Delete)
Future<bool> deleteEquipment(int id) async {
try {
_setLoading(true);
final response = await _deleteEquipmentUseCase(id);
return response.fold(
(failure) {
_setError('장비 삭제에 실패했습니다: ${failure.message}');
return false;
},
(_) {
// 목록 새로고침
loadEquipments(refresh: true);
clearError();
return true;
},
);
} catch (e) {
_setError('장비 삭제에 실패했습니다: $e');
return false;
} finally {
_setLoading(false);
}
}
/// 장비 복구
Future<bool> restoreEquipment(int id) async {
try {
_setLoading(true);
final response = await _restoreEquipmentUseCase(id);
return response.fold(
(failure) {
_setError('장비 복구에 실패했습니다: ${failure.message}');
return false;
},
(equipment) {
// 목록 새로고침
loadEquipments(refresh: true);
clearError();
return true;
},
);
} catch (e) {
_setError('장비 복구에 실패했습니다: $e');
return false;
} finally {
_setLoading(false);
}
}
/// 페이지 변경
Future<void> goToPage(int page) async {
if (page < 1 || page > _totalPages || page == _currentPage) return;
await loadEquipments(
page: page,
search: _searchQuery,
);
}
/// 검색 설정
void setSearch(String? search) {
_searchQuery = search;
loadEquipments(
search: search,
refresh: true,
);
}
/// 새로고침
Future<void> refresh() async {
await loadEquipments(
page: 1,
search: _searchQuery,
refresh: true,
);
}
/// 선택된 장비 초기화
void clearSelectedEquipment() {
_selectedEquipment = null;
notifyListeners();
}
}

View File

@@ -2,12 +2,14 @@ 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_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/model_usecase.dart';
import '../../../domain/usecases/models/get_models_usecase.dart';
import '../../../domain/usecases/vendor_usecase.dart';
import '../../../core/errors/failures.dart';
/// 장비 폼 컨트롤러 (생성/수정)
@@ -18,19 +20,22 @@ class EquipmentFormController extends ChangeNotifier {
final UpdateEquipmentUseCase _updateEquipmentUseCase;
final GetEquipmentDetailUseCase _getEquipmentDetailUseCase;
final GetCompaniesUseCase _getCompaniesUseCase;
final ModelUseCase _modelUseCase;
final GetModelsUseCase _getModelsUseCase;
final VendorUseCase _vendorUseCase;
EquipmentFormController(
this._createEquipmentUseCase,
this._updateEquipmentUseCase,
this._getEquipmentDetailUseCase,
this._getCompaniesUseCase,
this._modelUseCase,
this._getModelsUseCase,
this._vendorUseCase,
);
// 상태 관리
bool _isLoading = false;
bool _isLoadingCompanies = false;
bool _isLoadingVendors = false;
bool _isLoadingModels = false;
bool _isSaving = false;
String? _error;
@@ -41,11 +46,13 @@ class EquipmentFormController extends ChangeNotifier {
// 드롭다운 데이터
List<CompanyDto> _companies = [];
List<VendorDto> _vendors = [];
List<ModelDto> _models = [];
List<ModelDto> _filteredModels = [];
// 선택된 값
int? _selectedCompanyId;
int? _selectedVendorId;
int? _selectedModelId;
// 폼 컨트롤러들
@@ -57,12 +64,13 @@ class EquipmentFormController extends ChangeNotifier {
// 날짜 필드들
DateTime? _purchasedAt;
DateTime _warrantyStartedAt = DateTime.now();
DateTime _warrantyEndedAt = DateTime.now().add(const Duration(days: 365));
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;
@@ -70,9 +78,11 @@ class EquipmentFormController extends ChangeNotifier {
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;
@@ -105,10 +115,11 @@ class EquipmentFormController extends ChangeNotifier {
}
}
/// 초기 데이터 로드 (회사, 모델)
/// 초기 데이터 로드 (회사, 제조사, 모델)
Future<void> _loadInitialData() async {
await Future.wait([
_loadCompanies(),
_loadVendors(),
_loadModels(),
]);
}
@@ -142,14 +153,41 @@ class EquipmentFormController extends ChangeNotifier {
}
}
/// 제조사 목록 로드
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 {
_models = await _modelUseCase.getModels();
_filteredModels = _models;
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 {
@@ -184,9 +222,9 @@ class EquipmentFormController extends ChangeNotifier {
warrantyNumberController.text = equipment.warrantyNumber;
remarkController.text = equipment.remark ?? '';
_purchasedAt = equipment.purchasedAt;
_warrantyStartedAt = equipment.warrantyStartedAt;
_warrantyEndedAt = equipment.warrantyEndedAt;
_purchasedAt = equipment.purchasedAt?.toUtc(); // ✅ UTC 타임존으로 변환
_warrantyStartedAt = equipment.warrantyStartedAt.toUtc(); // ✅ UTC 타임존으로 변환
_warrantyEndedAt = equipment.warrantyEndedAt.toUtc(); // ✅ UTC 타임존으로 변환
// 선택된 회사에 따라 모델 필터링
_filterModelsByCompany(_selectedCompanyId);
@@ -197,8 +235,14 @@ class EquipmentFormController extends ChangeNotifier {
/// 회사 선택
void selectCompany(int? companyId) {
_selectedCompanyId = companyId;
notifyListeners();
}
/// 제조사 선택 (제조사별 모델 필터링 활성화)
void selectVendor(int? vendorId) {
_selectedVendorId = vendorId;
_selectedModelId = null; // 모델 선택 초기화
_filterModelsByCompany(companyId);
_filterModelsByVendor(vendorId); // 실제 제조사별 필터링 실행
notifyListeners();
}
@@ -208,13 +252,23 @@ class EquipmentFormController extends ChangeNotifier {
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 {
// 실제로는 vendor로 필터링해야 하지만,
// 현재 구조에서는 모든 모델을 보여주고 사용자가 선택하도록 함
// 회사별 모델 필터링은 현재 구조에서는 불가능 (모든 모델 표시)
_filteredModels = _models;
}
notifyListeners();
@@ -222,13 +276,13 @@ class EquipmentFormController extends ChangeNotifier {
/// 구매일 선택
void setPurchasedAt(DateTime? date) {
_purchasedAt = date;
_purchasedAt = date?.toUtc(); // ✅ UTC 타임존으로 변환
notifyListeners();
}
/// 워런티 시작일 선택
void setWarrantyStartedAt(DateTime date) {
_warrantyStartedAt = date;
_warrantyStartedAt = date.toUtc(); // ✅ UTC 타임존으로 변환
// 시작일이 종료일보다 늦으면 종료일을 1년 후로 설정
if (_warrantyStartedAt.isAfter(_warrantyEndedAt)) {
_warrantyEndedAt = _warrantyStartedAt.add(const Duration(days: 365));
@@ -238,7 +292,7 @@ class EquipmentFormController extends ChangeNotifier {
/// 워런티 종료일 선택
void setWarrantyEndedAt(DateTime date) {
_warrantyEndedAt = date;
_warrantyEndedAt = date.toUtc(); // ✅ UTC 타임존으로 변환
notifyListeners();
}
@@ -298,17 +352,17 @@ class EquipmentFormController extends ChangeNotifier {
/// 장비 생성
Future<bool> _createEquipment() async {
final request = EquipmentRequestDto(
companiesId: _selectedCompanyId!,
modelsId: _selectedModelId!,
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,
purchasedAt: (_purchasedAt ?? DateTime.now()).toUtc(), // 백엔드: 필수 필드 - 기본값 제공
purchasePrice: int.tryParse(purchasePriceController.text) ?? 0,
warrantyNumber: warrantyNumberController.text.trim(),
warrantyStartedAt: _warrantyStartedAt,
warrantyEndedAt: _warrantyEndedAt,
warrantyStartedAt: _warrantyStartedAt.toUtc(), // ✅ UTC 타임존으로 변환
warrantyEndedAt: _warrantyEndedAt.toUtc(), // ✅ UTC 타임존으로 변환
remark: remarkController.text.trim().isNotEmpty
? remarkController.text.trim()
: null,
@@ -337,11 +391,11 @@ class EquipmentFormController extends ChangeNotifier {
barcode: barcodeController.text.trim().isNotEmpty
? barcodeController.text.trim()
: null,
purchasedAt: _purchasedAt,
purchasedAt: _purchasedAt?.toUtc(), // ✅ UTC 타임존으로 변환
purchasePrice: int.tryParse(purchasePriceController.text) ?? 0,
warrantyNumber: warrantyNumberController.text.trim(),
warrantyStartedAt: _warrantyStartedAt,
warrantyEndedAt: _warrantyEndedAt,
warrantyStartedAt: _warrantyStartedAt.toUtc(), // ✅ UTC 타임존으로 변환
warrantyEndedAt: _warrantyEndedAt.toUtc(), // ✅ UTC 타임존으로 변환
remark: remarkController.text.trim().isNotEmpty
? remarkController.text.trim()
: null,
@@ -376,8 +430,8 @@ class EquipmentFormController extends ChangeNotifier {
remarkController.clear();
_purchasedAt = null;
_warrantyStartedAt = DateTime.now();
_warrantyEndedAt = DateTime.now().add(const Duration(days: 365));
_warrantyStartedAt = DateTime.now().toUtc(); // ✅ UTC 타임존으로 변환
_warrantyEndedAt = DateTime.now().toUtc().add(const Duration(days: 365)); // ✅ UTC 타임존으로 변환
_error = null;
}

View File

@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import '../../../data/models/equipment_history_dto.dart';
import '../../../domain/usecases/equipment_history_usecase.dart';
import '../../../utils/constants.dart';
import '../../../core/constants/app_constants.dart';
class EquipmentHistoryController extends ChangeNotifier {
final EquipmentHistoryUseCase _useCase;
@@ -18,7 +18,7 @@ class EquipmentHistoryController extends ChangeNotifier {
// 페이지네이션
int _currentPage = 1;
int _pageSize = PaginationConstants.defaultPageSize;
int _pageSize = AppConstants.historyPageSize;
int _totalCount = 0;
// 필터 (백엔드 실제 필드만)
@@ -242,7 +242,7 @@ class EquipmentHistoryController extends ChangeNotifier {
try {
final result = await _useCase.getEquipmentHistories(
page: 1,
pageSize: 100,
pageSize: AppConstants.bulkPageSize,
transactionType: transactionType,
equipmentsId: equipmentId,
warehousesId: warehouseId,
@@ -286,7 +286,7 @@ class EquipmentHistoryController extends ChangeNotifier {
try {
final result = await _useCase.getEquipmentHistories(
page: 1,
pageSize: 1000,
pageSize: AppConstants.maxBulkPageSize,
equipmentsId: equipmentId,
warehousesId: warehouseId,
);
@@ -309,7 +309,7 @@ class EquipmentHistoryController extends ChangeNotifier {
try {
final result = await _useCase.getEquipmentHistories(
page: 1,
pageSize: 1000,
pageSize: AppConstants.maxBulkPageSize,
warehousesId: warehouseId,
);

View File

@@ -1,3 +1,4 @@
import 'dart:async' show unawaited;
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:superport/models/equipment_unified_model.dart';
@@ -13,6 +14,7 @@ import 'package:superport/data/models/equipment_history_dto.dart';
///
/// 폼의 전체 상태, 유효성, 저장, 데이터 로딩 등 비즈니스 로직을 담당한다.
class EquipmentInFormController extends ChangeNotifier {
bool _disposed = false;
final EquipmentService _equipmentService = GetIt.instance<EquipmentService>();
// final WarehouseService _warehouseService = GetIt.instance<WarehouseService>(); // 사용되지 않음 - 제거
// final CompanyService _companyService = GetIt.instance<CompanyService>(); // 사용되지 않음 - 제거
@@ -37,15 +39,18 @@ class EquipmentInFormController extends ChangeNotifier {
/// canSave 상태 업데이트 (UI 렌더링 문제 해결)
void _updateCanSave() {
if (_disposed) return; // dispose된 경우 업데이트 방지
final hasEquipmentNumber = _serialNumber.trim().isNotEmpty;
final hasModelsId = _modelsId != null; // models_id 필수
final hasWarrantyNumber = warrantyNumberController.text.trim().isNotEmpty; // warranty_number 필수
final isNotSaving = !_isSaving;
final newCanSave = isNotSaving && hasEquipmentNumber && hasModelsId;
final newCanSave = isNotSaving && hasEquipmentNumber && hasModelsId && hasWarrantyNumber;
if (_canSave != newCanSave) {
_canSave = newCanSave;
print('🚀 [canSave 상태 변경] $_canSave → serialNumber: "$_serialNumber", modelsId: $_modelsId');
print('🚀 [canSave 상태 변경] $_canSave → serialNumber: "$_serialNumber", modelsId: $_modelsId, warrantyNumber: "${warrantyNumberController.text}"');
notifyListeners(); // 명시적 UI 업데이트
}
}
@@ -55,6 +60,7 @@ class EquipmentInFormController extends ChangeNotifier {
// 입력 상태 변수 (백엔드 API 구조에 맞게 수정)
String _serialNumber = ''; // 장비번호 (필수) - private으로 변경
String _barcode = ''; // 바코드 (선택사항) - 새로 추가
int? _modelsId; // 모델 ID (필수) - Vendor→Model cascade에서 선택
int? _vendorId; // 벤더 ID (UI용, API에는 전송 안함)
@@ -72,6 +78,14 @@ class EquipmentInFormController extends ChangeNotifier {
}
}
String get barcode => _barcode;
set barcode(String value) {
if (_barcode != value) {
_barcode = value;
print('DEBUG [Controller] barcode updated: "$_barcode"');
}
}
String get manufacturer => _manufacturer;
set manufacturer(String value) {
if (_manufacturer != value) {
@@ -116,12 +130,13 @@ class EquipmentInFormController extends ChangeNotifier {
// Vendor→Model 선택 콜백
void onVendorModelChanged(int? vendorId, int? modelId) {
if (_disposed) return;
_vendorId = vendorId;
_modelsId = modelId;
_updateCanSave();
notifyListeners();
}
DateTime? purchaseDate; // 구매일
DateTime? purchaseDate = DateTime.now(); // 구매일 (기본값: 현재 날짜)
double? purchasePrice; // 구매가격
// 삭제된 필드들 (백엔드 미지원)
@@ -142,6 +157,7 @@ class EquipmentInFormController extends ChangeNotifier {
int _initialStock = 1; // 초기 재고 수량 (기본값: 1)
int get initialStock => _initialStock;
set initialStock(int value) {
if (_disposed) return;
if (_initialStock != value && value > 0) {
_initialStock = value;
notifyListeners();
@@ -188,8 +204,17 @@ class EquipmentInFormController extends ChangeNotifier {
EquipmentInFormController({this.equipmentInId}) {
isEditMode = equipmentInId != null;
_loadDropdownData();
// 워런티 번호 기본값 설정
if (warrantyNumberController.text.isEmpty) {
warrantyNumberController.text = 'WR-${DateTime.now().millisecondsSinceEpoch}';
}
_updateCanSave(); // 초기 canSave 상태 설정
// ✅ 비동기 드롭다운 데이터 로드 시작 (await 불가능하므로 별도 처리)
unawaited(_loadDropdownData());
// 수정 모드일 때 초기 데이터 로드는 initializeForEdit() 메서드로 이동
}
@@ -243,8 +268,28 @@ class EquipmentInFormController extends ChangeNotifier {
void _processDropdownData(Map<String, dynamic> data) {
manufacturers = data['manufacturers'] as List<String>? ?? [];
equipmentNames = data['equipment_names'] as List<String>? ?? [];
companies = data['companies'] as Map<int, String>? ?? {};
warehouses = data['warehouses'] as Map<int, String>? ?? {};
// ✅ List<Map> → Map<int, String> 안전한 변환 (사전 로드된 데이터)
try {
final companiesList = data['companies'] as List<dynamic>? ?? [];
companies = Map<int, String>.fromIterable(
companiesList.where((item) => item != null && item['id'] != null && item['name'] != null),
key: (item) => item['id'] as int,
value: (item) => item['name'] as String,
);
final warehousesList = data['warehouses'] as List<dynamic>? ?? [];
warehouses = Map<int, String>.fromIterable(
warehousesList.where((item) => item != null && item['id'] != null && item['name'] != null),
key: (item) => item['id'] as int,
value: (item) => item['name'] as String,
);
} catch (e) {
DebugLogger.logError('사전 로드된 드롭다운 데이터 변환 실패', error: e);
companies = {};
warehouses = {};
}
DebugLogger.log('드롭다운 데이터 처리 완료', tag: 'EQUIPMENT_IN', data: {
'manufacturers_count': manufacturers.length,
@@ -255,7 +300,7 @@ class EquipmentInFormController extends ChangeNotifier {
}
// 드롭다운 데이터 로드 (매번 API 호출)
void _loadDropdownData() async {
Future<void> _loadDropdownData() async {
try {
DebugLogger.log('Equipment 폼 드롭다운 데이터 로드 시작', tag: 'EQUIPMENT_IN');
final result = await _lookupsService.getEquipmentFormDropdownData();
@@ -268,13 +313,33 @@ class EquipmentInFormController extends ChangeNotifier {
equipmentNames = [];
companies = {};
warehouses = {};
notifyListeners();
if (!_disposed) notifyListeners();
},
(data) {
manufacturers = data['manufacturers'] as List<String>;
equipmentNames = data['equipment_names'] as List<String>;
companies = data['companies'] as Map<int, String>;
warehouses = data['warehouses'] as Map<int, String>;
// ✅ List<Map> → Map<int, String> 안전한 변환
try {
final companiesList = data['companies'] as List<dynamic>? ?? [];
companies = Map<int, String>.fromIterable(
companiesList.where((item) => item != null && item['id'] != null && item['name'] != null),
key: (item) => item['id'] as int,
value: (item) => item['name'] as String,
);
final warehousesList = data['warehouses'] as List<dynamic>? ?? [];
warehouses = Map<int, String>.fromIterable(
warehousesList.where((item) => item != null && item['id'] != null && item['name'] != null),
key: (item) => item['id'] as int,
value: (item) => item['name'] as String,
);
} catch (e) {
DebugLogger.logError('드롭다운 데이터 변환 실패', error: e);
companies = {};
warehouses = {};
}
DebugLogger.log('드롭다운 데이터 로드 성공', tag: 'EQUIPMENT_IN', data: {
'manufacturers_count': manufacturers.length,
@@ -283,7 +348,7 @@ class EquipmentInFormController extends ChangeNotifier {
'warehouses_count': warehouses.length,
});
notifyListeners();
if (!_disposed) notifyListeners();
},
);
} catch (e) {
@@ -292,29 +357,45 @@ class EquipmentInFormController extends ChangeNotifier {
equipmentNames = [];
companies = {};
warehouses = {};
notifyListeners();
if (!_disposed) notifyListeners();
}
}
// 기존의 개별 로드 메서드들은 _loadDropdownData()로 통합됨
// warehouseLocations, partnerCompanies 리스트 변수들도 제거됨
// 전달받은 장비 데이터로 폼 초기화
// 전달받은 장비 데이터로 폼 초기화 (간소화: 백엔드 JOIN 데이터 직접 활용)
void _loadFromEquipment(EquipmentDto equipment) {
serialNumber = equipment.serialNumber;
barcode = equipment.barcode ?? '';
modelsId = equipment.modelsId;
// vendorId는 ModelDto에서 가져와야 함 (필요 시)
purchasePrice = equipment.purchasePrice.toDouble();
initialStock = 1; // EquipmentDto에는 initialStock 필드가 없음
purchasePrice = equipment.purchasePrice > 0 ? equipment.purchasePrice.toDouble() : null;
initialStock = 1;
selectedCompanyId = equipment.companiesId;
// selectedWarehouseId는 현재 위치를 추적해야 함 (EquipmentHistory에서)
remarkController.text = equipment.remark ?? '';
warrantyNumberController.text = equipment.warrantyNumber;
// ✅ 간소화: 백엔드 JOIN 데이터 직접 사용 (복잡한 Controller 조회 제거)
manufacturer = equipment.vendorName ?? '제조사 정보 없음';
name = equipment.modelName ?? '모델 정보 없음';
// 날짜 필드 설정
purchaseDate = equipment.purchasedAt;
warrantyStartDate = equipment.warrantyStartedAt;
warrantyEndDate = equipment.warrantyEndedAt;
// TextEditingController 동기화
remarkController.text = equipment.remark ?? '';
warrantyNumberController.text = equipment.warrantyNumber;
// 수정 모드에서 입고지 기본값 설정
if (isEditMode && selectedWarehouseId == null && warehouses.isNotEmpty) {
selectedWarehouseId = warehouses.keys.first;
}
// preloadedEquipment에 저장 (UI에서 JOIN 데이터 접근용)
preloadedEquipment = equipment;
_updateCanSave();
notifyListeners(); // UI 즉시 업데이트
}
// 기존 데이터 로드(수정 모드)
@@ -404,7 +485,7 @@ class EquipmentInFormController extends ChangeNotifier {
} finally {
_isLoading = false;
_updateCanSave(); // 데이터 로드 완료 시 canSave 상태 업데이트
notifyListeners();
if (!_disposed) notifyListeners();
}
}
@@ -442,7 +523,7 @@ class EquipmentInFormController extends ChangeNotifier {
_isSaving = true;
_error = null;
_updateCanSave(); // 저장 시작 시 canSave 상태 업데이트
notifyListeners();
if (!_disposed) notifyListeners();
try {
@@ -501,7 +582,7 @@ class EquipmentInFormController extends ChangeNotifier {
companiesId: validCompanyId,
modelsId: _modelsId,
serialNumber: _serialNumber.trim(),
barcode: null,
barcode: _barcode.trim().isEmpty ? null : _barcode.trim(),
purchasedAt: purchaseDate,
purchasePrice: purchasePrice?.toInt(),
warrantyNumber: validWarrantyNumber,
@@ -538,17 +619,19 @@ class EquipmentInFormController extends ChangeNotifier {
'companiesId': selectedCompanyId,
});
// Equipment 객체를 EquipmentRequestDto로 변환
// Equipment 객체를 EquipmentRequestDto로 변환 (백엔드 스펙에 맞게)
final createRequest = EquipmentRequestDto(
companiesId: selectedCompanyId ?? 0,
modelsId: _modelsId ?? 0,
companiesId: selectedCompanyId, // 백엔드: Option<i32> - null 허용
modelsId: _modelsId, // 백엔드: Option<i32> - null 허용
serialNumber: _serialNumber,
barcode: null,
purchasedAt: null,
barcode: _barcode.trim().isEmpty ? null : _barcode.trim(),
purchasedAt: (purchaseDate ?? DateTime.now()).toUtc(), // 단순 UTC 변환
purchasePrice: purchasePrice?.toInt() ?? 0,
warrantyNumber: '',
warrantyStartedAt: DateTime.now(),
warrantyEndedAt: DateTime.now().add(Duration(days: 365)),
warrantyNumber: warrantyNumberController.text.isNotEmpty
? warrantyNumberController.text
: 'WR-${DateTime.now().millisecondsSinceEpoch}',
warrantyStartedAt: warrantyStartDate.toUtc(), // 단순 UTC 변환
warrantyEndedAt: warrantyEndDate.toUtc(), // 단순 UTC 변환
remark: remarkController.text.isNotEmpty ? remarkController.text : null,
);
@@ -602,21 +685,22 @@ class EquipmentInFormController extends ChangeNotifier {
return true;
} on Failure catch (e) {
_error = e.message;
notifyListeners();
if (!_disposed) notifyListeners();
return false;
} catch (e) {
_error = 'An unexpected error occurred: $e';
notifyListeners();
if (!_disposed) notifyListeners();
return false;
} finally {
_isSaving = false;
_updateCanSave(); // 저장 완료 시 canSave 상태 업데이트
notifyListeners();
if (!_disposed) notifyListeners();
}
}
// 에러 처리
void clearError() {
if (_disposed) return;
_error = null;
notifyListeners();
}
@@ -625,6 +709,7 @@ class EquipmentInFormController extends ChangeNotifier {
@override
void dispose() {
_disposed = true; // dispose 상태 설정
remarkController.dispose();
warrantyNumberController.dispose();
super.dispose();

View File

@@ -10,6 +10,7 @@ import 'package:superport/core/services/lookups_service.dart';
import 'package:superport/data/models/lookups/lookup_data.dart';
import 'package:superport/utils/constants.dart';
import 'package:superport/data/models/equipment/equipment_dto.dart';
import 'package:superport/domain/usecases/equipment/search_equipment_usecase.dart';
/// 장비 목록 화면의 상태 및 비즈니스 로직을 담당하는 컨트롤러 (리팩토링 버전)
/// BaseListController를 상속받아 공통 기능을 재사용
@@ -76,7 +77,7 @@ class EquipmentListController extends BaseListController<UnifiedEquipment> {
status: _statusFilter != null ?
EquipmentStatusConverter.clientToServer(_statusFilter) : null,
search: params.search,
// companyId: _companyIdFilter, // 활성화: EquipmentService에서 지원하지 않음
companyId: _companyIdFilter, // 활성화: 회사별 필터링 지원
// includeInactive: _includeInactive, // 비활성화: EquipmentService에서 지원하지 않음
),
onError: (failure) {
@@ -110,6 +111,15 @@ class EquipmentListController extends BaseListController<UnifiedEquipment> {
equipmentNumber: dto.serialNumber ?? 'Unknown', // 장비번호 (required)
serialNumber: dto.serialNumber ?? 'Unknown', // 시리얼번호 (required)
quantity: 1, // 기본 수량
// ⚡ [FIX] 누락된 구매 정보 필드들 추가
purchasePrice: dto.purchasePrice.toDouble(), // int → double 변환
purchaseDate: dto.purchasedAt, // 구매일
barcode: dto.barcode, // 바코드
remark: dto.remark, // 비고
// 보증 정보
warrantyLicense: dto.warrantyNumber,
warrantyStartDate: dto.warrantyStartedAt,
warrantyEndDate: dto.warrantyEndedAt,
);
// 간단한 Company 정보 생성 (사용하지 않으므로 제거)
@@ -129,6 +139,10 @@ class EquipmentListController extends BaseListController<UnifiedEquipment> {
warehouseLocation: null, // EquipmentDto에 warehouse_name 필드 없음
// currentBranch는 EquipmentListDto에 없으므로 null (백엔드 API 구조 변경으로 지점 개념 제거)
currentBranch: null,
// ⚡ [FIX] 백엔드 직접 제공 필드들 추가 - 화면에서 N/A 문제 해결
companyName: dto.companyName, // API company_name → UI 회사명 컬럼
vendorName: dto.vendorName, // API vendor_name → UI 제조사 컬럼
modelName: dto.modelName, // API model_name → UI 모델명 컬럼
);
// 🔧 [DEBUG] 변환된 UnifiedEquipment 로깅 (필요 시 활성화)
// print('DEBUG [EquipmentListController] UnifiedEquipment ID: ${unifiedEquipment.id}, currentCompany: "${unifiedEquipment.currentCompany}", warehouseLocation: "${unifiedEquipment.warehouseLocation}"');
@@ -197,12 +211,34 @@ class EquipmentListController extends BaseListController<UnifiedEquipment> {
try {
final result = await _lookupsService.getEquipmentFormDropdownData();
result.fold(
(failure) => throw failure,
(data) => cachedDropdownData = data,
(failure) {
debugPrint('❌ 드롭다운 데이터 로드 실패: ${failure.message}');
// 실패해도 빈 데이터로 초기화하여 타입 오류 방지
cachedDropdownData = {
'manufacturers': <String>[],
'equipment_names': <String>[],
'companies': <Map<String, dynamic>>[],
'warehouses': <Map<String, dynamic>>[],
'category1_list': <String>[],
'category_combinations': <dynamic>[],
};
},
(data) {
debugPrint('✅ 드롭다운 데이터 로드 성공: ${data.keys}');
cachedDropdownData = data;
},
);
} catch (e) {
print('Failed to preload dropdown data: $e');
// 캐시 실패해도 계속 진행
debugPrint('❌ 드롭다운 데이터 로드 예외: $e');
// 예외 발생 시에도 빈 데이터로 초기화
cachedDropdownData = {
'manufacturers': <String>[],
'equipment_names': <String>[],
'companies': <Map<String, dynamic>>[],
'warehouses': <Map<String, dynamic>>[],
'category1_list': <String>[],
'category_combinations': <dynamic>[],
};
}
}
@@ -248,6 +284,60 @@ class EquipmentListController extends BaseListController<UnifiedEquipment> {
loadData(isRefresh: true);
}
/// 시리얼번호로 장비 검색
Future<EquipmentDto?> searchBySerial(String serial) async {
try {
final useCase = GetIt.instance<GetEquipmentBySerialUseCase>();
final result = await useCase(serial);
return result.fold(
(failure) {
throw Exception(failure.message);
},
(equipment) => equipment,
);
} catch (e) {
debugPrint('시리얼번호 검색 실패: $e');
rethrow;
}
}
/// 바코드로 장비 검색
Future<EquipmentDto?> searchByBarcode(String barcode) async {
try {
final useCase = GetIt.instance<GetEquipmentByBarcodeUseCase>();
final result = await useCase(barcode);
return result.fold(
(failure) {
throw Exception(failure.message);
},
(equipment) => equipment,
);
} catch (e) {
debugPrint('바코드 검색 실패: $e');
rethrow;
}
}
/// 회사별 장비 목록 조회
Future<List<EquipmentDto>?> getEquipmentsByCompany(int companyId) async {
try {
final useCase = GetIt.instance<GetEquipmentsByCompanyUseCase>();
final result = await useCase(companyId);
return result.fold(
(failure) {
throw Exception(failure.message);
},
(equipments) => equipments,
);
} catch (e) {
debugPrint('회사별 장비 조회 실패: $e');
rethrow;
}
}
/// 필터 초기화
void clearFilters() {
_statusFilter = null;