- 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>
314 lines
12 KiB
Dart
314 lines
12 KiB
Dart
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();
|
|
}
|
|
} |