feat: V/R 유지보수 시스템 전환 및 대시보드 테이블 형태 완성
- V/R 시스템 완전 전환: WARRANTY/CONTRACT/INSPECTION → V(방문)/R(원격) - 유지보수 대시보드 카드 → StandardDataTable 테이블 형태 전환 - "조회중..." 문제 해결: 백엔드 직접 필드 사용 (equipment_model, company_name) - MaintenanceDto 신규 필드 추가: company_id, company_name, equipment_serial, equipment_model - preloadEquipmentData 비활성화로 불필요한 equipment-history API 호출 제거 - CO-STAR 프레임워크 적용 및 CLAUDE.md v3.0 업데이트 - Flutter Analyze ERROR: 0 유지, 100% shadcn_ui 컴플라이언스 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,314 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:superport/data/models/equipment/equipment_dto.dart';
|
||||
import 'package:superport/data/models/company/company_dto.dart';
|
||||
import 'package:superport/data/models/stock_status_dto.dart';
|
||||
import 'package:superport/data/repositories/equipment_history_repository.dart';
|
||||
import 'package:superport/domain/repositories/company_repository.dart';
|
||||
import 'package:superport/data/repositories/company_repository_impl.dart';
|
||||
import 'package:superport/data/datasources/remote/company_remote_datasource.dart';
|
||||
import 'package:superport/data/datasources/remote/api_client.dart';
|
||||
import 'package:superport/services/equipment_warehouse_cache_service.dart';
|
||||
|
||||
class EquipmentOutboundController extends ChangeNotifier {
|
||||
final List<EquipmentDto> selectedEquipments;
|
||||
late final CompanyRepository _companyRepository;
|
||||
late final EquipmentHistoryRepository _equipmentHistoryRepository;
|
||||
late final EquipmentWarehouseCacheService _warehouseCacheService;
|
||||
|
||||
// Form controllers
|
||||
final TextEditingController remarkController = TextEditingController();
|
||||
|
||||
// State variables
|
||||
bool _isLoading = false;
|
||||
bool _isLoadingCompanies = false;
|
||||
bool _isLoadingWarehouseInfo = false;
|
||||
String? _errorMessage;
|
||||
String? _companyError;
|
||||
String? _warehouseError;
|
||||
|
||||
// Form data
|
||||
DateTime _transactionDate = DateTime.now();
|
||||
List<CompanyDto> _companies = [];
|
||||
CompanyDto? _selectedCompany;
|
||||
final Map<int, DateTime> _warrantyDates = {}; // 각 장비의 워런티 날짜 관리
|
||||
|
||||
// Getters
|
||||
bool get isLoading => _isLoading;
|
||||
bool get isLoadingCompanies => _isLoadingCompanies;
|
||||
bool get isLoadingWarehouseInfo => _isLoadingWarehouseInfo;
|
||||
String? get errorMessage => _errorMessage;
|
||||
String? get companyError => _companyError;
|
||||
String? get warehouseError => _warehouseError;
|
||||
DateTime get transactionDate => _transactionDate;
|
||||
List<CompanyDto> get companies => _companies;
|
||||
CompanyDto? get selectedCompany => _selectedCompany;
|
||||
|
||||
bool get canSubmit =>
|
||||
!_isLoading &&
|
||||
_selectedCompany != null &&
|
||||
selectedEquipments.isNotEmpty;
|
||||
|
||||
EquipmentOutboundController({
|
||||
required this.selectedEquipments,
|
||||
}) {
|
||||
// Initialize repositories directly with proper dependencies
|
||||
final apiClient = ApiClient();
|
||||
final companyRemoteDataSource = CompanyRemoteDataSourceImpl(apiClient);
|
||||
_companyRepository = CompanyRepositoryImpl(remoteDataSource: companyRemoteDataSource);
|
||||
|
||||
// Initialize EquipmentHistoryRepository with ApiClient's Dio instance
|
||||
// ApiClient has proper auth headers and base URL configuration
|
||||
final dio = apiClient.dio; // Use the authenticated Dio instance from ApiClient
|
||||
_equipmentHistoryRepository = EquipmentHistoryRepositoryImpl(dio);
|
||||
|
||||
// Initialize warehouse cache service
|
||||
_warehouseCacheService = EquipmentWarehouseCacheService();
|
||||
|
||||
// 각 장비의 현재 워런티 날짜로 초기화
|
||||
for (final equipment in selectedEquipments) {
|
||||
final id = equipment.id;
|
||||
final warrantyDate = equipment.warrantyEndedAt;
|
||||
if (id != null && warrantyDate != null) {
|
||||
_warrantyDates[id] = warrantyDate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set transactionDate(DateTime value) {
|
||||
_transactionDate = value;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
set selectedCompany(CompanyDto? value) {
|
||||
_selectedCompany = value;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> initialize() async {
|
||||
// 병렬로 회사 정보와 창고 캐시 로드
|
||||
await Future.wait([
|
||||
loadCompanies(),
|
||||
_loadWarehouseCache(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
Future<void> loadCompanies() async {
|
||||
print('[EquipmentOutboundController] loadCompanies called');
|
||||
_isLoadingCompanies = true;
|
||||
_companyError = null;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
print('[EquipmentOutboundController] Calling _companyRepository.getCompanies');
|
||||
final result = await _companyRepository.getCompanies(
|
||||
limit: 1000, // 모든 회사를 가져오기 위해 큰 값 설정
|
||||
);
|
||||
|
||||
result.fold(
|
||||
(failure) {
|
||||
print('[EquipmentOutboundController] Company loading failed: ${failure.message}');
|
||||
_companyError = failure.message;
|
||||
},
|
||||
(data) {
|
||||
print('[EquipmentOutboundController] Companies loaded successfully: ${data.items.length} companies');
|
||||
// Convert Company to CompanyDto - only use required fields
|
||||
_companies = data.items
|
||||
.map((company) => CompanyDto(
|
||||
id: company.id,
|
||||
name: company.name,
|
||||
contactName: '', // Default value for required field
|
||||
contactPhone: '', // Default value for required field
|
||||
contactEmail: '', // Default value for required field
|
||||
address: company.address.toString(),
|
||||
isCustomer: company.isCustomer,
|
||||
))
|
||||
.where((c) => c.isCustomer == true)
|
||||
.toList();
|
||||
print('[EquipmentOutboundController] Filtered customer companies: ${_companies.length}');
|
||||
},
|
||||
);
|
||||
} catch (e, stackTrace) {
|
||||
print('[EquipmentOutboundController] Exception in loadCompanies: $e');
|
||||
print('[EquipmentOutboundController] Stack trace: $stackTrace');
|
||||
_companyError = '회사 목록을 불러오는 중 오류가 발생했습니다: $e';
|
||||
} finally {
|
||||
_isLoadingCompanies = false;
|
||||
notifyListeners();
|
||||
print('[EquipmentOutboundController] loadCompanies completed');
|
||||
}
|
||||
}
|
||||
|
||||
/// 창고 캐시 로딩
|
||||
Future<void> _loadWarehouseCache() async {
|
||||
if (_warehouseCacheService.needsRefresh()) {
|
||||
_isLoadingWarehouseInfo = true;
|
||||
_warehouseError = null;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
final success = await _warehouseCacheService.loadCache();
|
||||
if (!success) {
|
||||
_warehouseError = _warehouseCacheService.lastError ?? '창고 정보 로딩 실패';
|
||||
}
|
||||
} catch (e) {
|
||||
_warehouseError = '창고 정보 로딩 중 오류: $e';
|
||||
} finally {
|
||||
_isLoadingWarehouseInfo = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 장비의 현재 창고 정보 조회 (Stock Status 기반)
|
||||
///
|
||||
/// [equipment]: 조회할 장비
|
||||
///
|
||||
/// Returns: 창고명 (Stock Status 우선, Fallback으로 Equipment DTO 사용)
|
||||
String getEquipmentCurrentWarehouse(EquipmentDto equipment) {
|
||||
// 디버깅: 실제 Equipment DTO 데이터 출력
|
||||
print('[EquipmentOutboundController] Equipment ${equipment.id} 창고 정보:');
|
||||
print(' - warehousesId: ${equipment.warehousesId}');
|
||||
print(' - warehousesName: ${equipment.warehousesName}');
|
||||
print(' - serialNumber: ${equipment.serialNumber}');
|
||||
|
||||
if (_warehouseError != null) {
|
||||
print('[EquipmentOutboundController] Stock Status API 실패, Equipment DTO 사용');
|
||||
final fallbackName = equipment.warehousesName ?? '창고 미지정';
|
||||
print(' - Fallback 결과: $fallbackName');
|
||||
return fallbackName;
|
||||
}
|
||||
|
||||
// Primary: Stock Status API 기반 정보 사용
|
||||
final stockInfo = _warehouseCacheService.getEquipmentStock(equipment.id);
|
||||
print('[EquipmentOutboundController] Stock Status API 결과:');
|
||||
print(' - stockInfo 존재: ${stockInfo != null}');
|
||||
if (stockInfo != null) {
|
||||
print(' - stockInfo.warehouseName: ${stockInfo.warehouseName}');
|
||||
print(' - stockInfo.warehouseId: ${stockInfo.warehouseId}');
|
||||
}
|
||||
|
||||
final finalResult = stockInfo?.warehouseName ??
|
||||
equipment.warehousesName ??
|
||||
'입출고 이력 없음';
|
||||
print(' - 최종 결과: $finalResult');
|
||||
|
||||
return finalResult;
|
||||
}
|
||||
|
||||
/// 장비의 현재 창고 ID 조회
|
||||
int? getEquipmentCurrentWarehouseId(EquipmentDto equipment) {
|
||||
// Primary: Stock Status API 기반 정보 사용
|
||||
final stockInfo = _warehouseCacheService.getEquipmentStock(equipment.id);
|
||||
return stockInfo?.warehouseId ?? equipment.warehousesId;
|
||||
}
|
||||
|
||||
/// 장비의 재고 현황 정보 조회
|
||||
StockStatusDto? getEquipmentStockStatus(EquipmentDto equipment) {
|
||||
return _warehouseCacheService.getEquipmentStock(equipment.id);
|
||||
}
|
||||
|
||||
/// 출고 후 창고 캐시 갱신
|
||||
Future<void> _refreshWarehouseCache() async {
|
||||
print('[EquipmentOutboundController] 출고 완료 후 창고 캐시 갱신 시작...');
|
||||
|
||||
try {
|
||||
await _warehouseCacheService.refreshCache();
|
||||
print('[EquipmentOutboundController] 창고 캐시 갱신 완료');
|
||||
} catch (e) {
|
||||
print('[EquipmentOutboundController] 창고 캐시 갱신 실패: $e');
|
||||
// 갱신 실패해도 출고 프로세스는 성공으로 간주
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> processOutbound() async {
|
||||
print('[EquipmentOutboundController] processOutbound called');
|
||||
print('[EquipmentOutboundController] canSubmit: $canSubmit');
|
||||
print('[EquipmentOutboundController] selectedEquipments count: ${selectedEquipments.length}');
|
||||
print('[EquipmentOutboundController] selectedCompany: ${_selectedCompany?.name} (ID: ${_selectedCompany?.id})');
|
||||
print('[EquipmentOutboundController] API Base URL: ${_equipmentHistoryRepository.toString()}');
|
||||
|
||||
if (!canSubmit) {
|
||||
print('[EquipmentOutboundController] Cannot submit - validation failed');
|
||||
return false;
|
||||
}
|
||||
|
||||
_isLoading = true;
|
||||
_errorMessage = null;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
print('[EquipmentOutboundController] Starting outbound process for ${selectedEquipments.length} equipments');
|
||||
|
||||
// Process each selected equipment
|
||||
for (int i = 0; i < selectedEquipments.length; i++) {
|
||||
final equipment = selectedEquipments[i];
|
||||
|
||||
// 개선된 창고 정보 조회 (Stock Status API 우선)
|
||||
final currentWarehouseName = getEquipmentCurrentWarehouse(equipment);
|
||||
final currentWarehouseId = getEquipmentCurrentWarehouseId(equipment);
|
||||
|
||||
print('[EquipmentOutboundController] Processing equipment ${i+1}/${selectedEquipments.length}');
|
||||
print('[EquipmentOutboundController] Equipment ID: ${equipment.id}');
|
||||
print('[EquipmentOutboundController] Equipment Serial: ${equipment.serialNumber}');
|
||||
print('[EquipmentOutboundController] Current Warehouse (Stock Status): $currentWarehouseName (ID: $currentWarehouseId)');
|
||||
print('[EquipmentOutboundController] Original Warehouse (DTO): ${equipment.warehousesName} (ID: ${equipment.warehousesId})');
|
||||
|
||||
await _equipmentHistoryRepository.createStockOut(
|
||||
equipmentsId: equipment.id,
|
||||
warehousesId: currentWarehouseId ?? equipment.warehousesId, // 개선된 창고 정보 사용
|
||||
companyIds: _selectedCompany?.id != null ? [_selectedCompany!.id!] : null,
|
||||
quantity: 1,
|
||||
transactedAt: _transactionDate,
|
||||
remark: remarkController.text.isNotEmpty ? remarkController.text : null,
|
||||
);
|
||||
|
||||
print('[EquipmentOutboundController] Successfully processed equipment ${equipment.id}');
|
||||
}
|
||||
|
||||
print('[EquipmentOutboundController] All equipments processed successfully');
|
||||
|
||||
// 출고 완료 후 창고 캐시 갱신 (백그라운드에서 실행)
|
||||
unawaited(_refreshWarehouseCache());
|
||||
|
||||
return true;
|
||||
} catch (e, stackTrace) {
|
||||
print('[EquipmentOutboundController] ERROR during outbound process: $e');
|
||||
print('[EquipmentOutboundController] Stack trace: $stackTrace');
|
||||
_errorMessage = '출고 처리 중 오류가 발생했습니다: $e';
|
||||
notifyListeners();
|
||||
return false;
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
print('[EquipmentOutboundController] processOutbound completed');
|
||||
}
|
||||
}
|
||||
|
||||
String formatDate(DateTime date) {
|
||||
return DateFormat('yyyy-MM-dd').format(date);
|
||||
}
|
||||
|
||||
String formatPrice(int? price) {
|
||||
if (price == null) return '-';
|
||||
final formatter = NumberFormat('#,###');
|
||||
return '₩${formatter.format(price)}';
|
||||
}
|
||||
|
||||
DateTime? getWarrantyDate(int equipmentId) {
|
||||
return _warrantyDates[equipmentId];
|
||||
}
|
||||
|
||||
void updateWarrantyDate(int equipmentId, DateTime date) {
|
||||
_warrantyDates[equipmentId] = date;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
remarkController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user